Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@

- [#6535](https://github.com/ChainSafe/forest/pull/6535): Fixed incorrect replace by fee behavior when at limits of pending messages in mempool.

- [#6541](https://github.com/ChainSafe/forest/pull/6541): Fixed "actor not found" errors when running Foundry (forge) scripts. The `eth_getBalance`, `eth_getTransactionCount`, and `eth_getCode` methods now return default values (0 balance, 0 nonce, empty code) for non-existent addresses, matching Lotus and standard Ethereum behavior.

## Forest v0.31.1 "Quadrantids"

This is a non-mandatory release for all node operators. It includes the support for more V2 API's and a few critical API fixes.
Expand Down
55 changes: 38 additions & 17 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ impl Block {

let (state_root, msgs_and_receipts) = execute_tipset(&ctx, &tipset).await?;

let state_tree = StateTree::new_from_root(ctx.store_owned(), &state_root)?;
let state_tree = ctx.state_manager.get_state_tree(&state_root)?;

let mut full_transactions = vec![];
let mut gas_used = 0;
Expand Down Expand Up @@ -974,7 +974,7 @@ impl RpcMethod<2> for EthGetBalance {
block_param,
ResolveNullTipset::TakeOlder,
)?;
let balance = eth_get_balance(&ctx, &address, &ts)?;
let balance = eth_get_balance(&ctx, &address, &ts).await?;
Ok(balance)
}
}
Expand All @@ -998,20 +998,26 @@ impl RpcMethod<2> for EthGetBalanceV2 {
) -> Result<Self::Ok, ServerError> {
let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
.await?;
let balance = eth_get_balance(&ctx, &address, &ts)?;
let balance = eth_get_balance(&ctx, &address, &ts).await?;
Ok(balance)
}
}

fn eth_get_balance<DB: Blockstore>(
async fn eth_get_balance<DB: Blockstore + Send + Sync + 'static>(
ctx: &Ctx<DB>,
address: &EthAddress,
ts: &Tipset,
) -> Result<EthBigInt> {
let fil_addr = address.to_filecoin_address()?;
let state = StateTree::new_from_root(ctx.store_owned(), ts.parent_state())?;
let actor = state.get_required_actor(&fil_addr)?;
Ok(EthBigInt(actor.balance.atto().clone()))
let (state_cid, _) = ctx
.state_manager
.tipset_state(ts, StateLookupPolicy::Enabled)
.await?;
let state_tree = ctx.state_manager.get_state_tree(&state_cid)?;
match state_tree.get_actor(&fil_addr)? {
Some(actor) => Ok(EthBigInt(actor.balance.atto().clone())),
None => Ok(EthBigInt::default()), // Balance is 0 if the actor doesn't exist
}
}

fn get_tipset_from_hash<DB: Blockstore>(
Expand Down Expand Up @@ -1439,7 +1445,7 @@ fn new_eth_tx_from_message_lookup<DB: Blockstore>(

let smsg = get_signed_message(ctx, message_lookup.message)?;

let state = StateTree::new_from_root(ctx.store().into(), ts.parent_state())?;
let state = ctx.state_manager.get_state_tree(ts.parent_state())?;

Ok(ApiEthTx {
block_hash: parent_ts_cid.into(),
Expand Down Expand Up @@ -1694,7 +1700,7 @@ async fn get_block_receipts<DB: Blockstore + Send + Sync + 'static>(
let (state_root, msgs_and_receipts) = execute_tipset(ctx, &ts_ref).await?;

// Load the state tree
let state_tree = StateTree::new_from_root(ctx.store_owned(), &state_root)?;
let state_tree = ctx.state_manager.get_state_tree(&state_root)?;

let mut eth_receipts = Vec::with_capacity(msgs_and_receipts.len());
for (i, (msg, receipt)) in msgs_and_receipts.into_iter().enumerate() {
Expand Down Expand Up @@ -2483,7 +2489,14 @@ where
.state_manager
.tipset_state(ts, StateLookupPolicy::Enabled)
.await?;
let actor = ctx.state_manager.get_required_actor(&to_address, state)?;
let state_tree = ctx.state_manager.get_state_tree(&state)?;
let Some(actor) = state_tree
.get_actor(&to_address)
.with_context(|| format!("failed to lookup contract {}", eth_address.0))?
else {
return Ok(Default::default());
};

// Not a contract. We could try to distinguish between accounts and "native" contracts here,
// but it's not worth it.
if !is_evm_actor(&actor.code) {
Expand Down Expand Up @@ -2599,7 +2612,11 @@ async fn get_storage_at<DB: Blockstore + Send + Sync + 'static>(
.tipset_state(&ts, StateLookupPolicy::Enabled)
.await?;
let make_empty_result = || EthBytes(vec![0; EVM_WORD_LENGTH]);
let Some(actor) = ctx.state_manager.get_actor(&to_address, state)? else {
let Some(actor) = ctx
.state_manager
.get_actor(&to_address, state)
.with_context(|| format!("failed to lookup contract {}", eth_address.0))?
else {
return Ok(make_empty_result());
};

Expand Down Expand Up @@ -2726,8 +2743,12 @@ where
.tipset_state(ts, StateLookupPolicy::Enabled)
.await?;

let state = StateTree::new_from_root(ctx.store_owned(), &state_cid)?;
let actor = state.get_required_actor(&addr)?;
let state_tree = ctx.state_manager.get_state_tree(&state_cid)?;
let actor = match state_tree.get_actor(&addr)? {
Some(actor) => actor,
None => return Ok(EthUint64(0)),
};

if is_evm_actor(&actor.code) {
let evm_state = evm::State::load(ctx.store(), actor.code, actor.state)?;
if !evm_state.is_alive() {
Expand Down Expand Up @@ -2855,7 +2876,7 @@ where
)
})?;

let state = StateTree::new_from_root(ctx.store_owned(), ts.parent_state())?;
let state = ctx.state_manager.get_state_tree(ts.parent_state())?;

let tx = new_eth_tx(ctx, &state, ts.epoch(), &ts.key().cid()?, &msg.cid(), index)?;

Expand Down Expand Up @@ -2890,7 +2911,7 @@ impl RpcMethod<2> for EthGetTransactionByBlockHashAndIndex {
)
})?;

let state = StateTree::new_from_root(ctx.store_owned(), ts.parent_state())?;
let state = ctx.state_manager.get_state_tree(ts.parent_state())?;

let tx = new_eth_tx(
&ctx,
Expand Down Expand Up @@ -3872,7 +3893,7 @@ where
DB: Blockstore + Send + Sync + 'static,
{
let (state_root, trace) = ctx.state_manager.execution_trace(ts)?;
let state = StateTree::new_from_root(ctx.store_owned(), &state_root)?;
let state = ctx.state_manager.get_state_tree(&state_root)?;
let cid = ts.key().cid()?;
let block_hash: EthHash = cid.into();
let mut all_traces = vec![];
Expand Down Expand Up @@ -4026,7 +4047,7 @@ where
{
let (state_root, trace) = ctx.state_manager.execution_trace(ts)?;

let state = StateTree::new_from_root(ctx.store_owned(), &state_root)?;
let state = ctx.state_manager.get_state_tree(&state_root)?;

let mut all_traces = vec![];
for ir in trace.into_iter() {
Expand Down
72 changes: 69 additions & 3 deletions src/tool/subcommands/api_cmd/api_compare_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use ipld_core::ipld::Ipld;
use itertools::Itertools as _;
use jsonrpsee::types::ErrorCode;
use libp2p::PeerId;
use libsecp256k1::{PublicKey, SecretKey};
use num_traits::Signed;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -123,6 +124,13 @@ static KNOWN_CALIBNET_F4_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
.into()
});

fn generate_eth_random_address() -> anyhow::Result<EthAddress> {
let rng = &mut crate::utils::rand::forest_os_rng();
let secret_key = SecretKey::random(rng);
let public_key = PublicKey::from_secret_key(&secret_key);
EthAddress::eth_address_from_pub_key(&public_key.serialize())
}

const TICKET_QUALITY_GREEDY: f64 = 0.9;
const TICKET_QUALITY_OPTIMAL: f64 = 0.8;
const ZERO_ADDRESS: &str = "0x0000000000000000000000000000000000000000";
Expand Down Expand Up @@ -1642,6 +1650,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetBalance::request((
generate_eth_random_address().unwrap(),
BlockNumberOrHash::from_predefined(Predefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetBalanceV2::request((
EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa").unwrap(),
Expand Down Expand Up @@ -1713,6 +1728,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetBalanceV2::request((
generate_eth_random_address().unwrap(),
ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetBlockByNumber::request((
BlockNumberOrPredefined::BlockNumber(EthInt64(shared_tipset.epoch())),
Expand Down Expand Up @@ -1930,6 +1952,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetTransactionCount::request((
generate_eth_random_address().unwrap(),
BlockNumberOrHash::from_predefined(Predefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetTransactionCountV2::request((
EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(),
Expand Down Expand Up @@ -1973,6 +2002,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetTransactionCountV2::request((
generate_eth_random_address().unwrap(),
ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetStorageAt::request((
// https://filfox.info/en/address/f410fpoidg73f7krlfohnla52dotowde5p2sejxnd4mq
Expand Down Expand Up @@ -2007,6 +2043,14 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetStorageAt::request((
generate_eth_random_address().unwrap(),
EthBytes(vec![0x0]),
BlockNumberOrHash::from_predefined(Predefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetStorageAtV2::request((
EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(),
Expand All @@ -2031,6 +2075,14 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetStorageAtV2::request((
generate_eth_random_address().unwrap(),
EthBytes(vec![0x0]),
ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthFeeHistory::request((
10.into(),
Expand Down Expand Up @@ -2186,6 +2238,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetCode::request((
generate_eth_random_address().unwrap(),
BlockNumberOrHash::from_predefined(Predefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetCodeV2::request((
// https://filfox.info/en/address/f410fpoidg73f7krlfohnla52dotowde5p2sejxnd4mq
Expand All @@ -2208,6 +2267,13 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
))
.unwrap(),
),
RpcTest::identity(
EthGetCodeV2::request((
generate_eth_random_address().unwrap(),
ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest),
))
.unwrap(),
),
RpcTest::identity(
EthGetTransactionByBlockNumberAndIndex::request((
BlockNumberOrPredefined::BlockNumber(shared_tipset.epoch().into()),
Expand Down Expand Up @@ -2561,9 +2627,9 @@ fn eth_state_tests_with_tipset<DB: Blockstore>(
tests.push(RpcTest::identity(EthGetTransactionByHashLimited::request(
(tx.hash.clone(), shared_tipset.epoch()),
)?));
tests.push(RpcTest::identity(
EthTraceTransaction::request((tx.hash.to_string(),)).unwrap(),
));
tests.push(RpcTest::identity(EthTraceTransaction::request((tx
.hash
.to_string(),))?));
if smsg.message.from.protocol() == Protocol::Delegated
&& smsg.message.to.protocol() == Protocol::Delegated
{
Expand Down
12 changes: 11 additions & 1 deletion src/tool/subcommands/api_cmd/test_snapshots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ filecoin_ethfeehistory_1737446676883828.rpcsnap.json.zst
filecoin_ethfeehistory_v2_1767605175056660.rpcsnap.json.zst
filecoin_ethgasprice_1758725940980141.rpcsnap.json.zst
filecoin_ethgetbalance_1740048634848277.rpcsnap.json.zst
filecoin_ethgetbalance_v2_1768188109932986.rpcsnap.json.zst
filecoin_ethgetbalance_v1_unknown_addr_1770287845559622.rpcsnap.json.zst
filecoin_ethgetbalance_v2_latest_1770291948779489.rpcsnap.json.zst
filecoin_ethgetbalance_v2_pending_1770291949065157.rpcsnap.json.zst
filecoin_ethgetbalance_v2_safe_1770291948803158.rpcsnap.json.zst
filecoin_ethgetbalance_v2_unknown_addr_1770287845559744.rpcsnap.json.zst
filecoin_ethgetblockbyhash_1740132537807408.rpcsnap.json.zst
filecoin_ethgetblockbynumber_1737446676696328.rpcsnap.json.zst
filecoin_ethgetblockbynumber_v2_1768192171057588.rpcsnap.json.zst
Expand All @@ -77,17 +81,21 @@ filecoin_ethgetblocktransactioncountbynumber_v2_1769099953141937.rpcsnap.json.zs
filecoin_ethgetcode_1765803672602510.rpcsnap.json.zst # latest
filecoin_ethgetcode_1765803672604518.rpcsnap.json.zst # concrete
filecoin_ethgetcode_1765803672655291.rpcsnap.json.zst # pending
filecoin_ethgetcode_v1_unknown_addr_1770288914643996.rpcsnap.json.zst
filecoin_ethgetcode_v2_1769605928230413.rpcsnap.json.zst
filecoin_ethgetcode_v2_finalized_1769605928335700.rpcsnap.json.zst
filecoin_ethgetcode_v2_safe_1769605928230488.rpcsnap.json.zst
filecoin_ethgetcode_v2_unknown_addr_1770288914644057.rpcsnap.json.zst
filecoin_ethgetlogs_1759922913569082.rpcsnap.json.zst
filecoin_ethgetmessagecidbytransactionhash_1737446676697418.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_ethgetstorageat_v1_unknown_addr_1770288667897125.rpcsnap.json.zst
filecoin_ethgetstorageat_v2_1769611091439289.rpcsnap.json.zst
filecoin_ethgetstorageat_v2_finalized_1769611091511354.rpcsnap.json.zst
filecoin_ethgetstorageat_v2_safe_1769611091439347.rpcsnap.json.zst
filecoin_ethgetstorageat_v2_unknown_addr_1770288667897031.rpcsnap.json.zst
filecoin_ethgettransactionbyblockhashandindex_1740132538373654.rpcsnap.json.zst
filecoin_ethgettransactionbyblocknumberandindex_1740132538304408.rpcsnap.json.zst
filecoin_ethgettransactionbyblocknumberandindex_latest_1769103643171646.rpcsnap.json.zst
Expand All @@ -98,7 +106,9 @@ filecoin_ethgettransactionbyblocknumberandindex_v2_safe_1769103643174698.rpcsnap
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_unknown_addr_1770288294132366.rpcsnap.json.zst
filecoin_ethgettransactionhashbycid_1737446676698540.rpcsnap.json.zst
filecoin_ethgettransactionreceipt_1741272955712904.rpcsnap.json.zst
filecoin_ethgettransactionreceipt_1765811578590165.rpcsnap.json.zst # transaction not found
Expand Down
Loading