diff --git a/CHANGELOG.md b/CHANGELOG.md index eef12fe707..5ed40e1cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ git # Madara Changelog ## Next release +- feat(rpc): add_invoke_tx, add_deploy_account_tx, add_declare_tx - feat(rpc): tx_receipt, re-execute tx - feat(script): added CI scripts for starting Deoxys and comparing JSON RPC calls diff --git a/Cargo.lock b/Cargo.lock index 034dad6c47..97208525bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6084,6 +6084,7 @@ dependencies = [ "sp-runtime", "starknet-core", "starknet-ff 0.3.5 (git+https://github.com/jbcaron/starknet-rs.git?branch=classes)", + "starknet-providers", "starknet_api", "thiserror", "tokio", diff --git a/crates/client/rpc/Cargo.toml b/crates/client/rpc/Cargo.toml index b3476875d0..b3c878fafb 100644 --- a/crates/client/rpc/Cargo.toml +++ b/crates/client/rpc/Cargo.toml @@ -44,6 +44,7 @@ blockifier = { workspace = true, default-features = true } starknet-core = { workspace = true } starknet-ff = { workspace = true } starknet_api = { workspace = true, default-features = true } +starknet-providers = { workspace = true } # Others anyhow = { workspace = true } hex = { workspace = true, default-features = true } diff --git a/crates/client/rpc/src/errors.rs b/crates/client/rpc/src/errors.rs index 65b26f3085..932360dc2a 100644 --- a/crates/client/rpc/src/errors.rs +++ b/crates/client/rpc/src/errors.rs @@ -1,5 +1,6 @@ use jsonrpsee::types::error::{CallError, ErrorObject}; use pallet_starknet_runtime_api::StarknetTransactionExecutionError; +use starknet_core::types::StarknetError; // Comes from the RPC Spec: // https://github.com/starkware-libs/starknet-specs/blob/0e859ff905795f789f1dfd6f7340cdaf5015acc8/api/starknet_write_api.json#L227 @@ -11,6 +12,10 @@ pub enum StarknetRpcApiError { ContractNotFound = 20, #[error("Block not found")] BlockNotFound = 24, + #[error("Invalid transaction hash")] + InvalidTxnHash = 25, + #[error("Invalid tblock hash")] + InvalidBlockHash = 26, #[error("Invalid transaction index in a block")] InvalidTxnIndex = 27, #[error("Class hash not found")] @@ -29,14 +34,36 @@ pub enum StarknetRpcApiError { FailedToFetchPendingTransactions = 38, #[error("Contract error")] ContractError = 40, + #[error("Transaction execution error")] + TxnExecutionError = 41, #[error("Invalid contract class")] InvalidContractClass = 50, #[error("Class already declared")] ClassAlreadyDeclared = 51, + #[error("Invalid transaction nonce")] + InvalidTxnNonce = 52, + #[error("Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")] + InsufficientMaxFee = 53, + #[error("Account balance is smaller than the transaction's max_fee")] + InsufficientAccountBalance = 54, #[error("Account validation failed")] ValidationFailure = 55, + #[error("Compilation failed")] + CompilationFailed = 56, + #[error("Contract class size is too large")] + ContractClassSizeTooLarge = 57, + #[error("Sender address is not an account contract")] + NonAccount = 58, + #[error("A transaction with the same hash already exists in the mempool")] + DuplicateTxn = 59, + #[error("The compiled class hash did not match the one supplied in the transaction")] + CompiledClassHashMismatch = 60, #[error("The transaction version is not supported")] - UnsupportedTxVersion = 61, + UnsupportedTxnVersion = 61, + #[error("The contract class version is not supported")] + UnsupportedContractClassVersion = 62, + #[error("An unexpected error occurred")] + ErrUnexpectedError = 63, #[error("Internal server error")] InternalServerError = 500, #[error("Unimplemented method")] @@ -62,3 +89,36 @@ impl From for jsonrpsee::core::Error { jsonrpsee::core::Error::Call(CallError::Custom(ErrorObject::owned(err as i32, err.to_string(), None::<()>))) } } + +impl From for StarknetRpcApiError { + fn from(err: StarknetError) -> Self { + match err { + StarknetError::FailedToReceiveTransaction => StarknetRpcApiError::FailedToReceiveTxn, + StarknetError::ContractNotFound => StarknetRpcApiError::ContractNotFound, + StarknetError::BlockNotFound => StarknetRpcApiError::BlockNotFound, + StarknetError::InvalidTransactionIndex => StarknetRpcApiError::InvalidTxnIndex, + StarknetError::ClassHashNotFound => StarknetRpcApiError::ClassHashNotFound, + StarknetError::TransactionHashNotFound => StarknetRpcApiError::TxnHashNotFound, + StarknetError::PageSizeTooBig => StarknetRpcApiError::PageSizeTooBig, + StarknetError::NoBlocks => StarknetRpcApiError::NoBlocks, + StarknetError::InvalidContinuationToken => StarknetRpcApiError::InvalidContinuationToken, + StarknetError::TooManyKeysInFilter => StarknetRpcApiError::TooManyKeysInFilter, + StarknetError::ContractError(_) => StarknetRpcApiError::ContractError, + StarknetError::ClassAlreadyDeclared => StarknetRpcApiError::ClassAlreadyDeclared, + StarknetError::InvalidTransactionNonce => StarknetRpcApiError::InvalidTxnNonce, + StarknetError::InsufficientMaxFee => StarknetRpcApiError::InsufficientMaxFee, + StarknetError::InsufficientAccountBalance => StarknetRpcApiError::InsufficientAccountBalance, + StarknetError::ValidationFailure => StarknetRpcApiError::ValidationFailure, + StarknetError::CompilationFailed => StarknetRpcApiError::CompilationFailed, + StarknetError::ContractClassSizeIsTooLarge => StarknetRpcApiError::ContractClassSizeTooLarge, + StarknetError::NonAccount => StarknetRpcApiError::NonAccount, + StarknetError::DuplicateTx => StarknetRpcApiError::DuplicateTxn, + StarknetError::CompiledClassHashMismatch => StarknetRpcApiError::CompiledClassHashMismatch, + StarknetError::UnsupportedTxVersion => StarknetRpcApiError::UnsupportedTxnVersion, + StarknetError::UnsupportedContractClassVersion => StarknetRpcApiError::UnsupportedContractClassVersion, + StarknetError::UnexpectedError(_) => StarknetRpcApiError::ErrUnexpectedError, + StarknetError::NoTraceAvailable(_) => StarknetRpcApiError::InternalServerError, + StarknetError::InvalidTransactionHash => StarknetRpcApiError::InvalidTxnHash, + } + } +} diff --git a/crates/client/rpc/src/lib.rs b/crates/client/rpc/src/lib.rs index 91d3239803..5f14fa6911 100644 --- a/crates/client/rpc/src/lib.rs +++ b/crates/client/rpc/src/lib.rs @@ -64,6 +64,7 @@ use starknet_core::types::{ MaybePendingTransactionReceipt, MsgFromL1, MsgToL1, StateDiff, StateUpdate, SyncStatus, SyncStatusType, Transaction, TransactionExecutionStatus, TransactionFinalityStatus, TransactionReceipt, }; +use starknet_providers::{Provider, ProviderError, SequencerGatewayProvider}; use crate::constants::{MAX_EVENTS_CHUNK_SIZE, MAX_EVENTS_KEYS}; use crate::types::RpcEventFilter; @@ -293,54 +294,21 @@ where &self, declare_transaction: BroadcastedDeclareTransaction, ) -> RpcResult { - let best_block_hash = self.client.info().best_hash; - - let opt_sierra_contract_class = if let BroadcastedDeclareTransaction::V2(ref tx) = declare_transaction { - Some(flattened_sierra_to_sierra_contract_class(tx.contract_class.clone())) - } else { - None - }; - - let transaction: UserTransaction = declare_transaction.try_into().map_err(|e| { - error!("Failed to convert BroadcastedDeclareTransaction to UserTransaction, error: {e}"); - StarknetRpcApiError::InternalServerError - })?; - let class_hash = match transaction { - UserTransaction::Declare(ref tx, _) => tx.class_hash(), - _ => Err(StarknetRpcApiError::InternalServerError)?, - }; - - let current_block_hash = self.client.info().best_hash; - let contract_class = self - .overrides - .for_block_hash(self.client.as_ref(), current_block_hash) - .contract_class_by_class_hash(current_block_hash, (*class_hash).into()); + let config = get_config(); + let sequencer = SequencerGatewayProvider::new(config.feeder_gateway, config.gateway, config.chain_id); - if let Some(contract_class) = contract_class { - error!("Contract class already exists: {:?}", contract_class); - return Err(StarknetRpcApiError::ClassAlreadyDeclared.into()); - } - - let extrinsic = convert_tx_to_extrinsic(self.client.clone(), best_block_hash, transaction.clone()).await?; - - submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?; - - let chain_id = Felt252Wrapper(self.chain_id()?.0); - - let tx_hash = transaction.compute_hash::(chain_id, false, None).into(); - - if let Some(sierra_contract_class) = opt_sierra_contract_class { - if let Some(e) = self - .backend - .sierra_classes() - .store_sierra_class(Felt252Wrapper::from(class_hash.0).into(), sierra_contract_class) - .err() - { - log::error!("Failed to store the sierra contract class for declare tx `{tx_hash:x}`: {e}") + let sequencer_response = match sequencer.add_declare_transaction(declare_transaction).await { + Ok(response) => response, + Err(ProviderError::StarknetError(e)) => { + return Err(StarknetRpcApiError::from(e).into()); } - } + Err(e) => { + error!("Failed to add invoke transaction to sequencer: {e}"); + return Err(StarknetRpcApiError::InternalServerError.into()); + } + }; - Ok(DeclareTransactionResult { transaction_hash: tx_hash, class_hash: class_hash.0 }) + Ok(sequencer_response) } /// Add an Invoke Transaction to invoke a contract function @@ -356,20 +324,21 @@ where &self, invoke_transaction: BroadcastedInvokeTransaction, ) -> RpcResult { - let best_block_hash = self.client.info().best_hash; - - let transaction: UserTransaction = invoke_transaction.try_into().map_err(|e| { - error!("Failed to convert BroadcastedInvokeTransaction to UserTransaction: {e}"); - StarknetRpcApiError::InternalServerError - })?; - - let extrinsic = convert_tx_to_extrinsic(self.client.clone(), best_block_hash, transaction.clone()).await?; - - submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?; + let config = get_config(); + let sequencer = SequencerGatewayProvider::new(config.feeder_gateway, config.gateway, config.chain_id); - let chain_id = Felt252Wrapper(self.chain_id()?.0); + let sequencer_response = match sequencer.add_invoke_transaction(invoke_transaction).await { + Ok(response) => response, + Err(ProviderError::StarknetError(e)) => { + return Err(StarknetRpcApiError::from(e).into()); + } + Err(e) => { + error!("Failed to add invoke transaction to sequencer: {e}"); + return Err(StarknetRpcApiError::InternalServerError.into()); + } + }; - Ok(InvokeTransactionResult { transaction_hash: transaction.compute_hash::(chain_id, false, None).into() }) + Ok(sequencer_response) } /// Add an Deploy Account Transaction @@ -386,27 +355,21 @@ where &self, deploy_account_transaction: BroadcastedDeployAccountTransaction, ) -> RpcResult { - let best_block_hash = self.client.info().best_hash; - - let transaction: UserTransaction = deploy_account_transaction.try_into().map_err(|e| { - error!("Failed to convert BroadcastedDeployAccountTransaction to UserTransaction, error: {e}",); - StarknetRpcApiError::InternalServerError - })?; - - let extrinsic = convert_tx_to_extrinsic(self.client.clone(), best_block_hash, transaction.clone()).await?; - - submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?; + let config = get_config(); + let sequencer = SequencerGatewayProvider::new(config.feeder_gateway, config.gateway, config.chain_id); - let chain_id = Felt252Wrapper(self.chain_id()?.0); - let account_address = match &transaction { - UserTransaction::DeployAccount(tx) => tx.account_address(), - _ => Err(StarknetRpcApiError::InternalServerError)?, + let sequencer_response = match sequencer.add_deploy_account_transaction(deploy_account_transaction).await { + Ok(response) => response, + Err(ProviderError::StarknetError(e)) => { + return Err(StarknetRpcApiError::from(e).into()); + } + Err(e) => { + error!("Failed to add invoke transaction to sequencer: {e}"); + return Err(StarknetRpcApiError::InternalServerError.into()); + } }; - Ok(DeployAccountTransactionResult { - transaction_hash: transaction.compute_hash::(chain_id, false, None).into(), - contract_address: account_address.into(), - }) + Ok(sequencer_response) } }