Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: standardize errors #611

Open
wants to merge 8 commits into
base: next
Choose a base branch
from

Conversation

Mirko-von-Leipzig
Copy link
Contributor

@Mirko-von-Leipzig Mirko-von-Leipzig commented Jan 13, 2025

Updates errors to follow the guidelines in 0xPolygonMiden/miden-base#966.

This PR also removes the wildcard route option from the faucet. Currently the faucet allows visiting arbitrary paths (e.g. https://testnet.miden.io/asdas). Clearly this isn't behaving as intended and since we only have the single static file I've removed the wildcard entirely.

I looked into the axum defaults relating to #499 (comment) and decided the defaults of Error for failures, Debug for success are fine - though maybe we want to log the latter as info? I've also enabled the built-in axum rejects by setting axum::rejections=trace by default.

Closes #484, #499 and #581.

I do feel we could simplify our error handling strategy but that can wait for another day. In general we have many error variants, but we never actually do anything with them except bubble them up. Or put differently, aside from differentiating between internal/user-facing errors, we don't usually care about the errors.

@bobbinth bobbinth requested a review from igamigo January 13, 2025 17:15
Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

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

Looks good overall, although I left a comment with a question. Basically not sure why we took out the #[from] and the error messages from the display messages.

Also, regarding this:

I looked into the axum defaults relating to #499 (comment) and decided the defaults of Error for failures, Debug for success are fine - though maybe we want to log the latter as info? I've also enabled the built-in axum rejects by setting axum::rejections=trace by default.

These defaults sound sensible to me

Comment on lines +69 to 70
#[error("failed to retrieve transaction inputs from the store")]
StoreConnectionFailed(#[from] StoreError),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these (this and other occurrences) be annotated with #[source] instead? If #[from] is kept, we should probably include the inner error message. Seems like we would lose information on display otherwise, but maybe there's something I'm missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#[from] is a superset of #[source], quoting from the thiserror docs (emphasis mine):

The Error trait’s source() method is implemented to return whichever field has a #[source] attribute or is named source, if any. This is for identifying the underlying lower level error that caused your error.

The #[from] attribute always implies that the same field is #[source], so you don’t ever need to specify both attributes.

Any error type that implements std::error::Error or dereferences to dyn std::error::Error will work as a source.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This does mean that Display won't show the full backtrace; as described in the XOR Source section of the discussion in 0xPolygonMiden/miden-base#966.

We should go through all the "exit" routes of our tasks and main's to ensure they're doing the correct thing and returning a full error report.

AccountsNotFoundInDb(Vec<AccountId>),
#[error("Account {0} is not on the chain")]
#[error("account {0} is not on the chain")]
AccountNotOnChain(AccountId),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should likely be renamed to AccountNotPublic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you that makes much more sense :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That said it doesn't appear to be used anywhere?

@Mirko-von-Leipzig Mirko-von-Leipzig force-pushed the mirko/feat/standardize-errors branch from 4fa6f06 to 7e84e37 Compare January 14, 2025 06:47
Copy link
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

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

Looks good! Thank you! This is not a super deep review from my end, but upon a quick scan, I didn't see any issues.

I do feel we could simplify our error handling strategy but that can wait for another day. In general we have many error variants, but we never actually do anything with them except bubble them up. Or put differently, aside from differentiating between internal/user-facing errors, we don't usually care about the errors.

This could be a good idea, but I'd keep for another PR. Let's create an issue for it?

Copy link

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

Looks good to me. Thanks for working on this! If I understand correctly, the goal of this PR was to make errors fall in line with the guidelines and it does that so I will approve.

I left comments about where I think errors could be more concrete or add more context in their messages by refactoring them. Generally, I'm not a fan of using #[from] as it usually leads to generic error variants which cover multiple concrete cases, which, imho, should have their own concrete variants to ultimately result in better errors. I think #[from] is appropriate in places like ConversionError, where no more context can be added by introducing concrete variants so having to map_err is actually unnecessary.

This is also a matter of opinion whether applying my suggestions is necessary, so feel free to apply or not apply what you think makes sense for this codebase.

@@ -56,7 +56,7 @@ pub async fn get_tokens(

// Check that the amount is in the asset amount options
if !state.config.asset_amount_options.contains(&req.asset_amount) {
return Err(HandlerError::BadRequest("Invalid asset amount".to_string()));
return Err(HandlerError::BadRequest("invalid asset amount".to_string()));

Choose a reason for hiding this comment

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

Suggested change
return Err(HandlerError::BadRequest("invalid asset amount".to_string()));
return Err(HandlerError::BadRequest(format!(
"requested asset amount {} is not in list of allowed request amounts {:?}",
req.asset_amount, state.config.asset_amount_options
)));

Maybe this would be more helpful.

Comment on lines +72 to 73
#[error("transaction input error")]
TransactionInputError(#[from] TransactionInputError),

Choose a reason for hiding this comment

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

This variant is unused currently, can we remove it?

@@ -80,19 +82,19 @@ pub enum VerifyTxError {

#[derive(Debug, Error)]
pub enum AddTransactionError {
#[error("Transaction verification failed: {0}")]
#[error("transaction verification failed")]

Choose a reason for hiding this comment

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

Suggested change
#[error("transaction verification failed")]
#[error("to-be-added transaction failed verification")]

Nit: Could this be more specific, i.e. make it clearer which transaction failed (the one to be added)?

VerificationFailed(#[from] VerifyTxError),

#[error("Transaction input data is stale. Required data from {stale_limit} or newer, but inputs are from {input_block}.")]
#[error("transaction input data is stale. Required data from {stale_limit} or newer, but inputs are from {input_block}")]

Choose a reason for hiding this comment

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

I think we should probably also avoid . within error messages (and add this to the guidelines?) and first try to make a single sentence out of it, and if that's not possible, use ; instead.

So maybe something like:

Suggested change
#[error("transaction input data is stale. Required data from {stale_limit} or newer, but inputs are from {input_block}")]
#[error("transaction input data from block height {input_block} is stale because the oldest allowed block height is {stale_limit}")]

Comment on lines +94 to 95
#[error("deserialization failed: {0}")]
DeserializationError(String),

Choose a reason for hiding this comment

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

We should be able to make the inner error #[source] DeserializationError directly instead of String (and then not include it in the error message).

This variant is only used for ProvenTransaction deserialization. Can we then make this ProvenTransactionDeserializationFailed(#[source] DeserializationError) and adjust the error message accordingly? Alternatively just TransactionDeserializationFailed if we think it should be more generic. But I think having just DeserializationError is too generic because it adds too little context.

@@ -40,71 +40,65 @@ pub enum NullifierTreeError {
pub enum DatabaseError {

Choose a reason for hiding this comment

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

I think this error should be refactored to not have generic variants for each sub error, because they add very little context. Similar to a previous comment about the NullifierTree, if we apply a delta to an account and it fails, we would maybe get "account error: storage slot index is 10 but the slots length is 5", which could be more helpful by making it "failed to apply account delta to account 0x123: storage slot index is 10 but the slots length is 5". Especially if this error is part of a larger error chain, this intermediate context is important for locating what actually went wrong.

Not for this PR, but a larger question is whether this error should contain all of these variants in the first place (see also "kitchen-sink enum" in https://matklad.github.io/2020/10/15/study-of-std-io-error.html). I don't have a perfect solution for this, but perhaps we could think about whether DB connection errors like MissingDbConnection or SqliteError should be part of this error or if they are actually unrecoverable and should lead to a panic at the call site. But I don't know a lot about how the node works, so consider this just a question and not a request.

Comment on lines 123 to 145
pub enum DatabaseSetupError {
#[error("I/O error: {0}")]
#[error("I/O error")]
IoError(#[from] io::Error),
#[error("Database error: {0}")]
#[error("database error")]
DatabaseError(#[from] DatabaseError),
#[error("Genesis block error: {0}")]
#[error("genesis block error")]
GenesisBlockError(#[from] GenesisError),
#[error("Pool build error: {0}")]
#[error("pool build error")]
PoolBuildError(#[from] deadpool_sqlite::BuildError),
#[error("SQLite migration error: {0}")]
#[error("SQLite migration error")]
SqliteMigrationError(#[from] rusqlite_migration::Error),
}

#[derive(Debug, Error)]
pub enum GenesisError {
// ERRORS WITH AUTOMATIC CONVERSIONS FROM NESTED ERROR TYPES
// ---------------------------------------------------------------------------------------------
#[error("Database error: {0}")]
#[error("database error")]
DatabaseError(#[from] DatabaseError),
#[error("Block error: {0}")]
#[error("block error")]
BlockError(#[from] BlockError),
#[error("Merkle error: {0}")]
#[error("merkle error")]
MerkleError(#[from] MerkleError),

Choose a reason for hiding this comment

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

Might be worthwhile to check if any of these could be made more concrete as well.

Comment on lines -155 to 154
#[error("Apply block failed: {0}")]
#[error("apply block failed: {0}")]
ApplyBlockFailed(String),

Choose a reason for hiding this comment

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

Should we make this a #[source] error instead and make the error message say "genesis block"?

Comment on lines +155 to 159
#[error("failed to read genesis file \"{genesis_filepath}\": {error}")]
FailedToReadGenesisFile {
genesis_filepath: String,
error: io::Error,
},

Choose a reason for hiding this comment

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

Suggested change
#[error("failed to read genesis file \"{genesis_filepath}\": {error}")]
FailedToReadGenesisFile {
genesis_filepath: String,
error: io::Error,
},
#[error("failed to read genesis file \"{genesis_filepath}\"")]
FailedToReadGenesisFile {
genesis_filepath: String,
source: io::Error,
},

Comment on lines +175 to 176
#[error("invalid tx hash: expected {expected}, but got {actual}")]
InvalidTxHash { expected: RpoDigest, actual: RpoDigest },

Choose a reason for hiding this comment

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

Suggested change
#[error("invalid tx hash: expected {expected}, but got {actual}")]
InvalidTxHash { expected: RpoDigest, actual: RpoDigest },
#[error("invalid block tx hash: expected {expected}, but got {actual}")]
InvalidBlockTxHash { expected: RpoDigest, actual: RpoDigest },

Nit: This seems to be specific to the block tx hash.

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

Successfully merging this pull request may close these issues.

4 participants