From aff89c6c649572268ad34279b7468fc0ad35e9c6 Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Fri, 12 Dec 2025 19:46:47 +0100 Subject: [PATCH 01/10] fix: use correct state for `eth_getCode` and `eth_getStorageAt` --- CHANGELOG.md | 3 ++- src/rpc/methods/eth.rs | 11 +++++------ src/state_manager/mod.rs | 22 ++++++++++++++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ae186b48ec..b18146fbbd54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ - [#6327](https://github.com/ChainSafe/forest/pull/6327) Fixed: Forest returns 404 for all invalid api paths. +- [#6140](https://github.com/ChainSafe/forest/pull/6140) Fixed the `eth_getLogs` RPC method to accept `None` as the `address` parameter. + ## Forest v0.30.5 "Dulce de Leche" Non-mandatory release supporting new API methods and addressing a critical panic issue. @@ -119,7 +121,6 @@ The release includes new CLI commands for snapshot management and state inspecti - [#6103](https://github.com/ChainSafe/forest/pull/6103) Fixed `eth_getTransactionCount` to return the nonce of the requested tipset and not its parent. -- [#6140](https://github.com/ChainSafe/forest/pull/6140) Fixed the `eth_getLogs` RPC method to accept `None` as the `address` parameter. ## Forest v0.30.1 "Laurelin" diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 3c1dcea65cd9..b61ab5ef491b 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2157,9 +2157,10 @@ impl RpcMethod<2> for EthGetCode { ..Default::default() }; + let (state, _) = ctx.state_manager.tipset_state(&ts).await?; let api_invoc_result = 'invoc: { for ts in ts.chain(ctx.store()) { - match ctx.state_manager.call(&message, Some(ts)) { + match ctx.state_manager.call_on_state(state, &message, Some(ts)) { Ok(res) => { break 'invoc res; } @@ -2208,10 +2209,8 @@ impl RpcMethod<3> for EthGetStorageAt { ResolveNullTipset::TakeOlder, )?; let to_address = FilecoinAddress::try_from(ð_address)?; - let Some(actor) = ctx - .state_manager - .get_actor(&to_address, *ts.parent_state())? - else { + let (state, _) = ctx.state_manager.tipset_state(&ts).await?; + let Some(actor) = ctx.state_manager.get_actor(&to_address, state)? else { return Ok(make_empty_result()); }; @@ -2230,7 +2229,7 @@ impl RpcMethod<3> for EthGetStorageAt { }; let api_invoc_result = 'invoc: { for ts in ts.chain(ctx.store()) { - match ctx.state_manager.call(&message, Some(ts)) { + match ctx.state_manager.call_on_state(state, &message, Some(ts)) { Ok(res) => { break 'invoc res; } diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index 1fa90d31a6a6..d61bdefc237a 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -582,13 +582,14 @@ where #[instrument(skip(self, rand))] fn call_raw( &self, + state_cid: Option, msg: &Message, rand: ChainRand, tipset: &Tipset, ) -> Result { let mut msg = msg.clone(); - let state_cid = tipset.parent_state(); + let state_cid = state_cid.unwrap_or(*tipset.parent_state()); let tipset_messages = self .chain_store() @@ -606,14 +607,14 @@ where let mut vm = VM::new( ExecutionContext { heaviest_tipset: tipset.clone(), - state_tree_root: *state_cid, + state_tree_root: state_cid, epoch: height, rand: Box::new(rand), base_fee: tipset.block_headers().first().parent_base_fee.clone(), circ_supply: genesis_info.get_vm_circulating_supply( height, self.blockstore(), - state_cid, + &state_cid, )?, chain_config: self.chain_config().clone(), chain_index: self.chain_index().clone(), @@ -660,7 +661,20 @@ where pub fn call(&self, message: &Message, tipset: Option) -> Result { let ts = tipset.unwrap_or_else(|| self.heaviest_tipset()); let chain_rand = self.chain_rand(ts.clone()); - self.call_raw(message, chain_rand, &ts) + self.call_raw(None, message, chain_rand, &ts) + } + + /// Same as [`StateManager::call`] but runs the message on the given state and not + /// on the parent state of the tipset. + pub fn call_on_state( + &self, + state_cid: Cid, + message: &Message, + tipset: Option, + ) -> Result { + let ts = tipset.unwrap_or_else(|| self.cs.heaviest_tipset()); + let chain_rand = self.chain_rand(ts.clone()); + self.call_raw(Some(state_cid), message, chain_rand, &ts) } pub async fn apply_on_state_with_gas( From 6a7dbc85913ca4afa0f4f5ab75239f25bed84c65 Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 12:57:49 +0100 Subject: [PATCH 02/10] fix: remove 100M gas limit for messages --- src/message_pool/msgpool/msg_pool.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/message_pool/msgpool/msg_pool.rs b/src/message_pool/msgpool/msg_pool.rs index d4b8b3be4c5a..7ab95dda4706 100644 --- a/src/message_pool/msgpool/msg_pool.rs +++ b/src/message_pool/msgpool/msg_pool.rs @@ -603,12 +603,6 @@ where bls_sig_cache.push(msg.cid().into(), msg.signature().clone()); } - if msg.message().gas_limit > 100_000_000 { - return Err(Error::Other( - "given message has too high of a gas limit".to_string(), - )); - } - api.put_message(&ChainMessage::Signed(msg.clone()))?; api.put_message(&ChainMessage::Unsigned(msg.message().clone()))?; From 84e1dffab0d3c4a7a8948ee373d6a44695c03a0c Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 13:07:21 +0100 Subject: [PATCH 03/10] fix: receipt in `eth_getTransactionReceipt` is optional --- src/rpc/methods/eth.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index b61ab5ef491b..e43bc6f48afe 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2798,7 +2798,7 @@ async fn get_eth_transaction_receipt( ctx: Ctx, tx_hash: EthHash, limit: Option, -) -> Result { +) -> Result, ServerError> { let msg_cid = ctx.chain_store().get_mapping(&tx_hash)?.unwrap_or_else(|| { tracing::debug!( "could not find transaction hash {} in Ethereum mapping", @@ -2812,7 +2812,16 @@ async fn get_eth_transaction_receipt( .state_manager .search_for_message(None, msg_cid, limit, Some(true)) .await - .with_context(|| format!("failed to lookup Eth Txn {tx_hash} as {msg_cid}"))?; + .with_context(|| format!("failed to lookup Eth Txn {tx_hash} as {msg_cid}")); + + let option = match option { + Ok(opt) => opt, + // Ethereum clients expect an empty response when the message was not found + Err(e) => { + tracing::debug!("could not find transaction receipt for hash {tx_hash}: {e}"); + return Ok(None); + } + }; let (tipset, receipt) = option.context("not indexed")?; let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null); @@ -2845,7 +2854,7 @@ async fn get_eth_transaction_receipt( let tx_receipt = new_eth_tx_receipt(&ctx, &parent_ts, &tx, &message_lookup.receipt).await?; - Ok(tx_receipt) + Ok(Some(tx_receipt)) } pub enum EthGetTransactionReceipt {} @@ -2857,7 +2866,7 @@ impl RpcMethod<1> for EthGetTransactionReceipt { const API_PATHS: BitFlags = ApiPaths::all_with_v2(); const PERMISSION: Permission = Permission::Read; type Params = (EthHash,); - type Ok = EthTxReceipt; + type Ok = Option; async fn handle( ctx: Ctx, (tx_hash,): Self::Params, @@ -2875,7 +2884,7 @@ impl RpcMethod<2> for EthGetTransactionReceiptLimited { const API_PATHS: BitFlags = ApiPaths::all_with_v2(); const PERMISSION: Permission = Permission::Read; type Params = (EthHash, ChainEpoch); - type Ok = EthTxReceipt; + type Ok = Option; async fn handle( ctx: Ctx, (tx_hash, limit): Self::Params, From 111f7bc85cbbbb567d3636cafea8674dd7fc6ffd Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 13:12:50 +0100 Subject: [PATCH 04/10] fix: drop changelog changes (merge conflict resolution error) --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b18146fbbd54..08ae186b48ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,8 +37,6 @@ - [#6327](https://github.com/ChainSafe/forest/pull/6327) Fixed: Forest returns 404 for all invalid api paths. -- [#6140](https://github.com/ChainSafe/forest/pull/6140) Fixed the `eth_getLogs` RPC method to accept `None` as the `address` parameter. - ## Forest v0.30.5 "Dulce de Leche" Non-mandatory release supporting new API methods and addressing a critical panic issue. @@ -121,6 +119,7 @@ The release includes new CLI commands for snapshot management and state inspecti - [#6103](https://github.com/ChainSafe/forest/pull/6103) Fixed `eth_getTransactionCount` to return the nonce of the requested tipset and not its parent. +- [#6140](https://github.com/ChainSafe/forest/pull/6140) Fixed the `eth_getLogs` RPC method to accept `None` as the `address` parameter. ## Forest v0.30.1 "Laurelin" From 27439d27cd2e0e728aa878e312af140cb3e32e29 Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 13:51:33 +0100 Subject: [PATCH 05/10] chore: mpool regression test for message gas limit --- src/message/signed_message.rs | 8 ++++++++ src/message_pool/msgpool/msg_pool.rs | 25 +++++++++++++++++++++++++ src/rpc/methods/miner.rs | 2 +- src/shim/crypto.rs | 1 + src/utils/cache/lru.rs | 5 +++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/message/signed_message.rs b/src/message/signed_message.rs index 3e0b3f7624e5..3e4e1dd8cf21 100644 --- a/src/message/signed_message.rs +++ b/src/message/signed_message.rs @@ -98,6 +98,14 @@ impl SignedMessage { }; Ok(serialized.len()) } + + /// Creates a mock signed message for testing purposes. The signature check will fail if + /// invoked. + #[cfg(test)] + pub fn mock_bls_signed_message(message: Message) -> SignedMessage { + let signature = Signature::new_bls(vec![0; crate::shim::crypto::BLS_SIG_LEN]); + SignedMessage::new_unchecked(message, signature) + } } impl MessageTrait for SignedMessage { diff --git a/src/message_pool/msgpool/msg_pool.rs b/src/message_pool/msgpool/msg_pool.rs index 7ab95dda4706..c2fdc0987bb7 100644 --- a/src/message_pool/msgpool/msg_pool.rs +++ b/src/message_pool/msgpool/msg_pool.rs @@ -676,3 +676,28 @@ pub fn remove( Ok(()) } + +#[cfg(test)] +mod tests { + use crate::message_pool::test_provider::TestApi; + + use super::*; + use crate::shim::message::Message as ShimMessage; + + // Regression test for https://github.com/ChainSafe/forest/pull/6118 which fixed a bogus 100M + // gas limit. There are no limits on a single message. + #[test] + fn add_helper_message_gas_limit_test() { + let api = TestApi::default(); + let bls_sig_cache = SizeTrackingLruCache::new_mocked(); + let pending = SyncRwLock::new(HashMap::new()); + let message = ShimMessage { + gas_limit: 666_666_666, + ..ShimMessage::default() + }; + let msg = SignedMessage::mock_bls_signed_message(message); + let sequence = msg.message().sequence; + let res = add_helper(&api, &bls_sig_cache, &pending, msg, sequence); + assert!(res.is_ok()); + } +} diff --git a/src/rpc/methods/miner.rs b/src/rpc/methods/miner.rs index c878d213f7c6..3e4be66a665c 100644 --- a/src/rpc/methods/miner.rs +++ b/src/rpc/methods/miner.rs @@ -26,13 +26,13 @@ use enumflags2::BitFlags; use crate::shim::sector::PoStProof; use crate::utils::db::CborStoreExt; +use crate::shim::crypto::BLS_SIG_LEN; use anyhow::{Context as _, Result}; use bls_signatures::Serialize as _; use cid::Cid; use fil_actors_shared::fvm_ipld_amt::Amtv0 as Amt; use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::tuple::*; -use fvm_shared2::crypto::signature::BLS_SIG_LEN; use group::prime::PrimeCurveAffine as _; use itertools::Itertools; use parking_lot::RwLock; diff --git a/src/shim/crypto.rs b/src/shim/crypto.rs index 5b1ad4536d59..8af0cbeb34f0 100644 --- a/src/shim/crypto.rs +++ b/src/shim/crypto.rs @@ -14,6 +14,7 @@ use fvm_ipld_encoding::{ repr::{Deserialize_repr, Serialize_repr}, ser, strict_bytes, }; +pub use fvm_shared_latest::crypto::signature::BLS_SIG_LEN; pub use fvm_shared3::TICKET_RANDOMNESS_LOOKBACK; use get_size2::GetSize; use num::FromPrimitive; diff --git a/src/utils/cache/lru.rs b/src/utils/cache/lru.rs index 7d80426f1b4e..4c8abbc9ff76 100644 --- a/src/utils/cache/lru.rs +++ b/src/utils/cache/lru.rs @@ -149,6 +149,11 @@ where } size } + + #[cfg(test)] + pub(crate) fn new_mocked() -> Self { + Self::new_inner(Cow::Borrowed("mocked_cache"), NonZeroUsize::new(1)) + } } impl Collector for SizeTrackingLruCache From a72d3da7db825a9f92d1b4d18588da862a21fd0c Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 14:18:36 +0100 Subject: [PATCH 06/10] fix: rpc snapshot tests for `eth_getCode` and `eth_getStorageAt` --- src/tool/subcommands/api_cmd/test_snapshots.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 07de31a5b8c5..665b25df7968 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -61,10 +61,14 @@ filecoin_ethgetblockreceipts_1740132537907751.rpcsnap.json.zst filecoin_ethgetblockreceiptslimited_1740132537908421.rpcsnap.json.zst filecoin_ethgetblocktransactioncountbyhash_1740132538001911.rpcsnap.json.zst filecoin_ethgetblocktransactioncountbynumber_1737446676697272.rpcsnap.json.zst -filecoin_ethgetcode_1737446676697285.rpcsnap.json.zst +filecoin_ethgetcode_1765803672602510.rpcsnap.json.zst # latest +filecoin_ethgetcode_1765803672604518.rpcsnap.json.zst # concrete +filecoin_ethgetcode_1765803672655291.rpcsnap.json.zst # pending filecoin_ethgetlogs_1759922913569082.rpcsnap.json.zst filecoin_ethgetmessagecidbytransactionhash_1737446676697418.rpcsnap.json.zst -filecoin_ethgetstorageat_1737446676697795.rpcsnap.json.zst +filecoin_ethgetstorageat_1765803742043605.rpcsnap.json.zst # latest +filecoin_ethgetstorageat_1765803742046844.rpcsnap.json.zst # concrete +filecoin_ethgetstorageat_1765803742145789.rpcsnap.json.zst # pending filecoin_ethgettransactionbyblockhashandindex_1740132538373654.rpcsnap.json.zst filecoin_ethgettransactionbyblocknumberandindex_1740132538304408.rpcsnap.json.zst filecoin_ethgettransactionbyhash_1741272955520821.rpcsnap.json.zst From d003828a8104d3c006690cb854fa41dabd9364ee Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 16:32:18 +0100 Subject: [PATCH 07/10] chore(test): rpc snapshot test for null receipt --- src/tool/subcommands/api_cmd/api_compare_tests.rs | 11 +++++++++++ src/tool/subcommands/api_cmd/test_snapshots.txt | 1 + 2 files changed, 12 insertions(+) diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 09ac26d8eb87..8d0643814cc0 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -1983,6 +1983,17 @@ fn eth_tests_with_tipset(store: &Arc, shared_tipset: &Tipset // both nodes could fail on, e.g., "too many results, maximum supported is 500, try paginating // requests with After and Count" .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError), + RpcTest::identity( + EthGetTransactionReceipt::request(( + // A transaction that should not exist exist, to test the `null` response in case + // of missing transaction. + EthHash::from_str( + "0xf234567890123456789d6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f70809", + ) + .unwrap(), + )) + .unwrap(), + ), ]; for block in shared_tipset.block_headers() { diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 665b25df7968..416ff0e720f0 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -77,6 +77,7 @@ filecoin_ethgettransactioncount_1740132538183426.rpcsnap.json.zst filecoin_ethgettransactionhashbycid_1737446676698540.rpcsnap.json.zst filecoin_ethgettransactionreceipt_1741272955712904.rpcsnap.json.zst filecoin_ethgettransactionreceiptlimited_1741272955611272.rpcsnap.json.zst +filecoin_ethgettransactionreceipt_1765811578590165.rpcsnap.json.zst # transaction not found filecoin_ethmaxpriorityfeepergas_1758727346451988.rpcsnap.json.zst filecoin_ethnewblockfilter_1741779995902203.rpcsnap.json.zst filecoin_ethnewfilter_1741781607617949.rpcsnap.json.zst From f5afe4842b1358757de470081d84870a110af20a Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 16:38:13 +0100 Subject: [PATCH 08/10] chore: changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ae186b48ec..5bbeb8e040d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,12 @@ Non-mandatory release supporting new API methods and addressing a critical panic - [#6325](https://github.com/ChainSafe/forest/pull/6325) Fixed a panic that could occur under certain message pool conditions and the `Filecoin.MpoolSelect` RPC method. +- [#5979](https://github.com/ChainSafe/forest/issues/5979) Fixed an issue with `Filecoin.EthGetCode` and `Filecoin.EthGetStorageAt` returning parent tipset data instead of the requested tipset. + +- [#6118](https://github.com/ChainSafe/forest/pull/6118) Fixed the `Filecoin.EthGetTransactionReceipt` and `Filecoin.EthGetTransactionReceiptLimited` RPC methods to return null for non-existent transactions instead of an error. This aligns with the Ethereum RPC API provided by Lotus. + +- [#6118](https://github.com/ChainSafe/forest/pull/6118) Removed a legacy limit of 100M gas for messages which was preventing contract deployments. + ## Forest v0.30.4 "DeLorean" This is a non-mandatory release that fixes a chain sync issue that is caused by time traveling block(s). From 875df0283ea823457035425ea12ccdb1b79b728d Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Mon, 15 Dec 2025 16:38:46 +0100 Subject: [PATCH 09/10] chore: sort lists --- src/tool/subcommands/api_cmd/test_snapshots.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 416ff0e720f0..3d407d30c69c 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -76,8 +76,8 @@ filecoin_ethgettransactionbyhashlimited_1741272955509708.rpcsnap.json.zst filecoin_ethgettransactioncount_1740132538183426.rpcsnap.json.zst filecoin_ethgettransactionhashbycid_1737446676698540.rpcsnap.json.zst filecoin_ethgettransactionreceipt_1741272955712904.rpcsnap.json.zst -filecoin_ethgettransactionreceiptlimited_1741272955611272.rpcsnap.json.zst filecoin_ethgettransactionreceipt_1765811578590165.rpcsnap.json.zst # transaction not found +filecoin_ethgettransactionreceiptlimited_1741272955611272.rpcsnap.json.zst filecoin_ethmaxpriorityfeepergas_1758727346451988.rpcsnap.json.zst filecoin_ethnewblockfilter_1741779995902203.rpcsnap.json.zst filecoin_ethnewfilter_1741781607617949.rpcsnap.json.zst From 8762af83e40c150eae9e89b8897495e5ab2a0071 Mon Sep 17 00:00:00 2001 From: Hubert Date: Tue, 16 Dec 2025 08:04:28 +0100 Subject: [PATCH 10/10] Update src/tool/subcommands/api_cmd/api_compare_tests.rs Co-authored-by: hanabi1224 --- src/tool/subcommands/api_cmd/api_compare_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 8d0643814cc0..9a03ad5f1f49 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -1985,7 +1985,7 @@ fn eth_tests_with_tipset(store: &Arc, shared_tipset: &Tipset .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError), RpcTest::identity( EthGetTransactionReceipt::request(( - // A transaction that should not exist exist, to test the `null` response in case + // A transaction that should not exist, to test the `null` response in case // of missing transaction. EthHash::from_str( "0xf234567890123456789d6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f70809",