1pub(crate) mod errors;
5mod eth_tx;
6pub mod filter;
7pub mod pubsub;
8pub(crate) mod pubsub_trait;
9mod trace;
10pub mod types;
11mod utils;
12
13use self::eth_tx::*;
14use self::filter::hex_str_to_epoch;
15use self::types::*;
16use super::gas;
17use crate::blocks::{Tipset, TipsetKey};
18use crate::chain::{ChainStore, index::ResolveNullTipset};
19use crate::chain_sync::NodeSyncStatus;
20use crate::cid_collections::CidHashSet;
21use crate::eth::{
22 EAMMethod, EVMMethod, EthChainId as EthChainIdType, EthEip1559TxArgs, EthLegacyEip155TxArgs,
23 EthLegacyHomesteadTxArgs,
24};
25use crate::eth::{SAFE_EPOCH_DELAY, parse_eth_transaction};
26use crate::interpreter::VMTrace;
27use crate::lotus_json::{HasLotusJson, lotus_json_with_self};
28use crate::message::{ChainMessage, Message as _, SignedMessage};
29use crate::rpc::error::ServerError;
30use crate::rpc::eth::errors::EthErrors;
31use crate::rpc::eth::filter::{
32 SkipEvent, event::EventFilter, mempool::MempoolFilter, tipset::TipSetFilter,
33};
34use crate::rpc::eth::types::{EthBlockTrace, EthTrace};
35use crate::rpc::eth::utils::decode_revert_reason;
36use crate::rpc::methods::chain::ChainGetTipSetV2;
37use crate::rpc::state::ApiInvocResult;
38use crate::rpc::types::{ApiTipsetKey, EventEntry, MessageLookup};
39use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod};
40use crate::rpc::{EthEventHandler, LOOKBACK_NO_LIMIT};
41use crate::shim::actors::EVMActorStateLoad as _;
42use crate::shim::actors::eam;
43use crate::shim::actors::evm;
44use crate::shim::actors::is_evm_actor;
45use crate::shim::actors::system;
46use crate::shim::address::{Address as FilecoinAddress, Protocol};
47use crate::shim::crypto::Signature;
48use crate::shim::econ::{BLOCK_GAS_LIMIT, TokenAmount};
49use crate::shim::error::ExitCode;
50use crate::shim::executor::Receipt;
51use crate::shim::fvm_shared_latest::MethodNum;
52use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress};
53use crate::shim::gas::GasOutputs;
54use crate::shim::message::Message;
55use crate::shim::trace::{CallReturn, ExecutionEvent};
56use crate::shim::{clock::ChainEpoch, state_tree::StateTree};
57use crate::state_manager::{StateLookupPolicy, VMFlush};
58use crate::utils::cache::SizeTrackingLruCache;
59use crate::utils::db::BlockstoreExt as _;
60use crate::utils::encoding::from_slice_with_fallback;
61use crate::utils::get_size::{CidWrapper, big_int_heap_size_helper};
62use crate::utils::misc::env::env_or_default;
63use crate::utils::multihash::prelude::*;
64use ahash::HashSet;
65use anyhow::{Context, Error, Result, anyhow, bail, ensure};
66use cid::Cid;
67use enumflags2::{BitFlags, make_bitflags};
68use filter::{ParsedFilter, ParsedFilterTipsets};
69use fvm_ipld_blockstore::Blockstore;
70use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW, RawBytes};
71use get_size2::GetSize;
72use ipld_core::ipld::Ipld;
73use itertools::Itertools;
74use nonzero_ext::nonzero;
75use num::{BigInt, Zero as _};
76use nunny::Vec as NonEmpty;
77use schemars::JsonSchema;
78use serde::{Deserialize, Serialize};
79use std::num::NonZeroUsize;
80use std::ops::RangeInclusive;
81use std::str::FromStr;
82use std::sync::{Arc, LazyLock};
83use utils::{decode_payload, lookup_eth_address};
84
85static FOREST_TRACE_FILTER_MAX_RESULT: LazyLock<u64> =
86 LazyLock::new(|| env_or_default("FOREST_TRACE_FILTER_MAX_RESULT", 500));
87
88const MASKED_ID_PREFIX: [u8; 12] = [0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
89
90const BLOOM_SIZE: usize = 2048;
93
94const BLOOM_SIZE_IN_BYTES: usize = BLOOM_SIZE / 8;
96
97const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES];
99
100const EMPTY_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0x0; BLOOM_SIZE_IN_BYTES];
102
103const ADDRESS_LENGTH: usize = 20;
105
106const EVM_WORD_LENGTH: usize = 32;
108
109const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347";
113
114const EMPTY_ROOT: &str = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421";
116
117const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff";
119
120#[derive(
123 Eq,
124 Hash,
125 PartialEq,
126 Debug,
127 Deserialize,
128 Serialize,
129 Default,
130 Clone,
131 JsonSchema,
132 derive_more::From,
133 derive_more::Into,
134)]
135pub struct EthBigInt(
136 #[serde(with = "crate::lotus_json::hexify")]
137 #[schemars(with = "String")]
138 pub BigInt,
139);
140lotus_json_with_self!(EthBigInt);
141
142impl GetSize for EthBigInt {
143 fn get_heap_size(&self) -> usize {
144 big_int_heap_size_helper(&self.0)
145 }
146}
147
148impl From<TokenAmount> for EthBigInt {
149 fn from(amount: TokenAmount) -> Self {
150 (&amount).into()
151 }
152}
153
154impl From<&TokenAmount> for EthBigInt {
155 fn from(amount: &TokenAmount) -> Self {
156 Self(amount.atto().to_owned())
157 }
158}
159
160type GasPriceResult = EthBigInt;
161
162#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)]
163pub struct Nonce(
164 #[schemars(with = "String")]
165 #[serde(with = "crate::lotus_json::hexify_bytes")]
166 pub ethereum_types::H64,
167);
168lotus_json_with_self!(Nonce);
169
170impl GetSize for Nonce {
171 fn get_heap_size(&self) -> usize {
172 0
173 }
174}
175
176#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)]
177pub struct Bloom(
178 #[schemars(with = "String")]
179 #[serde(with = "crate::lotus_json::hexify_bytes")]
180 pub ethereum_types::Bloom,
181);
182lotus_json_with_self!(Bloom);
183
184impl GetSize for Bloom {
185 fn get_heap_size(&self) -> usize {
186 0
187 }
188}
189
190impl Bloom {
191 pub fn accrue(&mut self, input: &[u8]) {
192 self.0.accrue(ethereum_types::BloomInput::Raw(input));
193 }
194}
195
196#[derive(
197 Eq,
198 Hash,
199 PartialEq,
200 Debug,
201 Deserialize,
202 Serialize,
203 Default,
204 Clone,
205 Copy,
206 JsonSchema,
207 derive_more::From,
208 derive_more::Into,
209 derive_more::Deref,
210 GetSize,
211)]
212pub struct EthUint64(
213 #[schemars(with = "String")]
214 #[serde(with = "crate::lotus_json::hexify")]
215 pub u64,
216);
217
218lotus_json_with_self!(EthUint64);
219
220impl EthUint64 {
221 pub fn from_bytes(data: &[u8]) -> Result<Self> {
222 if data.len() != EVM_WORD_LENGTH {
223 bail!("eth int must be {EVM_WORD_LENGTH} bytes");
224 }
225
226 if data
229 .get(..24)
230 .is_none_or(|slice| slice.iter().any(|&byte| byte != 0))
231 {
232 bail!("eth int overflows 64 bits");
233 }
234
235 Ok(Self(u64::from_be_bytes(
237 data.get(24..EVM_WORD_LENGTH)
238 .ok_or_else(|| anyhow::anyhow!("data too short"))?
239 .try_into()?,
240 )))
241 }
242
243 pub fn to_hex_string(self) -> String {
244 format!("0x{}", hex::encode(self.0.to_be_bytes()))
245 }
246}
247
248#[derive(
249 PartialEq,
250 Debug,
251 Deserialize,
252 Serialize,
253 Default,
254 Clone,
255 Copy,
256 JsonSchema,
257 derive_more::From,
258 derive_more::Into,
259 derive_more::Deref,
260)]
261pub struct EthInt64(
262 #[schemars(with = "String")]
263 #[serde(with = "crate::lotus_json::hexify")]
264 pub i64,
265);
266
267lotus_json_with_self!(EthInt64);
268
269impl EthHash {
270 pub fn to_cid(self) -> cid::Cid {
272 let mh = MultihashCode::Blake2b256
273 .wrap(self.0.as_bytes())
274 .expect("should not fail");
275 Cid::new_v1(DAG_CBOR, mh)
276 }
277
278 pub fn empty_uncles() -> Self {
279 Self(ethereum_types::H256::from_str(EMPTY_UNCLES).unwrap())
280 }
281
282 pub fn empty_root() -> Self {
283 Self(ethereum_types::H256::from_str(EMPTY_ROOT).unwrap())
284 }
285}
286
287impl From<Cid> for EthHash {
288 fn from(cid: Cid) -> Self {
289 let (_, digest, _) = cid.hash().into_inner();
290 EthHash(ethereum_types::H256::from_slice(&digest[0..32]))
291 }
292}
293
294impl From<[u8; EVM_WORD_LENGTH]> for EthHash {
295 fn from(value: [u8; EVM_WORD_LENGTH]) -> Self {
296 Self(ethereum_types::H256(value))
297 }
298}
299
300#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
301#[serde(rename_all = "camelCase")]
302pub enum Predefined {
303 Earliest,
304 Pending,
305 #[default]
306 Latest,
307}
308
309#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
310#[serde(rename_all = "camelCase")]
311pub enum ExtPredefined {
312 Earliest,
313 Pending,
314 #[default]
315 Latest,
316 Safe,
317 Finalized,
318}
319
320impl TryFrom<&ExtPredefined> for Predefined {
321 type Error = ();
322 fn try_from(ext: &ExtPredefined) -> Result<Self, Self::Error> {
323 match ext {
324 ExtPredefined::Earliest => Ok(Predefined::Earliest),
325 ExtPredefined::Pending => Ok(Predefined::Pending),
326 ExtPredefined::Latest => Ok(Predefined::Latest),
327 _ => Err(()),
328 }
329 }
330}
331
332#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
333#[serde(rename_all = "camelCase")]
334pub struct BlockNumber {
335 block_number: EthInt64,
336}
337
338#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
339#[serde(rename_all = "camelCase")]
340pub struct BlockHash {
341 block_hash: EthHash,
342 #[serde(default)]
343 require_canonical: bool,
344}
345
346#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
347#[serde(untagged)]
348pub enum BlockNumberOrHash {
349 #[schemars(with = "String")]
350 PredefinedBlock(Predefined),
351 BlockNumber(EthInt64),
352 BlockHash(EthHash),
353 BlockNumberObject(BlockNumber),
354 BlockHashObject(BlockHash),
355}
356
357lotus_json_with_self!(BlockNumberOrHash);
358
359impl BlockNumberOrHash {
360 pub fn from_predefined(predefined: Predefined) -> Self {
361 Self::PredefinedBlock(predefined)
362 }
363
364 pub fn from_block_number(number: i64) -> Self {
365 Self::BlockNumber(EthInt64(number))
366 }
367
368 pub fn from_block_hash(hash: EthHash) -> Self {
369 Self::BlockHash(hash)
370 }
371
372 pub fn from_block_number_object(number: i64) -> Self {
376 Self::BlockNumberObject(BlockNumber {
377 block_number: EthInt64(number),
378 })
379 }
380
381 pub fn from_block_hash_object(hash: EthHash, require_canonical: bool) -> Self {
385 Self::BlockHashObject(BlockHash {
386 block_hash: hash,
387 require_canonical,
388 })
389 }
390
391 pub fn from_str(s: &str) -> Result<Self, Error> {
392 match s {
393 "earliest" => Ok(BlockNumberOrHash::from_predefined(Predefined::Earliest)),
394 "pending" => Ok(BlockNumberOrHash::from_predefined(Predefined::Pending)),
395 "latest" | "" => Ok(BlockNumberOrHash::from_predefined(Predefined::Latest)),
396 hex if hex.starts_with("0x") => {
397 let epoch = hex_str_to_epoch(hex)?;
398 Ok(BlockNumberOrHash::from_block_number(epoch))
399 }
400 _ => Err(anyhow!("Invalid block identifier")),
401 }
402 }
403}
404
405#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
406#[serde(untagged)]
407pub enum ExtBlockNumberOrHash {
408 #[schemars(with = "String")]
409 PredefinedBlock(ExtPredefined),
410 BlockNumber(EthInt64),
411 BlockHash(EthHash),
412 BlockNumberObject(BlockNumber),
413 BlockHashObject(BlockHash),
414}
415
416lotus_json_with_self!(ExtBlockNumberOrHash);
417
418#[allow(dead_code)]
419impl ExtBlockNumberOrHash {
420 pub fn from_predefined(ext_predefined: ExtPredefined) -> Self {
421 Self::PredefinedBlock(ext_predefined)
422 }
423
424 pub fn from_block_number(number: i64) -> Self {
425 Self::BlockNumber(EthInt64(number))
426 }
427
428 pub fn from_block_hash(hash: EthHash) -> Self {
429 Self::BlockHash(hash)
430 }
431
432 pub fn from_block_number_object(number: i64) -> Self {
436 Self::BlockNumberObject(BlockNumber {
437 block_number: EthInt64(number),
438 })
439 }
440
441 pub fn from_block_hash_object(hash: EthHash, require_canonical: bool) -> Self {
445 Self::BlockHashObject(BlockHash {
446 block_hash: hash,
447 require_canonical,
448 })
449 }
450
451 pub fn from_str(s: &str) -> Result<Self, Error> {
452 match s {
453 "earliest" => Ok(ExtBlockNumberOrHash::from_predefined(
454 ExtPredefined::Earliest,
455 )),
456 "pending" => Ok(ExtBlockNumberOrHash::from_predefined(
457 ExtPredefined::Pending,
458 )),
459 "latest" | "" => Ok(ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest)),
460 "safe" => Ok(ExtBlockNumberOrHash::from_predefined(ExtPredefined::Safe)),
461 "finalized" => Ok(ExtBlockNumberOrHash::from_predefined(
462 ExtPredefined::Finalized,
463 )),
464 hex if hex.starts_with("0x") => {
465 let epoch = hex_str_to_epoch(hex)?;
466 Ok(ExtBlockNumberOrHash::from_block_number(epoch))
467 }
468 _ => Err(anyhow!("Invalid block identifier")),
469 }
470 }
471}
472
473#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
475#[serde(rename_all = "camelCase")]
476pub enum EthTraceType {
477 Trace,
480 StateDiff,
485}
486
487lotus_json_with_self!(EthTraceType);
488
489#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
491#[serde(rename_all = "camelCase")]
492pub struct EthTraceResults {
493 pub output: EthBytes,
495 pub state_diff: Option<StateDiff>,
497 #[serde(default)]
499 pub trace: Vec<EthTrace>,
500}
501
502lotus_json_with_self!(EthTraceResults);
503
504#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, GetSize)]
505#[serde(untagged)] pub enum Transactions {
507 Hash(Vec<String>),
508 Full(Vec<ApiEthTx>),
509}
510
511impl Transactions {
512 pub fn is_empty(&self) -> bool {
513 match self {
514 Self::Hash(v) => v.is_empty(),
515 Self::Full(v) => v.is_empty(),
516 }
517 }
518}
519
520impl PartialEq for Transactions {
521 fn eq(&self, other: &Self) -> bool {
522 match (self, other) {
523 (Self::Hash(a), Self::Hash(b)) => a == b,
524 (Self::Full(a), Self::Full(b)) => a == b,
525 _ => self.is_empty() && other.is_empty(),
526 }
527 }
528}
529
530impl Default for Transactions {
531 fn default() -> Self {
532 Self::Hash(vec![])
533 }
534}
535
536#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, JsonSchema, GetSize)]
537#[serde(rename_all = "camelCase")]
538pub struct Block {
539 pub hash: EthHash,
540 pub parent_hash: EthHash,
541 pub sha3_uncles: EthHash,
542 pub miner: EthAddress,
543 pub state_root: EthHash,
544 pub transactions_root: EthHash,
545 pub receipts_root: EthHash,
546 pub logs_bloom: Bloom,
547 pub difficulty: EthUint64,
548 pub total_difficulty: EthUint64,
549 pub number: EthUint64,
550 pub gas_limit: EthUint64,
551 pub gas_used: EthUint64,
552 pub timestamp: EthUint64,
553 pub extra_data: EthBytes,
554 pub mix_hash: EthHash,
555 pub nonce: Nonce,
556 pub base_fee_per_gas: EthBigInt,
557 pub size: EthUint64,
558 pub transactions: Transactions,
560 pub uncles: Vec<EthHash>,
561}
562
563#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565pub enum TxInfo {
566 Hash,
568 Full,
570}
571
572impl From<bool> for TxInfo {
573 fn from(full: bool) -> Self {
574 if full { TxInfo::Full } else { TxInfo::Hash }
575 }
576}
577
578impl Block {
579 pub fn new(has_transactions: bool, tipset_len: usize) -> Self {
580 Self {
581 gas_limit: EthUint64(BLOCK_GAS_LIMIT.saturating_mul(tipset_len as _)),
582 logs_bloom: Bloom(ethereum_types::Bloom(FULL_BLOOM)),
583 sha3_uncles: EthHash::empty_uncles(),
584 transactions_root: if has_transactions {
585 EthHash::default()
586 } else {
587 EthHash::empty_root()
588 },
589 ..Default::default()
590 }
591 }
592
593 pub async fn from_filecoin_tipset<DB: Blockstore + Send + Sync + 'static>(
597 ctx: Ctx<DB>,
598 tipset: crate::blocks::Tipset,
599 tx_info: TxInfo,
600 ) -> Result<Self> {
601 static ETH_BLOCK_CACHE: LazyLock<SizeTrackingLruCache<CidWrapper, Block>> =
602 LazyLock::new(|| {
603 const DEFAULT_CACHE_SIZE: NonZeroUsize = nonzero!(500usize);
604 let cache_size = std::env::var("FOREST_ETH_BLOCK_CACHE_SIZE")
605 .ok()
606 .and_then(|s| s.parse().ok())
607 .unwrap_or(DEFAULT_CACHE_SIZE);
608 SizeTrackingLruCache::new_with_metrics("eth_block".into(), cache_size)
609 });
610
611 let block_cid = tipset.key().cid()?;
612 let mut block = if let Some(b) = ETH_BLOCK_CACHE.get_cloned(&block_cid.into()) {
613 b
614 } else {
615 let parent_cid = tipset.parents().cid()?;
616 let block_number = EthUint64(tipset.epoch() as u64);
617 let block_hash: EthHash = block_cid.into();
618
619 let (state_root, msgs_and_receipts) = execute_tipset(&ctx, &tipset).await?;
620
621 let state_tree = ctx.state_manager.get_state_tree(&state_root)?;
622
623 let mut full_transactions = vec![];
624 let mut gas_used = 0;
625 for (i, (msg, receipt)) in msgs_and_receipts.iter().enumerate() {
626 let ti = EthUint64(i as u64);
627 gas_used += receipt.gas_used();
628 let smsg = match msg {
629 ChainMessage::Signed(msg) => msg.clone(),
630 ChainMessage::Unsigned(msg) => {
631 let sig = Signature::new_bls(vec![]);
632 SignedMessage::new_unchecked(msg.clone(), sig)
633 }
634 };
635
636 let mut tx = new_eth_tx_from_signed_message(
637 &smsg,
638 &state_tree,
639 ctx.chain_config().eth_chain_id,
640 )?;
641 tx.block_hash = block_hash;
642 tx.block_number = block_number;
643 tx.transaction_index = ti;
644 full_transactions.push(tx);
645 }
646
647 let b = Block {
648 hash: block_hash,
649 number: block_number,
650 parent_hash: parent_cid.into(),
651 timestamp: EthUint64(tipset.block_headers().first().timestamp),
652 base_fee_per_gas: tipset
653 .block_headers()
654 .first()
655 .parent_base_fee
656 .clone()
657 .into(),
658 gas_used: EthUint64(gas_used),
659 transactions: Transactions::Full(full_transactions),
660 ..Block::new(!msgs_and_receipts.is_empty(), tipset.len())
661 };
662 ETH_BLOCK_CACHE.push(block_cid.into(), b.clone());
663 b
664 };
665
666 if tx_info == TxInfo::Hash
667 && let Transactions::Full(transactions) = &block.transactions
668 {
669 block.transactions =
670 Transactions::Hash(transactions.iter().map(|tx| tx.hash.to_string()).collect())
671 }
672
673 Ok(block)
674 }
675}
676
677lotus_json_with_self!(Block);
678
679#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, JsonSchema, GetSize)]
680#[serde(rename_all = "camelCase")]
681pub struct ApiEthTx {
682 pub chain_id: EthUint64,
683 pub nonce: EthUint64,
684 pub hash: EthHash,
685 pub block_hash: EthHash,
686 pub block_number: EthUint64,
687 pub transaction_index: EthUint64,
688 pub from: EthAddress,
689 #[serde(skip_serializing_if = "Option::is_none", default)]
690 pub to: Option<EthAddress>,
691 pub value: EthBigInt,
692 pub r#type: EthUint64,
693 pub input: EthBytes,
694 pub gas: EthUint64,
695 #[serde(skip_serializing_if = "Option::is_none", default)]
696 pub max_fee_per_gas: Option<EthBigInt>,
697 #[serde(skip_serializing_if = "Option::is_none", default)]
698 pub max_priority_fee_per_gas: Option<EthBigInt>,
699 #[serde(skip_serializing_if = "Option::is_none", default)]
700 pub gas_price: Option<EthBigInt>,
701 #[schemars(with = "Option<Vec<EthHash>>")]
702 #[serde(with = "crate::lotus_json")]
703 pub access_list: Vec<EthHash>,
704 pub v: EthBigInt,
705 pub r: EthBigInt,
706 pub s: EthBigInt,
707}
708lotus_json_with_self!(ApiEthTx);
709
710impl ApiEthTx {
711 fn gas_fee_cap(&self) -> anyhow::Result<EthBigInt> {
712 self.max_fee_per_gas
713 .as_ref()
714 .or(self.gas_price.as_ref())
715 .cloned()
716 .context("gas fee cap is not set")
717 }
718
719 fn gas_premium(&self) -> anyhow::Result<EthBigInt> {
720 self.max_priority_fee_per_gas
721 .as_ref()
722 .or(self.gas_price.as_ref())
723 .cloned()
724 .context("gas premium is not set")
725 }
726}
727
728#[derive(Debug, Clone, Default, PartialEq, Eq)]
729pub struct EthSyncingResult {
730 pub done_sync: bool,
731 pub starting_block: i64,
732 pub current_block: i64,
733 pub highest_block: i64,
734}
735
736#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
737#[serde(untagged)]
738pub enum EthSyncingResultLotusJson {
739 DoneSync(bool),
740 Syncing {
741 #[schemars(with = "i64")]
742 #[serde(rename = "startingblock", with = "crate::lotus_json::hexify")]
743 starting_block: i64,
744 #[schemars(with = "i64")]
745 #[serde(rename = "currentblock", with = "crate::lotus_json::hexify")]
746 current_block: i64,
747 #[schemars(with = "i64")]
748 #[serde(rename = "highestblock", with = "crate::lotus_json::hexify")]
749 highest_block: i64,
750 },
751}
752
753impl HasLotusJson for EthSyncingResult {
756 type LotusJson = EthSyncingResultLotusJson;
757
758 #[cfg(test)]
759 fn snapshots() -> Vec<(serde_json::Value, Self)> {
760 vec![]
761 }
762
763 fn into_lotus_json(self) -> Self::LotusJson {
764 match self {
765 Self {
766 done_sync: false,
767 starting_block,
768 current_block,
769 highest_block,
770 } => EthSyncingResultLotusJson::Syncing {
771 starting_block,
772 current_block,
773 highest_block,
774 },
775 _ => EthSyncingResultLotusJson::DoneSync(false),
776 }
777 }
778
779 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
780 match lotus_json {
781 EthSyncingResultLotusJson::DoneSync(syncing) => {
782 if syncing {
783 tracing::error!("Invalid EthSyncingResultLotusJson: {syncing}");
785 }
786 Self {
787 done_sync: true,
788 ..Default::default()
789 }
790 }
791 EthSyncingResultLotusJson::Syncing {
792 starting_block,
793 current_block,
794 highest_block,
795 } => Self {
796 done_sync: false,
797 starting_block,
798 current_block,
799 highest_block,
800 },
801 }
802 }
803}
804
805#[derive(PartialEq, Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
806#[serde(rename_all = "camelCase")]
807pub struct EthTxReceipt {
808 transaction_hash: EthHash,
809 transaction_index: EthUint64,
810 block_hash: EthHash,
811 block_number: EthUint64,
812 from: EthAddress,
813 to: Option<EthAddress>,
814 root: EthHash,
815 status: EthUint64,
816 contract_address: Option<EthAddress>,
817 cumulative_gas_used: EthUint64,
818 gas_used: EthUint64,
819 effective_gas_price: EthBigInt,
820 logs_bloom: EthBytes,
821 logs: Vec<EthLog>,
822 r#type: EthUint64,
823}
824lotus_json_with_self!(EthTxReceipt);
825
826impl EthTxReceipt {
827 fn new() -> Self {
828 Self {
829 logs_bloom: EthBytes(EMPTY_BLOOM.to_vec()),
830 ..Self::default()
831 }
832 }
833}
834
835#[derive(PartialEq, Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
837#[serde(rename_all = "camelCase")]
838pub struct EthLog {
839 address: EthAddress,
841 data: EthBytes,
843 topics: Vec<EthHash>,
845 removed: bool,
847 log_index: EthUint64,
850 transaction_index: EthUint64,
853 transaction_hash: EthHash,
855 block_hash: EthHash,
857 block_number: EthUint64,
859}
860lotus_json_with_self!(EthLog);
861
862pub enum Web3ClientVersion {}
863impl RpcMethod<0> for Web3ClientVersion {
864 const NAME: &'static str = "Filecoin.Web3ClientVersion";
865 const NAME_ALIAS: Option<&'static str> = Some("web3_clientVersion");
866 const PARAM_NAMES: [&'static str; 0] = [];
867 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
868 const PERMISSION: Permission = Permission::Read;
869
870 type Params = ();
871 type Ok = String;
872
873 async fn handle(
874 _: Ctx<impl Blockstore + Send + Sync + 'static>,
875 (): Self::Params,
876 ) -> Result<Self::Ok, ServerError> {
877 Ok(format!(
878 "forest/{}",
879 *crate::utils::version::FOREST_VERSION_STRING
880 ))
881 }
882}
883
884pub enum EthAccounts {}
885impl RpcMethod<0> for EthAccounts {
886 const NAME: &'static str = "Filecoin.EthAccounts";
887 const NAME_ALIAS: Option<&'static str> = Some("eth_accounts");
888 const PARAM_NAMES: [&'static str; 0] = [];
889 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
890 const PERMISSION: Permission = Permission::Read;
891
892 type Params = ();
893 type Ok = Vec<String>;
894
895 async fn handle(
896 _: Ctx<impl Blockstore + Send + Sync + 'static>,
897 (): Self::Params,
898 ) -> Result<Self::Ok, ServerError> {
899 Ok(vec![])
901 }
902}
903
904pub enum EthBlockNumber {}
905impl RpcMethod<0> for EthBlockNumber {
906 const NAME: &'static str = "Filecoin.EthBlockNumber";
907 const NAME_ALIAS: Option<&'static str> = Some("eth_blockNumber");
908 const PARAM_NAMES: [&'static str; 0] = [];
909 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
910 const PERMISSION: Permission = Permission::Read;
911
912 type Params = ();
913 type Ok = EthUint64;
914
915 async fn handle(
916 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
917 (): Self::Params,
918 ) -> Result<Self::Ok, ServerError> {
919 let heaviest = ctx.chain_store().heaviest_tipset();
925 if heaviest.epoch() == 0 {
926 return Ok(EthUint64::default());
928 }
929 let effective_parent = heaviest.parents();
931 if let Ok(Some(parent)) = ctx.chain_index().load_tipset(effective_parent) {
932 Ok((parent.epoch() as u64).into())
933 } else {
934 Ok(EthUint64::default())
935 }
936 }
937}
938
939pub enum EthChainId {}
940impl RpcMethod<0> for EthChainId {
941 const NAME: &'static str = "Filecoin.EthChainId";
942 const NAME_ALIAS: Option<&'static str> = Some("eth_chainId");
943 const PARAM_NAMES: [&'static str; 0] = [];
944 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
945 const PERMISSION: Permission = Permission::Read;
946
947 type Params = ();
948 type Ok = String;
949
950 async fn handle(
951 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
952 (): Self::Params,
953 ) -> Result<Self::Ok, ServerError> {
954 Ok(format!("{:#x}", ctx.chain_config().eth_chain_id))
955 }
956}
957
958pub enum EthGasPrice {}
959impl RpcMethod<0> for EthGasPrice {
960 const NAME: &'static str = "Filecoin.EthGasPrice";
961 const NAME_ALIAS: Option<&'static str> = Some("eth_gasPrice");
962 const PARAM_NAMES: [&'static str; 0] = [];
963 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
964 const PERMISSION: Permission = Permission::Read;
965 const DESCRIPTION: Option<&'static str> = Some("Returns the current gas price in attoFIL");
966
967 type Params = ();
968 type Ok = GasPriceResult;
969
970 async fn handle(
971 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
972 (): Self::Params,
973 ) -> Result<Self::Ok, ServerError> {
974 let ts = ctx.chain_store().heaviest_tipset();
977 let block0 = ts.block_headers().first();
978 let base_fee = block0.parent_base_fee.atto();
979 let tip = crate::rpc::gas::estimate_gas_premium(&ctx, 0, &ApiTipsetKey(None))
980 .await
981 .map(|gas_premium| gas_premium.atto().to_owned())
982 .unwrap_or_default();
983 Ok(EthBigInt(base_fee + tip))
984 }
985}
986
987pub enum EthGetBalance {}
988impl RpcMethod<2> for EthGetBalance {
989 const NAME: &'static str = "Filecoin.EthGetBalance";
990 const NAME_ALIAS: Option<&'static str> = Some("eth_getBalance");
991 const PARAM_NAMES: [&'static str; 2] = ["address", "blockParam"];
992 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
993 const PERMISSION: Permission = Permission::Read;
994 const DESCRIPTION: Option<&'static str> =
995 Some("Returns the balance of an Ethereum address at the specified block state");
996
997 type Params = (EthAddress, BlockNumberOrHash);
998 type Ok = EthBigInt;
999
1000 async fn handle(
1001 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1002 (address, block_param): Self::Params,
1003 ) -> Result<Self::Ok, ServerError> {
1004 let ts = tipset_by_block_number_or_hash(
1005 ctx.chain_store(),
1006 block_param,
1007 ResolveNullTipset::TakeOlder,
1008 )?;
1009 let balance = eth_get_balance(&ctx, &address, &ts).await?;
1010 Ok(balance)
1011 }
1012}
1013
1014pub enum EthGetBalanceV2 {}
1015impl RpcMethod<2> for EthGetBalanceV2 {
1016 const NAME: &'static str = "Filecoin.EthGetBalance";
1017 const NAME_ALIAS: Option<&'static str> = Some("eth_getBalance");
1018 const PARAM_NAMES: [&'static str; 2] = ["address", "blockParam"];
1019 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
1020 const PERMISSION: Permission = Permission::Read;
1021 const DESCRIPTION: Option<&'static str> =
1022 Some("Returns the balance of an Ethereum address at the specified block state");
1023
1024 type Params = (EthAddress, ExtBlockNumberOrHash);
1025 type Ok = EthBigInt;
1026
1027 async fn handle(
1028 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1029 (address, block_param): Self::Params,
1030 ) -> Result<Self::Ok, ServerError> {
1031 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
1032 .await?;
1033 let balance = eth_get_balance(&ctx, &address, &ts).await?;
1034 Ok(balance)
1035 }
1036}
1037
1038async fn eth_get_balance<DB: Blockstore + Send + Sync + 'static>(
1039 ctx: &Ctx<DB>,
1040 address: &EthAddress,
1041 ts: &Tipset,
1042) -> Result<EthBigInt> {
1043 let fil_addr = address.to_filecoin_address()?;
1044 let (state_cid, _) = ctx
1045 .state_manager
1046 .tipset_state(ts, StateLookupPolicy::Enabled)
1047 .await?;
1048 let state_tree = ctx.state_manager.get_state_tree(&state_cid)?;
1049 match state_tree.get_actor(&fil_addr)? {
1050 Some(actor) => Ok(EthBigInt(actor.balance.atto().clone())),
1051 None => Ok(EthBigInt::default()), }
1053}
1054
1055fn get_tipset_from_hash<DB: Blockstore>(
1056 chain_store: &ChainStore<DB>,
1057 block_hash: &EthHash,
1058) -> anyhow::Result<Tipset> {
1059 let tsk = chain_store.get_required_tipset_key(block_hash)?;
1060 Tipset::load_required(chain_store.blockstore(), &tsk)
1061}
1062
1063fn resolve_predefined_tipset<DB: Blockstore>(
1064 chain: &ChainStore<DB>,
1065 head: Tipset,
1066 predefined: Predefined,
1067) -> anyhow::Result<Tipset> {
1068 match predefined {
1069 Predefined::Earliest => bail!("block param \"earliest\" is not supported"),
1070 Predefined::Pending => Ok(head),
1071 Predefined::Latest => Ok(chain.chain_index().load_required_tipset(head.parents())?),
1072 }
1073}
1074
1075async fn resolve_predefined_tipset_v2<DB: Blockstore + Send + Sync + 'static>(
1076 ctx: &Ctx<DB>,
1077 head: Tipset,
1078 tag: ExtPredefined,
1079) -> anyhow::Result<Tipset> {
1080 if let Ok(common) = Predefined::try_from(&tag) {
1081 resolve_predefined_tipset(ctx.chain_store(), head, common)
1082 } else {
1083 match tag {
1084 ExtPredefined::Safe => Ok(ChainGetTipSetV2::get_latest_safe_tipset(ctx).await?),
1085 ExtPredefined::Finalized => Ok(ChainGetTipSetV2::get_latest_finalized_tipset(ctx)
1086 .await?
1087 .unwrap_or(ctx.chain_index().tipset_by_height(
1088 0,
1089 head,
1090 ResolveNullTipset::TakeOlder,
1091 )?)),
1092 _ => bail!("unknown block tag: {:?}", tag),
1093 }
1094 }
1095}
1096
1097fn resolve_ext_predefined_tipset<DB: Blockstore>(
1098 chain: &ChainStore<DB>,
1099 head: Tipset,
1100 ext_predefined: ExtPredefined,
1101 resolve: ResolveNullTipset,
1102) -> anyhow::Result<Tipset> {
1103 if let Ok(common) = Predefined::try_from(&ext_predefined) {
1104 resolve_predefined_tipset(chain, head, common)
1105 } else {
1106 let latest_height = head.epoch() - 1;
1107 match ext_predefined {
1109 ExtPredefined::Safe => {
1110 let safe_height = latest_height - SAFE_EPOCH_DELAY;
1111 Ok(chain
1112 .chain_index()
1113 .tipset_by_height(safe_height, head, resolve)?)
1114 }
1115 ExtPredefined::Finalized => {
1116 let finality_height = latest_height - chain.chain_config().policy.chain_finality;
1117 Ok(chain
1118 .chain_index()
1119 .tipset_by_height(finality_height, head, resolve)?)
1120 }
1121 _ => bail!("Unhandled ExtPredefined variant: {:?}", ext_predefined),
1122 }
1123 }
1124}
1125
1126fn resolve_block_number_tipset<DB: Blockstore>(
1127 chain: &ChainStore<DB>,
1128 head: Tipset,
1129 block_number: EthInt64,
1130 resolve: ResolveNullTipset,
1131) -> anyhow::Result<Tipset> {
1132 let height = ChainEpoch::from(block_number.0);
1133 if height > head.epoch() - 1 {
1134 bail!("requested a future epoch (beyond \"latest\")");
1135 }
1136 Ok(chain
1137 .chain_index()
1138 .tipset_by_height(height, head, resolve)?)
1139}
1140
1141fn resolve_block_hash_tipset<DB: Blockstore>(
1142 chain: &ChainStore<DB>,
1143 head: Tipset,
1144 block_hash: &EthHash,
1145 require_canonical: bool,
1146 resolve: ResolveNullTipset,
1147) -> anyhow::Result<Tipset> {
1148 let ts = get_tipset_from_hash(chain, block_hash)?;
1149 if require_canonical {
1151 let walk_ts = chain
1153 .chain_index()
1154 .tipset_by_height(ts.epoch(), head, resolve)?;
1155 if walk_ts != ts {
1157 bail!("tipset is not canonical");
1158 }
1159 }
1160 Ok(ts)
1161}
1162
1163fn tipset_by_block_number_or_hash<DB: Blockstore>(
1164 chain: &ChainStore<DB>,
1165 block_param: BlockNumberOrHash,
1166 resolve: ResolveNullTipset,
1167) -> anyhow::Result<Tipset> {
1168 let head = chain.heaviest_tipset();
1169 match block_param {
1170 BlockNumberOrHash::PredefinedBlock(predefined) => {
1171 resolve_predefined_tipset(chain, head, predefined)
1172 }
1173 BlockNumberOrHash::BlockNumber(block_number)
1174 | BlockNumberOrHash::BlockNumberObject(BlockNumber { block_number }) => {
1175 resolve_block_number_tipset(chain, head, block_number, resolve)
1176 }
1177 BlockNumberOrHash::BlockHash(block_hash) => {
1178 resolve_block_hash_tipset(chain, head, &block_hash, false, resolve)
1179 }
1180 BlockNumberOrHash::BlockHashObject(BlockHash {
1181 block_hash,
1182 require_canonical,
1183 }) => resolve_block_hash_tipset(chain, head, &block_hash, require_canonical, resolve),
1184 }
1185}
1186
1187async fn tipset_by_block_number_or_hash_v2<DB: Blockstore + Send + Sync + 'static>(
1188 ctx: &Ctx<DB>,
1189 block_param: ExtBlockNumberOrHash,
1190 resolve: ResolveNullTipset,
1191) -> anyhow::Result<Tipset> {
1192 let chain = ctx.chain_store();
1193 let head = chain.heaviest_tipset();
1194 match block_param {
1195 ExtBlockNumberOrHash::PredefinedBlock(predefined) => {
1196 resolve_predefined_tipset_v2(ctx, head, predefined).await
1197 }
1198 ExtBlockNumberOrHash::BlockNumber(block_number)
1199 | ExtBlockNumberOrHash::BlockNumberObject(BlockNumber { block_number }) => {
1200 resolve_block_number_tipset(chain, head, block_number, resolve)
1201 }
1202 ExtBlockNumberOrHash::BlockHash(block_hash) => {
1203 resolve_block_hash_tipset(chain, head, &block_hash, false, resolve)
1204 }
1205 ExtBlockNumberOrHash::BlockHashObject(BlockHash {
1206 block_hash,
1207 require_canonical,
1208 }) => resolve_block_hash_tipset(chain, head, &block_hash, require_canonical, resolve),
1209 }
1210}
1211
1212fn tipset_by_ext_block_number_or_hash<DB: Blockstore>(
1213 chain: &ChainStore<DB>,
1214 block_param: ExtBlockNumberOrHash,
1215 resolve: ResolveNullTipset,
1216) -> anyhow::Result<Tipset> {
1217 let head = chain.heaviest_tipset();
1218 match block_param {
1219 ExtBlockNumberOrHash::PredefinedBlock(ext_predefined) => {
1220 resolve_ext_predefined_tipset(chain, head, ext_predefined, resolve)
1221 }
1222 ExtBlockNumberOrHash::BlockNumber(block_number)
1223 | ExtBlockNumberOrHash::BlockNumberObject(BlockNumber { block_number }) => {
1224 resolve_block_number_tipset(chain, head, block_number, resolve)
1225 }
1226 ExtBlockNumberOrHash::BlockHash(block_hash) => {
1227 resolve_block_hash_tipset(chain, head, &block_hash, false, resolve)
1228 }
1229 ExtBlockNumberOrHash::BlockHashObject(BlockHash {
1230 block_hash,
1231 require_canonical,
1232 }) => resolve_block_hash_tipset(chain, head, &block_hash, require_canonical, resolve),
1233 }
1234}
1235
1236async fn execute_tipset<DB: Blockstore + Send + Sync + 'static>(
1237 data: &Ctx<DB>,
1238 tipset: &Tipset,
1239) -> Result<(Cid, Vec<(ChainMessage, Receipt)>)> {
1240 let msgs = data.chain_store().messages_for_tipset(tipset)?;
1241
1242 let (state_root, _) = data
1243 .state_manager
1244 .tipset_state(tipset, StateLookupPolicy::Enabled)
1245 .await?;
1246 let receipts = data.state_manager.tipset_message_receipts(tipset).await?;
1247
1248 if msgs.len() != receipts.len() {
1249 bail!("receipts and message array lengths didn't match for tipset: {tipset:?}")
1250 }
1251
1252 Ok((
1253 state_root,
1254 msgs.into_iter().zip(receipts.into_iter()).collect(),
1255 ))
1256}
1257
1258fn is_eth_address(addr: &VmAddress) -> bool {
1259 if addr.protocol() != Protocol::Delegated {
1260 return false;
1261 }
1262 let f4_addr: Result<DelegatedAddress, _> = addr.payload().try_into();
1263
1264 f4_addr.is_ok()
1265}
1266
1267pub fn eth_tx_from_signed_eth_message(
1273 smsg: &SignedMessage,
1274 chain_id: EthChainIdType,
1275) -> Result<(EthAddress, EthTx)> {
1276 let from = smsg.message().from;
1278 if !is_eth_address(&from) {
1279 bail!("sender must be an eth account, was {from}");
1280 }
1281 let from = EthAddress::from_filecoin_address(&from)?;
1284 let tx = EthTx::from_signed_message(chain_id, smsg)?;
1285 Ok((from, tx))
1286}
1287
1288fn encode_filecoin_params_as_abi(
1291 method: MethodNum,
1292 codec: u64,
1293 params: &fvm_ipld_encoding::RawBytes,
1294) -> Result<EthBytes> {
1295 let mut buffer: Vec<u8> = vec![0x86, 0x8e, 0x10, 0xc4];
1296 buffer.append(&mut encode_filecoin_returns_as_abi(method, codec, params));
1297 Ok(EthBytes(buffer))
1298}
1299
1300fn encode_filecoin_returns_as_abi(
1301 exit_code: u64,
1302 codec: u64,
1303 data: &fvm_ipld_encoding::RawBytes,
1304) -> Vec<u8> {
1305 encode_as_abi_helper(exit_code, codec, data)
1306}
1307
1308fn round_up_word(value: usize) -> usize {
1310 value.div_ceil(EVM_WORD_LENGTH) * EVM_WORD_LENGTH
1311}
1312
1313fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec<u8> {
1315 let static_args = [
1321 param1,
1322 param2,
1323 (EVM_WORD_LENGTH * 3) as u64,
1324 data.len() as u64,
1325 ];
1326 let padding = [0u8; 24];
1327 let buf: Vec<u8> = padding
1328 .iter() .chain(static_args[0].to_be_bytes().iter()) .chain(padding.iter())
1331 .chain(static_args[1].to_be_bytes().iter())
1332 .chain(padding.iter())
1333 .chain(static_args[2].to_be_bytes().iter())
1334 .chain(padding.iter())
1335 .chain(static_args[3].to_be_bytes().iter())
1336 .chain(data.iter()) .chain(std::iter::repeat_n(
1338 &0u8,
1339 round_up_word(data.len()) - data.len(),
1340 )) .cloned()
1342 .collect();
1343
1344 buf
1345}
1346
1347fn eth_tx_from_native_message<DB: Blockstore>(
1360 msg: &Message,
1361 state: &StateTree<DB>,
1362 chain_id: EthChainIdType,
1363) -> Result<ApiEthTx> {
1364 let from = match lookup_eth_address(&msg.from(), state) {
1366 Ok(Some(from)) => from,
1367 _ => bail!(
1368 "failed to lookup sender address {} when converting a native message to an eth txn",
1369 msg.from()
1370 ),
1371 };
1372 let mut to = match lookup_eth_address(&msg.to(), state) {
1375 Ok(Some(addr)) => Some(addr),
1376 Ok(None) => Some(EthAddress(
1377 ethereum_types::H160::from_str(REVERTED_ETH_ADDRESS).unwrap(),
1378 )),
1379 Err(err) => {
1380 bail!(err)
1381 }
1382 };
1383
1384 let codec = if !msg.params().is_empty() { CBOR } else { 0 };
1389
1390 let input = 'decode: {
1393 if (msg.method_num() == EVMMethod::InvokeContract as MethodNum
1394 || msg.method_num() == EAMMethod::CreateExternal as MethodNum)
1395 && let Ok(buffer) = decode_payload(msg.params(), codec)
1396 {
1397 if msg.method_num() == EAMMethod::CreateExternal as MethodNum {
1399 to = None;
1400 }
1401 break 'decode buffer;
1402 }
1403 encode_filecoin_params_as_abi(msg.method_num(), codec, msg.params())?
1406 };
1407
1408 Ok(ApiEthTx {
1409 to,
1410 from,
1411 input,
1412 nonce: EthUint64(msg.sequence),
1413 chain_id: EthUint64(chain_id),
1414 value: msg.value.clone().into(),
1415 r#type: EthUint64(EIP_1559_TX_TYPE.into()),
1416 gas: EthUint64(msg.gas_limit),
1417 max_fee_per_gas: Some(msg.gas_fee_cap.clone().into()),
1418 max_priority_fee_per_gas: Some(msg.gas_premium.clone().into()),
1419 access_list: vec![],
1420 ..ApiEthTx::default()
1421 })
1422}
1423
1424pub fn new_eth_tx_from_signed_message<DB: Blockstore>(
1425 smsg: &SignedMessage,
1426 state: &StateTree<DB>,
1427 chain_id: EthChainIdType,
1428) -> Result<ApiEthTx> {
1429 let (tx, hash) = if smsg.is_delegated() {
1430 let (from, tx) = eth_tx_from_signed_eth_message(smsg, chain_id)?;
1432 let hash = tx.eth_hash()?.into();
1433 let tx = ApiEthTx { from, ..tx.into() };
1434 (tx, hash)
1435 } else if smsg.is_secp256k1() {
1436 let tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?;
1438 (tx, smsg.cid().into())
1439 } else {
1440 let tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?;
1442 (tx, smsg.message().cid().into())
1443 };
1444 Ok(ApiEthTx { hash, ..tx })
1445}
1446
1447fn new_eth_tx_from_message_lookup<DB: Blockstore>(
1451 ctx: &Ctx<DB>,
1452 message_lookup: &MessageLookup,
1453 tx_index: Option<u64>,
1454) -> Result<ApiEthTx> {
1455 let ts = ctx
1456 .chain_store()
1457 .load_required_tipset_or_heaviest(&message_lookup.tipset)?;
1458
1459 let parent_ts = ctx
1461 .chain_store()
1462 .load_required_tipset_or_heaviest(ts.parents())?;
1463
1464 let parent_ts_cid = parent_ts.key().cid()?;
1465
1466 let tx_index = tx_index.map_or_else(
1468 || {
1469 let msgs = ctx.chain_store().messages_for_tipset(&parent_ts)?;
1470 msgs.iter()
1471 .position(|msg| msg.cid() == message_lookup.message)
1472 .context("cannot find the msg in the tipset")
1473 .map(|i| i as u64)
1474 },
1475 Ok,
1476 )?;
1477
1478 let smsg = get_signed_message(ctx, message_lookup.message)?;
1479
1480 let state = ctx.state_manager.get_state_tree(ts.parent_state())?;
1481
1482 Ok(ApiEthTx {
1483 block_hash: parent_ts_cid.into(),
1484 block_number: (parent_ts.epoch() as u64).into(),
1485 transaction_index: tx_index.into(),
1486 ..new_eth_tx_from_signed_message(&smsg, &state, ctx.chain_config().eth_chain_id)?
1487 })
1488}
1489
1490fn new_eth_tx<DB: Blockstore>(
1491 ctx: &Ctx<DB>,
1492 state: &StateTree<DB>,
1493 block_height: ChainEpoch,
1494 msg_tipset_cid: &Cid,
1495 msg_cid: &Cid,
1496 tx_index: u64,
1497) -> Result<ApiEthTx> {
1498 let smsg = get_signed_message(ctx, *msg_cid)?;
1499 let tx = new_eth_tx_from_signed_message(&smsg, state, ctx.chain_config().eth_chain_id)?;
1500
1501 Ok(ApiEthTx {
1502 block_hash: (*msg_tipset_cid).into(),
1503 block_number: (block_height as u64).into(),
1504 transaction_index: tx_index.into(),
1505 ..tx
1506 })
1507}
1508
1509async fn new_eth_tx_receipt<DB: Blockstore + Send + Sync + 'static>(
1510 ctx: &Ctx<DB>,
1511 tipset: &Tipset,
1512 tx: &ApiEthTx,
1513 msg_receipt: &Receipt,
1514) -> anyhow::Result<EthTxReceipt> {
1515 let mut tx_receipt = EthTxReceipt {
1516 transaction_hash: tx.hash,
1517 from: tx.from,
1518 to: tx.to,
1519 transaction_index: tx.transaction_index,
1520 block_hash: tx.block_hash,
1521 block_number: tx.block_number,
1522 r#type: tx.r#type,
1523 status: (msg_receipt.exit_code().is_success() as u64).into(),
1524 gas_used: msg_receipt.gas_used().into(),
1525 ..EthTxReceipt::new()
1526 };
1527
1528 tx_receipt.cumulative_gas_used = EthUint64::default();
1529
1530 let gas_fee_cap = tx.gas_fee_cap()?;
1531 let gas_premium = tx.gas_premium()?;
1532
1533 let gas_outputs = GasOutputs::compute(
1534 msg_receipt.gas_used(),
1535 tx.gas.into(),
1536 &tipset.block_headers().first().parent_base_fee,
1537 &gas_fee_cap.0.into(),
1538 &gas_premium.0.into(),
1539 );
1540 let total_spent: BigInt = gas_outputs.total_spent().into();
1541
1542 let mut effective_gas_price = EthBigInt::default();
1543 if msg_receipt.gas_used() > 0 {
1544 effective_gas_price = (total_spent / msg_receipt.gas_used()).into();
1545 }
1546 tx_receipt.effective_gas_price = effective_gas_price;
1547
1548 if tx_receipt.to.is_none() && msg_receipt.exit_code().is_success() {
1549 let ret: eam::CreateExternalReturn =
1551 from_slice_with_fallback(msg_receipt.return_data().bytes())?;
1552
1553 tx_receipt.contract_address = Some(ret.eth_address.0.into());
1554 }
1555
1556 if msg_receipt.events_root().is_some() {
1557 let logs =
1558 eth_logs_for_block_and_transaction(ctx, tipset, &tx.block_hash, &tx.hash).await?;
1559 if !logs.is_empty() {
1560 tx_receipt.logs = logs;
1561 }
1562 }
1563
1564 let mut bloom = Bloom::default();
1565 for log in tx_receipt.logs.iter() {
1566 for topic in log.topics.iter() {
1567 bloom.accrue(topic.0.as_bytes());
1568 }
1569 bloom.accrue(log.address.0.as_bytes());
1570 }
1571 tx_receipt.logs_bloom = bloom.into();
1572
1573 Ok(tx_receipt)
1574}
1575
1576pub async fn eth_logs_for_block_and_transaction<DB: Blockstore + Send + Sync + 'static>(
1577 ctx: &Ctx<DB>,
1578 ts: &Tipset,
1579 block_hash: &EthHash,
1580 tx_hash: &EthHash,
1581) -> anyhow::Result<Vec<EthLog>> {
1582 let spec = EthFilterSpec {
1583 block_hash: Some(*block_hash),
1584 ..Default::default()
1585 };
1586
1587 eth_logs_with_filter(ctx, ts, Some(spec), Some(tx_hash)).await
1588}
1589
1590pub async fn eth_logs_with_filter<DB: Blockstore + Send + Sync + 'static>(
1591 ctx: &Ctx<DB>,
1592 ts: &Tipset,
1593 spec: Option<EthFilterSpec>,
1594 tx_hash: Option<&EthHash>,
1595) -> anyhow::Result<Vec<EthLog>> {
1596 let mut events = vec![];
1597 EthEventHandler::collect_events(
1598 ctx,
1599 ts,
1600 spec.as_ref(),
1601 SkipEvent::OnUnresolvedAddress,
1602 &mut events,
1603 )
1604 .await?;
1605
1606 let logs = eth_filter_logs_from_events(ctx, &events)?;
1607 Ok(match tx_hash {
1608 Some(hash) => logs
1609 .into_iter()
1610 .filter(|log| &log.transaction_hash == hash)
1611 .collect(),
1612 None => logs, })
1614}
1615
1616fn get_signed_message<DB: Blockstore>(ctx: &Ctx<DB>, message_cid: Cid) -> Result<SignedMessage> {
1617 let result: Result<SignedMessage, crate::chain::Error> =
1618 crate::chain::message_from_cid(ctx.store(), &message_cid);
1619
1620 result.or_else(|_| {
1621 let msg: Message = crate::chain::message_from_cid(ctx.store(), &message_cid)
1623 .with_context(|| format!("failed to find msg {message_cid}"))?;
1624 Ok(SignedMessage::new_unchecked(
1625 msg,
1626 Signature::new_bls(vec![]),
1627 ))
1628 })
1629}
1630
1631pub enum EthGetBlockByHash {}
1632impl RpcMethod<2> for EthGetBlockByHash {
1633 const NAME: &'static str = "Filecoin.EthGetBlockByHash";
1634 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockByHash");
1635 const PARAM_NAMES: [&'static str; 2] = ["blockHash", "fullTxInfo"];
1636 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
1637 const PERMISSION: Permission = Permission::Read;
1638
1639 type Params = (EthHash, bool);
1640 type Ok = Block;
1641
1642 async fn handle(
1643 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1644 (block_hash, full_tx_info): Self::Params,
1645 ) -> Result<Self::Ok, ServerError> {
1646 let ts = tipset_by_block_number_or_hash(
1647 ctx.chain_store(),
1648 BlockNumberOrHash::from_block_hash(block_hash),
1649 ResolveNullTipset::TakeOlder,
1650 )?;
1651 Block::from_filecoin_tipset(ctx, ts, full_tx_info.into())
1652 .await
1653 .map_err(ServerError::from)
1654 }
1655}
1656
1657pub enum EthGetBlockByNumber {}
1658impl RpcMethod<2> for EthGetBlockByNumber {
1659 const NAME: &'static str = "Filecoin.EthGetBlockByNumber";
1660 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockByNumber");
1661 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "fullTxInfo"];
1662 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1663 const PERMISSION: Permission = Permission::Read;
1664 const DESCRIPTION: Option<&'static str> =
1665 Some("Retrieves a block by its number or a special tag.");
1666
1667 type Params = (BlockNumberOrPredefined, bool);
1668 type Ok = Block;
1669
1670 async fn handle(
1671 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1672 (block_param, full_tx_info): Self::Params,
1673 ) -> Result<Self::Ok, ServerError> {
1674 let ts = tipset_by_ext_block_number_or_hash(
1675 ctx.chain_store(),
1676 block_param.into(),
1677 ResolveNullTipset::TakeOlder,
1678 )?;
1679 Block::from_filecoin_tipset(ctx, ts, full_tx_info.into())
1680 .await
1681 .map_err(ServerError::from)
1682 }
1683}
1684
1685pub enum EthGetBlockByNumberV2 {}
1686impl RpcMethod<2> for EthGetBlockByNumberV2 {
1687 const NAME: &'static str = "Filecoin.EthGetBlockByNumber";
1688 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockByNumber");
1689 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "fullTxInfo"];
1690 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
1691 const PERMISSION: Permission = Permission::Read;
1692 const DESCRIPTION: Option<&'static str> =
1693 Some("Retrieves a block by its number or a special tag.");
1694
1695 type Params = (BlockNumberOrPredefined, bool);
1696 type Ok = Block;
1697
1698 async fn handle(
1699 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1700 (block_param, full_tx_info): Self::Params,
1701 ) -> Result<Self::Ok, ServerError> {
1702 let ts = tipset_by_block_number_or_hash_v2(
1703 &ctx,
1704 block_param.into(),
1705 ResolveNullTipset::TakeOlder,
1706 )
1707 .await?;
1708 Block::from_filecoin_tipset(ctx, ts, full_tx_info.into())
1709 .await
1710 .map_err(ServerError::from)
1711 }
1712}
1713
1714async fn get_block_receipts<DB: Blockstore + Send + Sync + 'static>(
1715 ctx: &Ctx<DB>,
1716 ts: Tipset,
1717 limit: Option<ChainEpoch>,
1718) -> Result<Vec<EthTxReceipt>> {
1719 if let Some(limit) = limit
1720 && limit > LOOKBACK_NO_LIMIT
1721 && ts.epoch() < ctx.chain_store().heaviest_tipset().epoch() - limit
1722 {
1723 bail!(
1724 "tipset {} is older than the allowed lookback limit",
1725 ts.key().format_lotus()
1726 );
1727 }
1728 let ts_ref = Arc::new(ts);
1729 let ts_key = ts_ref.key();
1730
1731 let (state_root, msgs_and_receipts) = execute_tipset(ctx, &ts_ref).await?;
1733
1734 let state_tree = ctx.state_manager.get_state_tree(&state_root)?;
1736
1737 let mut eth_receipts = Vec::with_capacity(msgs_and_receipts.len());
1738 for (i, (msg, receipt)) in msgs_and_receipts.into_iter().enumerate() {
1739 let tx = new_eth_tx(
1740 ctx,
1741 &state_tree,
1742 ts_ref.epoch(),
1743 &ts_key.cid()?,
1744 &msg.cid(),
1745 i as u64,
1746 )?;
1747
1748 let receipt = new_eth_tx_receipt(ctx, &ts_ref, &tx, &receipt).await?;
1749 eth_receipts.push(receipt);
1750 }
1751 Ok(eth_receipts)
1752}
1753
1754pub enum EthGetBlockReceipts {}
1755impl RpcMethod<1> for EthGetBlockReceipts {
1756 const NAME: &'static str = "Filecoin.EthGetBlockReceipts";
1757 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockReceipts");
1758 const PARAM_NAMES: [&'static str; 1] = ["blockParam"];
1759 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1760 const PERMISSION: Permission = Permission::Read;
1761 const DESCRIPTION: Option<&'static str> = Some(
1762 "Retrieves all transaction receipts for a block by its number, hash or a special tag.",
1763 );
1764
1765 type Params = (BlockNumberOrHash,);
1766 type Ok = Vec<EthTxReceipt>;
1767
1768 async fn handle(
1769 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1770 (block_param,): Self::Params,
1771 ) -> Result<Self::Ok, ServerError> {
1772 let ts = tipset_by_block_number_or_hash(
1773 ctx.chain_store(),
1774 block_param,
1775 ResolveNullTipset::TakeOlder,
1776 )?;
1777 get_block_receipts(&ctx, ts, None)
1778 .await
1779 .map_err(ServerError::from)
1780 }
1781}
1782
1783pub enum EthGetBlockReceiptsV2 {}
1784impl RpcMethod<1> for EthGetBlockReceiptsV2 {
1785 const NAME: &'static str = "Filecoin.EthGetBlockReceipts";
1786 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockReceipts");
1787 const PARAM_NAMES: [&'static str; 1] = ["blockParam"];
1788 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
1789 const PERMISSION: Permission = Permission::Read;
1790 const DESCRIPTION: Option<&'static str> = Some(
1791 "Retrieves all transaction receipts for a block by its number, hash or a special tag.",
1792 );
1793
1794 type Params = (ExtBlockNumberOrHash,);
1795 type Ok = Vec<EthTxReceipt>;
1796
1797 async fn handle(
1798 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1799 (block_param,): Self::Params,
1800 ) -> Result<Self::Ok, ServerError> {
1801 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
1802 .await?;
1803 get_block_receipts(&ctx, ts, None)
1804 .await
1805 .map_err(ServerError::from)
1806 }
1807}
1808
1809pub enum EthGetBlockReceiptsLimited {}
1810impl RpcMethod<2> for EthGetBlockReceiptsLimited {
1811 const NAME: &'static str = "Filecoin.EthGetBlockReceiptsLimited";
1812 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockReceiptsLimited");
1813 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "limit"];
1814 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1815 const PERMISSION: Permission = Permission::Read;
1816 const DESCRIPTION: Option<&'static str> = Some(
1817 "Retrieves all transaction receipts for a block identified by its number, hash or a special tag along with an optional limit on the chain epoch for state resolution.",
1818 );
1819
1820 type Params = (BlockNumberOrHash, ChainEpoch);
1821 type Ok = Vec<EthTxReceipt>;
1822
1823 async fn handle(
1824 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1825 (block_param, limit): Self::Params,
1826 ) -> Result<Self::Ok, ServerError> {
1827 let ts = tipset_by_block_number_or_hash(
1828 ctx.chain_store(),
1829 block_param,
1830 ResolveNullTipset::TakeOlder,
1831 )?;
1832 get_block_receipts(&ctx, ts, Some(limit))
1833 .await
1834 .map_err(ServerError::from)
1835 }
1836}
1837
1838pub enum EthGetBlockReceiptsLimitedV2 {}
1839impl RpcMethod<2> for EthGetBlockReceiptsLimitedV2 {
1840 const NAME: &'static str = "Filecoin.EthGetBlockReceiptsLimited";
1841 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockReceiptsLimited");
1842 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "limit"];
1843 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
1844 const PERMISSION: Permission = Permission::Read;
1845 const DESCRIPTION: Option<&'static str> = Some(
1846 "Retrieves all transaction receipts for a block identified by its number, hash or a special tag along with an optional limit on the chain epoch for state resolution.",
1847 );
1848
1849 type Params = (ExtBlockNumberOrHash, ChainEpoch);
1850 type Ok = Vec<EthTxReceipt>;
1851
1852 async fn handle(
1853 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1854 (block_param, limit): Self::Params,
1855 ) -> Result<Self::Ok, ServerError> {
1856 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
1857 .await?;
1858 get_block_receipts(&ctx, ts, Some(limit))
1859 .await
1860 .map_err(ServerError::from)
1861 }
1862}
1863
1864pub enum EthGetBlockTransactionCountByHash {}
1865impl RpcMethod<1> for EthGetBlockTransactionCountByHash {
1866 const NAME: &'static str = "Filecoin.EthGetBlockTransactionCountByHash";
1867 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockTransactionCountByHash");
1868 const PARAM_NAMES: [&'static str; 1] = ["blockHash"];
1869 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
1870 const PERMISSION: Permission = Permission::Read;
1871
1872 type Params = (EthHash,);
1873 type Ok = EthUint64;
1874
1875 async fn handle(
1876 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1877 (block_hash,): Self::Params,
1878 ) -> Result<Self::Ok, ServerError> {
1879 let ts = get_tipset_from_hash(ctx.chain_store(), &block_hash)?;
1880
1881 let head = ctx.chain_store().heaviest_tipset();
1882 if ts.epoch() > head.epoch() {
1883 return Err(anyhow::anyhow!("requested a future epoch (beyond \"latest\")").into());
1884 }
1885 let count = count_messages_in_tipset(ctx.store(), &ts)?;
1886 Ok(EthUint64(count as _))
1887 }
1888}
1889
1890pub enum EthGetBlockTransactionCountByNumber {}
1891impl RpcMethod<1> for EthGetBlockTransactionCountByNumber {
1892 const NAME: &'static str = "Filecoin.EthGetBlockTransactionCountByNumber";
1893 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockTransactionCountByNumber");
1894 const PARAM_NAMES: [&'static str; 1] = ["blockNumber"];
1895 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1896 const PERMISSION: Permission = Permission::Read;
1897 const DESCRIPTION: Option<&'static str> =
1898 Some("Returns the number of transactions in a block identified by its block number.");
1899
1900 type Params = (EthInt64,);
1901 type Ok = EthUint64;
1902
1903 async fn handle(
1904 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1905 (block_number,): Self::Params,
1906 ) -> Result<Self::Ok, ServerError> {
1907 let height = block_number.0;
1908 let head = ctx.chain_store().heaviest_tipset();
1909 if height > head.epoch() {
1910 return Err(anyhow::anyhow!("requested a future epoch (beyond \"latest\")").into());
1911 }
1912 let ts = ctx
1913 .chain_index()
1914 .tipset_by_height(height, head, ResolveNullTipset::TakeOlder)?;
1915 let count = count_messages_in_tipset(ctx.store(), &ts)?;
1916 Ok(EthUint64(count as _))
1917 }
1918}
1919
1920pub enum EthGetBlockTransactionCountByNumberV2 {}
1921impl RpcMethod<1> for EthGetBlockTransactionCountByNumberV2 {
1922 const NAME: &'static str = "Filecoin.EthGetBlockTransactionCountByNumber";
1923 const NAME_ALIAS: Option<&'static str> = Some("eth_getBlockTransactionCountByNumber");
1924 const PARAM_NAMES: [&'static str; 1] = ["blockNumber"];
1925 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
1926 const PERMISSION: Permission = Permission::Read;
1927 const DESCRIPTION: Option<&'static str> = Some(
1928 "Returns the number of transactions in a block identified by its block number or a special tag.",
1929 );
1930
1931 type Params = (BlockNumberOrPredefined,);
1932 type Ok = EthUint64;
1933
1934 async fn handle(
1935 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1936 (block_number,): Self::Params,
1937 ) -> Result<Self::Ok, ServerError> {
1938 let ts = tipset_by_block_number_or_hash_v2(
1939 &ctx,
1940 block_number.into(),
1941 ResolveNullTipset::TakeOlder,
1942 )
1943 .await?;
1944 let count = count_messages_in_tipset(ctx.store(), &ts)?;
1945 Ok(EthUint64(count as _))
1946 }
1947}
1948
1949pub enum EthGetMessageCidByTransactionHash {}
1950impl RpcMethod<1> for EthGetMessageCidByTransactionHash {
1951 const NAME: &'static str = "Filecoin.EthGetMessageCidByTransactionHash";
1952 const NAME_ALIAS: Option<&'static str> = Some("eth_getMessageCidByTransactionHash");
1953 const PARAM_NAMES: [&'static str; 1] = ["txHash"];
1954 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
1955 const PERMISSION: Permission = Permission::Read;
1956
1957 type Params = (EthHash,);
1958 type Ok = Option<Cid>;
1959
1960 async fn handle(
1961 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
1962 (tx_hash,): Self::Params,
1963 ) -> Result<Self::Ok, ServerError> {
1964 let result = ctx.chain_store().get_mapping(&tx_hash);
1965 match result {
1966 Ok(Some(cid)) => return Ok(Some(cid)),
1967 Ok(None) => tracing::debug!("Undefined key {tx_hash}"),
1968 _ => {
1969 result?;
1970 }
1971 }
1972
1973 let cid = tx_hash.to_cid();
1975
1976 let result: Result<Vec<SignedMessage>, crate::chain::Error> =
1977 crate::chain::messages_from_cids(ctx.store(), &[cid]);
1978 if result.is_ok() {
1979 return Ok(Some(cid));
1981 }
1982
1983 let result: Result<Vec<Message>, crate::chain::Error> =
1984 crate::chain::messages_from_cids(ctx.store(), &[cid]);
1985 if result.is_ok() {
1986 return Ok(Some(cid));
1988 }
1989
1990 Ok(None)
1992 }
1993}
1994
1995fn count_messages_in_tipset(store: &impl Blockstore, ts: &Tipset) -> anyhow::Result<usize> {
1996 let mut message_cids = CidHashSet::default();
1997 for block in ts.block_headers() {
1998 let (bls_messages, secp_messages) = crate::chain::store::block_messages(store, block)?;
1999 for m in bls_messages {
2000 message_cids.insert(m.cid());
2001 }
2002 for m in secp_messages {
2003 message_cids.insert(m.cid());
2004 }
2005 }
2006 Ok(message_cids.len())
2007}
2008
2009pub enum EthSyncing {}
2010impl RpcMethod<0> for EthSyncing {
2011 const NAME: &'static str = "Filecoin.EthSyncing";
2012 const NAME_ALIAS: Option<&'static str> = Some("eth_syncing");
2013 const PARAM_NAMES: [&'static str; 0] = [];
2014 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
2015 const PERMISSION: Permission = Permission::Read;
2016
2017 type Params = ();
2018 type Ok = EthSyncingResult;
2019
2020 async fn handle(
2021 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2022 (): Self::Params,
2023 ) -> Result<Self::Ok, ServerError> {
2024 let sync_status: crate::chain_sync::SyncStatusReport =
2025 crate::rpc::sync::SyncStatus::handle(ctx, ()).await?;
2026 match sync_status.status {
2027 NodeSyncStatus::Synced => Ok(EthSyncingResult {
2028 done_sync: true,
2029 ..Default::default()
2031 }),
2032 NodeSyncStatus::Syncing => {
2033 let starting_block = match sync_status.get_min_starting_block() {
2034 Some(e) => Ok(e),
2035 None => Err(ServerError::internal_error(
2036 "missing syncing information, try again",
2037 None,
2038 )),
2039 }?;
2040
2041 Ok(EthSyncingResult {
2042 done_sync: sync_status.is_synced(),
2043 starting_block,
2044 current_block: sync_status.current_head_epoch,
2045 highest_block: sync_status.network_head_epoch,
2046 })
2047 }
2048 _ => Err(ServerError::internal_error("node is not syncing", None)),
2049 }
2050 }
2051}
2052
2053pub enum EthEstimateGas {}
2054
2055impl RpcMethod<2> for EthEstimateGas {
2056 const NAME: &'static str = "Filecoin.EthEstimateGas";
2057 const NAME_ALIAS: Option<&'static str> = Some("eth_estimateGas");
2058 const N_REQUIRED_PARAMS: usize = 1;
2059 const PARAM_NAMES: [&'static str; 2] = ["tx", "blockParam"];
2060 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2061 const PERMISSION: Permission = Permission::Read;
2062
2063 type Params = (EthCallMessage, Option<BlockNumberOrHash>);
2064 type Ok = EthUint64;
2065
2066 async fn handle(
2067 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2068 (tx, block_param): Self::Params,
2069 ) -> Result<Self::Ok, ServerError> {
2070 let tipset = if let Some(block_param) = block_param {
2071 tipset_by_block_number_or_hash(
2072 ctx.chain_store(),
2073 block_param,
2074 ResolveNullTipset::TakeOlder,
2075 )?
2076 } else {
2077 ctx.chain_store().heaviest_tipset()
2078 };
2079 eth_estimate_gas(&ctx, tx, tipset).await
2080 }
2081}
2082
2083pub enum EthEstimateGasV2 {}
2084
2085impl RpcMethod<2> for EthEstimateGasV2 {
2086 const NAME: &'static str = "Filecoin.EthEstimateGas";
2087 const NAME_ALIAS: Option<&'static str> = Some("eth_estimateGas");
2088 const N_REQUIRED_PARAMS: usize = 1;
2089 const PARAM_NAMES: [&'static str; 2] = ["tx", "blockParam"];
2090 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
2091 const PERMISSION: Permission = Permission::Read;
2092
2093 type Params = (EthCallMessage, Option<ExtBlockNumberOrHash>);
2094 type Ok = EthUint64;
2095
2096 async fn handle(
2097 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2098 (tx, block_param): Self::Params,
2099 ) -> Result<Self::Ok, ServerError> {
2100 let tipset = if let Some(block_param) = block_param {
2101 tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
2102 .await?
2103 } else {
2104 ctx.chain_store().heaviest_tipset()
2105 };
2106 eth_estimate_gas(&ctx, tx, tipset).await
2107 }
2108}
2109
2110async fn eth_estimate_gas<DB>(
2111 ctx: &Ctx<DB>,
2112 tx: EthCallMessage,
2113 tipset: Tipset,
2114) -> Result<EthUint64, ServerError>
2115where
2116 DB: Blockstore + Send + Sync + 'static,
2117{
2118 let mut msg = Message::try_from(tx)?;
2119 msg.gas_limit = 0;
2122
2123 match gas::estimate_message_gas(ctx, msg.clone(), None, tipset.key().clone().into()).await {
2124 Err(mut err) => {
2125 msg.set_gas_limit(BLOCK_GAS_LIMIT);
2132 if let Err(e) = apply_message(ctx, Some(tipset), msg).await {
2133 if e.downcast_ref::<EthErrors>()
2135 .is_some_and(|eth_err| matches!(eth_err, EthErrors::ExecutionReverted { .. }))
2136 {
2137 return Err(e.into());
2138 }
2139
2140 err = e.into();
2141 }
2142
2143 Err(anyhow::anyhow!("failed to estimate gas: {err}").into())
2144 }
2145 Ok(gassed_msg) => {
2146 let expected_gas = eth_gas_search(ctx, gassed_msg, &tipset.key().into()).await?;
2147 Ok(expected_gas.into())
2148 }
2149 }
2150}
2151
2152async fn apply_message<DB>(
2153 ctx: &Ctx<DB>,
2154 tipset: Option<Tipset>,
2155 msg: Message,
2156) -> Result<ApiInvocResult, Error>
2157where
2158 DB: Blockstore + Send + Sync + 'static,
2159{
2160 let (invoc_res, _) = ctx
2161 .state_manager
2162 .apply_on_state_with_gas(tipset, msg, StateLookupPolicy::Enabled, VMFlush::Skip)
2163 .await
2164 .map_err(|e| anyhow::anyhow!("failed to apply on state with gas: {e}"))?;
2165
2166 match &invoc_res.msg_rct {
2168 None => return Err(anyhow::anyhow!("no message receipt in execution result")),
2169 Some(receipt) => {
2170 if !receipt.exit_code().is_success() {
2171 let (data, reason) = decode_revert_reason(receipt.return_data());
2172
2173 return Err(EthErrors::execution_reverted(
2174 ExitCode::from(receipt.exit_code()),
2175 reason.as_str(),
2176 invoc_res.error.as_str(),
2177 data.as_slice(),
2178 )
2179 .into());
2180 }
2181 }
2182 };
2183
2184 Ok(invoc_res)
2185}
2186
2187pub async fn eth_gas_search<DB>(
2188 data: &Ctx<DB>,
2189 msg: Message,
2190 tsk: &ApiTipsetKey,
2191) -> anyhow::Result<u64>
2192where
2193 DB: Blockstore + Send + Sync + 'static,
2194{
2195 let (_invoc_res, apply_ret, prior_messages, ts) =
2196 gas::GasEstimateGasLimit::estimate_call_with_gas(data, msg.clone(), tsk, VMTrace::Traced)
2197 .await?;
2198 if apply_ret.msg_receipt().exit_code().is_success() {
2199 return Ok(msg.gas_limit());
2200 }
2201
2202 let exec_trace = apply_ret.exec_trace();
2203 let _expected_exit_code: ExitCode = fvm_shared4::error::ExitCode::SYS_OUT_OF_GAS.into();
2204 if exec_trace.iter().any(|t| {
2205 matches!(
2206 t,
2207 &ExecutionEvent::CallReturn(CallReturn {
2208 exit_code: Some(_expected_exit_code),
2209 ..
2210 })
2211 )
2212 }) {
2213 let ret = gas_search(data, &msg, &prior_messages, ts).await?;
2214 Ok(((ret as f64) * data.mpool.config.gas_limit_overestimation) as u64)
2215 } else {
2216 anyhow::bail!(
2217 "message execution failed: exit {}, reason: {}",
2218 apply_ret.msg_receipt().exit_code(),
2219 apply_ret.failure_info().unwrap_or_default(),
2220 );
2221 }
2222}
2223
2224async fn gas_search<DB>(
2229 data: &Ctx<DB>,
2230 msg: &Message,
2231 prior_messages: &[ChainMessage],
2232 ts: Tipset,
2233) -> anyhow::Result<u64>
2234where
2235 DB: Blockstore + Send + Sync + 'static,
2236{
2237 let mut high = msg.gas_limit;
2238 let mut low = msg.gas_limit;
2239
2240 async fn can_succeed<DB>(
2241 data: &Ctx<DB>,
2242 mut msg: Message,
2243 prior_messages: &[ChainMessage],
2244 ts: Tipset,
2245 limit: u64,
2246 ) -> anyhow::Result<bool>
2247 where
2248 DB: Blockstore + Send + Sync + 'static,
2249 {
2250 msg.gas_limit = limit;
2251 let (_invoc_res, apply_ret, _, _) = data
2252 .state_manager
2253 .call_with_gas(
2254 &mut msg.into(),
2255 prior_messages,
2256 Some(ts),
2257 VMTrace::NotTraced,
2258 StateLookupPolicy::Enabled,
2259 VMFlush::Skip,
2260 )
2261 .await?;
2262 Ok(apply_ret.msg_receipt().exit_code().is_success())
2263 }
2264
2265 while high < BLOCK_GAS_LIMIT {
2266 if can_succeed(data, msg.clone(), prior_messages, ts.clone(), high).await? {
2267 break;
2268 }
2269 low = high;
2270 high = high.saturating_mul(2).min(BLOCK_GAS_LIMIT);
2271 }
2272
2273 let mut check_threshold = high / 100;
2274 while (high - low) > check_threshold {
2275 let median = (high + low) / 2;
2276 if can_succeed(data, msg.clone(), prior_messages, ts.clone(), median).await? {
2277 high = median;
2278 } else {
2279 low = median;
2280 }
2281 check_threshold = median / 100;
2282 }
2283
2284 Ok(high)
2285}
2286
2287pub enum EthFeeHistory {}
2288
2289impl RpcMethod<3> for EthFeeHistory {
2290 const NAME: &'static str = "Filecoin.EthFeeHistory";
2291 const NAME_ALIAS: Option<&'static str> = Some("eth_feeHistory");
2292 const N_REQUIRED_PARAMS: usize = 2;
2293 const PARAM_NAMES: [&'static str; 3] = ["blockCount", "newestBlockNumber", "rewardPercentiles"];
2294 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2295 const PERMISSION: Permission = Permission::Read;
2296
2297 type Params = (EthUint64, BlockNumberOrPredefined, Option<Vec<f64>>);
2298 type Ok = EthFeeHistoryResult;
2299
2300 async fn handle(
2301 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2302 (EthUint64(block_count), newest_block_number, reward_percentiles): Self::Params,
2303 ) -> Result<Self::Ok, ServerError> {
2304 let tipset = tipset_by_ext_block_number_or_hash(
2305 ctx.chain_store(),
2306 newest_block_number.into(),
2307 ResolveNullTipset::TakeOlder,
2308 )?;
2309
2310 eth_fee_history(ctx, tipset, block_count, reward_percentiles).await
2311 }
2312}
2313
2314pub enum EthFeeHistoryV2 {}
2315
2316impl RpcMethod<3> for EthFeeHistoryV2 {
2317 const NAME: &'static str = "Filecoin.EthFeeHistory";
2318 const NAME_ALIAS: Option<&'static str> = Some("eth_feeHistory");
2319 const N_REQUIRED_PARAMS: usize = 2;
2320 const PARAM_NAMES: [&'static str; 3] = ["blockCount", "newestBlockNumber", "rewardPercentiles"];
2321 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
2322 const PERMISSION: Permission = Permission::Read;
2323
2324 type Params = (EthUint64, ExtBlockNumberOrHash, Option<Vec<f64>>);
2325 type Ok = EthFeeHistoryResult;
2326
2327 async fn handle(
2328 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2329 (EthUint64(block_count), newest_block_number, reward_percentiles): Self::Params,
2330 ) -> Result<Self::Ok, ServerError> {
2331 let tipset = tipset_by_block_number_or_hash_v2(
2332 &ctx,
2333 newest_block_number,
2334 ResolveNullTipset::TakeOlder,
2335 )
2336 .await?;
2337
2338 eth_fee_history(ctx, tipset, block_count, reward_percentiles).await
2339 }
2340}
2341
2342async fn eth_fee_history<B: Blockstore + Send + Sync + 'static>(
2343 ctx: Ctx<B>,
2344 tipset: Tipset,
2345 block_count: u64,
2346 reward_percentiles: Option<Vec<f64>>,
2347) -> Result<EthFeeHistoryResult, ServerError> {
2348 if block_count > 1024 {
2349 return Err(anyhow::anyhow!("block count should be smaller than 1024").into());
2350 }
2351
2352 let reward_percentiles = reward_percentiles.unwrap_or_default();
2353 validate_reward_percentiles(&reward_percentiles)?;
2354
2355 let mut oldest_block_height = 1;
2356 let mut base_fee_array = vec![EthBigInt::from(
2361 &tipset.block_headers().first().parent_base_fee,
2362 )];
2363 let mut rewards_array = vec![];
2364 let mut gas_used_ratio_array = vec![];
2365 for ts in tipset
2366 .chain(ctx.store())
2367 .filter(|i| i.epoch() > 0)
2368 .take(block_count as _)
2369 {
2370 let base_fee = &ts.block_headers().first().parent_base_fee;
2371 let (_state_root, messages_and_receipts) = execute_tipset(&ctx, &ts).await?;
2372 let mut tx_gas_rewards = Vec::with_capacity(messages_and_receipts.len());
2373 for (message, receipt) in messages_and_receipts {
2374 let premium = message.effective_gas_premium(base_fee);
2375 tx_gas_rewards.push(GasReward {
2376 gas_used: receipt.gas_used(),
2377 premium,
2378 });
2379 }
2380 let (rewards, total_gas_used) =
2381 calculate_rewards_and_gas_used(&reward_percentiles, tx_gas_rewards);
2382 let max_gas = BLOCK_GAS_LIMIT * (ts.block_headers().len() as u64);
2383
2384 base_fee_array.push(EthBigInt::from(base_fee));
2386 gas_used_ratio_array.push((total_gas_used as f64) / (max_gas as f64));
2387 rewards_array.push(rewards);
2388
2389 oldest_block_height = ts.epoch();
2390 }
2391
2392 base_fee_array.reverse();
2394 gas_used_ratio_array.reverse();
2395 rewards_array.reverse();
2396
2397 Ok(EthFeeHistoryResult {
2398 oldest_block: EthUint64(oldest_block_height as _),
2399 base_fee_per_gas: base_fee_array,
2400 gas_used_ratio: gas_used_ratio_array,
2401 reward: if reward_percentiles.is_empty() {
2402 None
2403 } else {
2404 Some(rewards_array)
2405 },
2406 })
2407}
2408
2409fn validate_reward_percentiles(reward_percentiles: &[f64]) -> anyhow::Result<()> {
2410 if reward_percentiles.len() > 100 {
2411 anyhow::bail!("length of the reward percentile array cannot be greater than 100");
2412 }
2413
2414 for (&rp_prev, &rp) in std::iter::once(&0.0)
2415 .chain(reward_percentiles.iter())
2416 .tuple_windows()
2417 {
2418 if !(0. ..=100.).contains(&rp) {
2419 anyhow::bail!("invalid reward percentile: {rp} should be between 0 and 100");
2420 }
2421 if rp < rp_prev {
2422 anyhow::bail!(
2423 "invalid reward percentile: {rp} should be larger than or equal to {rp_prev}"
2424 );
2425 }
2426 }
2427
2428 Ok(())
2429}
2430
2431fn calculate_rewards_and_gas_used(
2432 reward_percentiles: &[f64],
2433 mut tx_gas_rewards: Vec<GasReward>,
2434) -> (Vec<EthBigInt>, u64) {
2435 const MIN_GAS_PREMIUM: u64 = 100000;
2436
2437 let gas_used_total = tx_gas_rewards.iter().map(|i| i.gas_used).sum();
2438 let mut rewards = reward_percentiles
2439 .iter()
2440 .map(|_| EthBigInt(MIN_GAS_PREMIUM.into()))
2441 .collect_vec();
2442 if !tx_gas_rewards.is_empty() {
2443 tx_gas_rewards.sort_by_key(|i| i.premium.clone());
2444 let mut idx = 0;
2445 let mut sum = 0;
2446 #[allow(clippy::indexing_slicing)]
2447 for (i, &percentile) in reward_percentiles.iter().enumerate() {
2448 let threshold = ((gas_used_total as f64) * percentile / 100.) as u64;
2449 while sum < threshold && idx < tx_gas_rewards.len() - 1 {
2450 sum += tx_gas_rewards[idx].gas_used;
2451 idx += 1;
2452 }
2453 rewards[i] = (&tx_gas_rewards[idx].premium).into();
2454 }
2455 }
2456 (rewards, gas_used_total)
2457}
2458
2459pub enum EthGetCode {}
2460impl RpcMethod<2> for EthGetCode {
2461 const NAME: &'static str = "Filecoin.EthGetCode";
2462 const NAME_ALIAS: Option<&'static str> = Some("eth_getCode");
2463 const PARAM_NAMES: [&'static str; 2] = ["ethAddress", "blockNumberOrHash"];
2464 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2465 const PERMISSION: Permission = Permission::Read;
2466 const DESCRIPTION: Option<&'static str> = Some(
2467 "Retrieves the contract code at a specific address and block state, identified by its number, hash, or a special tag.",
2468 );
2469
2470 type Params = (EthAddress, BlockNumberOrHash);
2471 type Ok = EthBytes;
2472
2473 async fn handle(
2474 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2475 (eth_address, block_param): Self::Params,
2476 ) -> Result<Self::Ok, ServerError> {
2477 let ts = tipset_by_block_number_or_hash(
2478 ctx.chain_store(),
2479 block_param,
2480 ResolveNullTipset::TakeOlder,
2481 )?;
2482 eth_get_code(&ctx, &ts, ð_address).await
2483 }
2484}
2485
2486pub enum EthGetCodeV2 {}
2487impl RpcMethod<2> for EthGetCodeV2 {
2488 const NAME: &'static str = "Filecoin.EthGetCode";
2489 const NAME_ALIAS: Option<&'static str> = Some("eth_getCode");
2490 const PARAM_NAMES: [&'static str; 2] = ["ethAddress", "blockNumberOrHash"];
2491 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
2492 const PERMISSION: Permission = Permission::Read;
2493 const DESCRIPTION: Option<&'static str> = Some(
2494 "Retrieves the contract code at a specific address and block state, identified by its number, hash, or a special tag.",
2495 );
2496
2497 type Params = (EthAddress, ExtBlockNumberOrHash);
2498 type Ok = EthBytes;
2499
2500 async fn handle(
2501 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2502 (eth_address, block_param): Self::Params,
2503 ) -> Result<Self::Ok, ServerError> {
2504 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
2505 .await?;
2506 eth_get_code(&ctx, &ts, ð_address).await
2507 }
2508}
2509
2510async fn eth_get_code<DB>(
2511 ctx: &Ctx<DB>,
2512 ts: &Tipset,
2513 eth_address: &EthAddress,
2514) -> Result<EthBytes, ServerError>
2515where
2516 DB: Blockstore + Send + Sync + 'static,
2517{
2518 let to_address = FilecoinAddress::try_from(eth_address)?;
2519 let (state, _) = ctx
2520 .state_manager
2521 .tipset_state(ts, StateLookupPolicy::Enabled)
2522 .await?;
2523 let state_tree = ctx.state_manager.get_state_tree(&state)?;
2524 let Some(actor) = state_tree
2525 .get_actor(&to_address)
2526 .with_context(|| format!("failed to lookup contract {}", eth_address.0))?
2527 else {
2528 return Ok(Default::default());
2529 };
2530
2531 if !is_evm_actor(&actor.code) {
2534 return Ok(Default::default());
2535 }
2536
2537 let message = Message {
2538 from: FilecoinAddress::SYSTEM_ACTOR,
2539 to: to_address,
2540 method_num: METHOD_GET_BYTE_CODE,
2541 gas_limit: BLOCK_GAS_LIMIT,
2542 ..Default::default()
2543 };
2544
2545 let api_invoc_result = 'invoc: {
2546 for ts in ts.clone().chain(ctx.store()) {
2547 match ctx.state_manager.call_on_state(state, &message, Some(ts)) {
2548 Ok(res) => {
2549 break 'invoc res;
2550 }
2551 Err(e) => tracing::warn!(%e),
2552 }
2553 }
2554 return Err(anyhow::anyhow!("Call failed").into());
2555 };
2556 let Some(msg_rct) = api_invoc_result.msg_rct else {
2557 return Err(anyhow::anyhow!("no message receipt").into());
2558 };
2559 if !msg_rct.exit_code().is_success() || !api_invoc_result.error.is_empty() {
2560 return Err(anyhow::anyhow!(
2561 "GetBytecode failed: exit={} error={}",
2562 msg_rct.exit_code(),
2563 api_invoc_result.error
2564 )
2565 .into());
2566 }
2567
2568 let get_bytecode_return: GetBytecodeReturn =
2569 fvm_ipld_encoding::from_slice(msg_rct.return_data().as_slice())?;
2570 if let Some(cid) = get_bytecode_return.0 {
2571 Ok(EthBytes(ctx.store().get_required(&cid)?))
2572 } else {
2573 Ok(Default::default())
2574 }
2575}
2576
2577pub enum EthGetStorageAt {}
2578impl RpcMethod<3> for EthGetStorageAt {
2579 const NAME: &'static str = "Filecoin.EthGetStorageAt";
2580 const NAME_ALIAS: Option<&'static str> = Some("eth_getStorageAt");
2581 const PARAM_NAMES: [&'static str; 3] = ["ethAddress", "position", "blockNumberOrHash"];
2582 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2583 const PERMISSION: Permission = Permission::Read;
2584 const DESCRIPTION: Option<&'static str> = Some(
2585 "Retrieves the storage value at a specific position for a contract
2586 at a given block state, identified by its number, hash, or a special tag.",
2587 );
2588
2589 type Params = (EthAddress, EthBytes, BlockNumberOrHash);
2590 type Ok = EthBytes;
2591
2592 async fn handle(
2593 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2594 (eth_address, position, block_number_or_hash): Self::Params,
2595 ) -> Result<Self::Ok, ServerError> {
2596 let ts = tipset_by_block_number_or_hash(
2597 ctx.chain_store(),
2598 block_number_or_hash,
2599 ResolveNullTipset::TakeOlder,
2600 )?;
2601 get_storage_at(&ctx, ts, eth_address, position).await
2602 }
2603}
2604
2605pub enum EthGetStorageAtV2 {}
2606impl RpcMethod<3> for EthGetStorageAtV2 {
2607 const NAME: &'static str = "Filecoin.EthGetStorageAt";
2608 const NAME_ALIAS: Option<&'static str> = Some("eth_getStorageAt");
2609 const PARAM_NAMES: [&'static str; 3] = ["ethAddress", "position", "blockNumberOrHash"];
2610 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
2611 const PERMISSION: Permission = Permission::Read;
2612 const DESCRIPTION: Option<&'static str> = Some(
2613 "Retrieves the storage value at a specific position for a contract
2614 at a given block state, identified by its number, hash, or a special tag.",
2615 );
2616
2617 type Params = (EthAddress, EthBytes, ExtBlockNumberOrHash);
2618 type Ok = EthBytes;
2619
2620 async fn handle(
2621 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2622 (eth_address, position, block_number_or_hash): Self::Params,
2623 ) -> Result<Self::Ok, ServerError> {
2624 let ts = tipset_by_block_number_or_hash_v2(
2625 &ctx,
2626 block_number_or_hash,
2627 ResolveNullTipset::TakeOlder,
2628 )
2629 .await?;
2630 get_storage_at(&ctx, ts, eth_address, position).await
2631 }
2632}
2633
2634async fn get_storage_at<DB: Blockstore + Send + Sync + 'static>(
2635 ctx: &Ctx<DB>,
2636 ts: Tipset,
2637 eth_address: EthAddress,
2638 position: EthBytes,
2639) -> Result<EthBytes, ServerError> {
2640 let to_address = FilecoinAddress::try_from(ð_address)?;
2641 let (state, _) = ctx
2642 .state_manager
2643 .tipset_state(&ts, StateLookupPolicy::Enabled)
2644 .await?;
2645 let make_empty_result = || EthBytes(vec![0; EVM_WORD_LENGTH]);
2646 let Some(actor) = ctx
2647 .state_manager
2648 .get_actor(&to_address, state)
2649 .with_context(|| format!("failed to lookup contract {}", eth_address.0))?
2650 else {
2651 return Ok(make_empty_result());
2652 };
2653
2654 if !is_evm_actor(&actor.code) {
2655 return Ok(make_empty_result());
2656 }
2657
2658 let params = RawBytes::new(GetStorageAtParams::new(position.0)?.serialize_params()?);
2659 let message = Message {
2660 from: FilecoinAddress::SYSTEM_ACTOR,
2661 to: to_address,
2662 method_num: METHOD_GET_STORAGE_AT,
2663 gas_limit: BLOCK_GAS_LIMIT,
2664 params,
2665 ..Default::default()
2666 };
2667 let api_invoc_result = 'invoc: {
2668 for ts in ts.chain(ctx.store()) {
2669 match ctx.state_manager.call_on_state(state, &message, Some(ts)) {
2670 Ok(res) => {
2671 break 'invoc res;
2672 }
2673 Err(e) => tracing::warn!(%e),
2674 }
2675 }
2676 return Err(anyhow::anyhow!("Call failed").into());
2677 };
2678 let Some(msg_rct) = api_invoc_result.msg_rct else {
2679 return Err(anyhow::anyhow!("no message receipt").into());
2680 };
2681 if !msg_rct.exit_code().is_success() || !api_invoc_result.error.is_empty() {
2682 return Err(
2683 anyhow::anyhow!("failed to lookup storage slot: {}", api_invoc_result.error).into(),
2684 );
2685 }
2686
2687 let mut ret = fvm_ipld_encoding::from_slice::<RawBytes>(msg_rct.return_data().as_slice())?
2688 .bytes()
2689 .to_vec();
2690 if ret.len() < EVM_WORD_LENGTH {
2691 let mut with_padding = vec![0; EVM_WORD_LENGTH.saturating_sub(ret.len())];
2692 with_padding.append(&mut ret);
2693 Ok(EthBytes(with_padding))
2694 } else {
2695 Ok(EthBytes(ret))
2696 }
2697}
2698
2699pub enum EthGetTransactionCount {}
2700impl RpcMethod<2> for EthGetTransactionCount {
2701 const NAME: &'static str = "Filecoin.EthGetTransactionCount";
2702 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionCount");
2703 const PARAM_NAMES: [&'static str; 2] = ["sender", "blockParam"];
2704 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2705 const PERMISSION: Permission = Permission::Read;
2706
2707 type Params = (EthAddress, BlockNumberOrHash);
2708 type Ok = EthUint64;
2709
2710 async fn handle(
2711 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2712 (sender, block_param): Self::Params,
2713 ) -> Result<Self::Ok, ServerError> {
2714 let addr = sender.to_filecoin_address()?;
2715 match block_param {
2716 BlockNumberOrHash::PredefinedBlock(Predefined::Pending) => {
2717 Ok(EthUint64(ctx.mpool.get_sequence(&addr)?))
2718 }
2719 _ => {
2720 let ts = tipset_by_block_number_or_hash(
2721 ctx.chain_store(),
2722 block_param,
2723 ResolveNullTipset::TakeOlder,
2724 )?;
2725 eth_get_transaction_count(&ctx, &ts, addr).await
2726 }
2727 }
2728 }
2729}
2730
2731pub enum EthGetTransactionCountV2 {}
2732impl RpcMethod<2> for EthGetTransactionCountV2 {
2733 const NAME: &'static str = "Filecoin.EthGetTransactionCount";
2734 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionCount");
2735 const PARAM_NAMES: [&'static str; 2] = ["sender", "blockParam"];
2736 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
2737 const PERMISSION: Permission = Permission::Read;
2738
2739 type Params = (EthAddress, ExtBlockNumberOrHash);
2740 type Ok = EthUint64;
2741
2742 async fn handle(
2743 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2744 (sender, block_param): Self::Params,
2745 ) -> Result<Self::Ok, ServerError> {
2746 let addr = sender.to_filecoin_address()?;
2747 match block_param {
2748 ExtBlockNumberOrHash::PredefinedBlock(ExtPredefined::Pending) => {
2749 Ok(EthUint64(ctx.mpool.get_sequence(&addr)?))
2750 }
2751 _ => {
2752 let ts = tipset_by_block_number_or_hash_v2(
2753 &ctx,
2754 block_param,
2755 ResolveNullTipset::TakeOlder,
2756 )
2757 .await?;
2758 eth_get_transaction_count(&ctx, &ts, addr).await
2759 }
2760 }
2761 }
2762}
2763
2764async fn eth_get_transaction_count<B>(
2765 ctx: &Ctx<B>,
2766 ts: &Tipset,
2767 addr: FilecoinAddress,
2768) -> Result<EthUint64, ServerError>
2769where
2770 B: Blockstore + Send + Sync + 'static,
2771{
2772 let (state_cid, _) = ctx
2773 .state_manager
2774 .tipset_state(ts, StateLookupPolicy::Enabled)
2775 .await?;
2776
2777 let state_tree = ctx.state_manager.get_state_tree(&state_cid)?;
2778 let actor = match state_tree.get_actor(&addr)? {
2779 Some(actor) => actor,
2780 None => return Ok(EthUint64(0)),
2781 };
2782
2783 if is_evm_actor(&actor.code) {
2784 let evm_state = evm::State::load(ctx.store(), actor.code, actor.state)?;
2785 if !evm_state.is_alive() {
2786 return Ok(EthUint64(0));
2787 }
2788 Ok(EthUint64(evm_state.nonce()))
2789 } else {
2790 Ok(EthUint64(actor.sequence))
2791 }
2792}
2793
2794pub enum EthMaxPriorityFeePerGas {}
2795impl RpcMethod<0> for EthMaxPriorityFeePerGas {
2796 const NAME: &'static str = "Filecoin.EthMaxPriorityFeePerGas";
2797 const NAME_ALIAS: Option<&'static str> = Some("eth_maxPriorityFeePerGas");
2798 const PARAM_NAMES: [&'static str; 0] = [];
2799 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
2800 const PERMISSION: Permission = Permission::Read;
2801
2802 type Params = ();
2803 type Ok = EthBigInt;
2804
2805 async fn handle(
2806 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2807 (): Self::Params,
2808 ) -> Result<Self::Ok, ServerError> {
2809 match gas::estimate_gas_premium(&ctx, 0, &ApiTipsetKey(None)).await {
2810 Ok(gas_premium) => Ok(EthBigInt(gas_premium.atto().clone())),
2811 Err(_) => Ok(EthBigInt(num_bigint::BigInt::zero())),
2812 }
2813 }
2814}
2815
2816pub enum EthProtocolVersion {}
2817impl RpcMethod<0> for EthProtocolVersion {
2818 const NAME: &'static str = "Filecoin.EthProtocolVersion";
2819 const NAME_ALIAS: Option<&'static str> = Some("eth_protocolVersion");
2820 const PARAM_NAMES: [&'static str; 0] = [];
2821 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
2822 const PERMISSION: Permission = Permission::Read;
2823
2824 type Params = ();
2825 type Ok = EthUint64;
2826
2827 async fn handle(
2828 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2829 (): Self::Params,
2830 ) -> Result<Self::Ok, ServerError> {
2831 let epoch = ctx.chain_store().heaviest_tipset().epoch();
2832 let version = u32::from(ctx.state_manager.get_network_version(epoch).0);
2833 Ok(EthUint64(version.into()))
2834 }
2835}
2836
2837pub enum EthGetTransactionByBlockNumberAndIndex {}
2838impl RpcMethod<2> for EthGetTransactionByBlockNumberAndIndex {
2839 const NAME: &'static str = "Filecoin.EthGetTransactionByBlockNumberAndIndex";
2840 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionByBlockNumberAndIndex");
2841 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "txIndex"];
2842 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2843 const PERMISSION: Permission = Permission::Read;
2844 const DESCRIPTION: Option<&'static str> =
2845 Some("Retrieves a transaction by its block number and index.");
2846
2847 type Params = (BlockNumberOrPredefined, EthUint64);
2848 type Ok = Option<ApiEthTx>;
2849
2850 async fn handle(
2851 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2852 (block_param, tx_index): Self::Params,
2853 ) -> Result<Self::Ok, ServerError> {
2854 let ts = tipset_by_ext_block_number_or_hash(
2855 ctx.chain_store(),
2856 block_param.into(),
2857 ResolveNullTipset::TakeOlder,
2858 )?;
2859 eth_tx_by_block_num_and_idx(&ctx, &ts, tx_index)
2860 }
2861}
2862
2863pub enum EthGetTransactionByBlockNumberAndIndexV2 {}
2864impl RpcMethod<2> for EthGetTransactionByBlockNumberAndIndexV2 {
2865 const NAME: &'static str = "Filecoin.EthGetTransactionByBlockNumberAndIndex";
2866 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionByBlockNumberAndIndex");
2867 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "txIndex"];
2868 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
2869 const PERMISSION: Permission = Permission::Read;
2870 const DESCRIPTION: Option<&'static str> =
2871 Some("Retrieves a transaction by its block number and index.");
2872
2873 type Params = (BlockNumberOrPredefined, EthUint64);
2874 type Ok = Option<ApiEthTx>;
2875
2876 async fn handle(
2877 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2878 (block_param, tx_index): Self::Params,
2879 ) -> Result<Self::Ok, ServerError> {
2880 let ts = tipset_by_block_number_or_hash_v2(
2881 &ctx,
2882 block_param.into(),
2883 ResolveNullTipset::TakeOlder,
2884 )
2885 .await?;
2886 eth_tx_by_block_num_and_idx(&ctx, &ts, tx_index)
2887 }
2888}
2889
2890fn eth_tx_by_block_num_and_idx<B>(
2891 ctx: &Ctx<B>,
2892 ts: &Tipset,
2893 tx_index: EthUint64,
2894) -> Result<Option<ApiEthTx>, ServerError>
2895where
2896 B: Blockstore + Send + Sync + 'static,
2897{
2898 let messages = ctx.chain_store().messages_for_tipset(ts)?;
2899
2900 let EthUint64(index) = tx_index;
2901 let msg = messages.get(index as usize).with_context(|| {
2902 format!(
2903 "failed to get transaction at index {}: index {} out of range: tipset contains {} messages",
2904 index,
2905 index,
2906 messages.len()
2907 )
2908 })?;
2909
2910 let state = ctx.state_manager.get_state_tree(ts.parent_state())?;
2911
2912 let tx = new_eth_tx(ctx, &state, ts.epoch(), &ts.key().cid()?, &msg.cid(), index)?;
2913
2914 Ok(Some(tx))
2915}
2916
2917pub enum EthGetTransactionByBlockHashAndIndex {}
2918impl RpcMethod<2> for EthGetTransactionByBlockHashAndIndex {
2919 const NAME: &'static str = "Filecoin.EthGetTransactionByBlockHashAndIndex";
2920 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionByBlockHashAndIndex");
2921 const PARAM_NAMES: [&'static str; 2] = ["blockHash", "txIndex"];
2922 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
2923 const PERMISSION: Permission = Permission::Read;
2924
2925 type Params = (EthHash, EthUint64);
2926 type Ok = Option<ApiEthTx>;
2927
2928 async fn handle(
2929 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2930 (block_hash, tx_index): Self::Params,
2931 ) -> Result<Self::Ok, ServerError> {
2932 let ts = get_tipset_from_hash(ctx.chain_store(), &block_hash)?;
2933
2934 let messages = ctx.chain_store().messages_for_tipset(&ts)?;
2935
2936 let EthUint64(index) = tx_index;
2937 let msg = messages.get(index as usize).with_context(|| {
2938 format!(
2939 "index {} out of range: tipset contains {} messages",
2940 index,
2941 messages.len()
2942 )
2943 })?;
2944
2945 let state = ctx.state_manager.get_state_tree(ts.parent_state())?;
2946
2947 let tx = new_eth_tx(
2948 &ctx,
2949 &state,
2950 ts.epoch(),
2951 &ts.key().cid()?,
2952 &msg.cid(),
2953 index,
2954 )?;
2955
2956 Ok(Some(tx))
2957 }
2958}
2959
2960pub enum EthGetTransactionByHash {}
2961impl RpcMethod<1> for EthGetTransactionByHash {
2962 const NAME: &'static str = "Filecoin.EthGetTransactionByHash";
2963 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionByHash");
2964 const PARAM_NAMES: [&'static str; 1] = ["txHash"];
2965 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
2966 const PERMISSION: Permission = Permission::Read;
2967
2968 type Params = (EthHash,);
2969 type Ok = Option<ApiEthTx>;
2970
2971 async fn handle(
2972 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2973 (tx_hash,): Self::Params,
2974 ) -> Result<Self::Ok, ServerError> {
2975 get_eth_transaction_by_hash(&ctx, &tx_hash, None).await
2976 }
2977}
2978
2979pub enum EthGetTransactionByHashLimited {}
2980impl RpcMethod<2> for EthGetTransactionByHashLimited {
2981 const NAME: &'static str = "Filecoin.EthGetTransactionByHashLimited";
2982 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionByHashLimited");
2983 const PARAM_NAMES: [&'static str; 2] = ["txHash", "limit"];
2984 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
2985 const PERMISSION: Permission = Permission::Read;
2986
2987 type Params = (EthHash, ChainEpoch);
2988 type Ok = Option<ApiEthTx>;
2989
2990 async fn handle(
2991 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2992 (tx_hash, limit): Self::Params,
2993 ) -> Result<Self::Ok, ServerError> {
2994 get_eth_transaction_by_hash(&ctx, &tx_hash, Some(limit)).await
2995 }
2996}
2997
2998async fn get_eth_transaction_by_hash(
2999 ctx: &Ctx<impl Blockstore + Send + Sync + 'static>,
3000 tx_hash: &EthHash,
3001 limit: Option<ChainEpoch>,
3002) -> Result<Option<ApiEthTx>, ServerError> {
3003 let message_cid = ctx.chain_store().get_mapping(tx_hash)?.unwrap_or_else(|| {
3004 tracing::debug!(
3005 "could not find transaction hash {} in Ethereum mapping",
3006 tx_hash
3007 );
3008 tx_hash.to_cid()
3010 });
3011
3012 if let Ok(Some((tipset, receipt))) = ctx
3014 .state_manager
3015 .search_for_message(None, message_cid, limit, Some(true))
3016 .await
3017 {
3018 let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null);
3019 let message_lookup = MessageLookup {
3020 receipt,
3021 tipset: tipset.key().clone(),
3022 height: tipset.epoch(),
3023 message: message_cid,
3024 return_dec: ipld,
3025 };
3026
3027 if let Ok(tx) = new_eth_tx_from_message_lookup(ctx, &message_lookup, None) {
3028 return Ok(Some(tx));
3029 }
3030 }
3031
3032 let (pending, _) = ctx.mpool.pending()?;
3034
3035 if let Some(smsg) = pending.iter().find(|item| item.cid() == message_cid) {
3036 if let Ok(eth_tx) = EthTx::from_signed_message(ctx.chain_config().eth_chain_id, smsg) {
3044 return Ok(Some(eth_tx.into()));
3045 }
3046 }
3047
3048 Ok(None)
3050}
3051
3052pub enum EthGetTransactionHashByCid {}
3053impl RpcMethod<1> for EthGetTransactionHashByCid {
3054 const NAME: &'static str = "Filecoin.EthGetTransactionHashByCid";
3055 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionHashByCid");
3056 const PARAM_NAMES: [&'static str; 1] = ["cid"];
3057 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3058 const PERMISSION: Permission = Permission::Read;
3059
3060 type Params = (Cid,);
3061 type Ok = Option<EthHash>;
3062
3063 async fn handle(
3064 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3065 (cid,): Self::Params,
3066 ) -> Result<Self::Ok, ServerError> {
3067 let smsgs_result: Result<Vec<SignedMessage>, crate::chain::Error> =
3068 crate::chain::messages_from_cids(ctx.store(), &[cid]);
3069 if let Ok(smsgs) = smsgs_result
3070 && let Some(smsg) = smsgs.first()
3071 {
3072 let hash = if smsg.is_delegated() {
3073 let chain_id = ctx.chain_config().eth_chain_id;
3074 let (_, tx) = eth_tx_from_signed_eth_message(smsg, chain_id)?;
3075 tx.eth_hash()?.into()
3076 } else if smsg.is_secp256k1() {
3077 smsg.cid().into()
3078 } else {
3079 smsg.message().cid().into()
3080 };
3081 return Ok(Some(hash));
3082 }
3083
3084 let msg_result = crate::chain::get_chain_message(ctx.store(), &cid);
3085 if let Ok(msg) = msg_result {
3086 return Ok(Some(msg.cid().into()));
3087 }
3088
3089 Ok(None)
3090 }
3091}
3092
3093pub enum EthCall {}
3094impl RpcMethod<2> for EthCall {
3095 const NAME: &'static str = "Filecoin.EthCall";
3096 const NAME_ALIAS: Option<&'static str> = Some("eth_call");
3097 const N_REQUIRED_PARAMS: usize = 2;
3098 const PARAM_NAMES: [&'static str; 2] = ["tx", "blockParam"];
3099 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3100 const PERMISSION: Permission = Permission::Read;
3101 type Params = (EthCallMessage, BlockNumberOrHash);
3102 type Ok = EthBytes;
3103 async fn handle(
3104 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3105 (tx, block_param): Self::Params,
3106 ) -> Result<Self::Ok, ServerError> {
3107 let ts = tipset_by_block_number_or_hash(
3108 ctx.chain_store(),
3109 block_param,
3110 ResolveNullTipset::TakeOlder,
3111 )?;
3112 eth_call(&ctx, tx, ts).await
3113 }
3114}
3115
3116pub enum EthCallV2 {}
3117impl RpcMethod<2> for EthCallV2 {
3118 const NAME: &'static str = "Filecoin.EthCall";
3119 const NAME_ALIAS: Option<&'static str> = Some("eth_call");
3120 const N_REQUIRED_PARAMS: usize = 2;
3121 const PARAM_NAMES: [&'static str; 2] = ["tx", "blockParam"];
3122 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
3123 const PERMISSION: Permission = Permission::Read;
3124 type Params = (EthCallMessage, ExtBlockNumberOrHash);
3125 type Ok = EthBytes;
3126 async fn handle(
3127 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3128 (tx, block_param): Self::Params,
3129 ) -> Result<Self::Ok, ServerError> {
3130 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
3131 .await?;
3132 eth_call(&ctx, tx, ts).await
3133 }
3134}
3135
3136async fn eth_call<DB>(
3137 ctx: &Ctx<DB>,
3138 tx: EthCallMessage,
3139 ts: Tipset,
3140) -> Result<EthBytes, ServerError>
3141where
3142 DB: Blockstore + Send + Sync + 'static,
3143{
3144 let msg = Message::try_from(tx)?;
3145 let invoke_result = apply_message(ctx, Some(ts), msg.clone()).await?;
3146
3147 if msg.to() == FilecoinAddress::ETHEREUM_ACCOUNT_MANAGER_ACTOR {
3148 Ok(EthBytes::default())
3149 } else {
3150 let msg_rct = invoke_result.msg_rct.context("no message receipt")?;
3151 let return_data = msg_rct.return_data();
3152 if return_data.is_empty() {
3153 Ok(Default::default())
3154 } else {
3155 let bytes = decode_payload(&return_data, CBOR)?;
3156 Ok(bytes)
3157 }
3158 }
3159}
3160
3161pub enum EthNewFilter {}
3162impl RpcMethod<1> for EthNewFilter {
3163 const NAME: &'static str = "Filecoin.EthNewFilter";
3164 const NAME_ALIAS: Option<&'static str> = Some("eth_newFilter");
3165 const PARAM_NAMES: [&'static str; 1] = ["filterSpec"];
3166 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3167 const PERMISSION: Permission = Permission::Read;
3168
3169 type Params = (EthFilterSpec,);
3170 type Ok = FilterID;
3171
3172 async fn handle(
3173 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3174 (filter_spec,): Self::Params,
3175 ) -> Result<Self::Ok, ServerError> {
3176 let eth_event_handler = ctx.eth_event_handler.clone();
3177 let chain_height = ctx.chain_store().heaviest_tipset().epoch();
3178 Ok(eth_event_handler.eth_new_filter(&filter_spec, chain_height)?)
3179 }
3180}
3181
3182pub enum EthNewPendingTransactionFilter {}
3183impl RpcMethod<0> for EthNewPendingTransactionFilter {
3184 const NAME: &'static str = "Filecoin.EthNewPendingTransactionFilter";
3185 const NAME_ALIAS: Option<&'static str> = Some("eth_newPendingTransactionFilter");
3186 const PARAM_NAMES: [&'static str; 0] = [];
3187 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3188 const PERMISSION: Permission = Permission::Read;
3189
3190 type Params = ();
3191 type Ok = FilterID;
3192
3193 async fn handle(
3194 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3195 (): Self::Params,
3196 ) -> Result<Self::Ok, ServerError> {
3197 let eth_event_handler = ctx.eth_event_handler.clone();
3198
3199 Ok(eth_event_handler.eth_new_pending_transaction_filter()?)
3200 }
3201}
3202
3203pub enum EthNewBlockFilter {}
3204impl RpcMethod<0> for EthNewBlockFilter {
3205 const NAME: &'static str = "Filecoin.EthNewBlockFilter";
3206 const NAME_ALIAS: Option<&'static str> = Some("eth_newBlockFilter");
3207 const PARAM_NAMES: [&'static str; 0] = [];
3208 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3209 const PERMISSION: Permission = Permission::Read;
3210
3211 type Params = ();
3212 type Ok = FilterID;
3213
3214 async fn handle(
3215 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3216 (): Self::Params,
3217 ) -> Result<Self::Ok, ServerError> {
3218 let eth_event_handler = ctx.eth_event_handler.clone();
3219
3220 Ok(eth_event_handler.eth_new_block_filter()?)
3221 }
3222}
3223
3224pub enum EthUninstallFilter {}
3225impl RpcMethod<1> for EthUninstallFilter {
3226 const NAME: &'static str = "Filecoin.EthUninstallFilter";
3227 const NAME_ALIAS: Option<&'static str> = Some("eth_uninstallFilter");
3228 const PARAM_NAMES: [&'static str; 1] = ["filterId"];
3229 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3230 const PERMISSION: Permission = Permission::Read;
3231
3232 type Params = (FilterID,);
3233 type Ok = bool;
3234
3235 async fn handle(
3236 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3237 (filter_id,): Self::Params,
3238 ) -> Result<Self::Ok, ServerError> {
3239 let eth_event_handler = ctx.eth_event_handler.clone();
3240
3241 Ok(eth_event_handler.eth_uninstall_filter(&filter_id)?)
3242 }
3243}
3244
3245pub enum EthUnsubscribe {}
3246impl RpcMethod<0> for EthUnsubscribe {
3247 const NAME: &'static str = "Filecoin.EthUnsubscribe";
3248 const NAME_ALIAS: Option<&'static str> = Some("eth_unsubscribe");
3249 const PARAM_NAMES: [&'static str; 0] = [];
3250 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3251 const PERMISSION: Permission = Permission::Read;
3252 const SUBSCRIPTION: bool = true;
3253
3254 type Params = ();
3255 type Ok = ();
3256
3257 async fn handle(
3263 _: Ctx<impl Blockstore + Send + Sync + 'static>,
3264 (): Self::Params,
3265 ) -> Result<Self::Ok, ServerError> {
3266 Ok(())
3267 }
3268}
3269
3270pub enum EthSubscribe {}
3271impl RpcMethod<0> for EthSubscribe {
3272 const NAME: &'static str = "Filecoin.EthSubscribe";
3273 const NAME_ALIAS: Option<&'static str> = Some("eth_subscribe");
3274 const PARAM_NAMES: [&'static str; 0] = [];
3275 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3276 const PERMISSION: Permission = Permission::Read;
3277 const SUBSCRIPTION: bool = true;
3278
3279 type Params = ();
3280 type Ok = ();
3281
3282 async fn handle(
3288 _: Ctx<impl Blockstore + Send + Sync + 'static>,
3289 (): Self::Params,
3290 ) -> Result<Self::Ok, ServerError> {
3291 Ok(())
3292 }
3293}
3294
3295pub enum EthAddressToFilecoinAddress {}
3296impl RpcMethod<1> for EthAddressToFilecoinAddress {
3297 const NAME: &'static str = "Filecoin.EthAddressToFilecoinAddress";
3298 const PARAM_NAMES: [&'static str; 1] = ["ethAddress"];
3299 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3300 const PERMISSION: Permission = Permission::Read;
3301 const DESCRIPTION: Option<&'static str> =
3302 Some("Converts an EthAddress into an f410 Filecoin Address");
3303 type Params = (EthAddress,);
3304 type Ok = FilecoinAddress;
3305 async fn handle(
3306 _ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3307 (eth_address,): Self::Params,
3308 ) -> Result<Self::Ok, ServerError> {
3309 Ok(eth_address.to_filecoin_address()?)
3310 }
3311}
3312
3313pub enum FilecoinAddressToEthAddress {}
3314impl RpcMethod<2> for FilecoinAddressToEthAddress {
3315 const NAME: &'static str = "Filecoin.FilecoinAddressToEthAddress";
3316 const N_REQUIRED_PARAMS: usize = 1;
3317 const PARAM_NAMES: [&'static str; 2] = ["filecoinAddress", "blockParam"];
3318 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3319 const PERMISSION: Permission = Permission::Read;
3320 const DESCRIPTION: Option<&'static str> =
3321 Some("Converts any Filecoin address to an EthAddress");
3322 type Params = (FilecoinAddress, Option<BlockNumberOrPredefined>);
3323 type Ok = EthAddress;
3324 async fn handle(
3325 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3326 (address, block_param): Self::Params,
3327 ) -> Result<Self::Ok, ServerError> {
3328 if let Ok(eth_address) = EthAddress::from_filecoin_address(&address) {
3329 Ok(eth_address)
3330 } else {
3331 let block_param = block_param.unwrap_or(BlockNumberOrPredefined::PredefinedBlock(
3332 ExtPredefined::Finalized,
3333 ));
3334 let ts = tipset_by_ext_block_number_or_hash(
3335 ctx.chain_store(),
3336 block_param.into(),
3337 ResolveNullTipset::TakeOlder,
3338 )?;
3339
3340 let id_address = ctx.state_manager.lookup_required_id(&address, &ts)?;
3341 Ok(EthAddress::from_filecoin_address(&id_address)?)
3342 }
3343 }
3344}
3345
3346async fn get_eth_transaction_receipt(
3347 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3348 tx_hash: EthHash,
3349 limit: Option<ChainEpoch>,
3350) -> Result<Option<EthTxReceipt>, ServerError> {
3351 let msg_cid = ctx.chain_store().get_mapping(&tx_hash)?.unwrap_or_else(|| {
3352 tracing::debug!(
3353 "could not find transaction hash {} in Ethereum mapping",
3354 tx_hash
3355 );
3356 tx_hash.to_cid()
3358 });
3359
3360 let option = ctx
3361 .state_manager
3362 .search_for_message(None, msg_cid, limit, Some(true))
3363 .await
3364 .with_context(|| format!("failed to lookup Eth Txn {tx_hash} as {msg_cid}"));
3365
3366 let option = match option {
3367 Ok(opt) => opt,
3368 Err(e) => {
3370 tracing::debug!("could not find transaction receipt for hash {tx_hash}: {e}");
3371 return Ok(None);
3372 }
3373 };
3374
3375 let (tipset, receipt) = option.context("not indexed")?;
3376 let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null);
3377 let message_lookup = MessageLookup {
3378 receipt,
3379 tipset: tipset.key().clone(),
3380 height: tipset.epoch(),
3381 message: msg_cid,
3382 return_dec: ipld,
3383 };
3384
3385 let tx = new_eth_tx_from_message_lookup(&ctx, &message_lookup, None)
3386 .with_context(|| format!("failed to convert {tx_hash} into an Eth Tx"))?;
3387
3388 let ts = ctx
3389 .chain_index()
3390 .load_required_tipset(&message_lookup.tipset)?;
3391
3392 let parent_ts = ctx
3394 .chain_index()
3395 .load_required_tipset(ts.parents())
3396 .map_err(|e| {
3397 format!(
3398 "failed to lookup tipset {} when constructing the eth txn receipt: {}",
3399 ts.parents(),
3400 e
3401 )
3402 })?;
3403
3404 let tx_receipt = new_eth_tx_receipt(&ctx, &parent_ts, &tx, &message_lookup.receipt).await?;
3405
3406 Ok(Some(tx_receipt))
3407}
3408
3409pub enum EthGetTransactionReceipt {}
3410impl RpcMethod<1> for EthGetTransactionReceipt {
3411 const NAME: &'static str = "Filecoin.EthGetTransactionReceipt";
3412 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionReceipt");
3413 const N_REQUIRED_PARAMS: usize = 1;
3414 const PARAM_NAMES: [&'static str; 1] = ["txHash"];
3415 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3416 const PERMISSION: Permission = Permission::Read;
3417 type Params = (EthHash,);
3418 type Ok = Option<EthTxReceipt>;
3419 async fn handle(
3420 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3421 (tx_hash,): Self::Params,
3422 ) -> Result<Self::Ok, ServerError> {
3423 get_eth_transaction_receipt(ctx, tx_hash, None).await
3424 }
3425}
3426
3427pub enum EthGetTransactionReceiptLimited {}
3428impl RpcMethod<2> for EthGetTransactionReceiptLimited {
3429 const NAME: &'static str = "Filecoin.EthGetTransactionReceiptLimited";
3430 const NAME_ALIAS: Option<&'static str> = Some("eth_getTransactionReceiptLimited");
3431 const N_REQUIRED_PARAMS: usize = 1;
3432 const PARAM_NAMES: [&'static str; 2] = ["txHash", "limit"];
3433 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3434 const PERMISSION: Permission = Permission::Read;
3435 type Params = (EthHash, ChainEpoch);
3436 type Ok = Option<EthTxReceipt>;
3437 async fn handle(
3438 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3439 (tx_hash, limit): Self::Params,
3440 ) -> Result<Self::Ok, ServerError> {
3441 get_eth_transaction_receipt(ctx, tx_hash, Some(limit)).await
3442 }
3443}
3444
3445pub enum EthSendRawTransaction {}
3446impl RpcMethod<1> for EthSendRawTransaction {
3447 const NAME: &'static str = "Filecoin.EthSendRawTransaction";
3448 const NAME_ALIAS: Option<&'static str> = Some("eth_sendRawTransaction");
3449 const PARAM_NAMES: [&'static str; 1] = ["rawTx"];
3450 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3451 const PERMISSION: Permission = Permission::Read;
3452
3453 type Params = (EthBytes,);
3454 type Ok = EthHash;
3455
3456 async fn handle(
3457 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3458 (raw_tx,): Self::Params,
3459 ) -> Result<Self::Ok, ServerError> {
3460 let tx_args = parse_eth_transaction(&raw_tx.0)?;
3461 let smsg = tx_args.get_signed_message(ctx.chain_config().eth_chain_id)?;
3462 let cid = ctx.mpool.as_ref().push(smsg).await?;
3463 Ok(cid.into())
3464 }
3465}
3466
3467pub enum EthSendRawTransactionUntrusted {}
3468impl RpcMethod<1> for EthSendRawTransactionUntrusted {
3469 const NAME: &'static str = "Filecoin.EthSendRawTransactionUntrusted";
3470 const NAME_ALIAS: Option<&'static str> = Some("eth_sendRawTransactionUntrusted");
3471 const PARAM_NAMES: [&'static str; 1] = ["rawTx"];
3472 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3473 const PERMISSION: Permission = Permission::Read;
3474
3475 type Params = (EthBytes,);
3476 type Ok = EthHash;
3477
3478 async fn handle(
3479 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3480 (raw_tx,): Self::Params,
3481 ) -> Result<Self::Ok, ServerError> {
3482 let tx_args = parse_eth_transaction(&raw_tx.0)?;
3483 let smsg = tx_args.get_signed_message(ctx.chain_config().eth_chain_id)?;
3484 let cid = ctx.mpool.as_ref().push_untrusted(smsg).await?;
3485 Ok(cid.into())
3486 }
3487}
3488
3489#[derive(Clone, Debug, PartialEq)]
3490pub struct CollectedEvent {
3491 pub(crate) entries: Vec<EventEntry>,
3492 pub(crate) emitter_addr: crate::shim::address::Address,
3493 pub(crate) event_idx: u64,
3494 pub(crate) reverted: bool,
3495 pub(crate) height: ChainEpoch,
3496 pub(crate) tipset_key: TipsetKey,
3497 msg_idx: u64,
3498 pub(crate) msg_cid: Cid,
3499}
3500
3501fn match_key(key: &str) -> Option<usize> {
3502 match key.get(0..2) {
3503 Some("t1") => Some(0),
3504 Some("t2") => Some(1),
3505 Some("t3") => Some(2),
3506 Some("t4") => Some(3),
3507 _ => None,
3508 }
3509}
3510
3511fn eth_log_from_event(entries: &[EventEntry]) -> Option<(EthBytes, Vec<EthHash>)> {
3512 let mut topics_found = [false; 4];
3513 let mut topics_found_count = 0;
3514 let mut data_found = false;
3515 let mut data: EthBytes = EthBytes::default();
3516 let mut topics: Vec<EthHash> = Vec::default();
3517 for entry in entries {
3518 if entry.codec != IPLD_RAW {
3521 return None;
3522 }
3523 if let Some(idx) = match_key(&entry.key) {
3525 let result: Result<[u8; EVM_WORD_LENGTH], _> = entry.value.0.clone().try_into();
3527 let bytes = if let Ok(value) = result {
3528 value
3529 } else {
3530 tracing::warn!(
3531 "got an EVM event topic with an invalid size (key: {}, size: {})",
3532 entry.key,
3533 entry.value.0.len()
3534 );
3535 return None;
3536 };
3537 if *topics_found.get(idx).expect("Infallible") {
3539 tracing::warn!("got a duplicate EVM event topic (key: {})", entry.key);
3540 return None;
3541 }
3542 *topics_found.get_mut(idx).expect("Infallible") = true;
3543 topics_found_count += 1;
3544 if topics.len() <= idx {
3546 topics.resize(idx + 1, EthHash::default());
3547 }
3548 *topics.get_mut(idx).expect("Infallible") = bytes.into();
3549 } else if entry.key == "d" {
3550 if data_found {
3552 tracing::warn!("got duplicate EVM event data");
3553 return None;
3554 }
3555 data_found = true;
3556 data = EthBytes(entry.value.0.clone());
3557 } else {
3558 tracing::warn!("unexpected event entry (key: {})", entry.key);
3561 }
3562 }
3563 if topics.len() != topics_found_count {
3565 tracing::warn!(
3566 "EVM event topic length mismatch (expected: {}, actual: {})",
3567 topics.len(),
3568 topics_found_count
3569 );
3570 return None;
3571 }
3572 Some((data, topics))
3573}
3574
3575fn eth_tx_hash_from_signed_message(
3576 message: &SignedMessage,
3577 eth_chain_id: EthChainIdType,
3578) -> anyhow::Result<EthHash> {
3579 if message.is_delegated() {
3580 let (_, tx) = eth_tx_from_signed_eth_message(message, eth_chain_id)?;
3581 Ok(tx.eth_hash()?.into())
3582 } else if message.is_secp256k1() {
3583 Ok(message.cid().into())
3584 } else {
3585 Ok(message.message().cid().into())
3586 }
3587}
3588
3589fn eth_tx_hash_from_message_cid<DB: Blockstore>(
3590 blockstore: &DB,
3591 message_cid: &Cid,
3592 eth_chain_id: EthChainIdType,
3593) -> anyhow::Result<Option<EthHash>> {
3594 if let Ok(smsg) = crate::chain::message_from_cid(blockstore, message_cid) {
3595 return Ok(Some(eth_tx_hash_from_signed_message(&smsg, eth_chain_id)?));
3597 }
3598 let result: Result<Message, _> = crate::chain::message_from_cid(blockstore, message_cid);
3599 if result.is_ok() {
3600 let hash: EthHash = (*message_cid).into();
3602 return Ok(Some(hash));
3603 }
3604 Ok(None)
3605}
3606
3607fn transform_events<F>(events: &[CollectedEvent], f: F) -> anyhow::Result<Vec<EthLog>>
3608where
3609 F: Fn(&CollectedEvent) -> anyhow::Result<Option<EthLog>>,
3610{
3611 events
3612 .iter()
3613 .filter_map(|event| match f(event) {
3614 Ok(Some(eth_log)) => Some(Ok(eth_log)),
3615 Ok(None) => None,
3616 Err(e) => Some(Err(e)),
3617 })
3618 .collect()
3619}
3620
3621fn eth_filter_logs_from_tipsets(events: &[CollectedEvent]) -> anyhow::Result<Vec<EthHash>> {
3622 events
3623 .iter()
3624 .map(|event| event.tipset_key.cid().map(Into::into))
3625 .collect()
3626}
3627
3628fn eth_filter_logs_from_messages<DB: Blockstore>(
3629 ctx: &Ctx<DB>,
3630 events: &[CollectedEvent],
3631) -> anyhow::Result<Vec<EthHash>> {
3632 events
3633 .iter()
3634 .filter_map(|event| {
3635 match eth_tx_hash_from_message_cid(
3636 ctx.store(),
3637 &event.msg_cid,
3638 ctx.state_manager.chain_config().eth_chain_id,
3639 ) {
3640 Ok(Some(hash)) => Some(Ok(hash)),
3641 Ok(None) => {
3642 tracing::warn!("Ignoring event");
3643 None
3644 }
3645 Err(err) => Some(Err(err)),
3646 }
3647 })
3648 .collect()
3649}
3650
3651fn eth_filter_logs_from_events<DB: Blockstore>(
3652 ctx: &Ctx<DB>,
3653 events: &[CollectedEvent],
3654) -> anyhow::Result<Vec<EthLog>> {
3655 transform_events(events, |event| {
3656 let (data, topics) = if let Some((data, topics)) = eth_log_from_event(&event.entries) {
3657 (data, topics)
3658 } else {
3659 tracing::warn!("Ignoring event");
3660 return Ok(None);
3661 };
3662 let transaction_hash = if let Some(transaction_hash) = eth_tx_hash_from_message_cid(
3663 ctx.store(),
3664 &event.msg_cid,
3665 ctx.state_manager.chain_config().eth_chain_id,
3666 )? {
3667 transaction_hash
3668 } else {
3669 tracing::warn!("Ignoring event");
3670 return Ok(None);
3671 };
3672 let address = EthAddress::from_filecoin_address(&event.emitter_addr)?;
3673 Ok(Some(EthLog {
3674 address,
3675 data,
3676 topics,
3677 removed: event.reverted,
3678 log_index: event.event_idx.into(),
3679 transaction_index: event.msg_idx.into(),
3680 transaction_hash,
3681 block_hash: event.tipset_key.cid()?.into(),
3682 block_number: (event.height as u64).into(),
3683 }))
3684 })
3685}
3686
3687fn eth_filter_result_from_events<DB: Blockstore>(
3688 ctx: &Ctx<DB>,
3689 events: &[CollectedEvent],
3690) -> anyhow::Result<EthFilterResult> {
3691 Ok(EthFilterResult::Logs(eth_filter_logs_from_events(
3692 ctx, events,
3693 )?))
3694}
3695
3696fn eth_filter_result_from_tipsets(events: &[CollectedEvent]) -> anyhow::Result<EthFilterResult> {
3697 Ok(EthFilterResult::Hashes(eth_filter_logs_from_tipsets(
3698 events,
3699 )?))
3700}
3701
3702fn eth_filter_result_from_messages<DB: Blockstore>(
3703 ctx: &Ctx<DB>,
3704 events: &[CollectedEvent],
3705) -> anyhow::Result<EthFilterResult> {
3706 Ok(EthFilterResult::Hashes(eth_filter_logs_from_messages(
3707 ctx, events,
3708 )?))
3709}
3710
3711pub enum EthGetLogs {}
3712impl RpcMethod<1> for EthGetLogs {
3713 const NAME: &'static str = "Filecoin.EthGetLogs";
3714 const NAME_ALIAS: Option<&'static str> = Some("eth_getLogs");
3715 const N_REQUIRED_PARAMS: usize = 1;
3716 const PARAM_NAMES: [&'static str; 1] = ["ethFilter"];
3717 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3718 const PERMISSION: Permission = Permission::Read;
3719 type Params = (EthFilterSpec,);
3720 type Ok = EthFilterResult;
3721 async fn handle(
3722 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3723 (eth_filter,): Self::Params,
3724 ) -> Result<Self::Ok, ServerError> {
3725 let pf = ctx
3726 .eth_event_handler
3727 .parse_eth_filter_spec(&ctx, ð_filter)?;
3728 let events = ctx
3729 .eth_event_handler
3730 .get_events_for_parsed_filter(&ctx, &pf, SkipEvent::OnUnresolvedAddress)
3731 .await?;
3732 Ok(eth_filter_result_from_events(&ctx, &events)?)
3733 }
3734}
3735
3736pub enum EthGetFilterLogs {}
3737impl RpcMethod<1> for EthGetFilterLogs {
3738 const NAME: &'static str = "Filecoin.EthGetFilterLogs";
3739 const NAME_ALIAS: Option<&'static str> = Some("eth_getFilterLogs");
3740 const N_REQUIRED_PARAMS: usize = 1;
3741 const PARAM_NAMES: [&'static str; 1] = ["filterId"];
3742 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3743 const PERMISSION: Permission = Permission::Write;
3744 type Params = (FilterID,);
3745 type Ok = EthFilterResult;
3746 async fn handle(
3747 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3748 (filter_id,): Self::Params,
3749 ) -> Result<Self::Ok, ServerError> {
3750 let eth_event_handler = ctx.eth_event_handler.clone();
3751 if let Some(store) = ð_event_handler.filter_store {
3752 let filter = store.get(&filter_id)?;
3753 if let Some(event_filter) = filter.as_any().downcast_ref::<EventFilter>() {
3754 let events = ctx
3755 .eth_event_handler
3756 .get_events_for_parsed_filter(
3757 &ctx,
3758 &event_filter.into(),
3759 SkipEvent::OnUnresolvedAddress,
3760 )
3761 .await?;
3762 let recent_events: Vec<CollectedEvent> = events
3763 .clone()
3764 .into_iter()
3765 .filter(|event| !event_filter.collected.contains(event))
3766 .collect();
3767 let filter = Arc::new(EventFilter {
3768 id: event_filter.id.clone(),
3769 tipsets: event_filter.tipsets.clone(),
3770 addresses: event_filter.addresses.clone(),
3771 keys_with_codec: event_filter.keys_with_codec.clone(),
3772 max_results: event_filter.max_results,
3773 collected: events.clone(),
3774 });
3775 store.update(filter);
3776 return Ok(eth_filter_result_from_events(&ctx, &recent_events)?);
3777 }
3778 }
3779 Err(anyhow::anyhow!("method not supported").into())
3780 }
3781}
3782
3783pub enum EthGetFilterChanges {}
3784impl RpcMethod<1> for EthGetFilterChanges {
3785 const NAME: &'static str = "Filecoin.EthGetFilterChanges";
3786 const NAME_ALIAS: Option<&'static str> = Some("eth_getFilterChanges");
3787 const N_REQUIRED_PARAMS: usize = 1;
3788 const PARAM_NAMES: [&'static str; 1] = ["filterId"];
3789 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
3790 const PERMISSION: Permission = Permission::Write;
3791 const DESCRIPTION: Option<&'static str> =
3792 Some("Returns event logs which occurred since the last poll");
3793
3794 type Params = (FilterID,);
3795 type Ok = EthFilterResult;
3796 async fn handle(
3797 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3798 (filter_id,): Self::Params,
3799 ) -> Result<Self::Ok, ServerError> {
3800 let eth_event_handler = ctx.eth_event_handler.clone();
3801 if let Some(store) = ð_event_handler.filter_store {
3802 let filter = store.get(&filter_id)?;
3803 if let Some(event_filter) = filter.as_any().downcast_ref::<EventFilter>() {
3804 let events = ctx
3805 .eth_event_handler
3806 .get_events_for_parsed_filter(
3807 &ctx,
3808 &event_filter.into(),
3809 SkipEvent::OnUnresolvedAddress,
3810 )
3811 .await?;
3812 let recent_events: Vec<CollectedEvent> = events
3813 .clone()
3814 .into_iter()
3815 .filter(|event| !event_filter.collected.contains(event))
3816 .collect();
3817 let filter = Arc::new(EventFilter {
3818 id: event_filter.id.clone(),
3819 tipsets: event_filter.tipsets.clone(),
3820 addresses: event_filter.addresses.clone(),
3821 keys_with_codec: event_filter.keys_with_codec.clone(),
3822 max_results: event_filter.max_results,
3823 collected: events.clone(),
3824 });
3825 store.update(filter);
3826 return Ok(eth_filter_result_from_events(&ctx, &recent_events)?);
3827 }
3828 if let Some(tipset_filter) = filter.as_any().downcast_ref::<TipSetFilter>() {
3829 let events = ctx
3830 .eth_event_handler
3831 .get_events_for_parsed_filter(
3832 &ctx,
3833 &ParsedFilter::new_with_tipset(ParsedFilterTipsets::Range(
3834 RangeInclusive::new(
3836 tipset_filter
3837 .collected
3838 .unwrap_or(ctx.chain_store().heaviest_tipset().epoch() - 1),
3839 -1,
3841 ),
3842 )),
3843 SkipEvent::OnUnresolvedAddress,
3844 )
3845 .await?;
3846 let new_collected = events
3847 .iter()
3848 .max_by_key(|event| event.height)
3849 .map(|e| e.height);
3850 if let Some(height) = new_collected {
3851 let filter = Arc::new(TipSetFilter {
3852 id: tipset_filter.id.clone(),
3853 max_results: tipset_filter.max_results,
3854 collected: Some(height),
3855 });
3856 store.update(filter);
3857 }
3858 return Ok(eth_filter_result_from_tipsets(&events)?);
3859 }
3860 if let Some(mempool_filter) = filter.as_any().downcast_ref::<MempoolFilter>() {
3861 let events = ctx
3862 .eth_event_handler
3863 .get_events_for_parsed_filter(
3864 &ctx,
3865 &ParsedFilter::new_with_tipset(ParsedFilterTipsets::Range(
3866 RangeInclusive::new(
3868 mempool_filter
3869 .collected
3870 .unwrap_or(ctx.chain_store().heaviest_tipset().epoch() - 1),
3871 -1,
3873 ),
3874 )),
3875 SkipEvent::OnUnresolvedAddress,
3876 )
3877 .await?;
3878 let new_collected = events
3879 .iter()
3880 .max_by_key(|event| event.height)
3881 .map(|e| e.height);
3882 if let Some(height) = new_collected {
3883 let filter = Arc::new(MempoolFilter {
3884 id: mempool_filter.id.clone(),
3885 max_results: mempool_filter.max_results,
3886 collected: Some(height),
3887 });
3888 store.update(filter);
3889 }
3890 return Ok(eth_filter_result_from_messages(&ctx, &events)?);
3891 }
3892 }
3893 Err(anyhow::anyhow!("method not supported").into())
3894 }
3895}
3896
3897pub enum EthTraceBlock {}
3898impl RpcMethod<1> for EthTraceBlock {
3899 const NAME: &'static str = "Filecoin.EthTraceBlock";
3900 const NAME_ALIAS: Option<&'static str> = Some("trace_block");
3901 const N_REQUIRED_PARAMS: usize = 1;
3902 const PARAM_NAMES: [&'static str; 1] = ["blockParam"];
3903 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3904 const PERMISSION: Permission = Permission::Read;
3905 const DESCRIPTION: Option<&'static str> = Some("Returns traces created at given block.");
3906
3907 type Params = (ExtBlockNumberOrHash,);
3908 type Ok = Vec<EthBlockTrace>;
3909 async fn handle(
3910 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3911 (block_param,): Self::Params,
3912 ) -> Result<Self::Ok, ServerError> {
3913 let ts = tipset_by_ext_block_number_or_hash(
3914 ctx.chain_store(),
3915 block_param,
3916 ResolveNullTipset::TakeOlder,
3917 )?;
3918 eth_trace_block(&ctx, &ts).await
3919 }
3920}
3921
3922pub enum EthTraceBlockV2 {}
3923impl RpcMethod<1> for EthTraceBlockV2 {
3924 const NAME: &'static str = "Filecoin.EthTraceBlock";
3925 const NAME_ALIAS: Option<&'static str> = Some("trace_block");
3926 const N_REQUIRED_PARAMS: usize = 1;
3927 const PARAM_NAMES: [&'static str; 1] = ["blockParam"];
3928 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
3929 const PERMISSION: Permission = Permission::Read;
3930 const DESCRIPTION: Option<&'static str> = Some("Returns traces created at given block.");
3931
3932 type Params = (ExtBlockNumberOrHash,);
3933 type Ok = Vec<EthBlockTrace>;
3934 async fn handle(
3935 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
3936 (block_param,): Self::Params,
3937 ) -> Result<Self::Ok, ServerError> {
3938 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
3939 .await?;
3940 eth_trace_block(&ctx, &ts).await
3941 }
3942}
3943
3944async fn eth_trace_block<DB>(ctx: &Ctx<DB>, ts: &Tipset) -> Result<Vec<EthBlockTrace>, ServerError>
3945where
3946 DB: Blockstore + Send + Sync + 'static,
3947{
3948 let (state_root, trace) = ctx.state_manager.execution_trace(ts)?;
3949 let state = ctx.state_manager.get_state_tree(&state_root)?;
3950 let cid = ts.key().cid()?;
3951 let block_hash: EthHash = cid.into();
3952 let mut all_traces = vec![];
3953 let mut msg_idx = 0;
3954 for ir in trace.into_iter() {
3955 if ir.msg.from == system::ADDRESS.into() {
3957 continue;
3958 }
3959 msg_idx += 1;
3960 let tx_hash = EthGetTransactionHashByCid::handle(ctx.clone(), (ir.msg_cid,)).await?;
3961 let tx_hash = tx_hash
3962 .with_context(|| format!("cannot find transaction hash for cid {}", ir.msg_cid))?;
3963 let mut env = trace::base_environment(&state, &ir.msg.from)
3964 .map_err(|e| format!("when processing message {}: {}", ir.msg_cid, e))?;
3965 if let Some(execution_trace) = ir.execution_trace {
3966 trace::build_traces(&mut env, &[], execution_trace)?;
3967 for trace in env.traces {
3968 all_traces.push(EthBlockTrace {
3969 trace: EthTrace {
3970 r#type: trace.r#type,
3971 subtraces: trace.subtraces,
3972 trace_address: trace.trace_address,
3973 action: trace.action,
3974 result: trace.result,
3975 error: trace.error,
3976 },
3977 block_hash,
3978 block_number: ts.epoch(),
3979 transaction_hash: tx_hash,
3980 transaction_position: msg_idx as i64,
3981 });
3982 }
3983 }
3984 }
3985 Ok(all_traces)
3986}
3987
3988pub enum EthTraceCall {}
3989impl RpcMethod<3> for EthTraceCall {
3990 const NAME: &'static str = "Forest.EthTraceCall";
3991 const NAME_ALIAS: Option<&'static str> = Some("trace_call");
3992 const N_REQUIRED_PARAMS: usize = 1;
3993 const PARAM_NAMES: [&'static str; 3] = ["tx", "traceTypes", "blockParam"];
3994 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::{ V1 | V2 });
3995 const PERMISSION: Permission = Permission::Read;
3996 const DESCRIPTION: Option<&'static str> =
3997 Some("Returns parity style trace results for the given transaction.");
3998
3999 type Params = (
4000 EthCallMessage,
4001 NonEmpty<EthTraceType>,
4002 Option<BlockNumberOrHash>,
4003 );
4004 type Ok = EthTraceResults;
4005 async fn handle(
4006 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
4007 (tx, trace_types, block_param): Self::Params,
4008 ) -> Result<Self::Ok, ServerError> {
4009 let msg = Message::try_from(tx)?;
4010 let block_param = block_param.unwrap_or(BlockNumberOrHash::from_str("latest")?);
4011 let ts = tipset_by_block_number_or_hash(
4012 ctx.chain_store(),
4013 block_param,
4014 ResolveNullTipset::TakeOlder,
4015 )?;
4016
4017 let (pre_state_root, _) = ctx
4018 .state_manager
4019 .tipset_state(&ts, StateLookupPolicy::Enabled)
4020 .await
4021 .map_err(|e| anyhow::anyhow!("failed to get tipset state: {e}"))?;
4022 let pre_state = StateTree::new_from_root(ctx.store_owned(), &pre_state_root)?;
4023
4024 let (invoke_result, post_state_root) = ctx
4025 .state_manager
4026 .apply_on_state_with_gas(
4027 Some(ts.clone()),
4028 msg.clone(),
4029 StateLookupPolicy::Enabled,
4030 VMFlush::Flush,
4031 )
4032 .await
4033 .map_err(|e| anyhow::anyhow!("failed to apply message: {e}"))?;
4034 let post_state_root =
4035 post_state_root.context("post-execution state root required for trace call")?;
4036 let post_state = StateTree::new_from_root(ctx.store_owned(), &post_state_root)?;
4037
4038 let mut trace_results = EthTraceResults {
4039 output: get_trace_output(&msg, &invoke_result)?,
4040 ..Default::default()
4041 };
4042
4043 let touched_addresses = invoke_result
4045 .execution_trace
4046 .as_ref()
4047 .map(extract_touched_eth_addresses)
4048 .unwrap_or_default();
4049
4050 if trace_types.contains(&EthTraceType::Trace)
4052 && let Some(exec_trace) = invoke_result.execution_trace
4053 {
4054 let mut env = trace::base_environment(&post_state, &msg.from())
4055 .map_err(|e| anyhow::anyhow!("failed to create trace environment: {e}"))?;
4056 trace::build_traces(&mut env, &[], exec_trace)?;
4057 trace_results.trace = env.traces;
4058 }
4059
4060 if trace_types.contains(&EthTraceType::StateDiff) {
4062 let mut all_touched = touched_addresses;
4064 if let Ok(caller_eth) = EthAddress::from_filecoin_address(&msg.from()) {
4065 all_touched.insert(caller_eth);
4066 }
4067 if let Ok(to_eth) = EthAddress::from_filecoin_address(&msg.to()) {
4068 all_touched.insert(to_eth);
4069 }
4070
4071 let state_diff =
4072 trace::build_state_diff(ctx.store(), &pre_state, &post_state, &all_touched)?;
4073 trace_results.state_diff = Some(state_diff);
4074 }
4075
4076 Ok(trace_results)
4077 }
4078}
4079
4080fn get_trace_output(msg: &Message, invoke_result: &ApiInvocResult) -> Result<EthBytes> {
4082 if msg.to() == FilecoinAddress::ETHEREUM_ACCOUNT_MANAGER_ACTOR {
4083 return Ok(EthBytes::default());
4084 }
4085
4086 let msg_rct = invoke_result
4087 .msg_rct
4088 .as_ref()
4089 .context("missing message receipt")?;
4090 let return_data = msg_rct.return_data();
4091
4092 if return_data.is_empty() {
4093 return Ok(EthBytes::default());
4094 }
4095
4096 match decode_payload(&return_data, CBOR) {
4097 Ok(payload) => Ok(payload),
4098 Err(e) => Err(anyhow::anyhow!("failed to decode return data: {e}")),
4099 }
4100}
4101
4102fn extract_touched_eth_addresses(trace: &crate::rpc::state::ExecutionTrace) -> HashSet<EthAddress> {
4104 let mut addresses = HashSet::default();
4105 let mut stack = vec![trace];
4106
4107 while let Some(current) = stack.pop() {
4108 if let Ok(eth_addr) = EthAddress::from_filecoin_address(¤t.msg.from) {
4109 addresses.insert(eth_addr);
4110 }
4111 if let Ok(eth_addr) = EthAddress::from_filecoin_address(¤t.msg.to) {
4112 addresses.insert(eth_addr);
4113 }
4114 stack.extend(¤t.subcalls);
4115 }
4116
4117 addresses
4118}
4119
4120pub enum EthTraceTransaction {}
4121impl RpcMethod<1> for EthTraceTransaction {
4122 const NAME: &'static str = "Filecoin.EthTraceTransaction";
4123 const NAME_ALIAS: Option<&'static str> = Some("trace_transaction");
4124 const N_REQUIRED_PARAMS: usize = 1;
4125 const PARAM_NAMES: [&'static str; 1] = ["txHash"];
4126 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
4127 const PERMISSION: Permission = Permission::Read;
4128 const DESCRIPTION: Option<&'static str> =
4129 Some("Returns the traces for a specific transaction.");
4130
4131 type Params = (String,);
4132 type Ok = Vec<EthBlockTrace>;
4133 async fn handle(
4134 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
4135 (tx_hash,): Self::Params,
4136 ) -> Result<Self::Ok, ServerError> {
4137 let eth_hash = EthHash::from_str(&tx_hash).context("invalid transaction hash")?;
4138 let eth_txn = get_eth_transaction_by_hash(&ctx, ð_hash, None)
4139 .await?
4140 .ok_or(ServerError::internal_error("transaction not found", None))?;
4141
4142 let ts = tipset_by_ext_block_number_or_hash(
4143 ctx.chain_store(),
4144 ExtBlockNumberOrHash::from_block_number(eth_txn.block_number.0 as i64),
4145 ResolveNullTipset::TakeOlder,
4146 )?;
4147
4148 let traces = eth_trace_block(&ctx, &ts)
4149 .await?
4150 .into_iter()
4151 .filter(|trace| trace.transaction_hash == eth_hash)
4152 .collect();
4153 Ok(traces)
4154 }
4155}
4156
4157pub enum EthTraceReplayBlockTransactions {}
4158impl RpcMethod<2> for EthTraceReplayBlockTransactions {
4159 const N_REQUIRED_PARAMS: usize = 2;
4160 const NAME: &'static str = "Filecoin.EthTraceReplayBlockTransactions";
4161 const NAME_ALIAS: Option<&'static str> = Some("trace_replayBlockTransactions");
4162 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "traceTypes"];
4163 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
4164 const PERMISSION: Permission = Permission::Read;
4165 const DESCRIPTION: Option<&'static str> = Some(
4166 "Replays all transactions in a block returning the requested traces for each transaction.",
4167 );
4168
4169 type Params = (ExtBlockNumberOrHash, Vec<String>);
4170 type Ok = Vec<EthReplayBlockTransactionTrace>;
4171
4172 async fn handle(
4173 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
4174 (block_param, trace_types): Self::Params,
4175 ) -> Result<Self::Ok, ServerError> {
4176 if trace_types.as_slice() != ["trace"] {
4177 return Err(ServerError::invalid_params(
4178 "only 'trace' is supported",
4179 None,
4180 ));
4181 }
4182
4183 let ts = tipset_by_ext_block_number_or_hash(
4184 ctx.chain_store(),
4185 block_param,
4186 ResolveNullTipset::TakeOlder,
4187 )?;
4188
4189 eth_trace_replay_block_transactions(&ctx, &ts).await
4190 }
4191}
4192
4193pub enum EthTraceReplayBlockTransactionsV2 {}
4194impl RpcMethod<2> for EthTraceReplayBlockTransactionsV2 {
4195 const N_REQUIRED_PARAMS: usize = 2;
4196 const NAME: &'static str = "Filecoin.EthTraceReplayBlockTransactions";
4197 const NAME_ALIAS: Option<&'static str> = Some("trace_replayBlockTransactions");
4198 const PARAM_NAMES: [&'static str; 2] = ["blockParam", "traceTypes"];
4199 const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V2);
4200 const PERMISSION: Permission = Permission::Read;
4201 const DESCRIPTION: Option<&'static str> = Some(
4202 "Replays all transactions in a block returning the requested traces for each transaction.",
4203 );
4204
4205 type Params = (ExtBlockNumberOrHash, Vec<String>);
4206 type Ok = Vec<EthReplayBlockTransactionTrace>;
4207
4208 async fn handle(
4209 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
4210 (block_param, trace_types): Self::Params,
4211 ) -> Result<Self::Ok, ServerError> {
4212 if trace_types.as_slice() != ["trace"] {
4213 return Err(ServerError::invalid_params(
4214 "only 'trace' is supported",
4215 None,
4216 ));
4217 }
4218
4219 let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder)
4220 .await?;
4221
4222 eth_trace_replay_block_transactions(&ctx, &ts).await
4223 }
4224}
4225
4226async fn eth_trace_replay_block_transactions<DB>(
4227 ctx: &Ctx<DB>,
4228 ts: &Tipset,
4229) -> Result<Vec<EthReplayBlockTransactionTrace>, ServerError>
4230where
4231 DB: Blockstore + Send + Sync + 'static,
4232{
4233 let (state_root, trace) = ctx.state_manager.execution_trace(ts)?;
4234
4235 let state = ctx.state_manager.get_state_tree(&state_root)?;
4236
4237 let mut all_traces = vec![];
4238 for ir in trace.into_iter() {
4239 if ir.msg.from == system::ADDRESS.into() {
4240 continue;
4241 }
4242
4243 let tx_hash = EthGetTransactionHashByCid::handle(ctx.clone(), (ir.msg_cid,)).await?;
4244 let tx_hash = tx_hash
4245 .with_context(|| format!("cannot find transaction hash for cid {}", ir.msg_cid))?;
4246
4247 let mut env = trace::base_environment(&state, &ir.msg.from)
4248 .map_err(|e| format!("when processing message {}: {}", ir.msg_cid, e))?;
4249
4250 if let Some(execution_trace) = ir.execution_trace {
4251 trace::build_traces(&mut env, &[], execution_trace)?;
4252
4253 let get_output = || -> EthBytes {
4254 env.traces
4255 .first()
4256 .map_or_else(EthBytes::default, |trace| match &trace.result {
4257 TraceResult::Call(r) => r.output.clone(),
4258 TraceResult::Create(r) => r.code.clone(),
4259 })
4260 };
4261
4262 all_traces.push(EthReplayBlockTransactionTrace {
4263 output: get_output(),
4264 state_diff: None,
4265 trace: env.traces.clone(),
4266 transaction_hash: tx_hash,
4267 vm_trace: None,
4268 });
4269 };
4270 }
4271
4272 Ok(all_traces)
4273}
4274
4275fn get_eth_block_number_from_string<DB: Blockstore>(
4276 chain_store: &ChainStore<DB>,
4277 block: Option<&str>,
4278 resolve: ResolveNullTipset,
4279) -> Result<EthUint64> {
4280 let block_param = match block {
4281 Some(block_str) => ExtBlockNumberOrHash::from_str(block_str)?,
4282 None => bail!("cannot parse fromBlock"),
4283 };
4284 Ok(EthUint64(
4285 tipset_by_ext_block_number_or_hash(chain_store, block_param, resolve)?.epoch() as u64,
4286 ))
4287}
4288
4289pub enum EthTraceFilter {}
4290impl RpcMethod<1> for EthTraceFilter {
4291 const N_REQUIRED_PARAMS: usize = 1;
4292 const NAME: &'static str = "Filecoin.EthTraceFilter";
4293 const NAME_ALIAS: Option<&'static str> = Some("trace_filter");
4294 const PARAM_NAMES: [&'static str; 1] = ["filter"];
4295 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
4296 const PERMISSION: Permission = Permission::Read;
4297 const DESCRIPTION: Option<&'static str> =
4298 Some("Returns the traces for transactions matching the filter criteria.");
4299 type Params = (EthTraceFilterCriteria,);
4300 type Ok = Vec<EthBlockTrace>;
4301
4302 async fn handle(
4303 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
4304 (filter,): Self::Params,
4305 ) -> Result<Self::Ok, ServerError> {
4306 let from_block = get_eth_block_number_from_string(
4307 ctx.chain_store(),
4308 filter.from_block.as_deref(),
4309 ResolveNullTipset::TakeNewer,
4310 )
4311 .context("cannot parse fromBlock")?;
4312
4313 let to_block = get_eth_block_number_from_string(
4314 ctx.chain_store(),
4315 filter.to_block.as_deref(),
4316 ResolveNullTipset::TakeOlder,
4317 )
4318 .context("cannot parse toBlock")?;
4319
4320 Ok(trace_filter(ctx, filter, from_block, to_block)
4321 .await?
4322 .into_iter()
4323 .sorted_by_key(|trace| {
4324 (
4325 trace.block_number,
4326 trace.transaction_position,
4327 trace.trace.trace_address.clone(),
4328 )
4329 })
4330 .collect_vec())
4331 }
4332}
4333
4334async fn trace_filter(
4335 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
4336 filter: EthTraceFilterCriteria,
4337 from_block: EthUint64,
4338 to_block: EthUint64,
4339) -> Result<HashSet<EthBlockTrace>> {
4340 let mut results = HashSet::default();
4341 if let Some(EthUint64(0)) = filter.count {
4342 return Ok(results);
4343 }
4344 let count = *filter.count.unwrap_or_default();
4345 ensure!(
4346 count <= *FOREST_TRACE_FILTER_MAX_RESULT,
4347 "invalid response count, requested {}, maximum supported is {}",
4348 count,
4349 *FOREST_TRACE_FILTER_MAX_RESULT
4350 );
4351
4352 let mut trace_counter = 0;
4353 for blk_num in from_block.0..=to_block.0 {
4354 let block_traces = EthTraceBlock::handle(
4355 ctx.clone(),
4356 (ExtBlockNumberOrHash::from_block_number(blk_num as i64),),
4357 )
4358 .await?;
4359 for block_trace in block_traces {
4360 if block_trace
4361 .trace
4362 .match_filter_criteria(&filter.from_address, &filter.to_address)?
4363 {
4364 trace_counter += 1;
4365 if let Some(after) = filter.after
4366 && trace_counter <= after.0
4367 {
4368 continue;
4369 }
4370
4371 results.insert(block_trace);
4372
4373 if filter.count.is_some() && results.len() >= count as usize {
4374 return Ok(results);
4375 } else if results.len() > *FOREST_TRACE_FILTER_MAX_RESULT as usize {
4376 bail!(
4377 "too many results, maximum supported is {}, try paginating requests with After and Count",
4378 *FOREST_TRACE_FILTER_MAX_RESULT
4379 );
4380 }
4381 }
4382 }
4383 }
4384
4385 Ok(results)
4386}
4387
4388#[cfg(test)]
4389mod test {
4390 use super::*;
4391 use crate::rpc::eth::EventEntry;
4392 use crate::rpc::state::{ExecutionTrace, MessageTrace, ReturnTrace};
4393 use crate::shim::{econ::TokenAmount, error::ExitCode};
4394 use crate::{
4395 db::MemoryDB,
4396 test_utils::{construct_bls_messages, construct_eth_messages, construct_messages},
4397 };
4398 use fvm_shared4::event::Flags;
4399 use quickcheck::Arbitrary;
4400 use quickcheck_macros::quickcheck;
4401
4402 impl Arbitrary for EthHash {
4403 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
4404 let arr: [u8; 32] = std::array::from_fn(|_ix| u8::arbitrary(g));
4405 Self(ethereum_types::H256(arr))
4406 }
4407 }
4408
4409 #[quickcheck]
4410 fn gas_price_result_serde_roundtrip(i: u128) {
4411 let r = EthBigInt(i.into());
4412 let encoded = serde_json::to_string(&r).unwrap();
4413 assert_eq!(encoded, format!("\"{i:#x}\""));
4414 let decoded: EthBigInt = serde_json::from_str(&encoded).unwrap();
4415 assert_eq!(r.0, decoded.0);
4416 }
4417
4418 #[test]
4419 fn test_abi_encoding() {
4420 const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001b1111111111111111111020200301000000044444444444444444010000000000";
4421 const DATA: &str = "111111111111111111102020030100000004444444444444444401";
4422 let expected_bytes = hex::decode(EXPECTED).unwrap();
4423 let data_bytes = hex::decode(DATA).unwrap();
4424
4425 assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes));
4426 }
4427
4428 #[test]
4429 fn test_abi_encoding_empty_bytes() {
4430 const EXPECTED: &str = "0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000";
4432 let expected_bytes = hex::decode(EXPECTED).unwrap();
4433 let data_bytes = vec![];
4434
4435 assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes));
4436 }
4437
4438 #[test]
4439 fn test_abi_encoding_one_byte() {
4440 const EXPECTED: &str = "0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000";
4444 let expected_bytes = hex::decode(EXPECTED).unwrap();
4445 let data_bytes = vec![253];
4446
4447 assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes));
4448 }
4449
4450 #[test]
4451 fn test_id_address_roundtrip() {
4452 let test_cases = [1u64, 2, 3, 100, 101];
4453
4454 for id in test_cases {
4455 let addr = FilecoinAddress::new_id(id);
4456
4457 let eth_addr = EthAddress::from_filecoin_address(&addr).unwrap();
4459 let fil_addr = eth_addr.to_filecoin_address().unwrap();
4460 assert_eq!(addr, fil_addr)
4461 }
4462 }
4463
4464 #[test]
4465 fn test_addr_serde_roundtrip() {
4466 let test_cases = [
4467 r#""0xd4c5fb16488Aa48081296299d54b0c648C9333dA""#,
4468 r#""0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d""#,
4469 r#""0x01184F793982104363F9a8a5845743f452dE0586""#,
4470 ];
4471
4472 for addr in test_cases {
4473 let eth_addr: EthAddress = serde_json::from_str(addr).unwrap();
4474
4475 let encoded = serde_json::to_string(ð_addr).unwrap();
4476 assert_eq!(encoded, addr.to_lowercase());
4477
4478 let decoded: EthAddress = serde_json::from_str(&encoded).unwrap();
4479 assert_eq!(eth_addr, decoded);
4480 }
4481 }
4482
4483 #[quickcheck]
4484 fn test_fil_address_roundtrip(addr: FilecoinAddress) {
4485 if let Ok(eth_addr) = EthAddress::from_filecoin_address(&addr) {
4486 let fil_addr = eth_addr.to_filecoin_address().unwrap();
4487
4488 let protocol = addr.protocol();
4489 assert!(protocol == Protocol::ID || protocol == Protocol::Delegated);
4490 assert_eq!(addr, fil_addr);
4491 }
4492 }
4493
4494 #[test]
4495 fn test_hash() {
4496 let test_cases = [
4497 r#""0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184""#,
4498 r#""0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738""#,
4499 ];
4500
4501 for hash in test_cases {
4502 let h: EthHash = serde_json::from_str(hash).unwrap();
4503
4504 let c = h.to_cid();
4505 let h1: EthHash = c.into();
4506 assert_eq!(h, h1);
4507 }
4508 }
4509
4510 #[quickcheck]
4511 fn test_eth_hash_roundtrip(eth_hash: EthHash) {
4512 let cid = eth_hash.to_cid();
4513 let hash = cid.into();
4514 assert_eq!(eth_hash, hash);
4515 }
4516
4517 #[test]
4518 fn test_block_constructor() {
4519 let block = Block::new(false, 1);
4520 assert_eq!(block.transactions_root, EthHash::empty_root());
4521
4522 let block = Block::new(true, 1);
4523 assert_eq!(block.transactions_root, EthHash::default());
4524 }
4525
4526 #[test]
4527 fn test_eth_tx_hash_from_signed_message() {
4528 let (_, signed) = construct_eth_messages(0);
4529 let tx_hash =
4530 eth_tx_hash_from_signed_message(&signed, crate::networks::calibnet::ETH_CHAIN_ID)
4531 .unwrap();
4532 assert_eq!(
4533 &format!("{tx_hash}"),
4534 "0xfc81dd8d9ffb045e7e2d494f925824098183263c7f402d69e18cc25e3422791b"
4535 );
4536
4537 let (_, signed) = construct_messages();
4538 let tx_hash =
4539 eth_tx_hash_from_signed_message(&signed, crate::networks::calibnet::ETH_CHAIN_ID)
4540 .unwrap();
4541 assert_eq!(tx_hash.to_cid(), signed.cid());
4542
4543 let (_, signed) = construct_bls_messages();
4544 let tx_hash =
4545 eth_tx_hash_from_signed_message(&signed, crate::networks::calibnet::ETH_CHAIN_ID)
4546 .unwrap();
4547 assert_eq!(tx_hash.to_cid(), signed.message().cid());
4548 }
4549
4550 #[test]
4551 fn test_eth_tx_hash_from_message_cid() {
4552 let blockstore = Arc::new(MemoryDB::default());
4553
4554 let (msg0, secp0) = construct_eth_messages(0);
4555 let (_msg1, secp1) = construct_eth_messages(1);
4556 let (msg2, bls0) = construct_bls_messages();
4557
4558 crate::chain::persist_objects(&blockstore, [msg0.clone(), msg2.clone()].iter()).unwrap();
4559 crate::chain::persist_objects(&blockstore, [secp0.clone(), bls0.clone()].iter()).unwrap();
4560
4561 let tx_hash = eth_tx_hash_from_message_cid(&blockstore, &secp0.cid(), 0).unwrap();
4562 assert!(tx_hash.is_some());
4563
4564 let tx_hash = eth_tx_hash_from_message_cid(&blockstore, &msg2.cid(), 0).unwrap();
4565 assert!(tx_hash.is_some());
4566
4567 let tx_hash = eth_tx_hash_from_message_cid(&blockstore, &secp1.cid(), 0).unwrap();
4568 assert!(tx_hash.is_none());
4569 }
4570
4571 #[test]
4572 fn test_eth_log_from_event() {
4573 let entries = vec![
4577 EventEntry {
4578 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4579 key: "t1".into(),
4580 codec: IPLD_RAW,
4581 value: vec![
4582 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4583 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4584 ]
4585 .into(),
4586 },
4587 EventEntry {
4588 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4589 key: "t2".into(),
4590 codec: IPLD_RAW,
4591 value: vec![
4592 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4593 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4594 ]
4595 .into(),
4596 },
4597 ];
4598 let (bytes, hashes) = eth_log_from_event(&entries).unwrap();
4599 assert!(bytes.0.is_empty());
4600 assert_eq!(hashes.len(), 2);
4601
4602 let entries = vec![
4603 EventEntry {
4604 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4605 key: "t1".into(),
4606 codec: IPLD_RAW,
4607 value: vec![
4608 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4609 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4610 ]
4611 .into(),
4612 },
4613 EventEntry {
4614 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4615 key: "t2".into(),
4616 codec: IPLD_RAW,
4617 value: vec![
4618 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4619 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4620 ]
4621 .into(),
4622 },
4623 EventEntry {
4624 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4625 key: "t3".into(),
4626 codec: IPLD_RAW,
4627 value: vec![
4628 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4629 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4630 ]
4631 .into(),
4632 },
4633 EventEntry {
4634 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4635 key: "t4".into(),
4636 codec: IPLD_RAW,
4637 value: vec![
4638 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4639 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4640 ]
4641 .into(),
4642 },
4643 ];
4644 let (bytes, hashes) = eth_log_from_event(&entries).unwrap();
4645 assert!(bytes.0.is_empty());
4646 assert_eq!(hashes.len(), 4);
4647
4648 let entries = vec![
4649 EventEntry {
4650 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4651 key: "t1".into(),
4652 codec: IPLD_RAW,
4653 value: vec![
4654 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4655 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4656 ]
4657 .into(),
4658 },
4659 EventEntry {
4660 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4661 key: "t1".into(),
4662 codec: IPLD_RAW,
4663 value: vec![
4664 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4665 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4666 ]
4667 .into(),
4668 },
4669 ];
4670 assert!(eth_log_from_event(&entries).is_none());
4671
4672 let entries = vec![
4673 EventEntry {
4674 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4675 key: "t3".into(),
4676 codec: IPLD_RAW,
4677 value: vec![
4678 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4679 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4680 ]
4681 .into(),
4682 },
4683 EventEntry {
4684 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4685 key: "t4".into(),
4686 codec: IPLD_RAW,
4687 value: vec![
4688 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4689 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4690 ]
4691 .into(),
4692 },
4693 EventEntry {
4694 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4695 key: "t1".into(),
4696 codec: IPLD_RAW,
4697 value: vec![
4698 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4699 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4700 ]
4701 .into(),
4702 },
4703 EventEntry {
4704 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4705 key: "t2".into(),
4706 codec: IPLD_RAW,
4707 value: vec![
4708 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4709 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4710 ]
4711 .into(),
4712 },
4713 ];
4714 let (bytes, hashes) = eth_log_from_event(&entries).unwrap();
4715 assert!(bytes.0.is_empty());
4716 assert_eq!(hashes.len(), 4);
4717
4718 let entries = vec![
4719 EventEntry {
4720 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4721 key: "t1".into(),
4722 codec: IPLD_RAW,
4723 value: vec![
4724 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4725 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4726 ]
4727 .into(),
4728 },
4729 EventEntry {
4730 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4731 key: "t3".into(),
4732 codec: IPLD_RAW,
4733 value: vec![
4734 116, 4, 227, 209, 4, 234, 120, 65, 195, 217, 230, 253, 32, 173, 254, 153, 180,
4735 173, 88, 107, 192, 141, 143, 59, 211, 175, 239, 137, 76, 241, 132, 222,
4736 ]
4737 .into(),
4738 },
4739 ];
4740 assert!(eth_log_from_event(&entries).is_none());
4741
4742 let entries = vec![EventEntry {
4743 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4744 key: "t1".into(),
4745 codec: DAG_CBOR,
4746 value: vec![
4747 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11, 81,
4748 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4749 ]
4750 .into(),
4751 }];
4752 assert!(eth_log_from_event(&entries).is_none());
4753
4754 let entries = vec![EventEntry {
4755 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4756 key: "t1".into(),
4757 codec: IPLD_RAW,
4758 value: vec![
4759 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11, 81,
4760 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149, 0,
4761 ]
4762 .into(),
4763 }];
4764 assert!(eth_log_from_event(&entries).is_none());
4765
4766 let entries = vec![
4767 EventEntry {
4768 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4769 key: "t1".into(),
4770 codec: IPLD_RAW,
4771 value: vec![
4772 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4773 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149,
4774 ]
4775 .into(),
4776 },
4777 EventEntry {
4778 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4779 key: "d".into(),
4780 codec: IPLD_RAW,
4781 value: vec![
4782 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 190,
4783 25, 34, 116, 232, 27, 26, 248,
4784 ]
4785 .into(),
4786 },
4787 ];
4788 let (bytes, hashes) = eth_log_from_event(&entries).unwrap();
4789 assert_eq!(bytes.0.len(), 32);
4790 assert_eq!(hashes.len(), 1);
4791
4792 let entries = vec![
4793 EventEntry {
4794 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4795 key: "t1".into(),
4796 codec: IPLD_RAW,
4797 value: vec![
4798 226, 71, 32, 244, 92, 183, 79, 45, 85, 241, 222, 235, 182, 9, 143, 80, 241, 11,
4799 81, 29, 171, 138, 125, 71, 196, 129, 154, 8, 220, 208, 184, 149, 0,
4800 ]
4801 .into(),
4802 },
4803 EventEntry {
4804 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4805 key: "d".into(),
4806 codec: IPLD_RAW,
4807 value: vec![
4808 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 190,
4809 25, 34, 116, 232, 27, 26, 248,
4810 ]
4811 .into(),
4812 },
4813 EventEntry {
4814 flags: (Flags::FLAG_INDEXED_ALL).bits(),
4815 key: "d".into(),
4816 codec: IPLD_RAW,
4817 value: vec![
4818 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 190,
4819 25, 34, 116, 232, 27, 26, 248,
4820 ]
4821 .into(),
4822 },
4823 ];
4824 assert!(eth_log_from_event(&entries).is_none());
4825 }
4826
4827 #[test]
4828 fn test_from_bytes_valid() {
4829 let zero_bytes = [0u8; 32];
4830 assert_eq!(
4831 EthUint64::from_bytes(&zero_bytes).unwrap().0,
4832 0,
4833 "zero bytes"
4834 );
4835
4836 let mut value_bytes = [0u8; 32];
4837 value_bytes[31] = 42;
4838 assert_eq!(
4839 EthUint64::from_bytes(&value_bytes).unwrap().0,
4840 42,
4841 "simple value"
4842 );
4843
4844 let mut max_bytes = [0u8; 32];
4845 max_bytes[24..32].copy_from_slice(&u64::MAX.to_be_bytes());
4846 assert_eq!(
4847 EthUint64::from_bytes(&max_bytes).unwrap().0,
4848 u64::MAX,
4849 "valid max value"
4850 );
4851 }
4852
4853 #[test]
4854 fn test_from_bytes_wrong_length() {
4855 let short_bytes = [0u8; 31];
4856 assert!(
4857 EthUint64::from_bytes(&short_bytes).is_err(),
4858 "bytes too short"
4859 );
4860
4861 let long_bytes = [0u8; 33];
4862 assert!(
4863 EthUint64::from_bytes(&long_bytes).is_err(),
4864 "bytes too long"
4865 );
4866
4867 let empty_bytes = [];
4868 assert!(
4869 EthUint64::from_bytes(&empty_bytes).is_err(),
4870 "bytes too short"
4871 );
4872 }
4873
4874 #[test]
4875 fn test_from_bytes_overflow() {
4876 let mut overflow_bytes = [0u8; 32];
4877 overflow_bytes[10] = 1;
4878 assert!(
4879 EthUint64::from_bytes(&overflow_bytes).is_err(),
4880 "overflow with non-zero byte at position 10"
4881 );
4882
4883 overflow_bytes = [0u8; 32];
4884 overflow_bytes[23] = 1;
4885 assert!(
4886 EthUint64::from_bytes(&overflow_bytes).is_err(),
4887 "overflow with non-zero byte at position 23"
4888 );
4889
4890 overflow_bytes = [0u8; 32];
4891 overflow_bytes
4892 .iter_mut()
4893 .take(24)
4894 .for_each(|byte| *byte = 0xFF);
4895
4896 assert!(
4897 EthUint64::from_bytes(&overflow_bytes).is_err(),
4898 "overflow bytes with non-zero bytes at positions 0-23"
4899 );
4900
4901 overflow_bytes = [0u8; 32];
4902 for i in 0..24 {
4903 overflow_bytes[i] = 0xFF;
4904 assert!(
4905 EthUint64::from_bytes(&overflow_bytes).is_err(),
4906 "overflow with non-zero byte at position {i}"
4907 );
4908 }
4909
4910 overflow_bytes = [0xFF; 32];
4911 assert!(
4912 EthUint64::from_bytes(&overflow_bytes).is_err(),
4913 "overflow with all ones"
4914 );
4915 }
4916
4917 fn create_execution_trace(from: FilecoinAddress, to: FilecoinAddress) -> ExecutionTrace {
4918 ExecutionTrace {
4919 msg: MessageTrace {
4920 from,
4921 to,
4922 value: TokenAmount::default(),
4923 method: 0,
4924 params: Default::default(),
4925 params_codec: 0,
4926 gas_limit: None,
4927 read_only: None,
4928 },
4929 msg_rct: ReturnTrace {
4930 exit_code: ExitCode::from(0u32),
4931 r#return: Default::default(),
4932 return_codec: 0,
4933 },
4934 invoked_actor: None,
4935 gas_charges: vec![],
4936 subcalls: vec![],
4937 }
4938 }
4939
4940 fn create_execution_trace_with_subcalls(
4941 from: FilecoinAddress,
4942 to: FilecoinAddress,
4943 subcalls: Vec<ExecutionTrace>,
4944 ) -> ExecutionTrace {
4945 let mut trace = create_execution_trace(from, to);
4946 trace.subcalls = subcalls;
4947 trace
4948 }
4949
4950 #[test]
4951 fn test_extract_touched_addresses_with_id_addresses() {
4952 let from = FilecoinAddress::new_id(100);
4954 let to = FilecoinAddress::new_id(200);
4955 let trace = create_execution_trace(from, to);
4956
4957 let addresses = extract_touched_eth_addresses(&trace);
4958
4959 assert_eq!(addresses.len(), 2);
4960 assert!(addresses.contains(&EthAddress::from_filecoin_address(&from).unwrap()));
4961 assert!(addresses.contains(&EthAddress::from_filecoin_address(&to).unwrap()));
4962 }
4963
4964 #[test]
4965 fn test_extract_touched_addresses_same_from_and_to() {
4966 let addr = FilecoinAddress::new_id(100);
4967 let trace = create_execution_trace(addr, addr);
4968
4969 let addresses = extract_touched_eth_addresses(&trace);
4970
4971 assert_eq!(addresses.len(), 1);
4973 assert!(addresses.contains(&EthAddress::from_filecoin_address(&addr).unwrap()));
4974 }
4975
4976 #[test]
4977 fn test_extract_touched_addresses_with_subcalls() {
4978 let addr1 = FilecoinAddress::new_id(100);
4979 let addr2 = FilecoinAddress::new_id(200);
4980 let addr3 = FilecoinAddress::new_id(300);
4981 let addr4 = FilecoinAddress::new_id(400);
4982
4983 let subcall = create_execution_trace(addr3, addr4);
4984 let trace = create_execution_trace_with_subcalls(addr1, addr2, vec![subcall]);
4985
4986 let addresses = extract_touched_eth_addresses(&trace);
4987
4988 assert_eq!(addresses.len(), 4);
4989 assert!(addresses.contains(&EthAddress::from_filecoin_address(&addr1).unwrap()));
4990 assert!(addresses.contains(&EthAddress::from_filecoin_address(&addr2).unwrap()));
4991 assert!(addresses.contains(&EthAddress::from_filecoin_address(&addr3).unwrap()));
4992 assert!(addresses.contains(&EthAddress::from_filecoin_address(&addr4).unwrap()));
4993 }
4994
4995 #[test]
4996 fn test_extract_touched_addresses_with_nested_subcalls() {
4997 let addr1 = FilecoinAddress::new_id(100);
4998 let addr2 = FilecoinAddress::new_id(200);
4999 let addr3 = FilecoinAddress::new_id(300);
5000 let addr4 = FilecoinAddress::new_id(400);
5001 let addr5 = FilecoinAddress::new_id(500);
5002 let addr6 = FilecoinAddress::new_id(600);
5003
5004 let nested_subcall = create_execution_trace(addr5, addr6);
5006 let subcall = create_execution_trace_with_subcalls(addr3, addr4, vec![nested_subcall]);
5007 let trace = create_execution_trace_with_subcalls(addr1, addr2, vec![subcall]);
5008
5009 let addresses = extract_touched_eth_addresses(&trace);
5010
5011 assert_eq!(addresses.len(), 6);
5012 for addr in [addr1, addr2, addr3, addr4, addr5, addr6] {
5013 assert!(addresses.contains(&EthAddress::from_filecoin_address(&addr).unwrap()));
5014 }
5015 }
5016
5017 #[test]
5018 fn test_extract_touched_addresses_with_multiple_subcalls() {
5019 let addr1 = FilecoinAddress::new_id(100);
5020 let addr2 = FilecoinAddress::new_id(200);
5021 let addr3 = FilecoinAddress::new_id(300);
5022 let addr4 = FilecoinAddress::new_id(400);
5023 let addr5 = FilecoinAddress::new_id(500);
5024 let addr6 = FilecoinAddress::new_id(600);
5025
5026 let subcall1 = create_execution_trace(addr3, addr4);
5027 let subcall2 = create_execution_trace(addr5, addr6);
5028 let trace = create_execution_trace_with_subcalls(addr1, addr2, vec![subcall1, subcall2]);
5029
5030 let addresses = extract_touched_eth_addresses(&trace);
5031
5032 assert_eq!(addresses.len(), 6);
5033 }
5034
5035 #[test]
5036 fn test_extract_touched_addresses_deduplicates_across_subcalls() {
5037 let addr1 = FilecoinAddress::new_id(100);
5039 let addr2 = FilecoinAddress::new_id(200);
5040
5041 let subcall = create_execution_trace(addr1, addr2); let trace = create_execution_trace_with_subcalls(addr1, addr2, vec![subcall]);
5043
5044 let addresses = extract_touched_eth_addresses(&trace);
5045
5046 assert_eq!(addresses.len(), 2);
5048 }
5049
5050 #[test]
5051 fn test_extract_touched_addresses_with_non_convertible_addresses() {
5052 let bls_addr = FilecoinAddress::new_bls(&[0u8; 48]).unwrap();
5054 let id_addr = FilecoinAddress::new_id(100);
5055
5056 let trace = create_execution_trace(bls_addr, id_addr);
5057 let addresses = extract_touched_eth_addresses(&trace);
5058
5059 assert_eq!(addresses.len(), 1);
5061 assert!(addresses.contains(&EthAddress::from_filecoin_address(&id_addr).unwrap()));
5062 }
5063}