Skip to content

Commit

Permalink
feat(rpc) Implement Filecoin.EthTraceBlock (#4991)
Browse files Browse the repository at this point in the history
  • Loading branch information
elmattic authored Jan 9, 2025
1 parent 62d7773 commit 1f1c818
Show file tree
Hide file tree
Showing 13 changed files with 1,108 additions and 67 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@

### Added

- [#4708](https://github.com/ChainSafe/forest/issues/4708) Add support for the
`Filecoin.EthTraceBlock` RPC method.

### Changed

### Removed
Expand Down
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ez-jsonrpc-types = "0.5"
fil_actor_account_state = { version = "19" }
fil_actor_cron_state = { version = "19" }
fil_actor_datacap_state = { version = "19" }
fil_actor_eam_state = { version = "19" }
fil_actor_evm_state = { version = "19" }
fil_actor_init_state = { version = "19" }
fil_actor_market_state = { version = "19" }
Expand Down Expand Up @@ -227,6 +228,7 @@ ariadne = "0.5"
assert_cmd = "2"
bimap = "0.6"
cargo_metadata = "0.19"
cbor4ii = { version = "0.2", default-features = false, features = ["serde1"] }
criterion = { version = "0.5", features = ["async_tokio", "csv"] }
cs_serde_bytes = "0.12"
derive-quickcheck-arbitrary = "0.1"
Expand Down
13 changes: 13 additions & 0 deletions src/eth/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use anyhow::{bail, ensure, Context};
use bytes::BufMut;
use bytes::BytesMut;
use cbor4ii::core::{dec::Decode as _, utils::SliceReader, Value};
use fvm_shared4::METHOD_CONSTRUCTOR;
use num::{bigint::Sign, BigInt, Signed as _};
use num_derive::FromPrimitive;
use num_traits::cast::ToPrimitive;
use rlp::Rlp;

Expand All @@ -32,13 +34,24 @@ use super::{
EthChainId, EIP_1559_TX_TYPE, EIP_2930_TX_TYPE,
};
// As per `ref-fvm`, which hardcodes it as well.
#[derive(FromPrimitive)]
#[repr(u64)]
pub enum EAMMethod {
Constructor = METHOD_CONSTRUCTOR,
Create = 2,
Create2 = 3,
CreateExternal = 4,
}

#[derive(FromPrimitive)]
#[repr(u64)]
pub enum EVMMethod {
Constructor = METHOD_CONSTRUCTOR,
Resurrect = 2,
GetBytecode = 3,
GetBytecodeHash = 4,
GetStorageAt = 5,
InvokeContractDelegate = 6,
// As per `ref-fvm`:
// it is very unfortunate but the hasher creates a circular dependency, so we use the raw
// number.
Expand Down
132 changes: 73 additions & 59 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

mod eth_tx;
pub mod filter;
mod trace;
pub mod types;
mod utils;

use self::eth_tx::*;
use self::filter::hex_str_to_epoch;
Expand All @@ -22,12 +24,14 @@ use crate::interpreter::VMTrace;
use crate::lotus_json::{lotus_json_with_self, HasLotusJson};
use crate::message::{ChainMessage, Message as _, SignedMessage};
use crate::rpc::error::ServerError;
use crate::rpc::eth::types::EthBlockTrace;
use crate::rpc::types::{ApiTipsetKey, EventEntry, MessageLookup};
use crate::rpc::EthEventHandler;
use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod};
use crate::shim::actors::eam;
use crate::shim::actors::evm;
use crate::shim::actors::is_evm_actor;
use crate::shim::actors::system;
use crate::shim::actors::EVMActorStateLoad as _;
use crate::shim::address::{Address as FilecoinAddress, Protocol};
use crate::shim::crypto::Signature;
Expand All @@ -44,8 +48,6 @@ use crate::utils::db::BlockstoreExt as _;
use crate::utils::encoding::from_slice_with_fallback;
use crate::utils::multihash::prelude::*;
use anyhow::{anyhow, bail, Context, Error, Result};
use cbor4ii::core::dec::Decode as _;
use cbor4ii::core::Value;
use cid::Cid;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::{RawBytes, CBOR, DAG_CBOR, IPLD_RAW};
Expand All @@ -56,6 +58,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::{ops::Add, sync::Arc};
use utils::{decode_payload, lookup_eth_address};

const MASKED_ID_PREFIX: [u8; 12] = [0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

Expand Down Expand Up @@ -185,7 +188,7 @@ impl EthHash {
let mh = MultihashCode::Blake2b256
.wrap(self.0.as_bytes())
.expect("should not fail");
Cid::new_v1(fvm_ipld_encoding::DAG_CBOR, mh)
Cid::new_v1(DAG_CBOR, mh)
}

pub fn empty_uncles() -> Self {
Expand Down Expand Up @@ -819,47 +822,6 @@ pub fn eth_tx_from_signed_eth_message(
Ok((from, tx))
}

fn lookup_eth_address<DB: Blockstore>(
addr: &FilecoinAddress,
state: &StateTree<DB>,
) -> Result<Option<EthAddress>> {
// Attempt to convert directly, if it's an f4 address.
if let Ok(eth_addr) = EthAddress::from_filecoin_address(addr) {
if !eth_addr.is_masked_id() {
return Ok(Some(eth_addr));
}
}

// Otherwise, resolve the ID addr.
let id_addr = match state.lookup_id(addr)? {
Some(id) => id,
_ => return Ok(None),
};

// Lookup on the target actor and try to get an f410 address.
let result = state.get_actor(addr);
if let Ok(Some(actor_state)) = result {
if let Some(addr) = actor_state.delegated_address {
if let Ok(eth_addr) = EthAddress::from_filecoin_address(&addr.into()) {
if !eth_addr.is_masked_id() {
// Conversable into an eth address, use it.
return Ok(Some(eth_addr));
}
}
} else {
// No delegated address -> use a masked ID address
}
} else if let Ok(None) = result {
// Not found -> use a masked ID address
} else {
// Any other error -> fail.
result?;
}

// Otherwise, use the masked address.
Ok(Some(EthAddress::from_actor_id(id_addr)))
}

/// See <https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector-and-argument-encoding>
/// for ABI specification
fn encode_filecoin_params_as_abi(
Expand Down Expand Up @@ -916,21 +878,6 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec<u8> {
buf
}

/// Decodes the payload using the given codec.
fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result<EthBytes> {
match codec {
DAG_CBOR | CBOR => {
let mut reader = cbor4ii::core::utils::SliceReader::new(payload.bytes());
match Value::decode(&mut reader) {
Ok(Value::Bytes(bytes)) => Ok(EthBytes(bytes)),
_ => bail!("failed to read params byte array"),
}
}
IPLD_RAW => Ok(EthBytes(payload.to_vec())),
_ => bail!("decode_payload: unsupported codec {codec}"),
}
}

/// Convert a native message to an eth transaction.
///
/// - The state-tree must be from after the message was applied (ideally the following tipset).
Expand Down Expand Up @@ -2655,6 +2602,73 @@ impl RpcMethod<1> for EthGetLogs {
}
}

pub enum EthTraceBlock {}
impl RpcMethod<1> for EthTraceBlock {
const NAME: &'static str = "Filecoin.EthTraceBlock";
const NAME_ALIAS: Option<&'static str> = Some("eth_traceBlock");
const N_REQUIRED_PARAMS: usize = 1;
const PARAM_NAMES: [&'static str; 1] = ["block_param"];
const API_PATHS: ApiPaths = ApiPaths::V1;
const PERMISSION: Permission = Permission::Read;
type Params = (BlockNumberOrHash,);
type Ok = Vec<EthBlockTrace>;
async fn handle(
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(block_param,): Self::Params,
) -> Result<Self::Ok, ServerError> {
let ts = tipset_by_block_number_or_hash(ctx.chain_store(), block_param)?;

let (state_root, trace) = ctx.state_manager.execution_trace(&ts)?;

let state = StateTree::new_from_root(ctx.store_owned(), &state_root)?;

let cid = ts.key().cid()?;

let block_hash: EthHash = cid.into();

let mut all_traces = vec![];
let mut msg_idx = 0;
for ir in trace.into_iter() {
// ignore messages from system actor
if ir.msg.from == system::ADDRESS.into() {
continue;
}

msg_idx += 1;

let tx_hash = EthGetTransactionHashByCid::handle(ctx.clone(), (ir.msg_cid,)).await?;

let tx_hash = tx_hash
.with_context(|| format!("cannot find transaction hash for cid {}", ir.msg_cid))?;

let mut env = trace::base_environment(&state, &ir.msg.from)
.map_err(|e| format!("when processing message {}: {}", ir.msg_cid, e))?;

if let Some(execution_trace) = ir.execution_trace {
trace::build_traces(&mut env, &[], execution_trace)?;

for trace in env.traces {
all_traces.push(EthBlockTrace {
r#type: trace.r#type,
subtraces: trace.subtraces,
trace_address: trace.trace_address,
action: trace.action,
result: trace.result,
error: trace.error,

block_hash: block_hash.clone(),
block_number: ts.epoch(),
transaction_hash: tx_hash.clone(),
transaction_position: msg_idx as i64,
});
}
}
}

Ok(all_traces)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
Loading

0 comments on commit 1f1c818

Please sign in to comment.