Skip to content

Commit

Permalink
refactor(katana-rpc-types): include api error data when converting to…
Browse files Browse the repository at this point in the history
… rpc error (starkware-libs#1717)

* refactor(katana-rpc-types): include data in error if struct variant

* use method for getting error message
  • Loading branch information
kariy authored Mar 28, 2024
1 parent 57fa2dc commit f84d207
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 27 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/katana/rpc/rpc-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ ethers = "2.0.11"
futures.workspace = true
jsonrpsee = { workspace = true, features = [ "macros", "server" ] }
serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
starknet.workspace = true
thiserror.workspace = true

[dev-dependencies]
serde_json.workspace = true
rstest.workspace = true
152 changes: 126 additions & 26 deletions crates/katana/rpc/rpc-types/src/error/starknet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ use jsonrpsee::types::error::CallError;
use jsonrpsee::types::ErrorObject;
use katana_core::sequencer_error::SequencerError;
use katana_provider::error::ProviderError;
use starknet::core::types::ContractErrorData;
use serde::Serialize;

/// Possible list of errors that can be returned by the Starknet API according to the spec: <https://github.com/starkware-libs/starknet-specs>.
#[derive(Debug, thiserror::Error, Clone)]
#[derive(Debug, thiserror::Error, Clone, Serialize)]
#[serde(untagged)]
#[repr(i32)]
pub enum StarknetApiError {
#[error("Failed to write transaction")]
Expand Down Expand Up @@ -111,37 +112,29 @@ impl StarknetApiError {
StarknetApiError::ProofLimitExceeded => 10000,
}
}
}

#[derive(serde::Serialize, serde::Deserialize)]
struct UnexpectedError {
reason: String,
}
pub fn message(&self) -> String {
self.to_string()
}

impl From<ProviderError> for StarknetApiError {
fn from(value: ProviderError) -> Self {
StarknetApiError::UnexpectedError { reason: value.to_string() }
pub fn data(&self) -> Option<serde_json::Value> {
match self {
StarknetApiError::ContractError { .. }
| StarknetApiError::UnexpectedError { .. }
| StarknetApiError::TransactionExecutionError { .. } => Some(serde_json::json!(self)),
_ => None,
}
}
}

impl From<StarknetApiError> for Error {
fn from(err: StarknetApiError) -> Self {
let code = err.code();
let message = err.to_string();

let err = match err {
StarknetApiError::ContractError { revert_error } => {
ErrorObject::owned(code, message, Some(ContractErrorData { revert_error }))
}

StarknetApiError::UnexpectedError { reason } => {
ErrorObject::owned(code, message, Some(UnexpectedError { reason }))
}

_ => ErrorObject::owned(code, message, None::<()>),
};

Error::Call(CallError::Custom(err))
Error::Call(CallError::Custom(ErrorObject::owned(err.code(), err.message(), err.data())))
}
}
impl From<ProviderError> for StarknetApiError {
fn from(value: ProviderError) -> Self {
StarknetApiError::UnexpectedError { reason: value.to_string() }
}
}

Expand All @@ -154,3 +147,110 @@ impl From<SequencerError> for StarknetApiError {
}
}
}

#[cfg(test)]
mod tests {
use rstest::rstest;
use serde_json::json;

use super::*;

#[rustfmt::skip]
#[rstest]
#[case(StarknetApiError::NoBlocks, 32, "There are no blocks")]
#[case(StarknetApiError::BlockNotFound, 24, "Block not found")]
#[case(StarknetApiError::InvalidCallData, 22, "Invalid call data")]
#[case(StarknetApiError::ContractNotFound, 20, "Contract not found")]
#[case(StarknetApiError::CompilationFailed, 56, "Compilation failed")]
#[case(StarknetApiError::ClassHashNotFound, 28, "Class hash not found")]
#[case(StarknetApiError::TxnHashNotFound, 29, "Transaction hash not found")]
#[case(StarknetApiError::ValidationFailure, 55, "Account validation failed")]
#[case(StarknetApiError::ClassAlreadyDeclared, 51, "Class already declared")]
#[case(StarknetApiError::InvalidContractClass, 50, "Invalid contract class")]
#[case(StarknetApiError::PageSizeTooBig, 31, "Requested page size is too big")]
#[case(StarknetApiError::FailedToReceiveTxn, 1, "Failed to write transaction")]
#[case(StarknetApiError::InvalidMessageSelector, 21, "Invalid message selector")]
#[case(StarknetApiError::InvalidTransactionNonce, 52, "Invalid transaction nonce")]
#[case(StarknetApiError::NonAccount, 58, "Sender address in not an account contract")]
#[case(StarknetApiError::InvalidTxnIndex, 27, "Invalid transaction index in a block")]
#[case(StarknetApiError::ProofLimitExceeded, 10000, "Too many storage keys requested")]
#[case(StarknetApiError::TooManyKeysInFilter, 34, "Too many keys provided in a filter")]
#[case(StarknetApiError::ContractClassSizeIsTooLarge, 57, "Contract class size is too large")]
#[case(StarknetApiError::FailedToFetchPendingTransactions, 38, "Failed to fetch pending transactions")]
#[case(StarknetApiError::UnsupportedTransactionVersion, 61, "The transaction version is not supported")]
#[case(StarknetApiError::UnsupportedContractClassVersion, 62, "The contract class version is not supported")]
#[case(StarknetApiError::InvalidContinuationToken, 33, "The supplied continuation token is invalid or unknown")]
#[case(StarknetApiError::DuplicateTransaction, 59, "A transaction with the same hash already exists in the mempool")]
#[case(StarknetApiError::InsufficientAccountBalance, 54, "Account balance is smaller than the transaction's max_fee")]
#[case(StarknetApiError::CompiledClassHashMismatch, 60, "The compiled class hash did not match the one supplied in the transaction")]
#[case(StarknetApiError::InsufficientMaxFee, 53, "Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")]
fn test_starknet_api_error_to_error_conversion_data_none(
#[case] starknet_error: StarknetApiError,
#[case] expected_code: i32,
#[case] expected_message: &str,
) {
let error: Error = starknet_error.into();
match error {
Error::Call(CallError::Custom(err)) => {
assert_eq!(err.code(), expected_code);
assert_eq!(err.message(), expected_message);
assert!(err.data().is_none(), "data should be None");
}
_ => panic!("Unexpected error variant"),
}
}

#[rstest]
#[case(
StarknetApiError::ContractError {
revert_error: "Contract error message".to_string(),
},
40,
"Contract error",
json!({
"revert_error": "Contract error message".to_string()
}),
)]
#[case(
StarknetApiError::TransactionExecutionError {
transaction_index: 1,
execution_error: "Transaction execution error message".to_string(),
},
41,
"Transaction execution error",
json!({
"transaction_index": 1,
"execution_error": "Transaction execution error message".to_string()
}),
)]
#[case(
StarknetApiError::UnexpectedError {
reason: "Unexpected error reason".to_string(),
},
63,
"An unexpected error occured",
json!({
"reason": "Unexpected error reason".to_string()
}),
)]
fn test_starknet_api_error_to_error_conversion_data_some(
#[case] starknet_error: StarknetApiError,
#[case] expected_code: i32,
#[case] expected_message: &str,
#[case] expected_data: serde_json::Value,
) {
let error: Error = starknet_error.into();
match error {
Error::Call(CallError::Custom(err)) => {
assert_eq!(err.code(), expected_code);
assert_eq!(err.message(), expected_message);
assert_eq!(
err.data().unwrap().to_string(),
expected_data.to_string(),
"data should exist"
);
}
_ => panic!("Unexpected error variant"),
}
}
}

0 comments on commit f84d207

Please sign in to comment.