From 1f8d667e23b8d72af5edc4bac910d44c3c8d123e Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 13 Apr 2026 15:34:28 +0800 Subject: [PATCH 1/4] feat(rpc): integrate ec finality calulator into Eth TipsetResolver --- src/rpc/methods/chain.rs | 23 +++------------- src/rpc/methods/eth.rs | 2 +- src/rpc/methods/eth/tipset_resolver.rs | 37 ++++++++++++++++++-------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 6f64ad8a34bb..463b5cc916e5 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -1069,28 +1069,13 @@ impl ChainGetTipSetV2 { pub async fn get_latest_finalized_tipset( ctx: &Ctx, ) -> anyhow::Result { - let Some(f3_finalized_head) = ctx.chain_store().f3_finalized_tipset() else { - return Self::get_ec_finalized_tipset(ctx); - }; - let head = ctx.chain_store().heaviest_tipset(); - // Latest F3 finalized tipset is older than EC finality, falling back to EC finality - if head.epoch() > f3_finalized_head.epoch() + ctx.chain_config().policy.chain_finality { - Self::get_ec_finalized_tipset(ctx) + if let Some(ts) = ChainGetTipSetFinalityStatus::get_finality_status(ctx).finalized_tip_set { + Ok(ts) } else { - Ok(f3_finalized_head) + crate::rpc::eth::tipset_resolver::get_fallback_ec_finalized_tipset(ctx.chain_store()) } } - pub fn get_ec_finalized_tipset(ctx: &Ctx) -> anyhow::Result { - let head = ctx.chain_store().heaviest_tipset(); - let ec_finality_epoch = (head.epoch() - ctx.chain_config().policy.chain_finality).max(0); - Ok(ctx.chain_index().tipset_by_height( - ec_finality_epoch, - head, - ResolveNullTipset::TakeOlder, - )?) - } - pub async fn get_tipset( ctx: &Ctx, selector: &TipsetSelector, @@ -1168,7 +1153,7 @@ impl ChainGetTipSetFinalityStatus { } } - fn get_ec_finality_threshold_depth_and_tipset_with_cache( + pub fn get_ec_finality_threshold_depth_and_tipset_with_cache( ctx: &Ctx, head: Tipset, ) -> (i64, Option) { diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 8d1ede8b72b4..caec17de9ecd 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -6,7 +6,7 @@ mod eth_tx; pub mod filter; pub mod pubsub; pub(crate) mod pubsub_trait; -mod tipset_resolver; +pub(crate) mod tipset_resolver; pub(crate) mod trace; pub mod types; mod utils; diff --git a/src/rpc/methods/eth/tipset_resolver.rs b/src/rpc/methods/eth/tipset_resolver.rs index 8ac6158ff041..9f7163ea9c27 100644 --- a/src/rpc/methods/eth/tipset_resolver.rs +++ b/src/rpc/methods/eth/tipset_resolver.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use super::*; -use crate::rpc::chain::SAFE_HEIGHT_DISTANCE; +use crate::rpc::chain::{ChainGetTipSetFinalityStatus, SAFE_HEIGHT_DISTANCE}; pub struct TipsetResolver<'a, DB> where @@ -167,17 +167,32 @@ where )?) } - /// Returns the tipset considered finalized by expected-consensus finality. - /// - /// The finalized epoch is computed as head.epoch() minus the chain's `policy.chain_finality`, clamped to zero. The tipset at that epoch is returned; when the exact height is unavailable, an older tipset is selected. + /// Returns the tipset considered finalized by the expected-consensus finality calculator(`FRC-0089`). pub fn get_ec_finalized_tipset(&self) -> anyhow::Result { let head = self.ctx.chain_store().heaviest_tipset(); - let ec_finality_epoch = - (head.epoch() - self.ctx.chain_config().policy.chain_finality).max(0); - Ok(self.ctx.chain_index().tipset_by_height( - ec_finality_epoch, - head, - ResolveNullTipset::TakeOlder, - )?) + let (_, ec_finalized_tipset) = + ChainGetTipSetFinalityStatus::get_ec_finality_threshold_depth_and_tipset_with_cache( + self.ctx, + head.clone(), + ); + if let Some(ts) = ec_finalized_tipset { + Ok(ts) + } else { + get_fallback_ec_finalized_tipset(self.ctx.chain_store()) + } } } + +/// Returns the tipset considered finalized by the fallback expected-consensus finality. +/// +/// The finalized epoch is computed as head.epoch() minus the chain's `policy.chain_finality`, clamped to zero. +/// The tipset at that epoch is returned; when the exact height is unavailable, an older tipset is selected. +pub fn get_fallback_ec_finalized_tipset( + cs: &ChainStore, +) -> anyhow::Result { + let head = cs.heaviest_tipset(); + let ec_finality_epoch = (head.epoch() - cs.chain_config().policy.chain_finality).max(0); + Ok(cs + .chain_index() + .tipset_by_height(ec_finality_epoch, head, ResolveNullTipset::TakeOlder)?) +} From 2a111464f738d6a76799d3357b0df12f29dde5ee Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 13 Apr 2026 20:18:12 +0800 Subject: [PATCH 2/4] update RPC snap tests --- src/tool/subcommands/api_cmd/api_compare_tests.rs | 2 +- src/tool/subcommands/api_cmd/test_snapshots.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index da5a70f63fee..6255e1451e3b 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -490,6 +490,7 @@ fn chain_tests(offline: bool) -> Vec { } else { RpcTest::identity(ChainGetTipSetFinalityStatus::request(()).unwrap()) }, + RpcTest::basic(ChainGetFinalizedTipset::request(()).unwrap()), ] } @@ -567,7 +568,6 @@ fn chain_tests_with_tipset( .clone() .into(),))?), RpcTest::identity(ChainTipSetWeight::request((tipset.key().into(),))?), - RpcTest::basic(ChainGetFinalizedTipset::request(())?), ]; if !offline { diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index a1ae73b00cef..bf6f1c036657 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -5,7 +5,7 @@ filecoin_chaingetevents_1764864316078100.rpcsnap.json.zst filecoin_chaingetevents_1765289237680041.rpcsnap.json.zst filecoin_chaingetevents_1765289237680294.rpcsnap.json.zst filecoin_chaingetevents_1765289237681455.rpcsnap.json.zst -filecoin_chaingetfinalizedtipset_1759828121342574.rpcsnap.json.zst +filecoin_chaingetfinalizedtipset_1776082510830037.rpcsnap.json.zst filecoin_chaingetgenesis_1736937286915866.rpcsnap.json.zst filecoin_chaingetmessage_1758734340836824.rpcsnap.json.zst filecoin_chaingetmessagesintipset_1758734696116136.rpcsnap.json.zst @@ -108,7 +108,7 @@ filecoin_ethgettransactionbyhash_1741272955520821.rpcsnap.json.zst filecoin_ethgettransactionbyhashlimited_1741272955509708.rpcsnap.json.zst filecoin_ethgettransactioncount_1740132538183426.rpcsnap.json.zst filecoin_ethgettransactioncount_v1_unknown_addr_1770288294132251.rpcsnap.json.zst -filecoin_ethgettransactioncount_v2_1767847407595348.rpcsnap.json.zst +filecoin_ethgettransactioncount_v2_1776080971678636.rpcsnap.json.zst filecoin_ethgettransactioncount_v2_unknown_addr_1770288294132366.rpcsnap.json.zst filecoin_ethgettransactionhashbycid_1737446676698540.rpcsnap.json.zst filecoin_ethgettransactionreceipt_1741272955712904.rpcsnap.json.zst From 42c39c8fb870bbb1ec7c139dc099f1b9ccfaf6f8 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 13 Apr 2026 20:23:23 +0800 Subject: [PATCH 3/4] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8404b43a573..cd6fd75e1c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ ### Changed +- [#6897](https://github.com/ChainSafe/forest/pull/6897): Integrated EC finality into Eth RPC methods. + - [#6821](https://github.com/ChainSafe/forest/pull/6821): Added message receipt size and event size to `forest-tool archive info` output. - [#6830](https://github.com/ChainSafe/forest/pull/6830): Make base fee FIP-0115 activation configurable via `FOREST_FEES_FIP0115HEIGHT` environment variable. The FIP will NOT be automatically activated on the next network upgrade with this change, for now. From 94e4e9b359652b4d9dee1527ea00a729cd1d95ed Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 13 Apr 2026 21:27:32 +0800 Subject: [PATCH 4/4] resolve AI comment --- src/rpc/methods/chain.rs | 22 ++++++++++++---------- src/rpc/methods/eth.rs | 2 +- src/rpc/methods/eth/tipset_resolver.rs | 20 +------------------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 4f95aae75d04..91e35470ae91 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -1070,11 +1070,9 @@ impl ChainGetTipSetV2 { pub async fn get_latest_finalized_tipset( ctx: &Ctx, ) -> anyhow::Result { - if let Some(ts) = ChainGetTipSetFinalityStatus::get_finality_status(ctx).finalized_tip_set { - Ok(ts) - } else { - crate::rpc::eth::tipset_resolver::get_fallback_ec_finalized_tipset(ctx.chain_store()) - } + ChainGetTipSetFinalityStatus::get_finality_status(ctx) + .finalized_tip_set + .context("failed to resolve finalized tipset") } pub async fn get_tipset( @@ -1131,7 +1129,7 @@ impl ChainGetTipSetFinalityStatus { pub fn get_finality_status(ctx: &Ctx) -> ChainFinalityStatus { let head = ctx.chain_store().heaviest_tipset(); let (ec_finality_threshold_depth, ec_finalized_tip_set) = - Self::get_ec_finality_threshold_depth_and_tipset_with_cache(ctx, head.clone()); + Self::get_ec_finality_threshold_depth_and_tipset_with_cache(ctx, head.shallow_clone()); let f3_finalized_tip_set = ctx.chain_store().f3_finalized_tipset(); let finalized_tip_set = match (&ec_finalized_tip_set, &f3_finalized_tip_set) { (Some(ec), Some(f3)) => { @@ -1193,7 +1191,7 @@ impl ChainGetTipSetFinalityStatus { let finality = ctx.chain_config().policy.chain_finality; let chain_len = finality as usize + FINALITY_CHAIN_EXTRA_EPOCHS; let mut chain = Vec::with_capacity(chain_len); - let mut ts = head.clone(); + let mut ts = head.shallow_clone(); while chain.len() < chain_len { chain.push(ts.len() as i64); if let Ok(parent) = ctx.chain_index().load_required_tipset(ts.parents()) { @@ -1230,12 +1228,16 @@ impl ChainGetTipSetFinalityStatus { let finalized = if depth >= 0 && let Ok(ts) = ctx.chain_index().tipset_by_height( (head.epoch() - depth).max(0), - head, + head.shallow_clone(), ResolveNullTipset::TakeOlder, ) { - Some(ts.shallow_clone()) + Some(ts) } else { - None + let ec_finality_epoch = + (head.epoch() - ctx.chain_config().policy.chain_finality).max(0); + ctx.chain_index() + .tipset_by_height(ec_finality_epoch, head, ResolveNullTipset::TakeOlder) + .ok() }; (depth, finalized) } diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 980a852885bd..3966c8e5b7b7 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -6,7 +6,7 @@ mod eth_tx; pub mod filter; pub mod pubsub; pub(crate) mod pubsub_trait; -pub(crate) mod tipset_resolver; +pub mod tipset_resolver; pub(crate) mod trace; pub mod types; mod utils; diff --git a/src/rpc/methods/eth/tipset_resolver.rs b/src/rpc/methods/eth/tipset_resolver.rs index 9f7163ea9c27..acdbd5959344 100644 --- a/src/rpc/methods/eth/tipset_resolver.rs +++ b/src/rpc/methods/eth/tipset_resolver.rs @@ -175,24 +175,6 @@ where self.ctx, head.clone(), ); - if let Some(ts) = ec_finalized_tipset { - Ok(ts) - } else { - get_fallback_ec_finalized_tipset(self.ctx.chain_store()) - } + ec_finalized_tipset.context("failed to resolve EC finalized tipset") } } - -/// Returns the tipset considered finalized by the fallback expected-consensus finality. -/// -/// The finalized epoch is computed as head.epoch() minus the chain's `policy.chain_finality`, clamped to zero. -/// The tipset at that epoch is returned; when the exact height is unavailable, an older tipset is selected. -pub fn get_fallback_ec_finalized_tipset( - cs: &ChainStore, -) -> anyhow::Result { - let head = cs.heaviest_tipset(); - let ec_finality_epoch = (head.epoch() - cs.chain_config().policy.chain_finality).max(0); - Ok(cs - .chain_index() - .tipset_by_height(ec_finality_epoch, head, ResolveNullTipset::TakeOlder)?) -}