Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@

- [#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.

- [#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
Expand Down
114 changes: 82 additions & 32 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3707,6 +3707,10 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions {
const PARAM_NAMES: [&'static str; 2] = ["blockParam", "traceTypes"];
const API_PATHS: BitFlags<ApiPaths> = 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<String>);
type Ok = Vec<EthReplayBlockTransactionTrace>;

Expand All @@ -3715,7 +3719,10 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions {
(block_param, trace_types): Self::Params,
) -> Result<Self::Ok, ServerError> {
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(
Expand All @@ -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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description?

@akaladarshi akaladarshi Jan 20, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice this earlier that some of the other API's also doesn't have a Description, I will create an issue for this so we can add in all API's.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai create a issue for the adding Description to all the API's available in the forest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@akaladarshi I'll create a GitHub issue to track adding DESCRIPTION fields to all RPC methods in Forest.


🏁 Script executed:

#!/bin/bash

# Create a GitHub issue for adding DESCRIPTION to all RPC methods
gh issue create \
  --repo ChainSafe/forest \
  --title "Add DESCRIPTION to all RPC methods" \
  --body "## Background

Many RPC method implementations in Forest are missing the \`DESCRIPTION\` constant, which should provide documentation about what each method does.

## Task

Add the \`DESCRIPTION\` field to all RPC method implementations throughout the codebase. This will improve API documentation and help users understand what each method does.

## Example

\`\`\`rust
impl RpcMethod<2> for EthTraceReplayBlockTransactions {
    const NAME: &'static str = \"Filecoin.EthTraceReplayBlockTransactions\";
    const NAME_ALIAS: Option<&'static str> = Some(\"trace_replayBlockTransactions\");
    const DESCRIPTION: Option<&'static str> = Some(
        \"Replays all transactions in a block returning the requested traces for each transaction.\",
    );
    // ...
}
\`\`\`

## Context

Related PR: `#6444`
Related comment: https://github.com/ChainSafe/forest/pull/6444#discussion_r2709084181

cc: `@akaladarshi`" \
  --assignee akaladarshi

Length of output: 47


@akaladarshi I've created issue #6460 to track adding DESCRIPTION fields to all RPC methods in Forest. The issue has been assigned to you and includes the context from this PR discussion.

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<ApiPaths> = 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<String>);
type Ok = Vec<EthReplayBlockTransactionTrace>;

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<impl Blockstore + Send + Sync + 'static>,
(block_param, trace_types): Self::Params,
) -> Result<Self::Ok, ServerError> {
if trace_types.as_slice() != ["trace"] {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a point in using anyhow here? Seems to me it'd be easier to define a new method on ServerError, e.g., ServerError::unsupported_params. It'd be cheaper.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can use the ServerError::invalid_params("only 'trace' is supported", None)

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<DB>(
ctx: &Ctx<DB>,
ts: &Tipset,
) -> Result<Vec<EthReplayBlockTransactionTrace>, 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<DB: Blockstore>(
Expand Down
2 changes: 0 additions & 2 deletions src/rpc/methods/eth/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this was incorrect?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the struct in Lotus?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the lotus struct for this, and yeah I saw in the lotus snapshot response that the fields are set to null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the Lotus snapshot response?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lotus returns something like this:

  "lotus_response": {
    "Ok": [
      {
        "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
        "stateDiff": null,
        "trace": [
          {
            "action": {
              "callType": "call",
              "from": "0xff00000000000000000000000000000000021cc9",
              "gas": "0xbdf9c",
              "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
              "to": "0x0f96b5f075e13c3a552c1481a1ae00f9c042d58b",
              "value": "0x917bd023e0703660"
            },
            "result": {
              "gasUsed": "0x12d48f",
              "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"
            },
            "subtraces": 0,
            "traceAddress": [],
            "type": "call"
          }
        ],
        "transactionHash": "0x77b925676c49edbc6096d6b67002202a11f8c0160cbba1fe40f4162cb734e598",
        "vmTrace": null
      },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, cool, makes sense.

pub state_diff: Option<String>,
pub trace: Vec<EthTrace>,
pub transaction_hash: EthHash,
#[serde(skip_serializing_if = "Option::is_none")]
pub vm_trace: Option<String>,
}
lotus_json_with_self!(EthReplayBlockTransactionTrace);
Expand Down
1 change: 1 addition & 0 deletions src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions src/tool/subcommands/api_cmd/api_compare_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2219,6 +2219,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, 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)),
Expand Down
3 changes: 2 additions & 1 deletion src/tool/subcommands/api_cmd/test_snapshots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading