Skip to content

Commit 75aa712

Browse files
Merge pull request #47 from Concordium/forward-comp/COR-1780-compatibility
Upward Unknown will be handled to raise an error
2 parents f15ea02 + a4db924 commit 75aa712

4 files changed

Lines changed: 135 additions & 67 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased changes
44

5+
- Update the Rust SDK for better forwards compatibility with future node versions and revised error handling or reporting for unknown transaction and event types. If within the database hook callback has unknown data variants in the block processing, the process would complain and cease, raising `IndexingError` to alert.
6+
57
## [0.14.0] - 2025-08-07
68

79
### Changed

deps/concordium-rust-sdk

Submodule concordium-rust-sdk updated 81 files

src/lib.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,13 @@ pub trait DatabaseHooks<D, P> {
168168
async fn insert_into_db(
169169
db_conn: &mut DBConn<P>,
170170
data: &D,
171-
) -> Result<BlockInsertSuccess, DatabaseError>;
171+
) -> Result<BlockInsertSuccess, IndexingError>;
172172

173173
/// Invoked by the database thread to request the latest recorded height in
174174
/// the database.
175175
async fn on_request_max_height(
176176
db: &DatabaseClient,
177-
) -> Result<Option<AbsoluteBlockHeight>, DatabaseError>;
177+
) -> Result<Option<AbsoluteBlockHeight>, IndexingError>;
178178
}
179179

180180
/// A collection of possible errors that can happen while using the node to
@@ -198,6 +198,17 @@ pub enum NodeError {
198198
OtherError(#[from] anyhow::Error),
199199
}
200200

201+
#[derive(Debug, thiserror::Error)]
202+
/// Possible errors while indexing blocks into the database
203+
pub enum IndexingError {
204+
/// Database error.
205+
#[error("Error using the database {0}.")]
206+
PostgresError(#[from] postgres::Error),
207+
/// Unknown Data type encountered error
208+
#[error("Please update the rust SDK. Reason for this could be due to {0}.")]
209+
UnknownData(String),
210+
}
211+
201212
/// Defines a set of necessary callbacks used while interacting with a node.
202213
#[async_trait]
203214
pub trait NodeHooks<D> {
@@ -289,6 +300,11 @@ where
289300
);
290301
}
291302
Err(e) => {
303+
if let IndexingError::UnknownData(reasons) = &e {
304+
log::error!("Encountered unknown data variant. Please update the rust SDK. Reason(s): {}", reasons);
305+
return Err(anyhow::anyhow!("Encountered unknown data variant. Please update the rust SDK. Reason(s): {}", reasons));
306+
}
307+
292308
successive_errors += 1;
293309
// wait for 2^(min(successive_errors - 1, 7)) seconds before attempting.
294310
// The reason for the min is that we bound the time between reconnects.

src/main.rs

Lines changed: 114 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ use concordium_rust_sdk::{
88
hashes::BlockHash, queries::BlockInfo, AbsoluteBlockHeight, BlockItemSummary,
99
ContractAddress, SpecialTransactionOutcome,
1010
},
11-
v2::{self, FinalizedBlockInfo},
11+
v2::{self, FinalizedBlockInfo, Upward},
1212
};
13-
use futures::TryStreamExt;
13+
use futures::{StreamExt, TryStreamExt};
1414
use std::{collections::HashSet, convert::TryFrom, hash::Hash};
1515
use structopt::StructOpt;
1616
use tokio_postgres::{
1717
types::{Json, ToSql},
1818
Transaction as DBTransaction,
1919
};
2020
use tonic::async_trait;
21+
use tonic::Status;
2122
use transaction_logger::{
2223
postgres::{self, DatabaseClient},
23-
run_service, set_shutdown, BlockInsertSuccess, DatabaseError, DatabaseHooks, NodeError,
24+
run_service, set_shutdown, BlockInsertSuccess, DatabaseHooks, IndexingError, NodeError,
2425
NodeHooks, PrepareStatements, SharedIndexerArgs,
2526
};
2627

28+
type ContractEvents = Vec<cis2::Event>;
29+
type ContractEffects = Vec<(ContractAddress, ContractEvents)>;
30+
2731
type DBConn = transaction_logger::DBConn<PreparedStatements>;
2832

2933
const MAX_CONNECT_ATTEMPTS: u32 = 6;
@@ -258,7 +262,7 @@ impl PreparedStatements {
258262
block_time: Timestamp,
259263
block_height: AbsoluteBlockHeight,
260264
ts: &BlockItemSummaryWithCanonicalAddresses,
261-
) -> Result<(), postgres::Error> {
265+
) -> Result<(), IndexingError> {
262266
let affected_addresses = &ts.addresses;
263267
let summary_row = SummaryRow {
264268
block_hash,
@@ -275,15 +279,27 @@ impl PreparedStatements {
275279
];
276280
tx.query_opt(&self.insert_ati, &values).await?;
277281
}
282+
278283
// insert contracts
279-
for affected in ts.summary.affected_contracts() {
284+
let affected_contracts = ts.summary.affected_contracts().known_or_else(|| {
285+
log::error!("Could not determine affected contracts for a BlockItemSummary of unknown type. {:?}", ts.summary);
286+
IndexingError::UnknownData("Could not determine affected contracts for a BlockItemSummary of unknown type.".to_string())
287+
})?; // if Unknown, throw an error
288+
289+
for contract_address in affected_contracts {
290+
let affected = contract_address.known_or_else(|| {
291+
log::error!("Could not determine affected contracts of an unknown type of AccountTransactionEffects. {:?}", ts.summary);
292+
IndexingError::UnknownData("Could not determine affected contracts of an unknown type of AccountTransactionEffects.".to_string())
293+
})?; // encountered unknown contract_address, throw an error, this will stop the insert into db process and alert the user to update the rust SDK
294+
280295
let index = affected.index;
281296
let subindex = affected.subindex;
282297
let values = [
283298
&(index as i64) as &(dyn ToSql + Sync),
284299
&(subindex as i64),
285300
&id,
286301
];
302+
287303
tx.query_opt(&self.insert_cti, &values).await?;
288304
}
289305

@@ -398,41 +414,41 @@ impl PreparedStatements {
398414
&self,
399415
tx: &DBTransaction<'_>,
400416
ts: &BlockItemSummary,
401-
) -> Result<(), postgres::Error> {
402-
if let Some(effects) = get_cis2_events(ts) {
403-
for (ca, events) in effects {
404-
for event in events {
405-
match event {
406-
cis2::Event::Transfer { .. } => {
407-
// do nothing, tokens are not created here.
408-
}
409-
cis2::Event::Mint {
410-
ref token_id,
411-
ref amount,
412-
..
413-
} => {
414-
self.cis2_increase_total_supply(tx, ca, token_id, amount)
415-
.await?;
416-
}
417-
cis2::Event::Burn {
418-
ref token_id,
419-
ref amount,
420-
..
421-
} => {
422-
self.cis2_decrease_total_supply(tx, ca, token_id, amount)
423-
.await?;
424-
}
425-
cis2::Event::UpdateOperator { .. } => {
426-
// do nothing, updating operators does not change
427-
// token suply
428-
}
429-
cis2::Event::TokenMetadata { .. } => {
430-
// do nothing, updating token metadata does not
431-
// change token supply.
432-
}
433-
cis2::Event::Unknown => {
434-
// do nothing, not a CIS2 event
435-
}
417+
) -> Result<(), IndexingError> {
418+
let effects = get_cis2_events(ts)?;
419+
420+
for (ca, events) in effects {
421+
for event in events {
422+
match event {
423+
cis2::Event::Transfer { .. } => {
424+
// do nothing, tokens are not created here.
425+
}
426+
cis2::Event::Mint {
427+
ref token_id,
428+
ref amount,
429+
..
430+
} => {
431+
self.cis2_increase_total_supply(tx, ca, token_id, amount)
432+
.await?;
433+
}
434+
cis2::Event::Burn {
435+
ref token_id,
436+
ref amount,
437+
..
438+
} => {
439+
self.cis2_decrease_total_supply(tx, ca, token_id, amount)
440+
.await?;
441+
}
442+
cis2::Event::UpdateOperator { .. } => {
443+
// do nothing, updating operators does not change
444+
// token suply
445+
}
446+
cis2::Event::TokenMetadata { .. } => {
447+
// do nothing, updating token metadata does not
448+
// change token supply.
449+
}
450+
cis2::Event::Unknown => {
451+
// do nothing, not a CIS2 event
436452
}
437453
}
438454
}
@@ -450,7 +466,7 @@ async fn insert_block(
450466
block_height: AbsoluteBlockHeight,
451467
item_summaries: &[BlockItemSummaryWithCanonicalAddresses],
452468
special_events: &[SpecialTransactionOutcome],
453-
) -> Result<chrono::Duration, postgres::Error> {
469+
) -> Result<chrono::Duration, IndexingError> {
454470
let start = chrono::Utc::now();
455471
let db_tx = db.client.as_mut().transaction().await?;
456472
let prepared = &db.prepared;
@@ -492,33 +508,55 @@ async fn get_last_block_height(
492508
/// parsing then the logs for that section of execution are ignored, since it
493509
/// indicates an error in the contract.
494510
///
495-
/// The return value of [`None`] means there are no understandable CIS2 logs
511+
/// The return value of empty vec means there are no understandable CIS2 logs
496512
/// produced.
497-
fn get_cis2_events(bi: &BlockItemSummary) -> Option<Vec<(ContractAddress, Vec<cis2::Event>)>> {
513+
fn get_cis2_events(bi: &BlockItemSummary) -> Result<ContractEffects, IndexingError> {
498514
match bi.contract_update_logs() {
499-
Some(log_iter) => Some(
500-
log_iter
501-
.flat_map(|(ca, logs)| {
502-
match logs
503-
.iter()
504-
.map(cis2::Event::try_from)
505-
.collect::<Result<Vec<cis2::Event>, _>>()
506-
{
507-
Ok(events) => Some((ca, events)),
508-
Err(_) => None,
509-
}
510-
})
511-
.collect(),
512-
),
515+
Some(log_iter) => {
516+
// Map each log into a Result
517+
let mut events: ContractEffects = Vec::new();
518+
for contract_log in log_iter {
519+
let (ca, logs) = contract_log.known_or_else(|| {
520+
log::error!(
521+
"Could not determine contract log, unknown type. {:?}",
522+
contract_log
523+
);
524+
IndexingError::UnknownData(
525+
"Could not determine contract log, unknown type.".to_string(),
526+
)
527+
})?;
528+
529+
let evs = logs
530+
.iter()
531+
.map(cis2::Event::try_from)
532+
.collect::<Result<Vec<cis2::Event>, _>>()
533+
.ok();
534+
535+
if let Some(evs) = evs {
536+
events.push((ca, evs));
537+
}
538+
}
539+
//if no events were parsed due to non cis2 logs, the vector will be empty
540+
//so just return the events vector, empty or not empty.
541+
Ok(events)
542+
}
513543
None => {
514-
let init = bi.contract_init()?;
515-
let cis2 = init
544+
let init = match bi.contract_init() {
545+
Some(init) => init,
546+
None => return Ok(vec![]),
547+
};
548+
549+
let cis2 = match init
516550
.events
517551
.iter()
518552
.map(cis2::Event::try_from)
519553
.collect::<Result<Vec<cis2::Event>, _>>()
520-
.ok()?;
521-
Some(vec![(init.address, cis2)])
554+
{
555+
Ok(vec) => vec,
556+
Err(_) => return Ok(vec![]),
557+
};
558+
559+
Ok(vec![(init.address, cis2)])
522560
}
523561
}
524562
}
@@ -531,7 +569,7 @@ impl DatabaseHooks<TransactionLogData, PreparedStatements> for DatabaseState {
531569
async fn insert_into_db(
532570
db_conn: &mut DBConn,
533571
(bi, item_summaries, special_events): &TransactionLogData,
534-
) -> Result<BlockInsertSuccess, DatabaseError> {
572+
) -> Result<BlockInsertSuccess, IndexingError> {
535573
let duration = insert_block(
536574
db_conn,
537575
bi.block_hash,
@@ -551,7 +589,7 @@ impl DatabaseHooks<TransactionLogData, PreparedStatements> for DatabaseState {
551589

552590
async fn on_request_max_height(
553591
db: &DatabaseClient,
554-
) -> Result<Option<AbsoluteBlockHeight>, DatabaseError> {
592+
) -> Result<Option<AbsoluteBlockHeight>, IndexingError> {
555593
let height = get_last_block_height(db).await?;
556594
Ok(height)
557595
}
@@ -602,18 +640,30 @@ impl NodeHooks<TransactionLogData> for CanonicalAddressCache {
602640
.try_collect()
603641
.await?
604642
};
643+
605644
let special_events = node
606645
.get_block_special_events(finalized_block_info.height)
607646
.await?
608647
.response
648+
.map(|upward_res| {
649+
upward_res.and_then(|upward| match upward {
650+
Upward::Known(special_transaction_outcome) => Ok(special_transaction_outcome),
651+
Upward::Unknown => {
652+
Err(Status::unknown("Unknown SpecialTransactionOutcome type"))
653+
}
654+
})
655+
})
609656
.try_collect()
610657
.await?;
611658

612659
// Map account addresses affected by each summary to their respective canonical
613660
// address
614661
let mut with_addresses = Vec::with_capacity(transaction_summaries.len());
615662
for summary in transaction_summaries {
616-
let affected_addresses = summary.affected_addresses();
663+
let affected_addresses = summary.affected_addresses().known_or_else(|| {
664+
Status::unknown("Could not determine affected addresses for BlockItem")
665+
})?; // if unknown, throw Err Status::unknown
666+
617667
let mut addresses = Vec::with_capacity(affected_addresses.len());
618668
// resolve canonical addresses. This part is only needed because the index
619669
// is currently expected by "canonical address",

0 commit comments

Comments
 (0)