diff --git a/crates/client/src/rpc/impls/eth/debug.rs b/crates/client/src/rpc/impls/eth/debug.rs index 84e47d40f8..d580e5cdaf 100644 --- a/crates/client/src/rpc/impls/eth/debug.rs +++ b/crates/client/src/rpc/impls/eth/debug.rs @@ -1,7 +1,7 @@ use crate::rpc::{ errors::invalid_params_msg, traits::eth_space::debug::Debug, - types::eth::{BlockNumber, TransactionRequest}, + types::eth::{BlockNumber, Bundle, SimulationContext, TransactionRequest}, }; use alloy_rpc_types_trace::geth::{ GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, @@ -77,4 +77,17 @@ impl Debug for GethDebugHandler { .trace_call(request, block_number, opts) .map_err(|err| err.into()) } + + fn debug_trace_call_many( + &self, + bundles: Vec, + simulation_context: SimulationContext, + // state_override: Option, + // timeout: Option, + opts: Option, + ) -> JsonRpcResult> { + self.inner + .trace_call_many(bundles, simulation_context, opts) + .map_err(|err| err.into()) + } } diff --git a/crates/client/src/rpc/traits/eth_space/debug.rs b/crates/client/src/rpc/traits/eth_space/debug.rs index f8efe19fe4..f5ff13a4b7 100644 --- a/crates/client/src/rpc/traits/eth_space/debug.rs +++ b/crates/client/src/rpc/traits/eth_space/debug.rs @@ -1,4 +1,4 @@ -use crate::rpc::types::eth::{BlockNumber, TransactionRequest}; +use crate::rpc::types::eth::{BlockNumber, Bundle, SimulationContext, TransactionRequest}; use alloy_rpc_types_trace::geth::{ GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult, @@ -38,4 +38,14 @@ pub trait Debug { &self, request: TransactionRequest, block_number: Option, opts: Option, ) -> JsonRpcResult; + + #[rpc(name = "debug_traceCallMany")] + fn debug_trace_call_many( + &self, + bundles: Vec, + simulation_context: SimulationContext, + // state_override: Option, + // timeout: Option, + opts: Option, + ) -> JsonRpcResult>; } diff --git a/crates/client/src/rpc/types/eth/mod.rs b/crates/client/src/rpc/types/eth/mod.rs index 414b4ce3cc..3c01c886b6 100644 --- a/crates/client/src/rpc/types/eth/mod.rs +++ b/crates/client/src/rpc/types/eth/mod.rs @@ -6,7 +6,7 @@ pub use cfx_rpc_eth_types::{ eth_pubsub, trace::{LocalizedTrace, Res}, trace_filter::TraceFilter, - AccountPendingTransactions, Block, BlockNumber, EthRpcLogFilter, - FilterChanges, Header, Log, Receipt, SyncInfo, SyncStatus, Transaction, + AccountPendingTransactions, Block, BlockNumber, Bundle, EthRpcLogFilter, + FilterChanges, Header, Log, Receipt, SimulationContext, SyncInfo, SyncStatus, Transaction, TransactionRequest, }; diff --git a/crates/rpc/rpc-eth-api/src/debug.rs b/crates/rpc/rpc-eth-api/src/debug.rs index 969a53fe67..cec79eab6c 100644 --- a/crates/rpc/rpc-eth-api/src/debug.rs +++ b/crates/rpc/rpc-eth-api/src/debug.rs @@ -2,7 +2,7 @@ use alloy_rpc_types_trace::geth::{ GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult, }; -use cfx_rpc_eth_types::{BlockNumber, TransactionRequest}; +use cfx_rpc_eth_types::{BlockNumber, Bundle, SimulationContext, TransactionRequest}; use cfx_types::H256; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; @@ -36,4 +36,12 @@ pub trait DebugApi { &self, request: TransactionRequest, block_number: Option, opts: Option, ) -> RpcResult; + + #[method(name = "traceCallMany")] + async fn debug_trace_call_many( + &self, + bundles: Vec, + simulation_context: SimulationContext, + opts: Option, + ) -> RpcResult>; } diff --git a/crates/rpc/rpc-eth-types/src/bundle.rs b/crates/rpc/rpc-eth-types/src/bundle.rs new file mode 100644 index 0000000000..cb75e8d1b7 --- /dev/null +++ b/crates/rpc/rpc-eth-types/src/bundle.rs @@ -0,0 +1,11 @@ +use crate::TransactionRequest; +use serde::Deserialize; + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Bundle { + /// Transactions + pub transactions: Vec, + // /// BlockOverride + // pub block_override: Option, +} \ No newline at end of file diff --git a/crates/rpc/rpc-eth-types/src/lib.rs b/crates/rpc/rpc-eth-types/src/lib.rs index 866c0bff2a..8680603001 100644 --- a/crates/rpc/rpc-eth-types/src/lib.rs +++ b/crates/rpc/rpc-eth-types/src/lib.rs @@ -1,11 +1,13 @@ mod block; mod block_number; +mod bundle; mod errors; pub mod eth_pubsub; mod fee_history; mod filter; mod log; mod receipt; +mod simulation_context; mod sync; pub mod trace; pub mod trace_filter; @@ -15,6 +17,7 @@ mod tx_pool; pub use block::{Block, Header}; pub use block_number::BlockNumber; +pub use bundle::Bundle; pub use cfx_rpc_primitives::{Bytes, U64}; pub use errors::Error; pub use eth_pubsub::*; @@ -22,6 +25,7 @@ pub use fee_history::FeeHistory; pub use filter::*; pub use log::Log; pub use receipt::Receipt; +pub use simulation_context::SimulationContext; pub use sync::{SyncInfo, SyncStatus}; pub use trace::*; pub use trace_filter::TraceFilter; diff --git a/crates/rpc/rpc-eth-types/src/simulation_context.rs b/crates/rpc/rpc-eth-types/src/simulation_context.rs new file mode 100644 index 0000000000..376f228019 --- /dev/null +++ b/crates/rpc/rpc-eth-types/src/simulation_context.rs @@ -0,0 +1,11 @@ +use crate::BlockNumber; +use serde::Deserialize; + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SimulationContext { + /// BlockNumber + pub block_number: Option, + // /// TransactionIndex + // pub transaction_index: Option, +} \ No newline at end of file diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 2a2be11bc8..78ba1606f6 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -6,9 +6,9 @@ use alloy_rpc_types_trace::geth::{ }; use async_trait::async_trait; use cfx_rpc_eth_api::DebugApiServer; -use cfx_rpc_eth_types::{BlockNumber, TransactionRequest}; +use cfx_rpc_eth_types::{BlockNumber, Bundle, SimulationContext,TransactionRequest}; use cfx_rpc_utils::error::jsonrpsee_error_helpers::invalid_params_msg; -use cfx_types::{AddressSpaceUtil, Space, H256, U256}; +use cfx_types::{AddressSpaceUtil, Space, H256, U256, H160}; use cfxcore::{ errors::Error as CoreError, ConsensusGraph, ConsensusGraphTrait, SharedConsensusGraph, @@ -19,6 +19,7 @@ use primitives::{ Block, BlockHashOrEpochNumber, BlockHeaderBuilder, EpochNumber, }; use std::sync::Arc; +use std::collections::HashMap; pub struct DebugApi { consensus: SharedConsensusGraph, @@ -157,6 +158,135 @@ impl DebugApi { Ok(res.trace.clone()) } + pub fn trace_call_many( + &self, + bundles: Vec, + simulation_context: SimulationContext, + opts: Option, + ) -> Result, CoreError> { + let opts = opts.unwrap_or_default(); + let block_num = simulation_context.block_number.unwrap_or_default(); + + let epoch_num = self + .get_block_epoch_num(block_num) + .map_err(|err| CoreError::Msg(err))?; + + // validate epoch state + self.consensus_graph() + .validate_stated_epoch(&EpochNumber::Number(epoch_num)) + .map_err(|err| CoreError::Msg(err))?; + + let epoch_block_hashes = self + .consensus_graph() + .get_block_hashes_by_epoch(EpochNumber::Number(epoch_num)) + .map_err(|err| CoreError::Msg(err))?; + let epoch_id = epoch_block_hashes + .last() + .ok_or(CoreError::Msg("should have block hash".to_string()))?; + + // construct blocks from call_request + let chain_id = self.consensus.best_chain_id(); + + // construct transactions + let mut transactions = Vec::new(); + // manually manage nonce + let mut nonce_map: HashMap, Option> = HashMap::new(); + + for bundle in bundles { + let requests = bundle.transactions; + + for mut request in requests { + if request.from.is_none() { + return Err(CoreError::InvalidParam( + "from is required".to_string(), + Default::default(), + )); + } + + // nonce auto fill + if request.nonce.is_none() { + if let Some(value) = nonce_map.get(&request.from) { + let nonce = value.unwrap() + U256::from(1); + request.nonce = Some(nonce); + nonce_map.insert(request.from, request.nonce); + } else { + let nonce = self.consensus_graph().next_nonce( + request.from.unwrap().with_evm_space(), + BlockHashOrEpochNumber::EpochNumber(EpochNumber::Number( + epoch_num, + )), + "num", + )?; + request.nonce = Some(nonce); + nonce_map.insert(request.from, request.nonce); + } + } else { + if let Some(value) = nonce_map.get(&request.from) { + let nonce = value.unwrap() + U256::from(1); + if request.nonce.unwrap() != nonce { + continue; + } + nonce_map.insert(request.from, request.nonce); + } else { + let nonce = self.consensus_graph().next_nonce( + request.from.unwrap().with_evm_space(), + BlockHashOrEpochNumber::EpochNumber(EpochNumber::Number( + epoch_num, + )), + "num", + )?; + if request.nonce.unwrap() != nonce { + continue; + } + nonce_map.insert(request.from, request.nonce); + } + } + + let signed_tx = request.sign_call( + chain_id.in_evm_space(), + self.max_estimation_gas_limit, + )?; + + transactions.push(Arc::new(signed_tx)); + } + } + + let epoch_blocks = self + .consensus_graph() + .data_man + .blocks_by_hash_list( + &epoch_block_hashes, + true, /* update_cache */ + ) + .ok_or(CoreError::Msg("blocks should exist".to_string()))?; + let pivot_block = epoch_blocks + .last() + .ok_or(CoreError::Msg("should have block".to_string()))?; + + let header = BlockHeaderBuilder::new() + .with_base_price(pivot_block.block_header.base_price()) + .with_parent_hash(pivot_block.block_header.hash()) + .with_height(epoch_num + 1) + .with_timestamp(pivot_block.block_header.timestamp() + 1) + .with_gas_limit(*pivot_block.block_header.gas_limit()) + .build(); + let block = Block::new(header, transactions); + let blocks: Vec> = vec![Arc::new(block)]; + + let traces = self.consensus_graph().collect_blocks_geth_trace( + *epoch_id, + epoch_num, + &blocks, + opts.tracing_options, + None, + )?; + + let result = traces.iter().map(|val| val.trace.clone()).collect(); + + Ok(result) + } + + pub fn trace_block_by_num( &self, block_num: u64, opts: Option, ) -> Result, CoreError> { @@ -287,4 +417,16 @@ impl DebugApiServer for DebugApi { self.trace_call(request, block_number, opts) .map_err(|e| e.into()) } + + async fn debug_trace_call_many( + &self, + bundles: Vec, + simulation_context: SimulationContext, + // state_override: Option, + // timeout: Option, + opts: Option, + ) -> RpcResult> { + self.trace_call_many(bundles, simulation_context, opts) + .map_err(|e| e.into()) + } }