Skip to content
Merged
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
8 changes: 8 additions & 0 deletions src/message/signed_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
31 changes: 25 additions & 6 deletions src/message_pool/msgpool/msg_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))?;

Expand Down Expand Up @@ -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());
}
}
30 changes: 19 additions & 11 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -2208,10 +2209,8 @@ impl RpcMethod<3> for EthGetStorageAt {
ResolveNullTipset::TakeOlder,
)?;
let to_address = FilecoinAddress::try_from(&eth_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());
};

Expand All @@ -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;
}
Expand Down Expand Up @@ -2799,7 +2798,7 @@ async fn get_eth_transaction_receipt(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
tx_hash: EthHash,
limit: Option<ChainEpoch>,
) -> Result<EthTxReceipt, ServerError> {
) -> Result<Option<EthTxReceipt>, ServerError> {
let msg_cid = ctx.chain_store().get_mapping(&tx_hash)?.unwrap_or_else(|| {
tracing::debug!(
"could not find transaction hash {} in Ethereum mapping",
Expand All @@ -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);
Expand Down Expand Up @@ -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 {}
Expand All @@ -2858,7 +2866,7 @@ impl RpcMethod<1> for EthGetTransactionReceipt {
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
const PERMISSION: Permission = Permission::Read;
type Params = (EthHash,);
type Ok = EthTxReceipt;
type Ok = Option<EthTxReceipt>;
async fn handle(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(tx_hash,): Self::Params,
Expand All @@ -2876,7 +2884,7 @@ impl RpcMethod<2> for EthGetTransactionReceiptLimited {
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
const PERMISSION: Permission = Permission::Read;
type Params = (EthHash, ChainEpoch);
type Ok = EthTxReceipt;
type Ok = Option<EthTxReceipt>;
async fn handle(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(tx_hash, limit): Self::Params,
Expand Down
2 changes: 1 addition & 1 deletion src/rpc/methods/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/shim/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 18 additions & 4 deletions src/state_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,13 +582,14 @@ where
#[instrument(skip(self, rand))]
fn call_raw(
&self,
state_cid: Option<Cid>,
msg: &Message,
rand: ChainRand<DB>,
tipset: &Tipset,
) -> Result<ApiInvocResult, Error> {
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()
Expand All @@ -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(),
Expand Down Expand Up @@ -660,7 +661,20 @@ where
pub fn call(&self, message: &Message, tipset: Option<Tipset>) -> Result<ApiInvocResult, Error> {
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<Tipset>,
) -> Result<ApiInvocResult, Error> {
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(
Expand Down
11 changes: 11 additions & 0 deletions src/tool/subcommands/api_cmd/api_compare_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1983,6 +1983,17 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, 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() {
Expand Down
9 changes: 7 additions & 2 deletions src/tool/subcommands/api_cmd/test_snapshots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,22 @@ 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
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
Expand Down
5 changes: 5 additions & 0 deletions src/utils/cache/lru.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V> Collector for SizeTrackingLruCache<K, V>
Expand Down
Loading