Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
504ec83
Impl EthTraceFilterV2
sudo-shashank Feb 4, 2026
cc77df0
update changelog
sudo-shashank Feb 4, 2026
0d0ea26
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 4, 2026
23c7054
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 5, 2026
55d12d2
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 5, 2026
356d8ca
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 6, 2026
c6a3309
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 7, 2026
8ac9a07
fix open rpc test
sudo-shashank Feb 7, 2026
2caa06c
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 9, 2026
1ad61df
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 9, 2026
366bf75
fix changelog
sudo-shashank Feb 9, 2026
1f26ac9
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 10, 2026
447b015
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 10, 2026
0c0b285
update test snapshot
sudo-shashank Feb 10, 2026
52bfe93
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 11, 2026
3249762
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 16, 2026
db32e8d
default to latest
sudo-shashank Feb 16, 2026
721b337
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 16, 2026
bb1ae0a
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 17, 2026
8c7e888
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 17, 2026
045b0c9
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 18, 2026
1e3d31a
refactor and add more test
sudo-shashank Feb 19, 2026
60720d2
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 19, 2026
f6d2029
exclude from offline check
sudo-shashank Feb 19, 2026
1d35eae
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 19, 2026
6ea084b
refactor for BlockNumberOrHash
sudo-shashank Feb 20, 2026
171e53c
add Default for BlockNumberOrHash
sudo-shashank Feb 20, 2026
591a1ed
fix parse_block_range test
sudo-shashank Feb 20, 2026
c149198
Merge branch 'main' into shashank/EthTraceFilterV2
sudo-shashank Feb 20, 2026
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 @@ -29,8 +29,12 @@

### Added

- [#6522](https://github.com/ChainSafe/forest/pull/6522): Implemented `Filecoin.EthTraceFilter` for API v2.

### Changed

- [#6522](https://github.com/ChainSafe/forest/pull/6522): `Filecoin.EthTraceFilter` filter options `from_block` and `to_block` now default to `latest` tag when omitted for v1 and v2 API.

### Removed

### Fixed
Expand Down
1 change: 1 addition & 0 deletions scripts/tests/api_compare/filter-list-offline
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
!Filecoin.EthGetTransactionByBlockNumberAndIndex
!eth_getTransactionByBlockNumberAndIndex
!Filecoin.ChainSetHead
!Filecoin.EthTraceFilter
173 changes: 123 additions & 50 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,17 +297,39 @@ impl From<[u8; EVM_WORD_LENGTH]> for EthHash {
}
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[derive(
PartialEq,
Debug,
Clone,
Serialize,
Deserialize,
Default,
JsonSchema,
strum::Display,
strum::EnumString,
)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum Predefined {
Earliest,
Pending,
#[default]
Latest,
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[derive(
PartialEq,
Debug,
Clone,
Serialize,
Deserialize,
Default,
JsonSchema,
strum::Display,
strum::EnumString,
)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum ExtPredefined {
Earliest,
Pending,
Expand Down Expand Up @@ -354,6 +376,12 @@ pub enum BlockNumberOrHash {
BlockHashObject(BlockHash),
}

impl Default for BlockNumberOrHash {
fn default() -> Self {
Self::PredefinedBlock(Predefined::default())
}
}

lotus_json_with_self!(BlockNumberOrHash);

impl BlockNumberOrHash {
Expand Down Expand Up @@ -389,16 +417,13 @@ impl BlockNumberOrHash {
}

pub fn from_str(s: &str) -> Result<Self, Error> {
match s {
"earliest" => Ok(BlockNumberOrHash::from_predefined(Predefined::Earliest)),
"pending" => Ok(BlockNumberOrHash::from_predefined(Predefined::Pending)),
"latest" | "" => Ok(BlockNumberOrHash::from_predefined(Predefined::Latest)),
hex if hex.starts_with("0x") => {
let epoch = hex_str_to_epoch(hex)?;
Ok(BlockNumberOrHash::from_block_number(epoch))
}
_ => Err(anyhow!("Invalid block identifier")),
if s.starts_with("0x") {
let epoch = hex_str_to_epoch(s)?;
return Ok(BlockNumberOrHash::from_block_number(epoch));
}
s.parse::<Predefined>()
.map(BlockNumberOrHash::from_predefined)
.map_err(|_| anyhow!("Invalid block identifier"))
}
}

Expand All @@ -413,6 +438,12 @@ pub enum ExtBlockNumberOrHash {
BlockHashObject(BlockHash),
}

impl Default for ExtBlockNumberOrHash {
fn default() -> Self {
Self::PredefinedBlock(ExtPredefined::default())
}
}

lotus_json_with_self!(ExtBlockNumberOrHash);

#[allow(dead_code)]
Expand Down Expand Up @@ -449,24 +480,13 @@ impl ExtBlockNumberOrHash {
}

pub fn from_str(s: &str) -> Result<Self, Error> {
match s {
"earliest" => Ok(ExtBlockNumberOrHash::from_predefined(
ExtPredefined::Earliest,
)),
"pending" => Ok(ExtBlockNumberOrHash::from_predefined(
ExtPredefined::Pending,
)),
"latest" | "" => Ok(ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest)),
"safe" => Ok(ExtBlockNumberOrHash::from_predefined(ExtPredefined::Safe)),
"finalized" => Ok(ExtBlockNumberOrHash::from_predefined(
ExtPredefined::Finalized,
)),
hex if hex.starts_with("0x") => {
let epoch = hex_str_to_epoch(hex)?;
Ok(ExtBlockNumberOrHash::from_block_number(epoch))
}
_ => Err(anyhow!("Invalid block identifier")),
if s.starts_with("0x") {
let epoch = hex_str_to_epoch(s)?;
return Ok(ExtBlockNumberOrHash::from_block_number(epoch));
}
s.parse::<ExtPredefined>()
.map(ExtBlockNumberOrHash::from_predefined)
.map_err(|_| anyhow!("Invalid block identifier"))
}
}

Expand Down Expand Up @@ -4115,15 +4135,31 @@ fn get_eth_block_number_from_string<DB: Blockstore>(
block: Option<&str>,
resolve: ResolveNullTipset,
) -> Result<EthUint64> {
let block_param = match block {
Some(block_str) => ExtBlockNumberOrHash::from_str(block_str)?,
None => bail!("cannot parse fromBlock"),
};
let block_param = block
.map(ExtBlockNumberOrHash::from_str)
.transpose()?
.unwrap_or_default();
Ok(EthUint64(
tipset_by_ext_block_number_or_hash(chain_store, block_param, resolve)?.epoch() as u64,
))
}

async fn get_eth_block_number_from_string_v2<DB: Blockstore + Send + Sync + 'static>(
ctx: &Ctx<DB>,
block: Option<&str>,
resolve: ResolveNullTipset,
) -> Result<EthUint64> {
let block_param = block
.map(ExtBlockNumberOrHash::from_str)
.transpose()?
.unwrap_or_default();
Ok(EthUint64(
tipset_by_block_number_or_hash_v2(ctx, block_param, resolve)
.await?
.epoch() as u64,
))
}

pub enum EthTraceFilter {}
impl RpcMethod<1> for EthTraceFilter {
const N_REQUIRED_PARAMS: usize = 1;
Expand Down Expand Up @@ -4155,17 +4191,44 @@ impl RpcMethod<1> for EthTraceFilter {
)
.context("cannot parse toBlock")?;

Ok(trace_filter(ctx, filter, from_block, to_block)
.await?
.into_iter()
.sorted_by_key(|trace| {
(
trace.block_number,
trace.transaction_position,
trace.trace.trace_address.clone(),
)
})
.collect_vec())
Ok(trace_filter(ctx, filter, from_block, to_block).await?)
}
}

pub enum EthTraceFilterV2 {}
impl RpcMethod<1> for EthTraceFilterV2 {
const N_REQUIRED_PARAMS: usize = 1;
const NAME: &'static str = "Filecoin.EthTraceFilter";
const NAME_ALIAS: Option<&'static str> = Some("trace_filter");
const PARAM_NAMES: [&'static str; 1] = ["filter"];
const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
const PERMISSION: Permission = Permission::Read;
const DESCRIPTION: Option<&'static str> =
Some("Returns the traces for transactions matching the filter criteria.");
type Params = (EthTraceFilterCriteria,);
type Ok = Vec<EthBlockTrace>;

async fn handle(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(filter,): Self::Params,
) -> Result<Self::Ok, ServerError> {
let from_block = get_eth_block_number_from_string_v2(
&ctx,
filter.from_block.as_deref(),
ResolveNullTipset::TakeNewer,
)
.await
.context("cannot parse fromBlock")?;

let to_block = get_eth_block_number_from_string_v2(
&ctx,
filter.to_block.as_deref(),
ResolveNullTipset::TakeOlder,
)
.await
.context("cannot parse toBlock")?;

Ok(trace_filter(ctx, filter, from_block, to_block).await?)
}
}

Expand All @@ -4174,10 +4237,10 @@ async fn trace_filter(
filter: EthTraceFilterCriteria,
from_block: EthUint64,
to_block: EthUint64,
) -> Result<HashSet<EthBlockTrace>> {
) -> Result<Vec<EthBlockTrace>> {
let mut results = HashSet::default();
if let Some(EthUint64(0)) = filter.count {
return Ok(results);
return Ok(Vec::new());
}
let count = *filter.count.unwrap_or_default();
ensure!(
Expand All @@ -4188,7 +4251,8 @@ async fn trace_filter(
);

let mut trace_counter = 0;
for blk_num in from_block.0..=to_block.0 {
'blocks: for blk_num in from_block.0..=to_block.0 {
// For BlockNumber, EthTraceBlock and EthTraceBlockV2 are equivalent.
let block_traces = EthTraceBlock::handle(
ctx.clone(),
(ExtBlockNumberOrHash::from_block_number(blk_num as i64),),
Expand All @@ -4209,7 +4273,7 @@ async fn trace_filter(
results.insert(block_trace);

if filter.count.is_some() && results.len() >= count as usize {
return Ok(results);
break 'blocks;
} else if results.len() > *FOREST_TRACE_FILTER_MAX_RESULT as usize {
bail!(
"too many results, maximum supported is {}, try paginating requests with After and Count",
Expand All @@ -4220,7 +4284,16 @@ async fn trace_filter(
}
}

Ok(results)
Ok(results
.into_iter()
.sorted_by_key(|trace| {
(
trace.block_number,
trace.transaction_position,
trace.trace.trace_address.clone(),
)
})
.collect_vec())
}

#[cfg(test)]
Expand Down
Loading
Loading