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