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). 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 d4b8b3be4c5a..c2fdc0987bb7 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()))?; @@ -682,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/eth.rs b/src/rpc/methods/eth.rs index 3c1dcea65cd9..e43bc6f48afe 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; } @@ -2799,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", @@ -2813,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); @@ -2846,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 {} @@ -2858,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, @@ -2876,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, 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/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( diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 09ac26d8eb87..9a03ad5f1f49 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, 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 07de31a5b8c5..3d407d30c69c 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 @@ -72,6 +76,7 @@ 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_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 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