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