Skip to content

Commit

Permalink
dev: error handling (kkrt-labs#865)
Browse files Browse the repository at this point in the history
* add inlines

* remove unnecessary trait bounds and functions

* refactor error handling

* fix tests

* fix rebasing

* add docs

* lint

* update log_map_err API

* use `inspect_err`

* fix rebasing
  • Loading branch information
greged93 authored Mar 19, 2024
1 parent ad866f0 commit 4e4a112
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 282 deletions.
2 changes: 1 addition & 1 deletion docker/hive/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ COPY . .

### Build the RPC server
# Define ARG for build platform
FROM rust:1.75 as rpc
FROM rust:1.76 as rpc

RUN apt-get update && apt-get install -y \
gcc-aarch64-linux-gnu libssl-dev clang libclang-dev
Expand Down
7 changes: 4 additions & 3 deletions src/eth_provider/contracts/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use reth_primitives::{BlockId, U256};
use reth_rpc_types::request::TransactionInput;
use reth_rpc_types::TransactionRequest;

use crate::eth_provider::error::KakarotError;
use crate::eth_provider::provider::EthProviderResult;
use crate::eth_provider::provider::EthereumProvider;
use crate::models::errors::ConversionError;

// abigen generates a lot of unused code, needs to be benchmarked if performances ever become a
// concern
Expand Down Expand Up @@ -48,8 +48,9 @@ impl<P: EthereumProvider> EthereumErc20<P> {
};

let ret = self.provider.call(request, Some(block_id)).await?;
let balance = U256::try_from_be_slice(&ret)
.ok_or_else(|| ConversionError::UintConversionError("Failed to convert call return to U256".to_string()))?;
let balance = U256::try_from_be_slice(&ret).ok_or(KakarotError::CallError(
cainome::cairo_serde::Error::Deserialize("failed to deserialize balance".to_string()),
))?;

Ok(balance)
}
Expand Down
12 changes: 7 additions & 5 deletions src/eth_provider/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use mongodb::{
};
use serde::de::DeserializeOwned;

use super::provider::EthProviderResult;
use super::error::KakarotError;

type DatabaseResult<T> = eyre::Result<T, KakarotError>;

/// Wrapper around a MongoDB database
pub struct Database(MongoDatabase);
Expand All @@ -26,9 +28,9 @@ impl Database {
collection: &str,
filter: impl Into<Option<Document>>,
project: impl Into<Option<Document>>,
) -> EthProviderResult<Vec<T>>
) -> DatabaseResult<Vec<T>>
where
T: DeserializeOwned + Unpin + Send + Sync,
T: DeserializeOwned,
{
let find_options = FindOptions::builder().projection(project).build();
let collection = self.0.collection::<T>(collection);
Expand All @@ -42,7 +44,7 @@ impl Database {
collection: &str,
filter: impl Into<Option<Document>>,
sort: impl Into<Option<Document>>,
) -> EthProviderResult<Option<T>>
) -> DatabaseResult<Option<T>>
where
T: DeserializeOwned + Unpin + Send + Sync,
{
Expand All @@ -53,7 +55,7 @@ impl Database {
}

/// Count the number of documents in a collection matching the filter
pub async fn count(&self, collection: &str, filter: impl Into<Option<Document>>) -> EthProviderResult<u64> {
pub async fn count(&self, collection: &str, filter: impl Into<Option<Document>>) -> DatabaseResult<u64> {
let collection = self.0.collection::<Document>(collection);
let count = collection.count_documents(filter, None).await?;
Ok(count)
Expand Down
205 changes: 171 additions & 34 deletions src/eth_provider/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use jsonrpsee::types::ErrorObject;
use thiserror::Error;

use crate::models::felt::ConversionError;

/// List of JSON-RPC error codes from ETH rpc spec.
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
Expand All @@ -22,47 +24,47 @@ pub enum EthRpcErrorCode {
JsonRpcVersionUnsupported = -32006,
}

/// Error that can occur when interacting with the provider.
/// Error that can occur when interacting with the ETH Api.
#[derive(Debug, Error)]
pub enum EthProviderError {
/// MongoDB error.
#[error(transparent)]
MongoDbError(#[from] mongodb::error::Error),
/// Starknet Provider error.
#[error(transparent)]
StarknetProviderError(#[from] starknet::providers::ProviderError),
/// EVM execution error.
#[error("EVM execution error: {0}")]
EvmExecutionError(String),
/// Contract call error.
#[error(transparent)]
ContractCallError(#[from] cainome::cairo_serde::Error),
pub enum EthApiError {
/// When a block is not found
#[error("unknown block")]
UnknownBlock,
/// When an unknown block number is encountered
#[error("unknown block number")]
UnknownBlockNumber,
/// When an invalid block range is provided
#[error("invalid block range")]
InvalidBlockRange,
/// Conversion error.
#[error("transaction conversion error")]
TransactionConversionError,
/// Error related to transaction
#[error(transparent)]
ConversionError(#[from] crate::models::errors::ConversionError),
/// Value not found in the database.
#[error("{0} not found.")]
ValueNotFound(String),
/// Method not supported.
#[error("Method not supported: {0}")]
MethodNotSupported(String),
/// Other error.
#[error(transparent)]
Other(#[from] eyre::Error),
TransactionError(#[from] TransactionError),
/// Error related to signing
#[error("signature error")]
SignatureError(#[from] SignatureError),
/// Unsupported feature
#[error("unsupported")]
Unsupported(&'static str),
/// Other internal error
#[error("internal error")]
Internal(KakarotError),
}

impl From<EthProviderError> for ErrorObject<'static> {
fn from(value: EthProviderError) -> Self {
impl From<EthApiError> for ErrorObject<'static> {
fn from(value: EthApiError) -> Self {
let msg = value.to_string();
match value {
EthProviderError::MongoDbError(msg) => rpc_err(EthRpcErrorCode::ResourceNotFound, msg.to_string()),
EthProviderError::StarknetProviderError(msg) => rpc_err(EthRpcErrorCode::InternalError, msg.to_string()),
EthProviderError::EvmExecutionError(_) => rpc_err(EthRpcErrorCode::ExecutionError, msg),
EthProviderError::ContractCallError(msg) => rpc_err(EthRpcErrorCode::ExecutionError, msg.to_string()),
EthProviderError::ConversionError(msg) => rpc_err(EthRpcErrorCode::ParseError, msg.to_string()),
EthProviderError::ValueNotFound(_) => rpc_err(EthRpcErrorCode::ResourceNotFound, msg),
EthProviderError::MethodNotSupported(_) => rpc_err(EthRpcErrorCode::MethodNotSupported, msg),
EthProviderError::Other(msg) => rpc_err(EthRpcErrorCode::InternalError, msg.to_string()),
EthApiError::UnknownBlock => rpc_err(EthRpcErrorCode::ResourceNotFound, msg),
EthApiError::UnknownBlockNumber => rpc_err(EthRpcErrorCode::ResourceNotFound, msg),
EthApiError::InvalidBlockRange => rpc_err(EthRpcErrorCode::InvalidParams, msg),
EthApiError::TransactionConversionError => rpc_err(EthRpcErrorCode::InvalidParams, msg),
EthApiError::TransactionError(err) => rpc_err(err.error_code(), msg),
EthApiError::SignatureError(_) => rpc_err(EthRpcErrorCode::InvalidParams, msg),
EthApiError::Unsupported(_) => rpc_err(EthRpcErrorCode::InternalError, msg),
EthApiError::Internal(_) => rpc_err(EthRpcErrorCode::InternalError, msg),
}
}
}
Expand All @@ -71,3 +73,138 @@ impl From<EthProviderError> for ErrorObject<'static> {
pub fn rpc_err(code: EthRpcErrorCode, msg: impl Into<String>) -> jsonrpsee::types::error::ErrorObject<'static> {
jsonrpsee::types::error::ErrorObject::owned(code as i32, msg.into(), None::<()>)
}

/// Error related to the Kakarot eth provider
/// which utilizes the starknet provider and
/// a database internally.
#[derive(Debug, Error)]
pub enum KakarotError {
/// Error related to the starknet provider.
#[error(transparent)]
ProviderError(#[from] starknet::providers::ProviderError),
/// Error related to the database.
#[error(transparent)]
DatabaseError(#[from] mongodb::error::Error),
/// Error related to the evm execution.
#[error(transparent)]
ExecutionError(EvmError),
/// Error related to a starknet call.
#[error(transparent)]
CallError(#[from] cainome::cairo_serde::Error),
/// Error related to starknet to eth conversion or vice versa.
#[error(transparent)]
ConversionError(#[from] ConversionError),
}

impl From<KakarotError> for EthApiError {
fn from(value: KakarotError) -> Self {
EthApiError::Internal(value)
}
}

/// Error related to EVM execution.
#[derive(Debug, Error)]
pub enum EvmError {
#[error("validation failed")]
ValidationError,
#[error("state modification error")]
StateModificationError,
#[error("unknown opcode")]
UnknownOpcode,
#[error("invalid jump dest")]
InvalidJumpDest,
#[error("invalid caller")]
NotKakarotEoaCaller,
#[error("view function error")]
ViewFunctionError,
#[error("stack overflow")]
StackOverflow,
#[error("stack underflow")]
StackUnderflow,
#[error("out of bounds read")]
OutOfBoundsRead,
#[error("unknown precompile {0}")]
UnknownPrecompile(String),
#[error("not implemented precompile {0}")]
NotImplementedPrecompile(String),
#[error("precompile input error")]
PrecompileInputError,
#[error("precompile flag error")]
PrecompileFlagError,
#[error("balance error")]
BalanceError,
#[error("address collision")]
AddressCollision,
#[error("out of gas")]
OutOfGas,
#[error("{0}")]
Other(String),
}

impl From<EvmError> for KakarotError {
fn from(value: EvmError) -> Self {
KakarotError::ExecutionError(value)
}
}

impl From<String> for EvmError {
fn from(value: String) -> Self {
let trimmed = value.as_str().trim_start_matches("Kakarot: ").trim_start_matches("Precompile: ");
match trimmed {
"eth validation failed" => EvmError::ValidationError,
"StateModificationError" => EvmError::StateModificationError,
"UnknownOpcode" => EvmError::UnknownOpcode,
"invalidJumpDestError" => EvmError::InvalidJumpDest,
"caller contract is not a Kakarot account" => EvmError::NotKakarotEoaCaller,
"entrypoint should only be called in view mode" => EvmError::ViewFunctionError,
"StackOverflow" => EvmError::StackOverflow,
"StackUnderflow" => EvmError::StackUnderflow,
"OutOfBoundsRead" => EvmError::OutOfBoundsRead,
s if s.contains("UnknownPrecompile") => {
EvmError::UnknownPrecompile(s.trim_start_matches("UnknownPrecompile ").to_string())
}
s if s.contains("NotImplementedPrecompile") => {
EvmError::NotImplementedPrecompile(s.trim_start_matches("NotImplementedPrecompile ").to_string())
}
"wrong input_length" => EvmError::PrecompileInputError,
"flag error" => EvmError::PrecompileFlagError,
"transfer amount exceeds balance" => EvmError::BalanceError,
"AddressCollision" => EvmError::AddressCollision,
s if s.contains("outOfGas") => EvmError::OutOfGas,
_ => EvmError::Other(value),
}
}
}

/// Error related to a transaction.
#[derive(Debug, Error)]
pub enum TransactionError {
/// Thrown when the chain id is invalid.
#[error("invalid chain id")]
InvalidChainId,
/// Thrown when the gas used overflows u128.
#[error("gas overflow")]
GasOverflow,
}

impl TransactionError {
pub fn error_code(&self) -> EthRpcErrorCode {
match self {
TransactionError::InvalidChainId => EthRpcErrorCode::InvalidInput,
TransactionError::GasOverflow => EthRpcErrorCode::TransactionRejected,
}
}
}

/// Error related to signature.
#[derive(Debug, Error)]
pub enum SignatureError {
/// Thrown when signer recovery fails.
#[error("could not recover signer")]
RecoveryError,
/// Thrown when signing fails.
#[error("failed to sign")]
SignError,
#[error("missing signature")]
MissingSignature,
}
Loading

0 comments on commit 4e4a112

Please sign in to comment.