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