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