diff --git a/CHANGELOG.md b/CHANGELOG.md index 329e68977c0f..5cdc720ba476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,12 +45,16 @@ - [#6421](https://github.com/ChainSafe/forest/pull/6421) Add an environment variable `FOREST_RPC_BACKFILL_FULL_TIPSET_FROM_NETWORK` to enable backfilling full tipsets from network in a few RPC methods. +- [#6444](https://github.com/ChainSafe/forest/pull/6444) Implemented `Filecoin.EthTraceReplayBlockTransactions` for API v2. + ### Changed - [#6368](https://github.com/ChainSafe/forest/pull/6368): Migrated build and development tooling from Makefile to `mise`. Contributors should install `mise` and use `mise run` commands instead of `make` commands. - [#6286](https://github.com/ChainSafe/forest/pull/6286) `Filecoin.ChainGetEvents` now returns an error if the events are not present in the db. +- [#6444](https://github.com/ChainSafe/forest/pull/6444) `EthReplayBlockTransactionTrace` responses now always include `stateDiff` and `vmTrace` fields (set to `null` when not available) for Lotus compatibility. + ### Removed ### Fixed diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 721352189c68..4dadca145759 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -3707,6 +3707,10 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions { const PARAM_NAMES: [&'static str; 2] = ["blockParam", "traceTypes"]; const API_PATHS: BitFlags = ApiPaths::all(); const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some( + "Replays all transactions in a block returning the requested traces for each transaction.", + ); + type Params = (ExtBlockNumberOrHash, Vec); type Ok = Vec; @@ -3715,7 +3719,10 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions { (block_param, trace_types): Self::Params, ) -> Result { if trace_types.as_slice() != ["trace"] { - return Err(anyhow::anyhow!("only trace is supported").into()); + return Err(ServerError::invalid_params( + "only 'trace' is supported", + None, + )); } let ts = tipset_by_ext_block_number_or_hash( @@ -3724,47 +3731,90 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions { ResolveNullTipset::TakeOlder, )?; - let (state_root, trace) = ctx.state_manager.execution_trace(&ts)?; + eth_trace_replay_block_transactions(&ctx, &ts).await + } +} - let state = StateTree::new_from_root(ctx.store_owned(), &state_root)?; +pub enum EthTraceReplayBlockTransactionsV2 {} +impl RpcMethod<2> for EthTraceReplayBlockTransactionsV2 { + const N_REQUIRED_PARAMS: usize = 2; + const NAME: &'static str = "Filecoin.EthTraceReplayBlockTransactions"; + const NAME_ALIAS: Option<&'static str> = Some("trace_replayBlockTransactions"); + const PARAM_NAMES: [&'static str; 2] = ["blockParam", "traceTypes"]; + const API_PATHS: BitFlags = make_bitflags!(ApiPaths::V2); + const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some( + "Replays all transactions in a block returning the requested traces for each transaction.", + ); - let mut all_traces = vec![]; - for ir in trace.into_iter() { - if ir.msg.from == system::ADDRESS.into() { - continue; - } + type Params = (ExtBlockNumberOrHash, Vec); + type Ok = Vec; - let tx_hash = EthGetTransactionHashByCid::handle(ctx.clone(), (ir.msg_cid,)).await?; - let tx_hash = tx_hash - .with_context(|| format!("cannot find transaction hash for cid {}", ir.msg_cid))?; + async fn handle( + ctx: Ctx, + (block_param, trace_types): Self::Params, + ) -> Result { + if trace_types.as_slice() != ["trace"] { + return Err(ServerError::invalid_params( + "only 'trace' is supported", + None, + )); + } - let mut env = trace::base_environment(&state, &ir.msg.from) - .map_err(|e| format!("when processing message {}: {}", ir.msg_cid, e))?; + let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder) + .await?; - if let Some(execution_trace) = ir.execution_trace { - trace::build_traces(&mut env, &[], execution_trace)?; + eth_trace_replay_block_transactions(&ctx, &ts).await + } +} - let get_output = || -> EthBytes { - env.traces - .first() - .map_or_else(EthBytes::default, |trace| match &trace.result { - TraceResult::Call(r) => r.output.clone(), - TraceResult::Create(r) => r.code.clone(), - }) - }; +async fn eth_trace_replay_block_transactions( + ctx: &Ctx, + ts: &Tipset, +) -> Result, ServerError> +where + DB: Blockstore + Send + Sync + 'static, +{ + let (state_root, trace) = ctx.state_manager.execution_trace(ts)?; - all_traces.push(EthReplayBlockTransactionTrace { - output: get_output(), - state_diff: None, - trace: env.traces.clone(), - transaction_hash: tx_hash.clone(), - vm_trace: None, - }); - }; + let state = StateTree::new_from_root(ctx.store_owned(), &state_root)?; + + let mut all_traces = vec![]; + for ir in trace.into_iter() { + if ir.msg.from == system::ADDRESS.into() { + continue; } - Ok(all_traces) + let tx_hash = EthGetTransactionHashByCid::handle(ctx.clone(), (ir.msg_cid,)).await?; + let tx_hash = tx_hash + .with_context(|| format!("cannot find transaction hash for cid {}", ir.msg_cid))?; + + let mut env = trace::base_environment(&state, &ir.msg.from) + .map_err(|e| format!("when processing message {}: {}", ir.msg_cid, e))?; + + if let Some(execution_trace) = ir.execution_trace { + trace::build_traces(&mut env, &[], execution_trace)?; + + let get_output = || -> EthBytes { + env.traces + .first() + .map_or_else(EthBytes::default, |trace| match &trace.result { + TraceResult::Call(r) => r.output.clone(), + TraceResult::Create(r) => r.code.clone(), + }) + }; + + all_traces.push(EthReplayBlockTransactionTrace { + output: get_output(), + state_diff: None, + trace: env.traces.clone(), + transaction_hash: tx_hash.clone(), + vm_trace: None, + }); + }; } + + Ok(all_traces) } fn get_eth_block_number_from_string( diff --git a/src/rpc/methods/eth/types.rs b/src/rpc/methods/eth/types.rs index 6f7ceeb17cd7..89ef1d88edf2 100644 --- a/src/rpc/methods/eth/types.rs +++ b/src/rpc/methods/eth/types.rs @@ -667,11 +667,9 @@ lotus_json_with_self!(EthBlockTrace); #[serde(rename_all = "camelCase")] pub struct EthReplayBlockTransactionTrace { pub output: EthBytes, - #[serde(skip_serializing_if = "Option::is_none")] pub state_diff: Option, pub trace: Vec, pub transaction_hash: EthHash, - #[serde(skip_serializing_if = "Option::is_none")] pub vm_trace: Option, } lotus_json_with_self!(EthReplayBlockTransactionTrace); diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 7244acf2f659..514cbc0cb205 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -147,6 +147,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::eth::EthTraceFilter); $callback!($crate::rpc::eth::EthTraceTransaction); $callback!($crate::rpc::eth::EthTraceReplayBlockTransactions); + $callback!($crate::rpc::eth::EthTraceReplayBlockTransactionsV2); $callback!($crate::rpc::eth::Web3ClientVersion); $callback!($crate::rpc::eth::EthSendRawTransaction); diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 9fdf04507358..a1649e444a16 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -2219,6 +2219,13 @@ fn eth_tests_with_tipset(store: &Arc, shared_tipset: &Tipset )) .unwrap(), ), + RpcTest::identity( + EthTraceReplayBlockTransactionsV2::request(( + ExtBlockNumberOrHash::from_block_number(shared_tipset.epoch()), + vec!["trace".to_string()], + )) + .unwrap(), + ), RpcTest::identity( EthTraceFilter::request((EthTraceFilterCriteria { from_block: Some(format!("0x{:x}", shared_tipset.epoch() - 100)), diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 4981fbc872bd..c3682fc4a7fe 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -94,7 +94,8 @@ filecoin_ethtraceblock_1737446676736475.rpcsnap.json.zst filecoin_ethtracefilter_1742371405673188.rpcsnap.json.zst filecoin_ethtracefilter_1742983898701553.rpcsnap.json.zst filecoin_ethtracefilter_1746449543820062.rpcsnap.json.zst -filecoin_ethtracereplayblocktransactions_1740132538658376.rpcsnap.json.zst +filecoin_ethtracereplayblocktransactions_1768898971081023.rpcsnap.json.zst +filecoin_ethtracereplayblocktransactions_1768898971153948.rpcsnap.json.zst filecoin_ethtracetransaction_1741765677273941.rpcsnap.json.zst filecoin_ethuninstallfilter_1737446676698857.rpcsnap.json.zst filecoin_evm_constructor_statedecodeparams_1755004924714690.rpcsnap.json.zst