From be4005a7e857f04034d8ed5cd40683ddcc19d0c1 Mon Sep 17 00:00:00 2001 From: Guillaume Potier Date: Fri, 24 Jan 2025 17:18:10 +0100 Subject: [PATCH] Rework new_eth_tx_receipt implementation (#5176) --- .config/lychee.toml | 2 +- CHANGELOG.md | 2 + Cargo.toml | 2 +- scripts/tests/api_compare/docker-compose.yml | 8 +- scripts/tests/api_compare/filter-list | 2 - scripts/tests/api_compare/filter-list-offline | 2 - src/rpc/methods/eth.rs | 120 ++++++++++-------- src/rpc/methods/eth/filter/mod.rs | 12 +- src/rpc/methods/eth/types.rs | 6 + 9 files changed, 93 insertions(+), 63 deletions(-) diff --git a/.config/lychee.toml b/.config/lychee.toml index 935b487e5b6e..248b29b4c8fd 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -12,7 +12,7 @@ exclude = [ "faucet.calibnet.chainsafe-fil.io/funds.html", # Bot protection "jwt.io", - "forest-explorer.chainsafe.dev" + "forest-explorer.chainsafe.dev", ] timeout = 30 max_retries = 6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c94456a4e83..f63bf2df4317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ - [#5150](https://github.com/ChainSafe/forest/pull/5150) Fix incorrect prototype for the `Filecoin.EthGetBlockReceiptsLimited` RPC method. +- [#5006](https://github.com/ChainSafe/forest/issues/5006) Fix incorrect logs, logs bloom and event index for the `Filecoin.EthGetBlockReceipts` RPC method. + ## Forest v.0.23.3 "Plumber" Mandatory release for calibnet node operators. It fixes a sync error at epoch 2281645. diff --git a/Cargo.toml b/Cargo.toml index 936a8f01331b..8a3a33c3bf0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ dialoguer = "0.11" digest = "0.10" directories = "6" displaydoc = "0.2" -ethereum-types = "0.15" +ethereum-types = { version = "0.15", features = ["ethbloom"] } ez-jsonrpc-types = "0.5" fil_actor_account_state = { version = "19" } fil_actor_cron_state = { version = "19" } diff --git a/scripts/tests/api_compare/docker-compose.yml b/scripts/tests/api_compare/docker-compose.yml index da53f8734e39..24361548bb4c 100644 --- a/scripts/tests/api_compare/docker-compose.yml +++ b/scripts/tests/api_compare/docker-compose.yml @@ -170,10 +170,14 @@ services: lotus wait-api --timeout 10m # copy Lotus token to shared volume cp /var/lib/lotus/token /data/lotus-token - # `sethead` right after `sync wait` to ensure the head is not set in middle of a sync lotus sync wait FULLNODE_API_INFO="$(cat /var/lib/lotus/token):/dns/lotus/tcp/${LOTUS_RPC_PORT}/http" - lotus chain sethead --epoch $(($(ls /data/*.car.zst | tail -n 1 | grep -Eo '[0-9]+' | tail -n 1) - 50)) + SNAPSHOT_EPOCH="$(ls /data/*.car.zst | tail -n 1 | grep -Eo '[0-9]+' | tail -n 1)" + # backfill the index db first + lotus index validate-backfill --from $$SNAPSHOT_EPOCH --to $(($$SNAPSHOT_EPOCH - 300)) + sleep 2 + # `sethead` right after `sync wait` to ensure the head is not set in middle of a sync + lotus chain sethead --epoch $(($$SNAPSHOT_EPOCH - 50)) # wait for 30s to make sure the re-validation starts sleep 30 lotus sync wait diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index 84b89d014258..d03d58fc1114 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -9,5 +9,3 @@ # TODO: https://github.com/ChainSafe/forest/issues/4996 !Filecoin.EthGetTransactionReceipt !Filecoin.EthGetTransactionReceiptLimited -# TODO: https://github.com/ChainSafe/forest/issues/5006 -!Filecoin.EthGetBlockReceipts diff --git a/scripts/tests/api_compare/filter-list-offline b/scripts/tests/api_compare/filter-list-offline index e88dfbaf4d42..eac58f93a623 100644 --- a/scripts/tests/api_compare/filter-list-offline +++ b/scripts/tests/api_compare/filter-list-offline @@ -39,5 +39,3 @@ # TODO: https://github.com/ChainSafe/forest/issues/4996 !Filecoin.EthGetTransactionReceipt !Filecoin.EthGetTransactionReceiptLimited -# TODO: https://github.com/ChainSafe/forest/issues/5006 -!Filecoin.EthGetBlockReceipts diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index eee4040f23ad..aaffea3f3187 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -144,6 +144,12 @@ pub struct Bloom( lotus_json_with_self!(Bloom); +impl Bloom { + pub fn accrue(&mut self, input: &[u8]) { + self.0.accrue(ethereum_types::BloomInput::Raw(input)); + } +} + #[derive( PartialEq, Debug, @@ -1043,10 +1049,11 @@ fn new_eth_tx( async fn new_eth_tx_receipt( ctx: &Ctx, + tipset: &Arc, tx: &ApiEthTx, - message_lookup: &MessageLookup, + msg_receipt: &Receipt, ) -> anyhow::Result { - let mut receipt = EthTxReceipt { + let mut tx_receipt = EthTxReceipt { transaction_hash: tx.hash.clone(), from: tx.from.clone(), to: tx.to.clone(), @@ -1054,54 +1061,79 @@ async fn new_eth_tx_receipt( block_hash: tx.block_hash.clone(), block_number: tx.block_number.clone(), r#type: tx.r#type.clone(), - status: (message_lookup.receipt.exit_code().is_success() as u64).into(), - gas_used: message_lookup.receipt.gas_used().into(), + status: (msg_receipt.exit_code().is_success() as u64).into(), + gas_used: msg_receipt.gas_used().into(), ..EthTxReceipt::new() }; - let ts = ctx - .chain_store() - .load_required_tipset_or_heaviest(&message_lookup.tipset)?; - - // This transaction is located in the parent tipset - let parent_ts = ctx - .chain_store() - .load_required_tipset_or_heaviest(ts.parents())?; - - let base_fee = parent_ts.block_headers().first().parent_base_fee.clone(); + tx_receipt.cumulative_gas_used = EthUint64::default(); let gas_fee_cap = tx.gas_fee_cap()?; let gas_premium = tx.gas_premium()?; let gas_outputs = GasOutputs::compute( - message_lookup.receipt.gas_used(), + msg_receipt.gas_used(), tx.gas.clone().into(), - &base_fee, + &tipset.block_headers().first().parent_base_fee, &gas_fee_cap.0.into(), &gas_premium.0.into(), ); - let total_spent: BigInt = gas_outputs.total_spent().into(); let mut effective_gas_price = EthBigInt::default(); - if message_lookup.receipt.gas_used() > 0 { - effective_gas_price = (total_spent / message_lookup.receipt.gas_used()).into(); + if msg_receipt.gas_used() > 0 { + effective_gas_price = (total_spent / msg_receipt.gas_used()).into(); } - receipt.effective_gas_price = effective_gas_price; + tx_receipt.effective_gas_price = effective_gas_price; - if receipt.to.is_none() && message_lookup.receipt.exit_code().is_success() { + if tx_receipt.to.is_none() && msg_receipt.exit_code().is_success() { // Create and Create2 return the same things. let ret: eam::CreateExternalReturn = - from_slice_with_fallback(message_lookup.receipt.return_data().bytes())?; + from_slice_with_fallback(msg_receipt.return_data().bytes())?; + + tx_receipt.contract_address = Some(ret.eth_address.0.into()); + } + + if msg_receipt.events_root().is_some() { + let logs = + eth_logs_for_block_and_transaction(ctx, tipset, &tx.block_hash, &tx.hash).await?; + if !logs.is_empty() { + tx_receipt.logs = logs; + } + } - receipt.contract_address = Some(ret.eth_address.0.into()); + let mut bloom = Bloom::default(); + for log in tx_receipt.logs.iter() { + for topic in log.topics.iter() { + bloom.accrue(topic.0.as_bytes()); + } + bloom.accrue(log.address.0.as_bytes()); } + tx_receipt.logs_bloom = bloom.into(); + + Ok(tx_receipt) +} + +async fn eth_logs_for_block_and_transaction( + ctx: &Ctx, + ts: &Arc, + block_hash: &EthHash, + tx_hash: &EthHash, +) -> anyhow::Result> { + let spec = EthFilterSpec { + block_hash: Some(block_hash.clone()), + ..Default::default() + }; let mut events = vec![]; - EthEventHandler::collect_events(ctx, &parent_ts, None, &mut events).await?; - receipt.logs = eth_filter_logs_from_events(ctx, &events)?; + EthEventHandler::collect_events(ctx, ts, Some(&spec), &mut events).await?; - Ok(receipt) + let logs = eth_filter_logs_from_events(ctx, &events)?; + let out: Vec<_> = logs + .into_iter() + .filter(|log| &log.transaction_hash == tx_hash) + .collect(); + Ok(out) } fn get_signed_message(ctx: &Ctx, message_cid: Cid) -> Result { @@ -1229,46 +1261,34 @@ impl RpcMethod<2> for EthGetBlockByNumber { async fn get_block_receipts( ctx: &Ctx, block_hash: EthHash, - limit: Option, + // TODO(forest): https://github.com/ChainSafe/forest/issues/5177 + _limit: Option, ) -> Result, ServerError> { let ts = get_tipset_from_hash(ctx.chain_store(), &block_hash)?; let ts_ref = Arc::new(ts); let ts_key = ts_ref.key(); - let (state_root, msgs_and_receipts) = execute_tipset(ctx, &ts_ref).await?; - let msgs_and_receipts = if let Some(limit) = limit { - msgs_and_receipts.into_iter().take(limit).collect() - } else { - msgs_and_receipts - }; + // Execute the tipset to get the messages and receipts + let (state_root, msgs_and_receipts) = execute_tipset(ctx, &ts_ref).await?; - let mut receipts = Vec::with_capacity(msgs_and_receipts.len()); - let state = StateTree::new_from_root(ctx.store_owned(), &state_root)?; + // Load the state tree + let state_tree = StateTree::new_from_root(ctx.store_owned(), &state_root)?; + let mut eth_receipts = Vec::with_capacity(msgs_and_receipts.len()); for (i, (msg, receipt)) in msgs_and_receipts.into_iter().enumerate() { - let return_dec = receipt.return_data().deserialize().unwrap_or(Ipld::Null); - - let message_lookup = MessageLookup { - receipt, - tipset: ts_key.clone(), - height: ts_ref.epoch(), - message: msg.cid(), - return_dec, - }; - let tx = new_eth_tx( ctx, - &state, + &state_tree, ts_ref.epoch(), &ts_key.cid()?, &msg.cid(), i as u64, )?; - let tx_receipt = new_eth_tx_receipt(ctx, &tx, &message_lookup).await?; - receipts.push(tx_receipt); + let receipt = new_eth_tx_receipt(ctx, &ts_ref, &tx, &receipt).await?; + eth_receipts.push(receipt); } - Ok(receipts) + Ok(eth_receipts) } pub enum EthGetBlockReceipts {} @@ -2340,7 +2360,7 @@ async fn get_eth_transaction_receipt( let tx = new_eth_tx_from_message_lookup(&ctx, &message_lookup, None) .with_context(|| format!("failed to convert {} into an Eth Tx", tx_hash))?; - let tx_receipt = new_eth_tx_receipt(&ctx, &tx, &message_lookup).await?; + let tx_receipt = new_eth_tx_receipt(&ctx, &tipset, &tx, &message_lookup.receipt).await?; Ok(tx_receipt) } diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index c2627b2452b2..69dce81adfd5 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -257,8 +257,9 @@ impl EthEventHandler { messages.len() == events.len(), "Length of messages and events do not match" ); + let mut event_count = 0; for (i, (message, events)) in messages.iter().zip(events.into_iter()).enumerate() { - for (j, event) in events.iter().enumerate() { + for event in events.iter() { let id_addr = Address::new_id(event.emitter()); let result = ctx .state_manager @@ -275,11 +276,10 @@ impl EthEventHandler { resolved } else { // Skip event + event_count += 1; continue; }; - let event_idx = j as u64; - let eth_emitter_addr = EthAddress::from_filecoin_address(&resolved)?; let entries: Vec = event.event().entries(); @@ -289,7 +289,7 @@ impl EthEventHandler { let matched = Self::do_match(spec, ð_emitter_addr, &entries); tracing::debug!( "Event {} {}match filter topics", - event_idx, + event_count, if matched { "" } else { "do not " } ); matched @@ -313,7 +313,7 @@ impl EthEventHandler { let ce = CollectedEvent { entries, emitter_addr: resolved, - event_idx, + event_idx: event_count, reverted: false, height, tipset_key: tipset_key.clone(), @@ -321,9 +321,11 @@ impl EthEventHandler { msg_cid: message.cid(), }; collected_events.push(ce); + event_count += 1; } } } + Ok(()) } diff --git a/src/rpc/methods/eth/types.rs b/src/rpc/methods/eth/types.rs index cac1efc27aae..63a2294435d8 100644 --- a/src/rpc/methods/eth/types.rs +++ b/src/rpc/methods/eth/types.rs @@ -36,6 +36,12 @@ impl From for EthBytes { } } +impl From for EthBytes { + fn from(value: Bloom) -> Self { + Self(value.0 .0.to_vec()) + } +} + impl FromStr for EthBytes { type Err = anyhow::Error;