Skip to content

Conversation

rbaconcordium
Copy link

@rbaconcordium rbaconcordium commented Sep 9, 2025

Jira-Id: COR-1780

Purpose

Enabling forward compatibility in transaction-logger with the recent changes on rust SDK

Changes

Ensuring function calls are handling Upward and if Upward.Unknown is encountered, an error will be raised. The insert_db overall would see this error and complain.
Have introduced also IndexingError, to wrap the different error types currently used within here, so that we could return at the higher level of the call. Using DatabaseError.Other does not appear to fit or could lead to inaccurate error display.

Checklist

  • [ X ] My code follows the style of this project.
  • [ X ] The code compiles without warnings.
  • [ X ] I have performed a self-review of the changes.
  • [ X ] I have documented my code, in particular the intent of the
    hard-to-understand areas.
  • [ X ] (If necessary) I have updated the CHANGELOG.

src/main.rs Outdated
tx.query_opt(&self.insert_cti, &values).await?;
let unwrapped_upwards = match ts.summary.affected_contracts() {
Upward::Known(contracts) => contracts,
Upward::Unknown => Vec::new(), // TODO: is this what we want to do for Unknown?
Copy link
Contributor

@allanbrondum allanbrondum Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think clarifications at this level are best handle before the PR, e.g. in the squad channel. Such that we have the fundamental clarifications done before opening the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But basically we should fail if any data we index on is unknown.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwing err

src/main.rs Outdated
}
Upward::Unknown => {
//TODO: is this what we want to do for Unknown?
println!(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always use the logging framework (log create here)), never write directly to stdout

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@rbaconcordium rbaconcordium marked this pull request as draft September 9, 2025 11:51
@rbaconcordium rbaconcordium marked this pull request as ready for review September 9, 2025 14:41
@squirer squirer self-requested a review September 9, 2025 15:35
src/main.rs Outdated
&id,
];
tx.query_opt(&self.insert_cti, &values).await?;
let upward_affected_contracts = match ts.summary.affected_contracts() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you will better describe your intent here by using the projection BlockItemSummaryDetails::account_transaction and then call affected_contracts on that if not None.

src/main.rs Outdated
};

for upward_contract_address in upward_affected_contracts {
match upward_contract_address {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try using let affected = upward_contract_address.known_or_else(|| ...)?;, I think you will get a more clean looking code instead of using match. These functional combinators are often preferred in Rust

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing

src/main.rs Outdated
@@ -494,31 +519,42 @@ async fn get_last_block_height(
///
/// The return value of [`None`] means there are no understandable CIS2 logs
/// produced.
fn get_cis2_events(bi: &BlockItemSummary) -> Option<Vec<(ContractAddress, Vec<cis2::Event>)>> {
fn get_cis2_events(bi: &BlockItemSummary) -> Cis2Result {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced using type aliases specific to a function makes the code more readable. Maybe the type ContractEffects is ok, as it is not specific to a function, but Cis2Result is too much aliasing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one came about when the clippy command was run, complained about the type in the signature being too complex of some sort. Hence, why I broke them down to types. Will take a look again if I can lessen the number of types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't include Result and Option in the alias, I think it will be more readable, and clippy should not complain still.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have amendded

src/main.rs Outdated
Comment on lines 662 to 666
let affected_addresses = match summary.affected_addresses() {
Upward::Known(addresses) => addresses,
Upward::Unknown => Vec::new(), // returning empty vec if unknown, the next line will be processing zero length vec anyway, so nothing happens
};

Copy link
Contributor

@allanbrondum allanbrondum Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct behaviour, since you will ignore transaction types that are unknown. And these transaction may actually affect accounts and we should index those effects then. You can project to account_transaction again here before calling affected_addresses, but you will need to fail when that function returns Unknown

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding an error throwing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwing an error seems missing.

src/main.rs Outdated
Upward::Known(special_transaction_outcome) => {
Ok(Some(special_transaction_outcome))
}
Upward::Unknown => Ok(None), // we ignore unknown special events, treat this as no special ones
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ignore unknown special events? We may need to index them

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing to throwing an error

src/main.rs Outdated
None => {
let init = bi.contract_init()?;
let cis2 = init
let init = bi.contract_init().ok_or(DatabaseError::OtherError(anyhow!(
Copy link
Contributor

@allanbrondum allanbrondum Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You changed the semantics here I believe? Before None was returned if contract_init() returned None, now it returns an Err

src/main.rs Outdated
.ok()?;
Some(vec![(init.address, cis2)])
.collect::<Result<_, _>>()
.map_err(|_| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here?

Copy link
Contributor

@allanbrondum allanbrondum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of the implemented behaviour is not correct when considering the role of the indexer, as noted in the comments above.

In addition, it will help to document any decisions made in the PR description (here about how to handle Unknown, but also in general).

@rbaconcordium rbaconcordium changed the title initial attempt of upward Upward Unknown will be handled to raise an error Sep 10, 2025
CHANGELOG.md Outdated
@@ -2,6 +2,8 @@

## Unreleased changes

- Added Upward usage in the transaction-logger service and throwing errors for unknown cases for affected_contracts
Copy link
Member

@DOBEN DOBEN Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correctly highlight name/fileName/pathName/typeNames/functionName... as I already pointed that out in the previous two PRs. I think no need to mention transaction-logger service as this is the changelog about the transaction-logger (meaning redundant info).

src/main.rs Outdated
@@ -258,7 +263,7 @@ impl PreparedStatements {
block_time: Timestamp,
block_height: AbsoluteBlockHeight,
ts: &BlockItemSummaryWithCanonicalAddresses,
) -> Result<(), postgres::Error> {
) -> Result<(), DatabaseError> {
Copy link
Member

@DOBEN DOBEN Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As our Unkown error is not really a DatabaseError (the error doesn't come from the database), I would create a new error type e.g.:

/// A collection of possible errors that can happen while indexing blocks into the database.
#[derive(Debug, Error)]
pub enum IndexingError {
    /// Database error.
    #[error("Error using the database {0}.")]
    PostgresError(#[from] postgres::Error),
    /// ...
    #[error("Please update the rust SDK. Type {0} is unkown.")]
    Unkown(String),
}

This general error case is a bit weird in my opinion (meaning an error for everything).

  /// Other errors while processing database data.
    #[error("Error happened on database thread {0}.")]
    OtherError(#[from] anyhow::Error),

src/main.rs Outdated
Comment on lines 531 to 534
logs.iter().map(cis2::Event::try_from).collect();
evs.map(|ev| (ca, ev)).map_err(|_| {
DatabaseError::OtherError(anyhow!("Failed to parse CIS2 event"))
})
Copy link
Member

@DOBEN DOBEN Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems not a database error (meaning that a problem with the database exists). I am also not a fan of having everything be an otherError. I think it would be also good to propagate error messages such as e.

Suggested change
logs.iter().map(cis2::Event::try_from).collect();
evs.map(|ev| (ca, ev)).map_err(|_| {
DatabaseError::OtherError(anyhow!("Failed to parse CIS2 event"))
})
logs.iter().map(cis2::Event::try_from).collect();
evs.map(|ev| (ca, ev)).map_err(|e| {
IndexerError::Parsing(anyhow!("Failed to parse CIS2 event: {e} "))
})

src/main.rs Outdated
),
.collect();

events.map(Some)
Copy link
Member

@DOBEN DOBEN Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when does the function get_cis2_events return Ok(None)? An option as return value only makes sense if None is returned at some point?

src/main.rs Outdated
DatabaseError::OtherError(anyhow!("failed to parse CIS2 event: {e}"))
})?;

Ok(Some(vec![(init.address, cis2)]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when does the function get_cis2_events return Ok(None)? An option as return value only makes sense if None is returned at some point?

src/main.rs Outdated
.map(|upward| match upward {
Upward::Known((ca, logs)) => {
let evs: Result<Vec<_>, _> =
logs.iter().map(cis2::Event::try_from).collect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have transaction logs here (that are not smart contract cis2 logs) e.g. a contract not related to tokens, then you will try to parse the logs into cis2 events and fail the parsing and return an error which will stop the indexer from progressing. I don't think that is what we want.

src/main.rs Outdated
Comment on lines 662 to 666
let affected_addresses = match summary.affected_addresses() {
Upward::Known(addresses) => addresses,
Upward::Unknown => Vec::new(), // returning empty vec if unknown, the next line will be processing zero length vec anyway, so nothing happens
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwing an error seems missing.

src/main.rs Outdated
Upward::Known(special_transaction_outcome) => {
Ok(Some(special_transaction_outcome))
}
Upward::Unknown => Err(Status::unknown(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems not a NodeError.

    /// Error establishing connection.
    #[error("GRPC error during node query {0}.")]
    NetworkError(#[from] v2::Status),
    ```

@rbaconcordium rbaconcordium requested a review from td202 September 11, 2025 15:35
CHANGELOG.md Outdated
Comment on lines 5 to 8
- Handling Upward Known and Unknown usage from main fn get_cis2_events
- Handling Upward Known and Unknown usage from BlockItemSummary fn affected_contracts
- Handling Upward Known and Unknown usage from Client fn get_block_special_events
- Added IndexingError type in lib
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a very useful changelog. I think it's best if the changelog focuses on behavioural changes, or what the effects of the changes are, rather than the implementation details, which are difficult to interpret without context. In this case, I think the relevant point is updating the Rust SDK for better forwards compatibility with future node versions, and revised error reporting for unknown transaction and event types.

src/lib.rs Outdated
Comment on lines 207 to 209
/// Parsing errors.
#[error("Error happened parsing {0}.")]
ParsingError(String),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be used anywhere - what is it for?

src/lib.rs Outdated
Comment on lines 211 to 215
#[error("Please update the rust SDK. Error could be due to {0}.")]
Unknown(String),
/// Other errors while processing database data.
#[error("Error happened on database thread {0}.")]
OtherError(#[from] anyhow::Error),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these error descriptions are a bit vague or unhelpful.

src/main.rs Outdated
Comment on lines 285 to 288
let upward_affected_contracts = ts.summary.affected_contracts().known_or_else(|| {
log::error!("Unknown upward error on ContractAddress {:?}", ts.summary);
IndexingError::Unknown("Unknown upward error on ContractAddress".to_string())
})?; // if Unknown, throw an error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An error occurs here if the BlockItemSummary is of an unknown type. A more appropriate error might be something like: "Could not determine affected contracts for a BlockItemSummary of unknown type."

src/main.rs Outdated
Comment on lines 290 to 294
for upward_contract_address in upward_affected_contracts {
let affected = upward_contract_address.known_or_else(|| {
log::error!("Unknown upward error on ContractAddress {:?}", ts.summary);
IndexingError::Unknown("Unknown upward error on ContractAddress".to_string())
})?; // encountered unknown contract_address, throw an error to prevent transaction insertion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the error would be due to an unknown AccountTransactionEffects type. "Could not determine affected contracts of an unknown type of AccountTransactionEffects."

src/main.rs Outdated
Comment on lines 532 to 534
return Err(IndexingError::Unknown(
"Unknown error in contract_update_logs".to_string(),
));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This occurs if the contract invocation summary includes an unknown ContractTraceElement.

src/main.rs Outdated
Comment on lines 546 to 548
let init = bi
.contract_init()
.ok_or_else(|| IndexingError::OtherError(anyhow!("Contract init failed")))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is supposed to fail in this way. contract_init will return None if the event is not a contract initialization - in which case this function should also return None (or an empty vector).

src/main.rs Outdated
Comment on lines 553 to 554
.filter_map(|ev| cis2::Event::try_from(ev).ok())
.collect();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this may be a change in behaviour - but possibly it was incorrect before. It appears that previously if any of the events was not a CIS2 event, then previously the function would return None, whereas now non-CIS2 events are ignored, but CIS2 events are retained. Probably this change should be documented if it's intentional.

src/main.rs Outdated
Ok(Some(special_transaction_outcome))
}
Upward::Unknown => Err(Status::unknown(
"Unknown upward error on SpecialTransactionOutcome",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Unknown upward error on SpecialTransactionOutcome",
"Unknown SpecialTransactionOutcome type",

src/main.rs Outdated
let affected_addresses = summary.affected_addresses();
let affected_addresses = summary
.affected_addresses()
.known_or_else(|| Status::unknown("Unknown upward error on AccountAddress"))?; // if unknown, throw Err Status::unknown
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the error should be something like "Could not determine affected addresses for BlockItem".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants