diff --git a/CHANGELOG.md b/CHANGELOG.md index 36a65b28b3..d58ea79af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Next release +- chore: split StarknetRpcApi trait in two, like in openRPC specs - refacto: move starknet runtime api in it's own crate - chore: update README.md and getting-started.md - chore: remove crates that have been copy-pasted from plkdtSDK diff --git a/Cargo.lock b/Cargo.lock index 40f82dc6d9..0b3c0046b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6638,6 +6638,7 @@ dependencies = [ name = "mc-rpc" version = "0.5.0" dependencies = [ + "anyhow", "blockifier", "frame-support", "frame-system", diff --git a/crates/client/rpc-core/src/lib.rs b/crates/client/rpc-core/src/lib.rs index efaacba4ae..c23bec9fa4 100644 --- a/crates/client/rpc-core/src/lib.rs +++ b/crates/client/rpc-core/src/lib.rs @@ -27,6 +27,31 @@ use starknet_core::types::{ #[derive(Serialize, Deserialize)] pub struct Felt(#[serde_as(as = "UfeHex")] pub FieldElement); +/// Starknet write rpc interface. +#[rpc(server, namespace = "starknet")] +pub trait StarknetWriteRpcApi { + /// Submit a new transaction to be added to the chain + #[method(name = "addInvokeTransaction")] + async fn add_invoke_transaction( + &self, + invoke_transaction: BroadcastedInvokeTransaction, + ) -> RpcResult; + + /// Submit a new class declaration transaction + #[method(name = "addDeployAccountTransaction")] + async fn add_deploy_account_transaction( + &self, + deploy_account_transaction: BroadcastedDeployAccountTransaction, + ) -> RpcResult; + + /// Submit a new deploy account transaction + #[method(name = "addDeclareTransaction")] + async fn add_declare_transaction( + &self, + declare_transaction: BroadcastedDeclareTransaction, + ) -> RpcResult; +} + /// Starknet rpc interface. #[rpc(server, namespace = "starknet")] pub trait StarknetRpcApi { @@ -83,20 +108,6 @@ pub trait StarknetRpcApi { #[method(name = "chainId")] fn chain_id(&self) -> RpcResult; - /// Add an Invoke Transaction to invoke a contract function - #[method(name = "addInvokeTransaction")] - async fn add_invoke_transaction( - &self, - invoke_transaction: BroadcastedInvokeTransaction, - ) -> RpcResult; - - /// Add a Deploy Account Transaction - #[method(name = "addDeployAccountTransaction")] - async fn add_deploy_account_transaction( - &self, - deploy_account_transaction: BroadcastedDeployAccountTransaction, - ) -> RpcResult; - /// Estimate the fee associated with transaction #[method(name = "estimateFee")] async fn estimate_fee( @@ -121,13 +132,6 @@ pub trait StarknetRpcApi { #[method(name = "getEvents")] async fn get_events(&self, filter: EventFilterWithPage) -> RpcResult; - /// Submit a new transaction to be added to the chain - #[method(name = "addDeclareTransaction")] - async fn add_declare_transaction( - &self, - declare_transaction: BroadcastedDeclareTransaction, - ) -> RpcResult; - /// Returns the information about a transaction by transaction hash. #[method(name = "getTransactionByHash")] fn get_transaction_by_hash(&self, transaction_hash: FieldElement) -> RpcResult; diff --git a/crates/client/rpc/Cargo.toml b/crates/client/rpc/Cargo.toml index e425d67a42..f719688bf9 100644 --- a/crates/client/rpc/Cargo.toml +++ b/crates/client/rpc/Cargo.toml @@ -42,6 +42,7 @@ starknet-core = { workspace = true } starknet-ff = { workspace = true } starknet_api = { workspace = true, default-features = false } # Others +anyhow = { workspace = true } hex = { workspace = true, default-features = true } jsonrpsee = { workspace = true, default-features = true, features = [ "server", diff --git a/crates/client/rpc/src/lib.rs b/crates/client/rpc/src/lib.rs index a8d0601c10..9cae8bd82e 100644 --- a/crates/client/rpc/src/lib.rs +++ b/crates/client/rpc/src/lib.rs @@ -13,13 +13,13 @@ use std::sync::Arc; use errors::StarknetRpcApiError; use jsonrpsee::core::{async_trait, RpcResult}; +use jsonrpsee::types::error::CallError; use log::error; use mc_db::Backend as MadaraBackend; pub use mc_rpc_core::utils::*; -use mc_rpc_core::Felt; -pub use mc_rpc_core::StarknetRpcApiServer; +pub use mc_rpc_core::{Felt, StarknetRpcApiServer, StarknetWriteRpcApiServer}; use mc_storage::OverrideHandle; -use mp_felt::Felt252Wrapper; +use mp_felt::{Felt252Wrapper, Felt252WrapperError}; use mp_hashers::HasherT; use mp_transactions::compute_hash::ComputeTransactionHash; use mp_transactions::to_starknet_core_transaction::to_starknet_core_tx; @@ -93,7 +93,7 @@ impl Starknet { impl Starknet where B: BlockT, - C: HeaderBackend + BlockBackend + 'static, + C: HeaderBackend + 'static, { pub fn current_block_number(&self) -> RpcResult { Ok(UniqueSaturatedInto::::unique_saturated_into(self.client.info().best_number)) @@ -103,10 +103,7 @@ where impl Starknet where B: BlockT, - C: HeaderBackend + StorageProvider + 'static, - C: ProvideRuntimeApi, - C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, - BE: Backend, + C: HeaderBackend + 'static, H: HasherT + Send + Sync + 'static, { pub fn current_block_hash(&self) -> Result { @@ -174,6 +171,133 @@ where /// Taken from https://github.com/paritytech/substrate/blob/master/client/rpc/src/author/mod.rs#L78 const TX_SOURCE: TransactionSource = TransactionSource::External; +#[async_trait] +#[allow(unused_variables)] +impl StarknetWriteRpcApiServer for Starknet +where + A: ChainApi + 'static, + B: BlockT, + P: TransactionPool + 'static, + BE: Backend + 'static, + C: HeaderBackend + BlockBackend + StorageProvider + 'static, + C: ProvideRuntimeApi, + C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, + H: HasherT + Send + Sync + 'static, +{ + /// Submit a new declare transaction to be added to the chain + /// + /// # Arguments + /// + /// * `declare_transaction` - the declare transaction to be added to the chain + /// + /// # Returns + /// + /// * `declare_transaction_result` - the result of the declare transaction + async fn add_declare_transaction( + &self, + declare_transaction: BroadcastedDeclareTransaction, + ) -> RpcResult { + let best_block_hash = self.client.info().best_hash; + + 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()); + + if let Some(contract_class) = contract_class { + error!("Contract class already exists: {:?}", contract_class); + return Err(StarknetRpcApiError::ClassAlreadyDeclared.into()); + } + + let extrinsic = convert_transaction(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); + + Ok(DeclareTransactionResult { + transaction_hash: transaction.compute_hash::(chain_id, false).into(), + class_hash: class_hash.0, + }) + } + + /// Add an Invoke Transaction to invoke a contract function + /// + /// # Arguments + /// + /// * `invoke tx` - + /// + /// # Returns + /// + /// * `transaction_hash` - transaction hash corresponding to the invocation + async fn add_invoke_transaction( + &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_transaction(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); + + Ok(InvokeTransactionResult { transaction_hash: transaction.compute_hash::(chain_id, false).into() }) + } + + /// Add an Deploy Account Transaction + /// + /// # Arguments + /// + /// * `deploy account transaction` - + /// + /// # Returns + /// + /// * `transaction_hash` - transaction hash corresponding to the invocation + /// * `contract_address` - address of the deployed contract account + async fn add_deploy_account_transaction( + &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_transaction(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 account_address = match &transaction { + UserTransaction::DeployAccount(tx) => tx.account_address(), + _ => Err(StarknetRpcApiError::InternalServerError)?, + }; + + Ok(DeployAccountTransactionResult { + transaction_hash: transaction.compute_hash::(chain_id, false).into(), + contract_address: account_address.into(), + }) + } +} + #[async_trait] #[allow(unused_variables)] impl StarknetRpcApiServer for Starknet @@ -413,12 +537,17 @@ where let block = get_block_by_block_hash(self.client.as_ref(), substrate_block_hash).unwrap_or_default(); let chain_id = self.chain_id()?; - let blockhash = block.header().hash::(); + let block_hash = block.header().hash::(); - let transaction_hashes = if let Some(tx_hashes) = self.get_cached_transaction_hashes(blockhash.into()) { + let transaction_hashes = if let Some(tx_hashes) = self.get_cached_transaction_hashes(block_hash.into()) { let mut v = Vec::with_capacity(tx_hashes.len()); for tx_hash in tx_hashes { - v.push(h256_to_felt(tx_hash)?); + v.push(h256_to_felt(tx_hash).map_err(|e| { + CallError::Failed(anyhow::anyhow!( + "The hash cached for block with hash {block_hash:?} is an invalid felt: '{tx_hash}'. The \ + caching db has probably been tempered" + )) + })?); } v } else { @@ -430,7 +559,7 @@ where transactions: transaction_hashes, // TODO: Status hardcoded, get status from block status: BlockStatus::AcceptedOnL2, - block_hash: blockhash.into(), + block_hash: block_hash.into(), parent_hash: parent_blockhash.into(), block_number: block.header().block_number, new_root: block.header().global_state_root.into(), @@ -473,118 +602,6 @@ where Ok(Felt(chain_id.0)) } - /// Submit a new declare transaction to be added to the chain - /// - /// # Arguments - /// - /// * `declare_transaction` - the declare transaction to be added to the chain - /// - /// # Returns - /// - /// * `declare_transaction_result` - the result of the declare transaction - async fn add_declare_transaction( - &self, - declare_transaction: BroadcastedDeclareTransaction, - ) -> RpcResult { - let best_block_hash = self.client.info().best_hash; - - 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()); - if let Some(contract_class) = contract_class { - error!("Contract class already exists: {:?}", contract_class); - return Err(StarknetRpcApiError::ClassAlreadyDeclared.into()); - } - - let extrinsic = convert_transaction(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); - - Ok(DeclareTransactionResult { - transaction_hash: transaction.compute_hash::(chain_id, false).into(), - class_hash: class_hash.0, - }) - } - - /// Add an Invoke Transaction to invoke a contract function - /// - /// # Arguments - /// - /// * `invoke tx` - - /// - /// # Returns - /// - /// * `transaction_hash` - transaction hash corresponding to the invocation - async fn add_invoke_transaction( - &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_transaction(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); - - Ok(InvokeTransactionResult { transaction_hash: transaction.compute_hash::(chain_id, false).into() }) - } - - /// Add an Deploy Account Transaction - /// - /// # Arguments - /// - /// * `deploy account transaction` - - /// - /// # Returns - /// - /// * `transaction_hash` - transaction hash corresponding to the invocation - /// * `contract_address` - address of the deployed contract account - async fn add_deploy_account_transaction( - &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_transaction(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 account_address = match &transaction { - UserTransaction::DeployAccount(tx) => tx.account_address(), - _ => Err(StarknetRpcApiError::InternalServerError)?, - }; - - Ok(DeployAccountTransactionResult { - transaction_hash: transaction.compute_hash::(chain_id, false).into(), - contract_address: account_address.into(), - }) - } - /// Estimate the fee associated with transaction /// /// # Arguments @@ -661,10 +678,26 @@ where let transaction = block.transactions().get(index as usize).ok_or(StarknetRpcApiError::InvalidTxnIndex)?; let chain_id = self.chain_id()?; - let transaction_hash = self - .get_cached_transaction_hashes(block.header().hash::().into()) - .map(|tx_hashes| h256_to_felt(*tx_hashes.get(index as usize).ok_or(StarknetRpcApiError::InvalidTxnIndex)?)) - .unwrap_or_else(|| Ok(transaction.compute_hash::(chain_id.0.into(), false).0))?; + let opt_cached_transaction_hashes = self.get_cached_transaction_hashes(block.header().hash::().into()); + + let transaction_hash = if let Some(cached_tx_hashes) = opt_cached_transaction_hashes { + cached_tx_hashes + .get(index as usize) + .map(|&h| h256_to_felt(h)) + .ok_or(CallError::Failed(anyhow::anyhow!( + "Number of cached tx hashes does not match the number of transactions in block with id {:?}", + block_id + )))? + .map_err(|e| { + CallError::Failed(anyhow::anyhow!( + "The hash cached for tx at index {index} of block with id {:?} is an invalid felt. The \ + caching db has probably been tempered", + block_id + )) + })? + } else { + transaction.compute_hash::(chain_id.0.into(), false).0 + }; Ok(to_starknet_core_tx(transaction.clone(), transaction_hash)) } @@ -672,34 +705,44 @@ where /// Get block information with full transactions given the block id fn get_block_with_txs(&self, block_id: BlockId) -> RpcResult { let substrate_block_hash = self.substrate_block_hash_from_starknet_block(block_id).map_err(|e| { - error!("'{e}'"); + error!("Block not found: '{e}'"); StarknetRpcApiError::BlockNotFound })?; let block = get_block_by_block_hash(self.client.as_ref(), substrate_block_hash).unwrap_or_default(); + let block_hash = block.header().hash::(); let chain_id = self.chain_id()?; let chain_id = Felt252Wrapper(chain_id.0); - let transaction_hashes = self.get_cached_transaction_hashes(block.header().hash::().into()); + let opt_cached_transaction_hashes = self.get_cached_transaction_hashes(block.header().hash::().into()); let mut transactions = Vec::with_capacity(block.transactions().len()); for (index, tx) in block.transactions().iter().enumerate() { - let hash = transaction_hashes - .as_ref() - .map(|tx_hashes| { - h256_to_felt(*tx_hashes.get(index).ok_or_else(|| { - error!("Transaction hash not found at index: {index} in transaction hashes cache."); - StarknetRpcApiError::InternalServerError - })?) - }) - .unwrap_or_else(|| Ok(tx.compute_hash::(chain_id.0.into(), false).0))?; - transactions.push(to_starknet_core_tx(tx.clone(), hash)); + let tx_hash = if let Some(cached_tx_hashes) = opt_cached_transaction_hashes.as_ref() { + cached_tx_hashes + .get(index) + .map(|&h| h256_to_felt(h)) + .ok_or(CallError::Failed(anyhow::anyhow!( + "Number of cached tx hashes does not match the number of transactions in block with hash {:?}", + block_hash + )))? + .map_err(|e| { + CallError::Failed(anyhow::anyhow!( + "The hash cached for tx at index {index} of block with hash {block_hash:?} is an invalid \ + felt. The caching db has probably been tempered" + )) + })? + } else { + tx.compute_hash::(chain_id.0.into(), false).0 + }; + + transactions.push(to_starknet_core_tx(tx.clone(), tx_hash)); } let block_with_txs = BlockWithTxs { // TODO: Get status from block status: BlockStatus::AcceptedOnL2, - block_hash: block.header().hash::().into(), + block_hash: block_hash.into(), parent_hash: block.header().parent_block_hash.into(), block_number: block.header().block_number, new_root: block.header().global_state_root.into(), @@ -1144,12 +1187,6 @@ where } } -fn h256_to_felt(h256: H256) -> Result { - match Felt252Wrapper::try_from(h256) { - Ok(felt) => Ok(felt.0), - Err(e) => { - error!("failed to convert H256 to FieldElement: {e}"); - Err(StarknetRpcApiError::InternalServerError) - } - } +fn h256_to_felt(h256: H256) -> Result { + Felt252Wrapper::try_from(h256).map(|f| f.0) }