Skip to content

Commit

Permalink
feat: add erigons debugTraceCallMany (#3878)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
  • Loading branch information
0x6020c0 and mattsse authored Aug 3, 2023
1 parent 3f9c00e commit e9cb414
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 7 deletions.
23 changes: 22 additions & 1 deletion crates/rpc/rpc-api/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256};
use reth_rpc_types::{
state::StateOverride,
trace::geth::{
BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
TraceResult,
},
CallRequest, RichBlock,
Bundle, CallRequest, RichBlock, StateContext,
};

/// Debug rpc interface.
Expand Down Expand Up @@ -102,4 +103,24 @@ pub trait DebugApi {
block_number: Option<BlockId>,
opts: Option<GethDebugTracingCallOptions>,
) -> RpcResult<GethTrace>;

/// The `debug_traceCallMany` method lets you run an `eth_callmany` within the context of the
/// given block execution using the final state of parent block as the base followed by n
/// transactions
///
/// The first argument is a list of bundles. Each bundle can overwrite the block headers. This
/// will affect all transaction in that bundle.
/// BlockNumber and transaction_index are optinal. Transaction_index
/// specifys the number of tx in the block to replay and -1 means all transactions should be
/// replayed.
/// The trace can be configured similar to `debug_traceTransaction`.
/// State override apply to all bundles.
#[method(name = "traceCallMany")]
async fn debug_trace_call_many(
&self,
bundles: Vec<Bundle>,
state_context: Option<StateContext>,
opts: Option<GethDebugTracingOptions>,
state_override: Option<StateOverride>,
) -> RpcResult<Vec<GethTrace>>;
}
94 changes: 92 additions & 2 deletions crates/rpc/rpc-types/src/eth/call.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,80 @@
use reth_primitives::{AccessList, Address, Bytes, U256, U64, U8};
use serde::{Deserialize, Serialize};
use reth_primitives::{AccessList, Address, BlockId, Bytes, U256, U64, U8};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::BlockOverrides;

/// Bundle of transactions
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Bundle {
/// Transactions
pub transactions: Vec<CallRequest>,
/// Block overides
pub block_override: Option<BlockOverrides>,
}

/// State context for callMany
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct StateContext {
/// Block Number
pub block_number: Option<BlockId>,
/// Inclusive number of tx to replay in block. -1 means replay all
pub transaction_index: Option<TransactionIndex>,
}

/// Represents a transaction index where -1 means all transactions
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum TransactionIndex {
/// -1 means all transactions
#[default]
All,
/// Transaction index
Index(usize),
}

impl TransactionIndex {
/// Returns true if this is the all variant
pub fn is_all(&self) -> bool {
matches!(self, TransactionIndex::All)
}

/// Returns the index if this is the index variant
pub fn index(&self) -> Option<usize> {
match self {
TransactionIndex::All => None,
TransactionIndex::Index(idx) => Some(*idx),
}
}
}

impl Serialize for TransactionIndex {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
TransactionIndex::All => serializer.serialize_i8(-1),
TransactionIndex::Index(idx) => idx.serialize(serializer),
}
}
}

impl<'de> Deserialize<'de> for TransactionIndex {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match isize::deserialize(deserializer)? {
-1 => Ok(TransactionIndex::All),
idx if idx < -1 => Err(serde::de::Error::custom(format!(
"Invalid transaction index, expected -1 or positive integer, got {}",
idx
))),
idx => Ok(TransactionIndex::Index(idx as usize)),
}
}
}

/// Call request
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -111,6 +186,21 @@ pub struct CallInputError;
mod tests {
use super::*;

#[test]
fn transaction_index() {
let s = "-1";
let idx = serde_json::from_str::<TransactionIndex>(s).unwrap();
assert_eq!(idx, TransactionIndex::All);

let s = "5";
let idx = serde_json::from_str::<TransactionIndex>(s).unwrap();
assert_eq!(idx, TransactionIndex::Index(5));

let s = "-2";
let res = serde_json::from_str::<TransactionIndex>(s);
assert!(res.is_err());
}

#[test]
fn serde_call_request() {
let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
Expand Down
2 changes: 1 addition & 1 deletion crates/rpc/rpc-types/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod work;

pub use account::*;
pub use block::*;
pub use call::{CallInput, CallInputError, CallRequest};
pub use call::{Bundle, CallInput, CallInputError, CallRequest, StateContext};
pub use fee::{FeeHistory, TxGasAndReward};
pub use filter::*;
pub use index::Index;
Expand Down
116 changes: 113 additions & 3 deletions crates/rpc/rpc/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::{
eth::{
error::{EthApiError, EthResult},
revm_utils::{
clone_into_empty_db, inspect, inspect_and_return_db, replay_transactions_until,
result_output, EvmOverrides,
clone_into_empty_db, inspect, inspect_and_return_db, prepare_call_env,
replay_transactions_until, result_output, transact, EvmOverrides,
},
EthTransactions, TransactionSource,
},
Expand All @@ -25,11 +25,12 @@ use reth_revm::{
use reth_rlp::{Decodable, Encodable};
use reth_rpc_api::DebugApiServer;
use reth_rpc_types::{
state::StateOverride,
trace::geth::{
BlockTraceResult, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType,
GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, TraceResult,
},
BlockError, CallRequest, RichBlock,
BlockError, Bundle, CallRequest, RichBlock, StateContext,
};
use reth_tasks::TaskSpawner;
use revm::{
Expand Down Expand Up @@ -333,6 +334,103 @@ where
Ok(frame.into())
}

/// The debug_traceCallMany method lets you run an `eth_callMany` within the context of the
/// given block execution using the first n transactions in the given block as base
pub async fn debug_trace_call_many(
&self,
bundles: Vec<Bundle>,
state_context: Option<StateContext>,
opts: Option<GethDebugTracingOptions>,
state_override: Option<StateOverride>,
) -> EthResult<Vec<GethTrace>> {
if bundles.is_empty() {
return Err(EthApiError::InvalidParams(String::from("bundles are empty.")))
}

let StateContext { transaction_index, block_number } = state_context.unwrap_or_default();
let transaction_index = transaction_index.unwrap_or_default();

let target_block = block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest));
let ((cfg, block_env, _), block) = futures::try_join!(
self.inner.eth_api.evm_env_at(target_block),
self.inner.eth_api.block_by_id(target_block),
)?;

let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
let tracing_options = opts.unwrap_or_default();
let gas_limit = self.inner.eth_api.call_gas_limit();

// we're essentially replaying the transactions in the block here, hence we need the state
// that points to the beginning of the block, which is the state at the parent block
let mut at = block.parent_hash;
let mut replay_block_txs = true;

// but if all transactions are to be replayed, we can use the state at the block itself
let num_txs = transaction_index.index().unwrap_or(block.body.len());
if num_txs == block.body.len() {
at = block.hash;
replay_block_txs = false;
}

let this = self.clone();
self.inner
.eth_api
.spawn_with_state_at_block(at.into(), move |state| {
let mut results = Vec::with_capacity(bundles.len());
let mut db = SubState::new(State::new(state));

if replay_block_txs {
// only need to replay the transactions in the block if not all transactions are
// to be replayed
let transactions = block.body.into_iter().take(num_txs);

// Execute all transactions until index
for tx in transactions {
let tx = tx.into_ecrecovered().ok_or(BlockError::InvalidSignature)?;
let tx = tx_env_with_recovered(&tx);
let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx };
let (res, _) = transact(&mut db, env)?;
db.commit(res.state);
}
}

// Trace all bundles
let mut bundles = bundles.into_iter().peekable();
while let Some(bundle) = bundles.next() {
//let mut result = Vec::with_capacity(bundle.len());
let Bundle { transactions, block_override } = bundle;
let overrides =
EvmOverrides::new(state_override.clone(), block_override.map(Box::new));

let mut transactions = transactions.into_iter().peekable();
while let Some(tx) = transactions.next() {
let env = prepare_call_env(
cfg.clone(),
block_env.clone(),
tx,
gas_limit,
&mut db,
overrides.clone(),
)?;

let (trace, state) = this.trace_transaction(
tracing_options.clone(),
env,
target_block,
&mut db,
)?;

if bundles.peek().is_none() && transactions.peek().is_none() {
db.commit(state);
}
results.push(trace);
}
}
Ok(results)
})
.await
}

/// Executes the configured transaction with the environment on the given database.
///
/// Returns the trace frame and the state that got updated after executing the transaction.
Expand Down Expand Up @@ -651,6 +749,18 @@ where
Ok(DebugApi::debug_trace_call(self, request, block_number, opts.unwrap_or_default())
.await?)
}

async fn debug_trace_call_many(
&self,
bundles: Vec<Bundle>,
state_context: Option<StateContext>,
opts: Option<GethDebugTracingOptions>,
state_override: Option<StateOverride>,
) -> RpcResult<Vec<GethTrace>> {
let _permit = self.acquire_trace_permit().await;
Ok(DebugApi::debug_trace_call_many(self, bundles, state_context, opts, state_override)
.await?)
}
}

impl<Provider, Eth> std::fmt::Debug for DebugApi<Provider, Eth> {
Expand Down

0 comments on commit e9cb414

Please sign in to comment.