From 0c24d271ad5b7adb0f63d4f6233ae13c76f57bb1 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 13:33:39 +0200 Subject: [PATCH 01/94] Add EthGetBlockByNumber method (wip) --- src/lotus_json/mod.rs | 22 +++++++++ src/rpc/auth_layer.rs | 2 + src/rpc/chain_api.rs | 52 +++++++++++++++++++ src/rpc/eth_api.rs | 68 +++++++++++++++++++++++++ src/rpc/mod.rs | 2 + src/rpc_api/mod.rs | 88 +++++++++++++++++++++++++++++++-- src/rpc_client/eth_ops.rs | 11 +++++ src/tool/subcommands/api_cmd.rs | 4 ++ 8 files changed, 245 insertions(+), 4 deletions(-) diff --git a/src/lotus_json/mod.rs b/src/lotus_json/mod.rs index 1b3befb9f9c9..624df6feeced 100644 --- a/src/lotus_json/mod.rs +++ b/src/lotus_json/mod.rs @@ -319,6 +319,28 @@ pub mod hexify_bytes { } } +pub mod hexify_vec_bytes { + use super::*; + + pub fn serialize(value: &Vec, serializer: S) -> Result + where + S: Serializer, + { + let v: Vec = value.iter().map(|b| format!("{:#x}", b)).collect(); + serializer.serialize_str(&format!("0x{}", v.join(""))) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let v: String = String::deserialize(deserializer)? + .parse() + .map_err(serde::de::Error::custom)?; + todo!() + } +} + /// Usage: `#[serde(with = "hexify")]` pub mod hexify { use super::*; diff --git a/src/rpc/auth_layer.rs b/src/rpc/auth_layer.rs index 855cf7551946..9081f8b93320 100644 --- a/src/rpc/auth_layer.rs +++ b/src/rpc/auth_layer.rs @@ -160,6 +160,8 @@ static ACCESS_MAP: Lazy> = Lazy::new(|| { access.insert(eth_api::ETH_CHAIN_ID, Access::Read); access.insert(eth_api::ETH_GAS_PRICE, Access::Read); access.insert(eth_api::ETH_GET_BALANCE, Access::Read); + access.insert(eth_api::ETH_GET_BLOCK_BY_HASH, Access::Read); + access.insert(eth_api::ETH_GET_BLOCK_BY_NUMBER, Access::Read); access.insert(eth_api::ETH_SYNCING, Access::Read); // Pubsub API diff --git a/src/rpc/chain_api.rs b/src/rpc/chain_api.rs index 314aece84f0f..d7a1dd0ebafe 100644 --- a/src/rpc/chain_api.rs +++ b/src/rpc/chain_api.rs @@ -72,6 +72,58 @@ pub async fn chain_get_parent_messages( } } +pub async fn get_parent_receipts( + data: Ctx, + message_receipts: Cid, +) -> Result> { + let store = data.state_manager.blockstore(); + + let mut receipts = Vec::new(); + + // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here) + if let Ok(amt) = Amt::::load(&message_receipts, store) + .map_err(|_| { + ErrorObjectOwned::owned::<()>( + 1, + format!("failed to root: ipld: could not find {}", message_receipts), + None, + ) + }) + { + amt.for_each(|_, receipt| { + receipts.push(ApiReceipt { + exit_code: receipt.exit_code.into(), + return_data: receipt.return_data.clone(), + gas_used: receipt.gas_used, + events_root: receipt.events_root, + }); + Ok(()) + })?; + } else { + // Fallback to Receipt_v2. + let amt = Amt::::load(&message_receipts, store).map_err( + |_| { + ErrorObjectOwned::owned::<()>( + 1, + format!("failed to root: ipld: could not find {}", message_receipts), + None, + ) + }, + )?; + amt.for_each(|_, receipt| { + receipts.push(ApiReceipt { + exit_code: receipt.exit_code.into(), + return_data: receipt.return_data.clone(), + gas_used: receipt.gas_used as _, + events_root: None, + }); + Ok(()) + })?; + } + + Ok(receipts) +} + pub async fn chain_get_parent_receipts( params: Params<'_>, data: Ctx, diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 858ddb5c85c3..6e1b9d2e7123 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -9,13 +9,16 @@ use crate::blocks::{Tipset, TipsetKey}; use crate::chain::{index::ResolveNullTipset, ChainStore}; use crate::chain_sync::SyncStage; use crate::lotus_json::LotusJson; +use crate::message::ChainMessage; use crate::rpc::error::JsonRpcError; use crate::rpc::sync_api::sync_state; use crate::rpc::Ctx; +use crate::rpc_api::data_types::ApiReceipt; use crate::rpc_api::data_types::RPCSyncState; use crate::rpc_api::{eth_api::BigInt as EthBigInt, eth_api::*}; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; +use crate::rpc::chain_api::get_parent_receipts; use anyhow::{bail, Context, Result}; use fvm_ipld_blockstore::Blockstore; use itertools::Itertools; @@ -165,3 +168,68 @@ fn tipset_by_block_number_or_hash( } } } + +pub async fn eth_get_block_by_hash( + params: Params<'_>, + data: Ctx, +) -> Result { + todo!() +} + +async fn execute_tipset( + data: Ctx, + tipset: Arc, +) -> Result<(Hash, Vec, Vec)> { + let msgs = data.chain_store.messages_for_tipset(&tipset)?; + + let (state_root, receipt_root) = data.state_manager.tipset_state(&tipset).await?; + + let receipts = get_parent_receipts(data, receipt_root).await?; + + if msgs.len() != receipts.len() { + bail!( + "receipts and message array lengths didn't match for tipset: {:?}", + tipset + ) + } + + Ok((state_root.into(), msgs, receipts)) +} + +pub async fn block_from_filecoin_tipset( + data: Ctx, + tipset: Arc, + full_tx_info: bool, +) -> Result { + let parent_cid = tipset.parents().cid()?; + + let block_number = Uint64(tipset.epoch() as u64); + + let tsk = tipset.key(); + let block_cid = tsk.cid()?; + let block_hash: Hash = block_cid.into(); + + let (state_root, msgs, receipts) = execute_tipset(data, tipset).await?; + + let mut block = Block::default(); + block.parent_hash = parent_cid.into(); + + Ok(block) +} + +pub async fn eth_get_block_by_number( + params: Params<'_>, + data: Ctx, +) -> Result { + let LotusJson((block_param, full_tx_info)): LotusJson<(BlockNumberOrHash, bool)> = + params.parse()?; + + // dbg!(&block_param); + // dbg!(&full_tx_info); + + let ts = tipset_by_block_number_or_hash(&data.chain_store, block_param)?; + + let block = block_from_filecoin_tipset(data, ts, full_tx_info).await?; + + Ok(block) +} diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index b772f35859a0..8de55554b20f 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -328,6 +328,8 @@ where module.register_async_method(ETH_CHAIN_ID, |_, state| eth_chain_id::(state))?; module.register_async_method(ETH_GAS_PRICE, |_, state| eth_gas_price::(state))?; module.register_async_method(ETH_GET_BALANCE, eth_get_balance::)?; + module.register_async_method(ETH_GET_BLOCK_BY_HASH, eth_get_block_by_hash::)?; + module.register_async_method(ETH_GET_BLOCK_BY_NUMBER, eth_get_block_by_number::)?; module.register_async_method(ETH_SYNCING, eth_syncing::)?; Ok(()) diff --git a/src/rpc_api/mod.rs b/src/rpc_api/mod.rs index 364b0e9da3fc..1ed4df0d1b8c 100644 --- a/src/rpc_api/mod.rs +++ b/src/rpc_api/mod.rs @@ -392,6 +392,8 @@ pub mod eth_api { pub const ETH_CHAIN_ID: &str = "Filecoin.EthChainId"; pub const ETH_GAS_PRICE: &str = "Filecoin.EthGasPrice"; pub const ETH_GET_BALANCE: &str = "Filecoin.EthGetBalance"; + pub const ETH_GET_BLOCK_BY_HASH: &str = "Filecoin.EthGetBlockByHash"; + pub const ETH_GET_BLOCK_BY_NUMBER: &str = "Filecoin.EthGetBlockByNumber"; pub const ETH_SYNCING: &str = "Filecoin.EthSyncing"; const MASKED_ID_PREFIX: [u8; 12] = [0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; @@ -406,7 +408,22 @@ pub mod eth_api { lotus_json_with_self!(BigInt); - #[derive(Debug, Deserialize, Serialize, Default, Clone)] + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] + pub struct Nonce(#[serde(with = "crate::lotus_json::hexify")] pub u64); + + lotus_json_with_self!(Nonce); + + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] + pub struct Uint64(#[serde(with = "crate::lotus_json::hexify")] pub u64); + + lotus_json_with_self!(Uint64); + + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] + pub struct Bytes(#[serde(with = "crate::lotus_json::hexify_vec_bytes")] pub Vec); + + lotus_json_with_self!(Bytes); + + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] pub struct Address( #[serde(with = "crate::lotus_json::hexify_bytes")] pub ethereum_types::Address, ); @@ -446,7 +463,7 @@ pub mod eth_api { } } - #[derive(Default, Clone)] + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] pub struct Hash(pub ethereum_types::H256); impl Hash { @@ -465,7 +482,17 @@ pub mod eth_api { } } - #[derive(Default, Clone)] + impl From for Hash { + fn from(cid: Cid) -> Self { + Hash(ethereum_types::H256::from_slice( + &cid.hash().digest()[0..32], + )) + } + } + + lotus_json_with_self!(Hash); + + #[derive(Debug, Default, Clone)] pub enum Predefined { Earliest, Pending, @@ -485,7 +512,7 @@ pub mod eth_api { } #[allow(dead_code)] - #[derive(Clone)] + #[derive(Debug, Clone)] pub enum BlockNumberOrHash { PredefinedBlock(Predefined), BlockNumber(i64), @@ -538,6 +565,59 @@ pub mod eth_api { } } + #[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Block { + pub hash: Hash, + pub parent_hash: Hash, + pub sha3_uncles: Hash, + pub miner: Address, + pub state_root: Hash, + pub transactions_root: Hash, + pub receipts_root: Hash, + pub logs_bloom: Bytes, + pub difficulty: Uint64, + pub total_difficulty: Uint64, + pub number: Uint64, + pub gas_limit: Uint64, + pub gas_used: Uint64, + pub timestamp: Uint64, + pub extra_data: Bytes, + pub mix_hash: Hash, + pub nonce: Nonce, + pub base_fee_per_gas: BigInt, + pub size: Uint64, + // can be Vec or Vec depending on query params + pub transactions: String, + pub uncles: Vec, + } + + lotus_json_with_self!(Block); + + #[derive(Debug, Clone, Default, Serialize, Deserialize)] + pub struct Tx { + pub chain_id: u64, + pub nonce: u64, + pub hash: Hash, + pub block_hash: Hash, + pub block_number: u64, + pub transaction_index: u64, + pub from: Address, + pub to: Address, + pub value: BigInt, + pub r#type: u64, + pub input: Vec, + pub gas: u64, + pub max_fee_per_gas: BigInt, + pub max_priority_fee_per_gas: BigInt, + pub access_list: Vec, + pub v: BigInt, + pub r: BigInt, + pub s: BigInt, + } + + lotus_json_with_self!(Tx); + #[derive(Debug, Clone, Default)] pub struct EthSyncingResult { pub done_sync: bool, diff --git a/src/rpc_client/eth_ops.rs b/src/rpc_client/eth_ops.rs index 01bee53b39ce..7b80521ffe5e 100644 --- a/src/rpc_client/eth_ops.rs +++ b/src/rpc_client/eth_ops.rs @@ -14,6 +14,17 @@ impl ApiInfo { RpcRequest::new_v1(ETH_BLOCK_NUMBER, ()) } + pub fn eth_get_block_by_hash_req(block_param: String, full_tx_info: bool) -> RpcRequest { + RpcRequest::new_v1(ETH_GET_BLOCK_BY_HASH, (block_param, full_tx_info)) + } + + pub fn eth_get_block_by_number_req( + block_param: BlockNumberOrHash, + full_tx_info: bool, + ) -> RpcRequest { + RpcRequest::new_v1(ETH_GET_BLOCK_BY_NUMBER, (block_param, full_tx_info)) + } + pub fn eth_chain_id_req() -> RpcRequest { RpcRequest::new_v1(ETH_CHAIN_ID, ()) } diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index 753dac67b6a8..4258180727d9 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -530,6 +530,10 @@ fn eth_tests_with_tipset(shared_tipset: &Tipset) -> Vec { EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), BlockNumberOrHash::from_block_number(shared_tipset.epoch()), )), + RpcTest::identity(ApiInfo::eth_get_block_by_number_req( + BlockNumberOrHash::from_block_number(shared_tipset.epoch()), + false, + )), ] } From ca7374d145ba867f9607bf980be3c4c319fb8392 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 15:35:34 +0200 Subject: [PATCH 02/94] Fix EthBlock construction --- src/rpc/eth_api.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 17589b43bfd5..043979aacc24 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -8,7 +8,7 @@ use crate::chain::{index::ResolveNullTipset, ChainStore}; use crate::chain_sync::SyncStage; use crate::lotus_json::LotusJson; use crate::lotus_json::{lotus_json_with_self, HasLotusJson}; -use crate::message::ChainMessage; +use crate::message::{ChainMessage, SignedMessage}; use crate::rpc::chain_api::get_parent_receipts; use crate::rpc::error::JsonRpcError; use crate::rpc::sync_api::sync_state; @@ -16,6 +16,7 @@ use crate::rpc::types::ApiReceipt; use crate::rpc::types::RPCSyncState; use crate::rpc::Ctx; use crate::shim::address::Address as FilecoinAddress; +use crate::shim::crypto::Signature; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; use cid::{ @@ -54,7 +55,7 @@ pub struct BigInt(#[serde(with = "crate::lotus_json::hexify")] pub num_bigint::B lotus_json_with_self!(BigInt); #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] -pub struct Nonce(#[serde(with = "crate::lotus_json::hexify")] pub u64); +pub struct Nonce(#[serde(with = "crate::lotus_json::hexify_bytes")] pub ethereum_types::H64); lotus_json_with_self!(Nonce); @@ -484,11 +485,11 @@ pub async fn eth_get_block_by_hash( async fn execute_tipset( data: Ctx, - tipset: Arc, + tipset: &Arc, ) -> Result<(Hash, Vec, Vec)> { let msgs = data.chain_store.messages_for_tipset(&tipset)?; - let (state_root, receipt_root) = data.state_manager.tipset_state(&tipset).await?; + let (state_root, receipt_root) = data.state_manager.tipset_state(tipset).await?; let receipts = get_parent_receipts(data, receipt_root).await?; @@ -515,10 +516,31 @@ pub async fn block_from_filecoin_tipset( let block_cid = tsk.cid()?; let block_hash: Hash = block_cid.into(); - let (state_root, msgs, receipts) = execute_tipset(data, tipset).await?; + let (state_root, msgs, receipts) = execute_tipset(data, &tipset).await?; + + let mut gas_used = 0; + for (i, msg) in msgs.iter().enumerate() { + let receipt = receipts[i].clone(); + let ti = Uint64(i as u64); + gas_used += receipt.gas_used; + let smsg = match msg { + ChainMessage::Signed(msg) => msg.clone(), + ChainMessage::Unsigned(msg) => { + let sig = Signature::new_bls(vec![]); + SignedMessage::new_unchecked(msg.clone(), sig) + } + }; + + // TODO: build tx and push to block transactions + } let mut block = Block::default(); + block.hash = block_hash; + block.number = block_number; block.parent_hash = parent_cid.into(); + block.timestamp = Uint64(tipset.block_headers()[0].timestamp); + block.base_fee_per_gas = BigInt(tipset.block_headers()[0].parent_base_fee.atto().clone()); + block.gas_used = Uint64(gas_used); Ok(block) } From 32c52ec4dff4b164226f0ab2880315f2d3b2f02a Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 15:56:52 +0200 Subject: [PATCH 03/94] Fix gas limit --- src/rpc/eth_api.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 043979aacc24..668be6fa8f73 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -17,6 +17,7 @@ use crate::rpc::types::RPCSyncState; use crate::rpc::Ctx; use crate::shim::address::Address as FilecoinAddress; use crate::shim::crypto::Signature; +use crate::shim::econ::BLOCK_GAS_LIMIT; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; use cid::{ @@ -236,6 +237,15 @@ pub struct Block { pub uncles: Vec, } +impl Block { + pub fn new() -> Self { + Self { + gas_limit: Uint64(BLOCK_GAS_LIMIT), + ..Default::default() + } + } +} + lotus_json_with_self!(Block); #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -534,7 +544,7 @@ pub async fn block_from_filecoin_tipset( // TODO: build tx and push to block transactions } - let mut block = Block::default(); + let mut block = Block::new(); block.hash = block_hash; block.number = block_number; block.parent_hash = parent_cid.into(); From 7da6a77afc61dd0909d435461ae8ff0064c4cad7 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 16:09:47 +0200 Subject: [PATCH 04/94] Refactor using first method --- src/rpc/eth_api.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 668be6fa8f73..bb35694c41d9 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -548,8 +548,15 @@ pub async fn block_from_filecoin_tipset( block.hash = block_hash; block.number = block_number; block.parent_hash = parent_cid.into(); - block.timestamp = Uint64(tipset.block_headers()[0].timestamp); - block.base_fee_per_gas = BigInt(tipset.block_headers()[0].parent_base_fee.atto().clone()); + block.timestamp = Uint64(tipset.block_headers().first().timestamp); + block.base_fee_per_gas = BigInt( + tipset + .block_headers() + .first() + .parent_base_fee + .atto() + .clone(), + ); block.gas_used = Uint64(gas_used); Ok(block) From f94b0eea3526c10f17ed71ace948611df223e367 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 16:42:51 +0200 Subject: [PATCH 05/94] Add full bloom filter --- src/rpc/eth_api.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index bb35694c41d9..e8a8faccb1f7 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -45,6 +45,12 @@ pub const ETH_SYNCING: &str = "Filecoin.EthSyncing"; const MASKED_ID_PREFIX: [u8; 12] = [0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +const BLOOM_SIZE: usize = 2048; + +const BLOOM_SIZE_IN_BYTES: usize = BLOOM_SIZE / 8; + +const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES]; + #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct GasPriceResult(#[serde(with = "crate::lotus_json::hexify")] pub num_bigint::BigInt); @@ -60,6 +66,11 @@ pub struct Nonce(#[serde(with = "crate::lotus_json::hexify_bytes")] pub ethereum lotus_json_with_self!(Nonce); +#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] +pub struct Bloom(#[serde(with = "crate::lotus_json::hexify_bytes")] pub ethereum_types::Bloom); + +lotus_json_with_self!(Bloom); + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] pub struct Uint64(#[serde(with = "crate::lotus_json::hexify")] pub u64); @@ -220,7 +231,7 @@ pub struct Block { pub state_root: Hash, pub transactions_root: Hash, pub receipts_root: Hash, - pub logs_bloom: Bytes, + pub logs_bloom: Bloom, pub difficulty: Uint64, pub total_difficulty: Uint64, pub number: Uint64, @@ -241,6 +252,7 @@ impl Block { pub fn new() -> Self { Self { gas_limit: Uint64(BLOCK_GAS_LIMIT), + logs_bloom: Bloom(ethereum_types::Bloom(FULL_BLOOM)), ..Default::default() } } From a282565db9aefe0b401a0fc467ea006312eecf08 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 17:15:46 +0200 Subject: [PATCH 06/94] Add empty uncles hash --- src/rpc/eth_api.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index e8a8faccb1f7..5b728f2933d6 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -51,6 +51,9 @@ const BLOOM_SIZE_IN_BYTES: usize = BLOOM_SIZE / 8; const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES]; +/// Keccak-256 of an RLP of an empty array +const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; + #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct GasPriceResult(#[serde(with = "crate::lotus_json::hexify")] pub num_bigint::BigInt); @@ -253,6 +256,7 @@ impl Block { Self { gas_limit: Uint64(BLOCK_GAS_LIMIT), logs_bloom: Bloom(ethereum_types::Bloom(FULL_BLOOM)), + sha3_uncles: Hash(ethereum_types::H256::from_str(EMPTY_UNCLES).unwrap()), ..Default::default() } } From ee5cae057c84cec68f234c6830e829981ee202c5 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 3 Apr 2024 19:18:58 +0200 Subject: [PATCH 07/94] Start to add support for txs --- src/rpc/eth_api.rs | 43 +++++++++++++++++++++++++++------ src/tool/subcommands/api_cmd.rs | 6 ++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 5b728f2933d6..e9422e05792b 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -247,7 +247,7 @@ pub struct Block { pub base_fee_per_gas: BigInt, pub size: Uint64, // can be Vec or Vec depending on query params - pub transactions: String, + pub transactions: Vec, pub uncles: Vec, } @@ -264,14 +264,15 @@ impl Block { lotus_json_with_self!(Block); -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Tx { pub chain_id: u64, pub nonce: u64, pub hash: Hash, pub block_hash: Hash, - pub block_number: u64, - pub transaction_index: u64, + pub block_number: Uint64, + pub transaction_index: Uint64, pub from: Address, pub to: Address, pub value: BigInt, @@ -512,7 +513,7 @@ pub async fn eth_get_block_by_hash( async fn execute_tipset( data: Ctx, tipset: &Arc, -) -> Result<(Hash, Vec, Vec)> { +) -> Result<(Cid, Vec, Vec)> { let msgs = data.chain_store.messages_for_tipset(&tipset)?; let (state_root, receipt_root) = data.state_manager.tipset_state(tipset).await?; @@ -526,7 +527,21 @@ async fn execute_tipset( ) } - Ok((state_root.into(), msgs, receipts)) + Ok((state_root, msgs, receipts)) +} + +pub fn tx_from_signed_message(smsg: SignedMessage, state: &StateTree) -> Result { + let mut tx: Tx = Tx::default(); + + if smsg.is_delegated() { + } else if smsg.is_secp256k1() { + // Secp Filecoin Message + tx.hash = smsg.cid()?.into(); + } else { + // BLS Filecoin message + tx.hash = smsg.message().cid()?.into(); + } + Ok(tx) } pub async fn block_from_filecoin_tipset( @@ -542,8 +557,11 @@ pub async fn block_from_filecoin_tipset( let block_cid = tsk.cid()?; let block_hash: Hash = block_cid.into(); - let (state_root, msgs, receipts) = execute_tipset(data, &tipset).await?; + let (state_root, msgs, receipts) = execute_tipset(data.clone(), &tipset).await?; + + let state_tree = StateTree::new_from_root(data.state_manager.blockstore_owned(), &state_root)?; + let mut transactions = vec![]; let mut gas_used = 0; for (i, msg) in msgs.iter().enumerate() { let receipt = receipts[i].clone(); @@ -558,6 +576,16 @@ pub async fn block_from_filecoin_tipset( }; // TODO: build tx and push to block transactions + let mut tx = tx_from_signed_message(smsg, &state_tree)?; + tx.block_hash = block_hash.clone(); + tx.block_number = block_number.clone(); + tx.transaction_index = ti; + + if full_tx_info { + transactions.push(tx); + } else { + // TODO: push in some other vector + } } let mut block = Block::new(); @@ -574,6 +602,7 @@ pub async fn block_from_filecoin_tipset( .clone(), ); block.gas_used = Uint64(gas_used); + block.transactions = transactions; Ok(block) } diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index 24153810981d..6c559db7a964 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -554,9 +554,13 @@ fn eth_tests_with_tipset(shared_tipset: &Tipset) -> Vec { EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), BlockNumberOrHash::from_block_number(shared_tipset.epoch()), )), + // RpcTest::identity(ApiInfo::eth_get_block_by_number_req( + // BlockNumberOrHash::from_block_number(shared_tipset.epoch()), + // false, + // )), RpcTest::identity(ApiInfo::eth_get_block_by_number_req( BlockNumberOrHash::from_block_number(shared_tipset.epoch()), - false, + true, )), ] } From c3ef03a99d458897e22f20cc9813155d5165275e Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 4 Apr 2024 13:40:37 +0200 Subject: [PATCH 08/94] Add support for eth message (wip) --- src/rpc/eth_api.rs | 119 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 4 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index e9422e05792b..52ea7f258a08 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -15,9 +15,11 @@ use crate::rpc::sync_api::sync_state; use crate::rpc::types::ApiReceipt; use crate::rpc::types::RPCSyncState; use crate::rpc::Ctx; -use crate::shim::address::Address as FilecoinAddress; -use crate::shim::crypto::Signature; +use crate::shim::address::{Address as FilecoinAddress, Protocol}; +use crate::shim::crypto::{Signature, SignatureType}; use crate::shim::econ::BLOCK_GAS_LIMIT; +use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; +use crate::shim::fvm_shared_latest::message::Message as VmMessage; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; use cid::{ @@ -29,6 +31,7 @@ use itertools::Itertools; use jsonrpsee::types::Params; use nonempty::nonempty; use num_bigint; +use num_bigint::Sign; use num_traits::Zero as _; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; @@ -54,6 +57,8 @@ const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES]; /// Keccak-256 of an RLP of an empty array const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; +const EIP_1559_TX_TYPE: u64 = 2; + #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct GasPriceResult(#[serde(with = "crate::lotus_json::hexify")] pub num_bigint::BigInt); @@ -107,6 +112,10 @@ impl Address { } } + pub fn from_filecoin_address(address: &FilecoinAddress) -> Result { + todo!() + } + fn is_masked_id(&self) -> bool { self.0.as_bytes().starts_with(&MASKED_ID_PREFIX) } @@ -289,6 +298,20 @@ pub struct Tx { lotus_json_with_self!(Tx); +struct TxArgs { + pub chain_id: u64, + pub nonce: u64, + pub to: Address, + pub value: BigInt, + pub max_fee_per_gas: BigInt, + pub max_priority_fee_per_gas: BigInt, + pub gas_limit: u64, + pub input: Vec, + pub v: BigInt, + pub r: BigInt, + pub s: BigInt, +} + #[derive(Debug, Clone, Default)] pub struct EthSyncingResult { pub done_sync: bool, @@ -530,15 +553,103 @@ async fn execute_tipset( Ok((state_root, msgs, receipts)) } -pub fn tx_from_signed_message(smsg: SignedMessage, state: &StateTree) -> Result { +fn is_eth_address(addr: &VmAddress) -> bool { + if addr.protocol() != Protocol::Delegated { + return false; + } + let f4_addr: Result = addr.payload().try_into(); + + f4_addr.is_ok() +} + +fn eth_tx_args_from_unsigned_eth_message(msg: &crate::shim::message::Message) -> Result { + todo!() +} + +fn recover_sig(sig: &Signature) -> Result<(BigInt, BigInt, BigInt)> { + if sig.signature_type() != SignatureType::Delegated { + bail!("recover_sig only supports Delegated signature"); + } + + let len = sig.bytes().len(); + if len != 65 { + bail!("signature should be 65 bytes long, but got {len} bytes",); + } + + let bytes = sig.bytes(); + + let r = num_bigint::BigInt::from_bytes_le(Sign::NoSign, &bytes[0..32]); + + let s = num_bigint::BigInt::from_bytes_le(Sign::NoSign, &bytes[32..64]); + + let v = num_bigint::BigInt::from_bytes_le(Sign::NoSign, &bytes[64..65]); + + Ok((BigInt(r), BigInt(s), BigInt(v))) +} + +/// `eth_tx_from_signed_eth_message` does NOT populate: +/// - `hash` +/// - `block_hash` +/// - `block_number` +/// - `transaction_index` +fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { + // The from address is always an f410f address, never an ID or other address. + let from = smsg.message().from; + if !is_eth_address(&from) { + bail!("sender must be an eth account, was {from}"); + } + + // Probably redundant, but we might as well check. + let sig_type = smsg.signature().signature_type(); + if sig_type != SignatureType::Delegated { + bail!("signature is not delegated type, is type: {sig_type}"); + } + + // This should be impossible to fail as we've already asserted that we have an + // Ethereum Address sender... + let from = Address::from_filecoin_address(&from)?; + + let tx_args = eth_tx_args_from_unsigned_eth_message(smsg.message())?; + + let (r, s, v) = recover_sig(smsg.signature())?; + + Ok(Tx { + nonce: tx_args.nonce, + chain_id: tx_args.chain_id, + to: tx_args.to, + from, + value: tx_args.value, + r#type: EIP_1559_TX_TYPE, + gas: tx_args.gas_limit, + max_fee_per_gas: tx_args.max_fee_per_gas, + max_priority_fee_per_gas: tx_args.max_priority_fee_per_gas, + access_list: vec![], + v, + r, + s, + input: tx_args.input, + ..Tx::default() + }) +} + +fn eth_tx_from_native_message(msg: &VmMessage, state: &StateTree) -> Result { + Ok(Tx::default()) +} + +pub fn new_eth_tx_from_signed_message(smsg: &SignedMessage, state: &StateTree) -> Result { let mut tx: Tx = Tx::default(); if smsg.is_delegated() { + // This is an eth tx + let eth_tx = eth_tx_from_signed_eth_message(smsg)?; + tx.hash = eth_tx.hash; } else if smsg.is_secp256k1() { // Secp Filecoin Message + tx = eth_tx_from_native_message(&smsg.message.clone().into(), state)?; tx.hash = smsg.cid()?.into(); } else { // BLS Filecoin message + tx = eth_tx_from_native_message(&smsg.message.clone().into(), state)?; tx.hash = smsg.message().cid()?.into(); } Ok(tx) @@ -576,7 +687,7 @@ pub async fn block_from_filecoin_tipset( }; // TODO: build tx and push to block transactions - let mut tx = tx_from_signed_message(smsg, &state_tree)?; + let mut tx = new_eth_tx_from_signed_message(&smsg, &state_tree)?; tx.block_hash = block_hash.clone(); tx.block_number = block_number.clone(); tx.transaction_index = ti; From a7f5b91dc3831d4e1de6275a40b143faa4bf7927 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 4 Apr 2024 15:22:17 +0200 Subject: [PATCH 09/94] Add from_filecoin_address impl --- src/rpc/eth_api.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 52ea7f258a08..d36a8db59f58 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -54,6 +54,8 @@ const BLOOM_SIZE_IN_BYTES: usize = BLOOM_SIZE / 8; const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES]; +const ADDRESS_LENGTH: usize = 20; + /// Keccak-256 of an RLP of an empty array const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; @@ -112,13 +114,46 @@ impl Address { } } - pub fn from_filecoin_address(address: &FilecoinAddress) -> Result { - todo!() + pub fn from_filecoin_address(addr: &FilecoinAddress) -> Result { + match addr.protocol() { + Protocol::ID => Ok(Self::from_actor_id(addr.id()?)), + Protocol::Delegated => { + let payload = addr.payload(); + let result: Result = payload.try_into(); + if let Ok(f4_addr) = result { + let namespace = f4_addr.namespace(); + if namespace != FilecoinAddress::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id()? { + bail!("invalid address {addr}"); + } + let eth_addr = cast_eth_addr(f4_addr.subaddress())?; + if eth_addr.is_masked_id() { + bail!( + "f410f addresses cannot embed masked-ID payloads: {}", + eth_addr.0 + ); + } + Ok(eth_addr) + } else { + bail!("invalid delegated address namespace in: {addr}") + } + } + _ => { + bail!("invalid address {addr}"); + } + } } fn is_masked_id(&self) -> bool { self.0.as_bytes().starts_with(&MASKED_ID_PREFIX) } + + fn from_actor_id(id: u64) -> Self { + let mut payload = ethereum_types::H160::default(); + payload.as_bytes_mut()[0] = 0xff; + payload.as_bytes_mut()[12..20].copy_from_slice(&id.to_be_bytes()); + + Self(payload) + } } impl FromStr for Address { @@ -131,6 +166,15 @@ impl FromStr for Address { } } +fn cast_eth_addr(bytes: &[u8]) -> Result
{ + if bytes.len() != ADDRESS_LENGTH { + bail!("cannot parse bytes into an Ethereum address: incorrect input length") + } + let mut payload = ethereum_types::H160::default(); + payload.as_bytes_mut().copy_from_slice(bytes); + Ok(Address(payload)) +} + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] pub struct Hash(pub ethereum_types::H256); From be215c832dce0538e5eb2566c617535e64ef91a7 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 4 Apr 2024 17:44:31 +0200 Subject: [PATCH 10/94] Add eth_tx_args_from_unsigned_eth_message impl --- src/rpc/eth_api.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index d36a8db59f58..cd73d91795b8 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -8,7 +8,7 @@ use crate::chain::{index::ResolveNullTipset, ChainStore}; use crate::chain_sync::SyncStage; use crate::lotus_json::LotusJson; use crate::lotus_json::{lotus_json_with_self, HasLotusJson}; -use crate::message::{ChainMessage, SignedMessage}; +use crate::message::{ChainMessage, Message, SignedMessage}; use crate::rpc::chain_api::get_parent_receipts; use crate::rpc::error::JsonRpcError; use crate::rpc::sync_api::sync_state; @@ -20,6 +20,7 @@ use crate::shim::crypto::{Signature, SignatureType}; use crate::shim::econ::BLOCK_GAS_LIMIT; use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; use crate::shim::fvm_shared_latest::message::Message as VmMessage; +use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; use cid::{ @@ -61,6 +62,32 @@ const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0 const EIP_1559_TX_TYPE: u64 = 2; +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const EIP_155_CHAIN_ID: u64 = 31415926; + +#[repr(u64)] +enum EAMMethod { + Constructor = METHOD_CONSTRUCTOR, + Create = 2, + Create2 = 3, + CreateExternal = 4, +} + +#[repr(u64)] +pub enum EVMMethod { + Constructor = METHOD_CONSTRUCTOR, + Resurrect = 2, + GetBytecode = 3, + GetBytecodeHash = 4, + GetStorageAt = 5, + InvokeContractDelegate = 6, + // it is very unfortunate but the hasher creates a circular dependency, so we use the raw + // number. + // InvokeContract = frc42_dispatch::method_hash!("InvokeEVM"), + InvokeContract = 3844450837, +} + #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct GasPriceResult(#[serde(with = "crate::lotus_json::hexify")] pub num_bigint::BigInt); @@ -342,6 +369,7 @@ pub struct Tx { lotus_json_with_self!(Tx); +#[derive(Debug, Clone, Default)] struct TxArgs { pub chain_id: u64, pub nonce: u64, @@ -607,7 +635,43 @@ fn is_eth_address(addr: &VmAddress) -> bool { } fn eth_tx_args_from_unsigned_eth_message(msg: &crate::shim::message::Message) -> Result { - todo!() + let mut to = Address::default(); + let mut params = vec![]; + + if msg.version != 0 { + bail!("unsupported msg version: {}", msg.version); + } + + if msg.params().bytes().len() > 0 { + params = msg.params().bytes().to_vec(); + } + + if msg.to == FilecoinAddress::ETHEREUM_ACCOUNT_MANAGER_ACTOR { + if msg.method_num() != EAMMethod::CreateExternal as u64 { + bail!("unsupported EAM method"); + } else if msg.method_num() == EVMMethod::InvokeContract as u64 { + let addr = Address::from_filecoin_address(&msg.to)?; + to = addr; + } else { + bail!( + "invalid methodnum {}: only allowed method is InvokeContract({})", + msg.method_num(), + EVMMethod::InvokeContract as u64 + ); + } + } + + Ok(TxArgs { + chain_id: EIP_155_CHAIN_ID, + nonce: msg.sequence, + to, + value: BigInt(msg.value.atto().clone()), + max_fee_per_gas: BigInt(msg.gas_fee_cap.atto().clone()), + max_priority_fee_per_gas: BigInt(msg.gas_premium.atto().clone()), + gas_limit: msg.gas_limit, + input: params, + ..TxArgs::default() + }) } fn recover_sig(sig: &Signature) -> Result<(BigInt, BigInt, BigInt)> { From 256627fb8ead125d05cedfe1fcb033485c76d571 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 4 Apr 2024 19:44:51 +0200 Subject: [PATCH 11/94] Add eth_tx_from_native_message impl (wip) --- src/rpc/eth_api.rs | 47 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index cd73d91795b8..82c116d28165 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -64,7 +64,8 @@ const EIP_1559_TX_TYPE: u64 = 2; // ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. // As per https://github.com/ethereum-lists/chains -const EIP_155_CHAIN_ID: u64 = 31415926; +// TODO: use chain_config instead +const EIP_155_CHAIN_ID: u64 = 0x4cb2f; #[repr(u64)] enum EAMMethod { @@ -347,8 +348,8 @@ lotus_json_with_self!(Block); #[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Tx { - pub chain_id: u64, - pub nonce: u64, + pub chain_id: Uint64, + pub nonce: Uint64, pub hash: Hash, pub block_hash: Hash, pub block_number: Uint64, @@ -356,9 +357,9 @@ pub struct Tx { pub from: Address, pub to: Address, pub value: BigInt, - pub r#type: u64, + pub r#type: Uint64, pub input: Vec, - pub gas: u64, + pub gas: Uint64, pub max_fee_per_gas: BigInt, pub max_priority_fee_per_gas: BigInt, pub access_list: Vec, @@ -722,13 +723,13 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { let (r, s, v) = recover_sig(smsg.signature())?; Ok(Tx { - nonce: tx_args.nonce, - chain_id: tx_args.chain_id, + nonce: Uint64(tx_args.nonce), + chain_id: Uint64(tx_args.chain_id), to: tx_args.to, from, value: tx_args.value, - r#type: EIP_1559_TX_TYPE, - gas: tx_args.gas_limit, + r#type: Uint64(EIP_1559_TX_TYPE), + gas: Uint64(tx_args.gas_limit), max_fee_per_gas: tx_args.max_fee_per_gas, max_priority_fee_per_gas: tx_args.max_priority_fee_per_gas, access_list: vec![], @@ -740,8 +741,34 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { }) } +/// Convert a native message to an eth transaction. +/// +/// - The state-tree must be from after the message was applied (ideally the following tipset). +/// - In some cases, the "to" address may be `0xff0000000000000000000000ffffffffffffffff`. This +/// means that the "to" address has not been assigned in the passed state-tree and can only +/// happen if the transaction reverted. +/// +/// `eth_tx_from_native_message` does NOT populate: +/// - `hash` +/// - `block_hash` +/// - `block_number` +/// - `transaction_index` fn eth_tx_from_native_message(msg: &VmMessage, state: &StateTree) -> Result { - Ok(Tx::default()) + // Lookup the from address. This must succeed. + + // Finally, convert the input parameters to "solidity ABI". + + let mut tx = Tx::default(); + tx.nonce = Uint64(msg.sequence); + tx.chain_id = Uint64(EIP_155_CHAIN_ID); + tx.value = BigInt(msg.value.atto().clone()); + tx.r#type = Uint64(EIP_1559_TX_TYPE); + tx.gas = Uint64(msg.gas_limit); + tx.max_fee_per_gas = BigInt(msg.gas_fee_cap.atto().clone()); + tx.max_priority_fee_per_gas = BigInt(msg.gas_premium.atto().clone()); + tx.access_list = vec![]; + + Ok(tx) } pub fn new_eth_tx_from_signed_message(smsg: &SignedMessage, state: &StateTree) -> Result { From d18ca39501e16b41071828e671e9e53d4f7d5478 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 5 Apr 2024 10:43:52 +0200 Subject: [PATCH 12/94] Add support for to/from addr to Tx --- src/rpc/eth_api.rs | 83 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 82c116d28165..f4a2ecf90e7e 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -62,6 +62,9 @@ const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0 const EIP_1559_TX_TYPE: u64 = 2; +/// The address used in messages to actors that have since been deleted. +const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff"; + // ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. // As per https://github.com/ethereum-lists/chains // TODO: use chain_config instead @@ -741,6 +744,40 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { }) } +fn lookup_eth_address( + addr: &FilecoinAddress, + state: &StateTree, +) -> Result
{ + // Attempt to convert directly, if it's an f4 address. + if let Ok(eth_addr) = Address::from_filecoin_address(addr) { + if !eth_addr.is_masked_id() { + return Ok(eth_addr); + } + } + + // Otherwise, resolve the ID addr. + let id_addr = state.lookup_id(addr)?; + + // Lookup on the target actor and try to get an f410 address. + if let Some(actor_state) = state.get_actor(addr)? { + if let Some(addr) = actor_state.delegated_address { + if let Ok(eth_addr) = Address::from_filecoin_address(&addr.into()) { + if !eth_addr.is_masked_id() { + // Conversable into an eth address, use it. + return Ok(eth_addr); + } + } + } else { + // No delegated address -> use a masked ID address + } + } else { + // Not found -> use a masked ID address + } + + // Otherwise, use the masked address. + Ok(Address::from_actor_id(id_addr.unwrap())) +} + /// Convert a native message to an eth transaction. /// /// - The state-tree must be from after the message was applied (ideally the following tipset). @@ -753,25 +790,53 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { /// - `block_hash` /// - `block_number` /// - `transaction_index` -fn eth_tx_from_native_message(msg: &VmMessage, state: &StateTree) -> Result { +fn eth_tx_from_native_message( + msg: &SignedMessage, + state: &StateTree, +) -> Result { // Lookup the from address. This must succeed. + let from = lookup_eth_address(&msg.from(), state).with_context(|| { + format!( + "failed to lookup sender address {} when converting a native message to an eth txn", + msg.from() + ) + })?; + // Lookup the to address. If the recipient doesn't exist, we replace the address with a + // known sentinel address. + let to = match lookup_eth_address(&msg.to(), state) { + Ok(addr) => addr, + Err(_err) => { + // TODO: bail in case of not "actor not found" errors + Address(ethereum_types::H160::from_str(REVERTED_ETH_ADDRESS)?) + + // bail!( + // "failed to lookup receiver address {} when converting a native message to an eth txn", + // msg.to() + // ) + } + }; // Finally, convert the input parameters to "solidity ABI". let mut tx = Tx::default(); - tx.nonce = Uint64(msg.sequence); + tx.to = to; + tx.from = from; + tx.nonce = Uint64(msg.message().sequence); tx.chain_id = Uint64(EIP_155_CHAIN_ID); - tx.value = BigInt(msg.value.atto().clone()); + tx.value = BigInt(msg.message().value.atto().clone()); tx.r#type = Uint64(EIP_1559_TX_TYPE); - tx.gas = Uint64(msg.gas_limit); - tx.max_fee_per_gas = BigInt(msg.gas_fee_cap.atto().clone()); - tx.max_priority_fee_per_gas = BigInt(msg.gas_premium.atto().clone()); + tx.gas = Uint64(msg.message().gas_limit); + tx.max_fee_per_gas = BigInt(msg.message().gas_fee_cap.atto().clone()); + tx.max_priority_fee_per_gas = BigInt(msg.message().gas_premium.atto().clone()); tx.access_list = vec![]; Ok(tx) } -pub fn new_eth_tx_from_signed_message(smsg: &SignedMessage, state: &StateTree) -> Result { +pub fn new_eth_tx_from_signed_message( + smsg: &SignedMessage, + state: &StateTree, +) -> Result { let mut tx: Tx = Tx::default(); if smsg.is_delegated() { @@ -780,11 +845,11 @@ pub fn new_eth_tx_from_signed_message(smsg: &SignedMessage, state: &StateTree tx.hash = eth_tx.hash; } else if smsg.is_secp256k1() { // Secp Filecoin Message - tx = eth_tx_from_native_message(&smsg.message.clone().into(), state)?; + tx = eth_tx_from_native_message(smsg, state)?; tx.hash = smsg.cid()?.into(); } else { // BLS Filecoin message - tx = eth_tx_from_native_message(&smsg.message.clone().into(), state)?; + tx = eth_tx_from_native_message(smsg, state)?; tx.hash = smsg.message().cid()?.into(); } Ok(tx) From ea58cc72d73490a6cd39519eff4287664dafd83c Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 5 Apr 2024 11:03:09 +0200 Subject: [PATCH 13/94] Refactor using trait --- src/rpc/eth_api.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index f4a2ecf90e7e..21585888fced 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -17,9 +17,8 @@ use crate::rpc::types::RPCSyncState; use crate::rpc::Ctx; use crate::shim::address::{Address as FilecoinAddress, Protocol}; use crate::shim::crypto::{Signature, SignatureType}; -use crate::shim::econ::BLOCK_GAS_LIMIT; +use crate::shim::econ::{TokenAmount, BLOCK_GAS_LIMIT}; use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; -use crate::shim::fvm_shared_latest::message::Message as VmMessage; use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; @@ -100,6 +99,12 @@ lotus_json_with_self!(GasPriceResult); #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] pub struct BigInt(#[serde(with = "crate::lotus_json::hexify")] pub num_bigint::BigInt); +impl From for BigInt { + fn from(amount: TokenAmount) -> Self { + Self(amount.atto().to_owned()) + } +} + lotus_json_with_self!(BigInt); #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone)] @@ -669,9 +674,9 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &crate::shim::message::Message) -> chain_id: EIP_155_CHAIN_ID, nonce: msg.sequence, to, - value: BigInt(msg.value.atto().clone()), - max_fee_per_gas: BigInt(msg.gas_fee_cap.atto().clone()), - max_priority_fee_per_gas: BigInt(msg.gas_premium.atto().clone()), + value: msg.value.clone().into(), + max_fee_per_gas: msg.gas_fee_cap.clone().into(), + max_priority_fee_per_gas: msg.gas_premium.clone().into(), gas_limit: msg.gas_limit, input: params, ..TxArgs::default() @@ -823,11 +828,11 @@ fn eth_tx_from_native_message( tx.from = from; tx.nonce = Uint64(msg.message().sequence); tx.chain_id = Uint64(EIP_155_CHAIN_ID); - tx.value = BigInt(msg.message().value.atto().clone()); + tx.value = msg.message().value.clone().into(); tx.r#type = Uint64(EIP_1559_TX_TYPE); tx.gas = Uint64(msg.message().gas_limit); - tx.max_fee_per_gas = BigInt(msg.message().gas_fee_cap.atto().clone()); - tx.max_priority_fee_per_gas = BigInt(msg.message().gas_premium.atto().clone()); + tx.max_fee_per_gas = msg.message().gas_fee_cap.clone().into(); + tx.max_priority_fee_per_gas = msg.message().gas_premium.clone().into(); tx.access_list = vec![]; Ok(tx) @@ -904,14 +909,12 @@ pub async fn block_from_filecoin_tipset( block.number = block_number; block.parent_hash = parent_cid.into(); block.timestamp = Uint64(tipset.block_headers().first().timestamp); - block.base_fee_per_gas = BigInt( - tipset - .block_headers() - .first() - .parent_base_fee - .atto() - .clone(), - ); + block.base_fee_per_gas = tipset + .block_headers() + .first() + .parent_base_fee + .clone() + .into(); block.gas_used = Uint64(gas_used); block.transactions = transactions; From f7a2bf55cb4f50ddacf99c83f47df7f6004ade64 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 5 Apr 2024 12:20:47 +0200 Subject: [PATCH 14/94] Add input to Tx (wip) --- src/lotus_json/mod.rs | 2 +- src/rpc/eth_api.rs | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/lotus_json/mod.rs b/src/lotus_json/mod.rs index 624df6feeced..9debe8db371d 100644 --- a/src/lotus_json/mod.rs +++ b/src/lotus_json/mod.rs @@ -326,7 +326,7 @@ pub mod hexify_vec_bytes { where S: Serializer, { - let v: Vec = value.iter().map(|b| format!("{:#x}", b)).collect(); + let v: Vec = value.iter().map(|b| format!("{:x}", b)).collect(); serializer.serialize_str(&format!("0x{}", v.join(""))) } diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 21585888fced..c75b48ddccb4 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -19,6 +19,7 @@ use crate::shim::address::{Address as FilecoinAddress, Protocol}; use crate::shim::crypto::{Signature, SignatureType}; use crate::shim::econ::{TokenAmount, BLOCK_GAS_LIMIT}; use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; +use crate::shim::fvm_shared_latest::MethodNum; use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; @@ -27,6 +28,7 @@ use cid::{ Cid, }; use fvm_ipld_blockstore::Blockstore; +use fvm_ipld_encoding::CBOR; use itertools::Itertools; use jsonrpsee::types::Params; use nonempty::nonempty; @@ -78,7 +80,7 @@ enum EAMMethod { } #[repr(u64)] -pub enum EVMMethod { +enum EVMMethod { Constructor = METHOD_CONSTRUCTOR, Resurrect = 2, GetBytecode = 3, @@ -366,7 +368,7 @@ pub struct Tx { pub to: Address, pub value: BigInt, pub r#type: Uint64, - pub input: Vec, + pub input: Bytes, pub gas: Uint64, pub max_fee_per_gas: BigInt, pub max_priority_fee_per_gas: BigInt, @@ -744,7 +746,7 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { v, r, s, - input: tx_args.input, + input: Bytes(tx_args.input), ..Tx::default() }) } @@ -808,7 +810,7 @@ fn eth_tx_from_native_message( })?; // Lookup the to address. If the recipient doesn't exist, we replace the address with a // known sentinel address. - let to = match lookup_eth_address(&msg.to(), state) { + let mut to = match lookup_eth_address(&msg.to(), state) { Ok(addr) => addr, Err(_err) => { // TODO: bail in case of not "actor not found" errors @@ -823,9 +825,29 @@ fn eth_tx_from_native_message( // Finally, convert the input parameters to "solidity ABI". + // For empty, we use "0" as the codec. Otherwise, we use CBOR for message + // parameters. + let codec = if !msg.params().is_empty() { CBOR } else { 0 }; + + // We try to decode the input as an EVM method invocation and/or a contract creation. If + // that fails, we encode the "native" parameters as Solidity ABI. + let mut input: Vec = vec![]; + if msg.method_num() == EVMMethod::InvokeContract as MethodNum + || msg.method_num() == EAMMethod::CreateExternal as MethodNum + { + input = msg.params().to_vec(); + if msg.method_num() == EAMMethod::CreateExternal as MethodNum { + // to = None; + } + } else { + // default + // input = encodeFilecoinParamsAsABI(msg.Method, codec, msg.Params) + } + let mut tx = Tx::default(); tx.to = to; tx.from = from; + tx.input = Bytes(input); tx.nonce = Uint64(msg.message().sequence); tx.chain_id = Uint64(EIP_155_CHAIN_ID); tx.value = msg.message().value.clone().into(); From 1bf353b1853f8d4a5be367f6660f6b8f5be0adaa Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 5 Apr 2024 12:41:19 +0200 Subject: [PATCH 15/94] Use chainId from chain config --- src/rpc/eth_api.rs | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index c75b48ddccb4..3eb57db3b727 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -66,11 +66,6 @@ const EIP_1559_TX_TYPE: u64 = 2; /// The address used in messages to actors that have since been deleted. const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff"; -// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. -// As per https://github.com/ethereum-lists/chains -// TODO: use chain_config instead -const EIP_155_CHAIN_ID: u64 = 0x4cb2f; - #[repr(u64)] enum EAMMethod { Constructor = METHOD_CONSTRUCTOR, @@ -382,7 +377,7 @@ lotus_json_with_self!(Tx); #[derive(Debug, Clone, Default)] struct TxArgs { - pub chain_id: u64, + pub chain_id: u32, pub nonce: u64, pub to: Address, pub value: BigInt, @@ -645,7 +640,10 @@ fn is_eth_address(addr: &VmAddress) -> bool { f4_addr.is_ok() } -fn eth_tx_args_from_unsigned_eth_message(msg: &crate::shim::message::Message) -> Result { +fn eth_tx_args_from_unsigned_eth_message( + msg: &crate::shim::message::Message, + chain_id: u32, +) -> Result { let mut to = Address::default(); let mut params = vec![]; @@ -673,7 +671,7 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &crate::shim::message::Message) -> } Ok(TxArgs { - chain_id: EIP_155_CHAIN_ID, + chain_id, nonce: msg.sequence, to, value: msg.value.clone().into(), @@ -711,7 +709,7 @@ fn recover_sig(sig: &Signature) -> Result<(BigInt, BigInt, BigInt)> { /// - `block_hash` /// - `block_number` /// - `transaction_index` -fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { +fn eth_tx_from_signed_eth_message(smsg: &SignedMessage, chain_id: u32) -> Result { // The from address is always an f410f address, never an ID or other address. let from = smsg.message().from; if !is_eth_address(&from) { @@ -728,13 +726,13 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage) -> Result { // Ethereum Address sender... let from = Address::from_filecoin_address(&from)?; - let tx_args = eth_tx_args_from_unsigned_eth_message(smsg.message())?; + let tx_args = eth_tx_args_from_unsigned_eth_message(smsg.message(), chain_id)?; let (r, s, v) = recover_sig(smsg.signature())?; Ok(Tx { nonce: Uint64(tx_args.nonce), - chain_id: Uint64(tx_args.chain_id), + chain_id: Uint64(chain_id as u64), to: tx_args.to, from, value: tx_args.value, @@ -800,6 +798,7 @@ fn lookup_eth_address( fn eth_tx_from_native_message( msg: &SignedMessage, state: &StateTree, + chain_id: u32, ) -> Result { // Lookup the from address. This must succeed. let from = lookup_eth_address(&msg.from(), state).with_context(|| { @@ -849,7 +848,7 @@ fn eth_tx_from_native_message( tx.from = from; tx.input = Bytes(input); tx.nonce = Uint64(msg.message().sequence); - tx.chain_id = Uint64(EIP_155_CHAIN_ID); + tx.chain_id = Uint64(chain_id as u64); tx.value = msg.message().value.clone().into(); tx.r#type = Uint64(EIP_1559_TX_TYPE); tx.gas = Uint64(msg.message().gas_limit); @@ -863,20 +862,21 @@ fn eth_tx_from_native_message( pub fn new_eth_tx_from_signed_message( smsg: &SignedMessage, state: &StateTree, + chain_id: u32, ) -> Result { let mut tx: Tx = Tx::default(); if smsg.is_delegated() { // This is an eth tx - let eth_tx = eth_tx_from_signed_eth_message(smsg)?; + let eth_tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; tx.hash = eth_tx.hash; } else if smsg.is_secp256k1() { // Secp Filecoin Message - tx = eth_tx_from_native_message(smsg, state)?; + tx = eth_tx_from_native_message(smsg, state, chain_id)?; tx.hash = smsg.cid()?.into(); } else { // BLS Filecoin message - tx = eth_tx_from_native_message(smsg, state)?; + tx = eth_tx_from_native_message(smsg, state, chain_id)?; tx.hash = smsg.message().cid()?.into(); } Ok(tx) @@ -914,7 +914,11 @@ pub async fn block_from_filecoin_tipset( }; // TODO: build tx and push to block transactions - let mut tx = new_eth_tx_from_signed_message(&smsg, &state_tree)?; + let mut tx = new_eth_tx_from_signed_message( + &smsg, + &state_tree, + data.state_manager.chain_config().eth_chain_id, + )?; tx.block_hash = block_hash.clone(); tx.block_number = block_number.clone(); tx.transaction_index = ti; From d3f782bdebc48ce996b7d4412237e39debeeb105 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 5 Apr 2024 15:16:59 +0200 Subject: [PATCH 16/94] Fix eth tx --- src/rpc/eth_api.rs | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 3eb57db3b727..8a1b867bc4f7 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -8,7 +8,7 @@ use crate::chain::{index::ResolveNullTipset, ChainStore}; use crate::chain_sync::SyncStage; use crate::lotus_json::LotusJson; use crate::lotus_json::{lotus_json_with_self, HasLotusJson}; -use crate::message::{ChainMessage, Message, SignedMessage}; +use crate::message::{ChainMessage, Message as _, SignedMessage}; use crate::rpc::chain_api::get_parent_receipts; use crate::rpc::error::JsonRpcError; use crate::rpc::sync_api::sync_state; @@ -21,6 +21,7 @@ use crate::shim::econ::{TokenAmount, BLOCK_GAS_LIMIT}; use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; use crate::shim::fvm_shared_latest::MethodNum; use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; +use crate::shim::message::Message; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; use cid::{ @@ -373,11 +374,16 @@ pub struct Tx { pub s: BigInt, } +impl Tx { + pub fn hash(&self) -> Hash { + Hash::default() + } +} + lotus_json_with_self!(Tx); #[derive(Debug, Clone, Default)] struct TxArgs { - pub chain_id: u32, pub nonce: u64, pub to: Address, pub value: BigInt, @@ -640,10 +646,7 @@ fn is_eth_address(addr: &VmAddress) -> bool { f4_addr.is_ok() } -fn eth_tx_args_from_unsigned_eth_message( - msg: &crate::shim::message::Message, - chain_id: u32, -) -> Result { +fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { let mut to = Address::default(); let mut params = vec![]; @@ -671,7 +674,6 @@ fn eth_tx_args_from_unsigned_eth_message( } Ok(TxArgs { - chain_id, nonce: msg.sequence, to, value: msg.value.clone().into(), @@ -726,7 +728,7 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage, chain_id: u32) -> Result // Ethereum Address sender... let from = Address::from_filecoin_address(&from)?; - let tx_args = eth_tx_args_from_unsigned_eth_message(smsg.message(), chain_id)?; + let tx_args = eth_tx_args_from_unsigned_eth_message(smsg.message())?; let (r, s, v) = recover_sig(smsg.signature())?; @@ -796,7 +798,7 @@ fn lookup_eth_address( /// - `block_number` /// - `transaction_index` fn eth_tx_from_native_message( - msg: &SignedMessage, + msg: &Message, state: &StateTree, chain_id: u32, ) -> Result { @@ -847,13 +849,13 @@ fn eth_tx_from_native_message( tx.to = to; tx.from = from; tx.input = Bytes(input); - tx.nonce = Uint64(msg.message().sequence); + tx.nonce = Uint64(msg.sequence); tx.chain_id = Uint64(chain_id as u64); - tx.value = msg.message().value.clone().into(); + tx.value = msg.value.clone().into(); tx.r#type = Uint64(EIP_1559_TX_TYPE); - tx.gas = Uint64(msg.message().gas_limit); - tx.max_fee_per_gas = msg.message().gas_fee_cap.clone().into(); - tx.max_priority_fee_per_gas = msg.message().gas_premium.clone().into(); + tx.gas = Uint64(msg.gas_limit); + tx.max_fee_per_gas = msg.gas_fee_cap.clone().into(); + tx.max_priority_fee_per_gas = msg.gas_premium.clone().into(); tx.access_list = vec![]; Ok(tx) @@ -865,18 +867,19 @@ pub fn new_eth_tx_from_signed_message( chain_id: u32, ) -> Result { let mut tx: Tx = Tx::default(); + tx.chain_id = Uint64(1); if smsg.is_delegated() { // This is an eth tx - let eth_tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; - tx.hash = eth_tx.hash; + tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; + tx.hash = tx.hash(); } else if smsg.is_secp256k1() { // Secp Filecoin Message - tx = eth_tx_from_native_message(smsg, state, chain_id)?; + tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; tx.hash = smsg.cid()?.into(); } else { // BLS Filecoin message - tx = eth_tx_from_native_message(smsg, state, chain_id)?; + tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; tx.hash = smsg.message().cid()?.into(); } Ok(tx) From 900ea4280aad7e13ea0daac55acf3b8e83c2ee32 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 8 Apr 2024 11:43:37 +0200 Subject: [PATCH 17/94] Add native message support (wip) --- src/rpc/eth_api.rs | 107 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 8a1b867bc4f7..a969f2894e34 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -24,12 +24,13 @@ use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; use crate::shim::message::Message; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; +use bytes::Buf; use cid::{ multihash::{self, MultihashDigest}, Cid, }; use fvm_ipld_blockstore::Blockstore; -use fvm_ipld_encoding::CBOR; +use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW}; use itertools::Itertools; use jsonrpsee::types::Params; use nonempty::nonempty; @@ -785,6 +786,82 @@ fn lookup_eth_address( Ok(Address::from_actor_id(id_addr.unwrap())) } +fn encode_filecoin_params_as_abi( + method: MethodNum, + codec: u64, + params: &fvm_ipld_encoding::RawBytes, +) -> Result { + let mut buffer: Vec = vec![0x86, 0x8e, 0x10, 0xc4]; + buffer.append(&mut encode_filecoin_returns_as_abi(method, codec, params)); + Ok(Bytes(buffer)) +} + +fn encode_filecoin_returns_as_abi( + exit_code: u64, + codec: u64, + data: &fvm_ipld_encoding::RawBytes, +) -> Vec { + encode_as_abi_helper(exit_code, codec, data) +} + +// Format 2 numbers followed by an arbitrary byte array as solidity ABI. Both our native +// inputs/outputs follow the same pattern, so we can reuse this code. +fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { + const EVM_WORD_SIZE: usize = 32; + + // The first two params are "static" numbers. Then, we record the offset of the "data" arg, + // then, at that offset, we record the length of the data. + // + // In practice, this means we have 4 256-bit words back to back where the third arg (the + // offset) is _always_ '32*3'. + let static_args = [ + param1, + param2, + (EVM_WORD_SIZE * 3) as u64, + data.len() as u64, + ]; + // We always pad out to the next EVM "word" (32 bytes). + let total_words = static_args.len() + + (data.len() / EVM_WORD_SIZE) + + if data.len() % EVM_WORD_SIZE != 0 { + 1 + } else { + 0 + }; + let len = total_words * EVM_WORD_SIZE; + let mut buf = vec![0u8; len]; + let mut offset = 0; + // Below, we use copy instead of "appending" to preserve all the zero padding. + for arg in static_args.iter() { + // Write each "arg" into the last 8 bytes of each 32 byte word. + offset += EVM_WORD_SIZE; + let start = offset - 8; + buf[start..offset].copy_from_slice(&arg.to_be_bytes()); + } + + // Finally, we copy in the data. + // TODO: investigate + //buf[offset..].copy_from_slice(data); + + buf +} + +/// Decodes the payload using the given codec. +fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result { + match codec { + // TODO: handle IDENTITY? + DAG_CBOR | CBOR => { + let result: Result, _> = serde_ipld_dagcbor::de::from_reader(payload.reader()); + match result { + Ok(buffer) => Ok(Bytes(buffer)), + Err(err) => bail!("decode_payload: failed to decode cbor payload: {err}"), + } + } + IPLD_RAW => Ok(Bytes(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). @@ -832,23 +909,27 @@ fn eth_tx_from_native_message( // We try to decode the input as an EVM method invocation and/or a contract creation. If // that fails, we encode the "native" parameters as Solidity ABI. - let mut input: Vec = vec![]; - if msg.method_num() == EVMMethod::InvokeContract as MethodNum - || msg.method_num() == EAMMethod::CreateExternal as MethodNum - { - input = msg.params().to_vec(); - if msg.method_num() == EAMMethod::CreateExternal as MethodNum { - // to = None; + let input = 'decode: { + if msg.method_num() == EVMMethod::InvokeContract as MethodNum + || msg.method_num() == EAMMethod::CreateExternal as MethodNum + { + if let Ok(buffer) = decode_payload(msg.params(), codec) { + // If this is a valid "create external", unset the "to" address. + if msg.method_num() == EAMMethod::CreateExternal as MethodNum { + // to = None; + } + break 'decode buffer; + } + // Yeah, we're going to ignore errors here because the user can send whatever they + // want and may send garbage. } - } else { - // default - // input = encodeFilecoinParamsAsABI(msg.Method, codec, msg.Params) - } + encode_filecoin_params_as_abi(msg.method_num(), codec, msg.params())? + }; let mut tx = Tx::default(); tx.to = to; tx.from = from; - tx.input = Bytes(input); + tx.input = input; tx.nonce = Uint64(msg.sequence); tx.chain_id = Uint64(chain_id as u64); tx.value = msg.value.clone().into(); From 9f28f805ad5f99cfeede98d3e8b5517cbcafa785 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 8 Apr 2024 19:05:20 +0200 Subject: [PATCH 18/94] Add unit test and fix slice copy and formatting issues --- src/lotus_json/mod.rs | 2 +- src/rpc/eth_api.rs | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/lotus_json/mod.rs b/src/lotus_json/mod.rs index 9debe8db371d..517758b72bed 100644 --- a/src/lotus_json/mod.rs +++ b/src/lotus_json/mod.rs @@ -326,7 +326,7 @@ pub mod hexify_vec_bytes { where S: Serializer, { - let v: Vec = value.iter().map(|b| format!("{:x}", b)).collect(); + let v: Vec = value.iter().map(|b| format!("{:02x}", b)).collect(); serializer.serialize_str(&format!("0x{}", v.join(""))) } diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index a969f2894e34..5bbacc74f68d 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -804,8 +804,8 @@ fn encode_filecoin_returns_as_abi( encode_as_abi_helper(exit_code, codec, data) } -// Format 2 numbers followed by an arbitrary byte array as solidity ABI. Both our native -// inputs/outputs follow the same pattern, so we can reuse this code. +/// Format 2 numbers followed by an arbitrary byte array as solidity ABI. Both our native +/// inputs/outputs follow the same pattern, so we can reuse this code. fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { const EVM_WORD_SIZE: usize = 32; @@ -823,7 +823,7 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { // We always pad out to the next EVM "word" (32 bytes). let total_words = static_args.len() + (data.len() / EVM_WORD_SIZE) - + if data.len() % EVM_WORD_SIZE != 0 { + + if (data.len() % EVM_WORD_SIZE) != 0 { 1 } else { 0 @@ -840,8 +840,8 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { } // Finally, we copy in the data. - // TODO: investigate - //buf[offset..].copy_from_slice(data); + let data_len = data.len(); + buf[offset..offset + data_len].copy_from_slice(data); buf } @@ -1052,6 +1052,7 @@ pub async fn eth_get_block_by_number( mod test { use super::*; use quickcheck_macros::quickcheck; + use std::num::ParseIntError; #[quickcheck] fn gas_price_result_serde_roundtrip(i: u128) { @@ -1061,4 +1062,21 @@ mod test { let decoded: GasPriceResult = serde_json::from_str(&encoded).unwrap(); assert_eq!(r.0, decoded.0); } + + pub fn decode_hex(s: &str) -> Result, ParseIntError> { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .collect() + } + + #[test] + fn test_abi_encoding() { + const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001b1111111111111111111020200301000000044444444444444444010000000000"; + const DATA: &str = "111111111111111111102020030100000004444444444444444401"; + let expected_bytes = decode_hex(EXPECTED).unwrap(); + let data_bytes = decode_hex(DATA).unwrap(); + + assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes)); + } } From c8cb12a70e712f05f9c5906cddcad673231a316f Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 9 Apr 2024 10:35:14 +0200 Subject: [PATCH 19/94] Fix the To address for unsigned eth tx --- src/rpc/eth_api.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 5bbacc74f68d..69cc95450982 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -662,16 +662,16 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { if msg.to == FilecoinAddress::ETHEREUM_ACCOUNT_MANAGER_ACTOR { if msg.method_num() != EAMMethod::CreateExternal as u64 { bail!("unsupported EAM method"); - } else if msg.method_num() == EVMMethod::InvokeContract as u64 { - let addr = Address::from_filecoin_address(&msg.to)?; - to = addr; - } else { - bail!( - "invalid methodnum {}: only allowed method is InvokeContract({})", - msg.method_num(), - EVMMethod::InvokeContract as u64 - ); } + } else if msg.method_num() == EVMMethod::InvokeContract as u64 { + let addr = Address::from_filecoin_address(&msg.to)?; + to = addr; + } else { + bail!( + "invalid methodnum {}: only allowed method is InvokeContract({})", + msg.method_num(), + EVMMethod::InvokeContract as u64 + ); } Ok(TxArgs { From eae9477ad0fd383266359af4ad0fd5611af892d1 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 9 Apr 2024 10:50:13 +0200 Subject: [PATCH 20/94] Minor refactor --- src/rpc/eth_api.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 69cc95450982..890baa3714b2 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -725,14 +725,14 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage, chain_id: u32) -> Result bail!("signature is not delegated type, is type: {sig_type}"); } - // This should be impossible to fail as we've already asserted that we have an - // Ethereum Address sender... - let from = Address::from_filecoin_address(&from)?; - let tx_args = eth_tx_args_from_unsigned_eth_message(smsg.message())?; let (r, s, v) = recover_sig(smsg.signature())?; + // This should be impossible to fail as we've already asserted that we have an + // Ethereum Address sender... + let from = Address::from_filecoin_address(&from)?; + Ok(Tx { nonce: Uint64(tx_args.nonce), chain_id: Uint64(chain_id as u64), @@ -997,7 +997,6 @@ pub async fn block_from_filecoin_tipset( } }; - // TODO: build tx and push to block transactions let mut tx = new_eth_tx_from_signed_message( &smsg, &state_tree, From c1e12817a9d6e4e03a99a98e521ab3d0ab5fcc33 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 9 Apr 2024 14:06:59 +0200 Subject: [PATCH 21/94] Add unit test for tx rpl encoding --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index a464836e162b..2e1b398d6b85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3322,6 +3322,7 @@ dependencies = [ "regex-automata 0.4.6", "reqwest 0.12.3", "rlimit", + "rlp", "rs-car-ipfs", "rustyline", "schemars", diff --git a/Cargo.toml b/Cargo.toml index 0527eb357918..5fc162e14b2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ git-version = "0.3" group = "0.13" hex = { version = "0.4", features = ["serde"] } http = "1.0" +rlp = "0.5.2" # TODO(forest): https://github.com/ChainSafe/forest/issues/3961 # bump hyper to 1.0 after https://github.com/paritytech/jsonrpsee/issues/1257 http02 = { version = "0.2", package = "http" } From 640b6f053feb6e0336b153180e7aa3b7bee18dd0 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 9 Apr 2024 14:19:02 +0200 Subject: [PATCH 22/94] Add missing test --- src/rpc/eth_api.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 890baa3714b2..bdc31ff13e5c 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -37,6 +37,7 @@ use nonempty::nonempty; use num_bigint; use num_bigint::Sign; use num_traits::Zero as _; +use rlp::encode; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use std::{ops::Add, sync::Arc}; @@ -377,7 +378,8 @@ pub struct Tx { impl Tx { pub fn hash(&self) -> Hash { - Hash::default() + let eth_tx_args: TxArgs = self.clone().into(); + eth_tx_args.hash() } } @@ -385,6 +387,7 @@ lotus_json_with_self!(Tx); #[derive(Debug, Clone, Default)] struct TxArgs { + pub chain_id: u64, pub nonce: u64, pub to: Address, pub value: BigInt, @@ -397,6 +400,35 @@ struct TxArgs { pub s: BigInt, } +impl From for TxArgs { + fn from(tx: Tx) -> Self { + Self { + chain_id: tx.chain_id.0, + nonce: tx.nonce.0, + to: tx.to, + value: tx.value, + max_fee_per_gas: tx.max_fee_per_gas, + max_priority_fee_per_gas: tx.max_priority_fee_per_gas, + gas_limit: tx.gas.0, + input: tx.input.0, + v: tx.v, + r: tx.r, + s: tx.s, + } + } +} + +impl TxArgs { + pub fn hash(&self) -> Hash { + let rlp = self.rlp_signed_message(); + Hash::default() + } + + pub fn rlp_signed_message(&self) -> Vec { + vec![] + } +} + #[derive(Debug, Clone, Default)] pub struct EthSyncingResult { pub done_sync: bool, @@ -1078,4 +1110,43 @@ mod test { assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes)); } + + #[test] + fn test_rlp_encoding() { + let eth_tx_args = TxArgs { + chain_id: 314159, + nonce: 486, + to: Address( + ethereum_types::H160::from_str("0xeb4a9cdb9f42d3a503d580a39b6e3736eb21fffd") + .unwrap(), + ), + value: BigInt(num_bigint::BigInt::from(0)), + max_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000120)), + max_priority_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000000)), + gas_limit: 37442471, + input: [ + 88, 196, 56, 52, 135, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 13, 77, 18, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 59, 98, 97, 102, 107, 114, 101, 105, 101, 111, 111, 117, 50, 109, 54, 53, 98, 118, + 55, 101, 97, 120, 110, 119, 103, 101, 109, 117, 98, 114, 54, 117, 120, 114, 105, + 105, 104, 103, 54, 100, 116, 100, 110, 108, 122, 102, 52, 105, 97, 111, 55, 104, + 108, 110, 106, 109, 100, 115, 114, 117, 0, 0, 0, 0, 0, + ] + .to_vec(), + v: BigInt(num_bigint::BigInt::default()), + r: BigInt(num_bigint::BigInt::default()), + s: BigInt(num_bigint::BigInt::default()), + }; + + let expected_hash = Hash( + ethereum_types::H256::from_str( + "0x9f2e70d5737c6b798eccea14895893fb48091ab3c59d0fe95508dc7efdae2e5f", + ) + .unwrap(), + ); + assert_eq!(expected_hash, eth_tx_args.hash()); + } } From 6028b65feaa06946623b66e541a81f138b685251 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 10:26:44 +0200 Subject: [PATCH 23/94] Start implementation of TxArgs rlp encoding --- src/rpc/eth_api.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 5b7da1e4c3bf..548562ea784b 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -24,7 +24,7 @@ use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; use crate::shim::message::Message; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; -use bytes::Buf; +use bytes::{Buf, BytesMut}; use cid::{ multihash::{self, MultihashDigest}, Cid, @@ -37,7 +37,7 @@ use nonempty::nonempty; use num_bigint; use num_bigint::Sign; use num_traits::Zero as _; -use rlp::encode; +use rlp::RlpStream; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use std::{ops::Add, sync::Arc}; @@ -418,6 +418,36 @@ impl From for TxArgs { } } +fn format_u64(value: &u64) -> BytesMut { + let bytes = value.to_be_bytes(); + let first_non_zero = bytes.iter().position(|&b| b != 0); + + match first_non_zero { + Some(i) => bytes[i..].into(), + None => { + // If all bytes are zero, return an empty slice + BytesMut::new() + } + } +} + +fn format_bigint(value: &BigInt) -> BytesMut { + let (_, bytes) = value.0.to_bytes_be(); + let first_non_zero = bytes.iter().position(|&b| b != 0); + + match first_non_zero { + Some(i) => bytes[i..].into(), + None => { + // If all bytes are zero, return an empty slice + BytesMut::new() + } + } +} + +fn format_address(value: &Address) -> BytesMut { + value.0.as_bytes().into() +} + impl TxArgs { pub fn hash(&self) -> Hash { let rlp = self.rlp_signed_message(); @@ -425,6 +455,34 @@ impl TxArgs { } pub fn rlp_signed_message(&self) -> Vec { + let mut stream = RlpStream::new_list(10); // THIS IS IMPORTANT + stream.append(&format_u64(&self.chain_id)); + stream.append(&format_u64(&self.nonce)); + stream.append(&format_bigint(&self.max_priority_fee_per_gas)); + stream.append(&format_bigint(&self.max_fee_per_gas)); + stream.append(&format_u64(&self.gas_limit)); + stream.append(&format_address(&self.to)); + stream.append(&format_bigint(&self.value)); + stream.append(&self.input); + let access_list: &[u8] = &[]; + stream.append(&access_list); + + stream.begin_list(3); + stream.append(&format_bigint(&self.v)); + stream.append(&format_bigint(&self.r)); + stream.append(&format_bigint(&self.s)); + + let mut rlp = stream.out()[..].to_vec(); + let mut bytes: Vec = vec![0x02]; + bytes.append(&mut rlp); + + let hex = bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(""); + dbg!(&hex); + vec![] } } From 37cec838a602a57f05e813b41780bf5863e200c8 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 11:25:59 +0200 Subject: [PATCH 24/94] Fix sig data in unit test --- src/rpc/eth_api.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 548562ea784b..594efde7f318 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -1194,9 +1194,19 @@ mod test { 108, 110, 106, 109, 100, 115, 114, 117, 0, 0, 0, 0, 0, ] .to_vec(), - v: BigInt(num_bigint::BigInt::default()), - r: BigInt(num_bigint::BigInt::default()), - s: BigInt(num_bigint::BigInt::default()), + v: BigInt(num_bigint::BigInt::from_str("1").unwrap()), + r: BigInt( + num_bigint::BigInt::from_str( + "84103132941276310528712440865285269631208564772362393569572880532520338257200", + ) + .unwrap(), + ), + s: BigInt( + num_bigint::BigInt::from_str( + "7820796778417228639067439047870612492553874254089570360061550763595363987236", + ) + .unwrap(), + ), }; let expected_hash = Hash( From f5032a370875c5af54eb8997e4dd0688db072fc8 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 11:58:10 +0200 Subject: [PATCH 25/94] Fix sig beeing inline during rlp encoding --- src/rpc/eth_api.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 594efde7f318..a497968228ab 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -455,7 +455,7 @@ impl TxArgs { } pub fn rlp_signed_message(&self) -> Vec { - let mut stream = RlpStream::new_list(10); // THIS IS IMPORTANT + let mut stream = RlpStream::new_list(12); // THIS IS IMPORTANT stream.append(&format_u64(&self.chain_id)); stream.append(&format_u64(&self.nonce)); stream.append(&format_bigint(&self.max_priority_fee_per_gas)); @@ -467,7 +467,6 @@ impl TxArgs { let access_list: &[u8] = &[]; stream.append(&access_list); - stream.begin_list(3); stream.append(&format_bigint(&self.v)); stream.append(&format_bigint(&self.r)); stream.append(&format_bigint(&self.s)); From 2249a81071c4fead5008bed3aa23ba74a52b5cfa Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 13:10:19 +0200 Subject: [PATCH 26/94] Fix input member that was starting with too much bytes --- src/rpc/eth_api.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index a497968228ab..3c26c92fbe81 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -1181,18 +1181,8 @@ mod test { max_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000120)), max_priority_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000000)), gas_limit: 37442471, - input: [ - 88, 196, 56, 52, 135, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 13, 77, 18, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 59, 98, 97, 102, 107, 114, 101, 105, 101, 111, 111, 117, 50, 109, 54, 53, 98, 118, - 55, 101, 97, 120, 110, 119, 103, 101, 109, 117, 98, 114, 54, 117, 120, 114, 105, - 105, 104, 103, 54, 100, 116, 100, 110, 108, 122, 102, 52, 105, 97, 111, 55, 104, - 108, 110, 106, 109, 100, 115, 114, 117, 0, 0, 0, 0, 0, - ] - .to_vec(), + // TODO: find out why our input was starting with the bytes 88 and 196 + input: decode_hex("383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000").unwrap(), v: BigInt(num_bigint::BigInt::from_str("1").unwrap()), r: BigInt( num_bigint::BigInt::from_str( From 02887c66bdd65a48f7cddd1223f53ac99db3479c Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 13:29:58 +0200 Subject: [PATCH 27/94] Fix access list rlp encoding --- src/rpc/eth_api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 3c26c92fbe81..f1aa9af013ae 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -465,7 +465,7 @@ impl TxArgs { stream.append(&format_bigint(&self.value)); stream.append(&self.input); let access_list: &[u8] = &[]; - stream.append(&access_list); + stream.append_list(&access_list); stream.append(&format_bigint(&self.v)); stream.append(&format_bigint(&self.r)); From 1a3bf51484547530b9854145dcbec2e10330f439 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 13:32:25 +0200 Subject: [PATCH 28/94] Add comment --- src/rpc/eth_api.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index f1aa9af013ae..dd99c1f28c95 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -1183,6 +1183,7 @@ mod test { gas_limit: 37442471, // TODO: find out why our input was starting with the bytes 88 and 196 input: decode_hex("383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000").unwrap(), + // TODO: find out why sig was init with default v: BigInt(num_bigint::BigInt::from_str("1").unwrap()), r: BigInt( num_bigint::BigInt::from_str( From c523216856e789f22326dc973d1c30c093c9b408 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 14:23:03 +0200 Subject: [PATCH 29/94] The unit test passing --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + src/rpc/eth_api.rs | 7 ++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62bb80a10d50..b5c0a0bf6445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3274,6 +3274,7 @@ dependencies = [ "itertools 0.12.1", "jsonrpsee", "jsonwebtoken", + "keccak-hash", "kubert-prometheus-process", "libc", "libipld", @@ -5171,6 +5172,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-hash" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b286e6b663fb926e1eeb68528e69cb70ed46c6d65871a21b2215ae8154c6d3c" +dependencies = [ + "primitive-types", + "tiny-keccak", +] + [[package]] name = "kubert-prometheus-process" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index fe82e2f05d93..25333740c100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] } cid = { version = "0.10", default-features = false, features = ["std"] } clap = { version = "4.5", features = ["derive"] } colored = "2.0" +keccak-hash = "0.10.0" # memory leak, see https://github.com/tokio-rs/console/pull/501 console-subscriber = { version = "0.2", features = ["parking_lot"] } convert_case = "0.6.0" diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index dd99c1f28c95..715d7acce3f1 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -33,6 +33,7 @@ use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW}; use itertools::Itertools; use jsonrpsee::types::Params; +use keccak_hash::keccak; use nonempty::nonempty; use num_bigint; use num_bigint::Sign; @@ -451,7 +452,7 @@ fn format_address(value: &Address) -> BytesMut { impl TxArgs { pub fn hash(&self) -> Hash { let rlp = self.rlp_signed_message(); - Hash::default() + Hash(keccak(&rlp)) } pub fn rlp_signed_message(&self) -> Vec { @@ -480,9 +481,9 @@ impl TxArgs { .map(|b| format!("{:02x}", b)) .collect::>() .join(""); - dbg!(&hex); + tracing::trace!("rlp: {}", &hex); - vec![] + bytes } } From e7700d01989b505467a084074b43616e37f0ce27 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 14:55:52 +0200 Subject: [PATCH 30/94] Fix sig that was badly recovered --- src/rpc/eth_api.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index 715d7acce3f1..c071292468ee 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -788,11 +788,11 @@ fn recover_sig(sig: &Signature) -> Result<(BigInt, BigInt, BigInt)> { let bytes = sig.bytes(); - let r = num_bigint::BigInt::from_bytes_le(Sign::NoSign, &bytes[0..32]); + let r = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[0..32]); - let s = num_bigint::BigInt::from_bytes_le(Sign::NoSign, &bytes[32..64]); + let s = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[32..64]); - let v = num_bigint::BigInt::from_bytes_le(Sign::NoSign, &bytes[64..65]); + let v = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[64..65]); Ok((BigInt(r), BigInt(s), BigInt(v))) } From 9e5185831b45e297835f278b1074bd70df2c7625 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 10 Apr 2024 17:03:04 +0200 Subject: [PATCH 31/94] Fix remaining cbor decoding issue --- src/rpc/eth_api.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rpc/eth_api.rs b/src/rpc/eth_api.rs index c071292468ee..0f806b091946 100644 --- a/src/rpc/eth_api.rs +++ b/src/rpc/eth_api.rs @@ -25,6 +25,7 @@ use crate::shim::message::Message; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; use anyhow::{bail, Context, Result}; use bytes::{Buf, BytesMut}; +use cbor4ii::core::{dec::Decode, utils::SliceReader, Value}; use cid::{ multihash::{self, MultihashDigest}, Cid, @@ -746,7 +747,12 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { } if msg.params().bytes().len() > 0 { - params = msg.params().bytes().to_vec(); + // TODO: could we do better? + let mut reader = SliceReader::new(msg.params().bytes()); + match Value::decode(&mut reader) { + Ok(Value::Bytes(bytes)) => params = bytes, + _ => bail!("failed to read params byte array"), + } } if msg.to == FilecoinAddress::ETHEREUM_ACCOUNT_MANAGER_ACTOR { @@ -1182,9 +1188,7 @@ mod test { max_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000120)), max_priority_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000000)), gas_limit: 37442471, - // TODO: find out why our input was starting with the bytes 88 and 196 input: decode_hex("383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000").unwrap(), - // TODO: find out why sig was init with default v: BigInt(num_bigint::BigInt::from_str("1").unwrap()), r: BigInt( num_bigint::BigInt::from_str( From f9c795fd61439956cef5a44553fae713b4c277e4 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 10:12:05 +0200 Subject: [PATCH 32/94] Apply clippy suggestions --- src/rpc/methods/eth.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index cea738a7968f..8d721c32b860 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -452,8 +452,7 @@ fn format_address(value: &Address) -> BytesMut { impl TxArgs { pub fn hash(&self) -> Hash { - let rlp = self.rlp_signed_message(); - Hash(keccak(&rlp)) + Hash(keccak(self.rlp_signed_message())) } pub fn rlp_signed_message(&self) -> Vec { @@ -467,7 +466,7 @@ impl TxArgs { stream.append(&format_bigint(&self.value)); stream.append(&self.input); let access_list: &[u8] = &[]; - stream.append_list(&access_list); + stream.append_list(access_list); stream.append(&format_bigint(&self.v)); stream.append(&format_bigint(&self.r)); @@ -743,7 +742,7 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { bail!("unsupported msg version: {}", msg.version); } - if msg.params().bytes().len() > 0 { + if !msg.params().bytes().is_empty() { // TODO: could we do better? let mut reader = SliceReader::new(msg.params().bytes()); match Value::decode(&mut reader) { From f9abe4426080401f9069c02c43aafdb2361e7b6b Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 12:00:15 +0200 Subject: [PATCH 33/94] Fix deserial of byte vector --- src/lotus_json/mod.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lotus_json/mod.rs b/src/lotus_json/mod.rs index 7fb07209b6ee..fd6720564c0d 100644 --- a/src/lotus_json/mod.rs +++ b/src/lotus_json/mod.rs @@ -335,10 +335,17 @@ pub mod hexify_vec_bytes { where D: Deserializer<'de>, { - let v: String = String::deserialize(deserializer)? - .parse() - .map_err(serde::de::Error::custom)?; - todo!() + let s = String::deserialize(deserializer)?; + #[allow(clippy::indexing_slicing)] + if s.len() >= 2 && &s[..2] == "0x" { + let result: Result, _> = (2..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .collect(); + result.map_err(serde::de::Error::custom) + } else { + Err(serde::de::Error::custom("Invalid hex")) + } } } From 79ab77f52621821ef62d38e41e861253b6f0c770 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 13:46:34 +0200 Subject: [PATCH 34/94] Try to make non-full tx mode work (wip) --- src/rpc/methods/eth.rs | 13 +++++++++++-- src/tool/subcommands/api_cmd.rs | 10 +++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 8d721c32b860..06e0e67d090d 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -240,6 +240,12 @@ impl From for Hash { } } +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:#x}", self.0) + } +} + lotus_json_with_self!(Hash); #[derive(Debug, Default, Clone)] @@ -338,7 +344,7 @@ pub struct Block { pub base_fee_per_gas: BigInt, pub size: Uint64, // can be Vec or Vec depending on query params - pub transactions: Vec, + pub transactions: Vec, pub uncles: Vec, } @@ -1076,6 +1082,7 @@ pub async fn block_from_filecoin_tipset( let state_tree = StateTree::new_from_root(data.state_manager.blockstore_owned(), &state_root)?; let mut transactions = vec![]; + let mut transaction_hashes = vec![]; let mut gas_used = 0; for (i, msg) in msgs.iter().enumerate() { let receipt = receipts[i].clone(); @@ -1102,6 +1109,8 @@ pub async fn block_from_filecoin_tipset( transactions.push(tx); } else { // TODO: push in some other vector + //transactions.push(tx); + transaction_hashes.push(tx.hash().to_string()); } } @@ -1117,7 +1126,7 @@ pub async fn block_from_filecoin_tipset( .clone() .into(); block.gas_used = Uint64(gas_used); - block.transactions = transactions; + block.transactions = transaction_hashes; Ok(block) } diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index 125b2e65a4cc..1420373ab13c 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -560,14 +560,14 @@ fn eth_tests_with_tipset(shared_tipset: &Tipset) -> Vec { EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), BlockNumberOrHash::from_block_number(shared_tipset.epoch()), )), - // RpcTest::identity(ApiInfo::eth_get_block_by_number_req( - // BlockNumberOrHash::from_block_number(shared_tipset.epoch()), - // false, - // )), RpcTest::identity(ApiInfo::eth_get_block_by_number_req( BlockNumberOrHash::from_block_number(shared_tipset.epoch()), - true, + false, )), + // RpcTest::identity(ApiInfo::eth_get_block_by_number_req( + // BlockNumberOrHash::from_block_number(shared_tipset.epoch()), + // true, + // )), ] } From a717875212dacc492280270a81fe93822b94801e Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 15:16:37 +0200 Subject: [PATCH 35/94] Fix incorrect usage of hash --- src/rpc/methods/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 06e0e67d090d..39c023d234ac 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1110,7 +1110,7 @@ pub async fn block_from_filecoin_tipset( } else { // TODO: push in some other vector //transactions.push(tx); - transaction_hashes.push(tx.hash().to_string()); + transaction_hashes.push(tx.hash.to_string()); } } From 619ad9f26e8132da22abf217bf302e08f0ece436 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 15:21:23 +0200 Subject: [PATCH 36/94] Rename method to avoid confusion --- src/rpc/methods/eth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 39c023d234ac..ec365df1052b 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -385,7 +385,7 @@ pub struct Tx { } impl Tx { - pub fn hash(&self) -> Hash { + pub fn eth_hash(&self) -> Hash { let eth_tx_args: TxArgs = self.clone().into(); eth_tx_args.hash() } @@ -1051,7 +1051,7 @@ pub fn new_eth_tx_from_signed_message( if smsg.is_delegated() { // This is an eth tx tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; - tx.hash = tx.hash(); + tx.hash = tx.eth_hash(); } else if smsg.is_secp256k1() { // Secp Filecoin Message tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; From be83048cd6879e7a1380876c63b18759663c6766 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 15:50:26 +0200 Subject: [PATCH 37/94] Complete support for full_tx_info flag --- src/rpc/methods/eth.rs | 23 +++++++++++++++++++---- src/tool/subcommands/api_cmd.rs | 8 ++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index ec365df1052b..2c9b73f29a5e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -321,6 +321,19 @@ impl HasLotusJson for BlockNumberOrHash { } } +#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] // try a Vec, then a Vec +pub enum Transactions { + Hash(Vec), + Full(Vec), +} + +impl Default for Transactions { + fn default() -> Self { + Self::Hash(vec![]) + } +} + #[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Block { @@ -344,7 +357,7 @@ pub struct Block { pub base_fee_per_gas: BigInt, pub size: Uint64, // can be Vec or Vec depending on query params - pub transactions: Vec, + pub transactions: Transactions, pub uncles: Vec, } @@ -1108,8 +1121,6 @@ pub async fn block_from_filecoin_tipset( if full_tx_info { transactions.push(tx); } else { - // TODO: push in some other vector - //transactions.push(tx); transaction_hashes.push(tx.hash.to_string()); } } @@ -1126,7 +1137,11 @@ pub async fn block_from_filecoin_tipset( .clone() .into(); block.gas_used = Uint64(gas_used); - block.transactions = transaction_hashes; + block.transactions = if full_tx_info { + Transactions::Full(transactions) + } else { + Transactions::Hash(transaction_hashes) + }; Ok(block) } diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index 1420373ab13c..dd3a4acf9072 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -564,10 +564,10 @@ fn eth_tests_with_tipset(shared_tipset: &Tipset) -> Vec { BlockNumberOrHash::from_block_number(shared_tipset.epoch()), false, )), - // RpcTest::identity(ApiInfo::eth_get_block_by_number_req( - // BlockNumberOrHash::from_block_number(shared_tipset.epoch()), - // true, - // )), + RpcTest::identity(ApiInfo::eth_get_block_by_number_req( + BlockNumberOrHash::from_block_number(shared_tipset.epoch()), + true, + )), ] } From cd69cb4cf61f4987ae195805b1e1814cfbfcdae9 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 17:16:00 +0200 Subject: [PATCH 38/94] Avoid allocating temp strings --- src/lotus_json/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lotus_json/mod.rs b/src/lotus_json/mod.rs index fd6720564c0d..fb7fdf8428e8 100644 --- a/src/lotus_json/mod.rs +++ b/src/lotus_json/mod.rs @@ -322,13 +322,18 @@ pub mod hexify_bytes { pub mod hexify_vec_bytes { use super::*; + use std::fmt::Write; - pub fn serialize(value: &Vec, serializer: S) -> Result + pub fn serialize(value: &[u8], serializer: S) -> Result where S: Serializer, { - let v: Vec = value.iter().map(|b| format!("{:02x}", b)).collect(); - serializer.serialize_str(&format!("0x{}", v.join(""))) + let mut s = String::with_capacity(2 + value.len() * 2); + s.push_str("0x"); + for b in value { + write!(s, "{:02x}", b).expect("failed to write to string"); + } + serializer.serialize_str(&s) } pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> From 3258c3100bad55ca96588cafa7a68fb7510102b2 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 17:54:20 +0200 Subject: [PATCH 39/94] Fix a few clippy errors --- src/rpc/methods/eth.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index c4cf3ff41191..0426056f7e21 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -234,9 +234,8 @@ impl FromStr for Hash { impl From for Hash { fn from(cid: Cid) -> Self { - Hash(ethereum_types::H256::from_slice( - &cid.hash().digest()[0..32], - )) + let (_, digest, _) = cid.hash().into_inner(); + Hash(ethereum_types::H256::from_slice(&digest[0..32])) } } @@ -724,7 +723,7 @@ async fn execute_tipset( data: Ctx, tipset: &Arc, ) -> Result<(Cid, Vec, Vec)> { - let msgs = data.chain_store.messages_for_tipset(&tipset)?; + let msgs = data.chain_store.messages_for_tipset(tipset)?; let (state_root, receipt_root) = data.state_manager.tipset_state(tipset).await?; @@ -800,18 +799,21 @@ fn recover_sig(sig: &Signature) -> Result<(BigInt, BigInt, BigInt)> { let len = sig.bytes().len(); if len != 65 { - bail!("signature should be 65 bytes long, but got {len} bytes",); + bail!("signature should be 65 bytes long, but got {len} bytes"); } let bytes = sig.bytes(); - let r = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[0..32]); + #[allow(clippy::indexing_slicing)] + { + let r = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[0..32]); - let s = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[32..64]); + let s = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[32..64]); - let v = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[64..65]); + let v = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[64..65]); - Ok((BigInt(r), BigInt(s), BigInt(v))) + Ok((BigInt(r), BigInt(s), BigInt(v))) + } } /// `eth_tx_from_signed_eth_message` does NOT populate: From 64691186faff23ef9fe85aed9e6d7341228021c1 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 17:57:43 +0200 Subject: [PATCH 40/94] Rework naming --- src/rpc/methods/eth.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 0426056f7e21..df05b5282121 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1092,8 +1092,8 @@ pub async fn block_from_filecoin_tipset( let state_tree = StateTree::new_from_root(data.state_manager.blockstore_owned(), &state_root)?; - let mut transactions = vec![]; - let mut transaction_hashes = vec![]; + let mut full_transactions = vec![]; + let mut hash_transactions = vec![]; let mut gas_used = 0; for (i, msg) in msgs.iter().enumerate() { let receipt = receipts[i].clone(); @@ -1117,9 +1117,9 @@ pub async fn block_from_filecoin_tipset( tx.transaction_index = ti; if full_tx_info { - transactions.push(tx); + full_transactions.push(tx); } else { - transaction_hashes.push(tx.hash.to_string()); + hash_transactions.push(tx.hash.to_string()); } } @@ -1136,9 +1136,9 @@ pub async fn block_from_filecoin_tipset( .into(); block.gas_used = Uint64(gas_used); block.transactions = if full_tx_info { - Transactions::Full(transactions) + Transactions::Full(full_transactions) } else { - Transactions::Hash(transaction_hashes) + Transactions::Hash(hash_transactions) }; Ok(block) From 66e108387b63f1cfa39f571e961345dcea717a79 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 11 Apr 2024 18:11:14 +0200 Subject: [PATCH 41/94] Fix another clippy error --- src/rpc/methods/eth.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index df05b5282121..fc562078609a 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -722,7 +722,7 @@ fn tipset_by_block_number_or_hash( async fn execute_tipset( data: Ctx, tipset: &Arc, -) -> Result<(Cid, Vec, Vec)> { +) -> Result<(Cid, Vec<(ChainMessage, ApiReceipt)>)> { let msgs = data.chain_store.messages_for_tipset(tipset)?; let (state_root, receipt_root) = data.state_manager.tipset_state(tipset).await?; @@ -736,7 +736,10 @@ async fn execute_tipset( ) } - Ok((state_root, msgs, receipts)) + Ok(( + state_root, + msgs.into_iter().zip(receipts.into_iter()).collect(), + )) } fn is_eth_address(addr: &VmAddress) -> bool { @@ -1088,15 +1091,14 @@ pub async fn block_from_filecoin_tipset( let block_cid = tsk.cid()?; let block_hash: Hash = block_cid.into(); - let (state_root, msgs, receipts) = execute_tipset(data.clone(), &tipset).await?; + let (state_root, msgs_and_receipts) = execute_tipset(data.clone(), &tipset).await?; let state_tree = StateTree::new_from_root(data.state_manager.blockstore_owned(), &state_root)?; let mut full_transactions = vec![]; let mut hash_transactions = vec![]; let mut gas_used = 0; - for (i, msg) in msgs.iter().enumerate() { - let receipt = receipts[i].clone(); + for (i, (msg, receipt)) in msgs_and_receipts.iter().enumerate() { let ti = Uint64(i as u64); gas_used += receipt.gas_used; let smsg = match msg { From 90a8af957f3a0ca670dc8435b6366cbe458cdb3b Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 09:30:15 +0200 Subject: [PATCH 42/94] Optimize format_u64 function --- src/rpc/methods/eth.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index fc562078609a..371564c0693e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -438,16 +438,15 @@ impl From for TxArgs { } } -fn format_u64(value: &u64) -> BytesMut { - let bytes = value.to_be_bytes(); - let first_non_zero = bytes.iter().position(|&b| b != 0); - - match first_non_zero { - Some(i) => bytes[i..].into(), - None => { - // If all bytes are zero, return an empty slice - BytesMut::new() - } +#[allow(clippy::indexing_slicing)] +fn format_u64(value: u64) -> BytesMut { + if value != 0 { + let i = (value.leading_zeros() / 8) as usize; + let bytes = value.to_be_bytes(); + bytes[i..].into() + } else { + // If all bytes are zero, return an empty slice + BytesMut::new() } } @@ -475,11 +474,11 @@ impl TxArgs { pub fn rlp_signed_message(&self) -> Vec { let mut stream = RlpStream::new_list(12); // THIS IS IMPORTANT - stream.append(&format_u64(&self.chain_id)); - stream.append(&format_u64(&self.nonce)); + stream.append(&format_u64(self.chain_id)); + stream.append(&format_u64(self.nonce)); stream.append(&format_bigint(&self.max_priority_fee_per_gas)); stream.append(&format_bigint(&self.max_fee_per_gas)); - stream.append(&format_u64(&self.gas_limit)); + stream.append(&format_u64(self.gas_limit)); stream.append(&format_address(&self.to)); stream.append(&format_bigint(&self.value)); stream.append(&self.input); From 491fd33d4c6daf11a98d48a513f43cebfa8ca2bb Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 09:35:08 +0200 Subject: [PATCH 43/94] Allow indexing slicing --- src/rpc/methods/eth.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 371564c0693e..ad6f1ffb17ee 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -450,6 +450,7 @@ fn format_u64(value: u64) -> BytesMut { } } +#[allow(clippy::indexing_slicing)] fn format_bigint(value: &BigInt) -> BytesMut { let (_, bytes) = value.0.to_bytes_be(); let first_non_zero = bytes.iter().position(|&b| b != 0); From f6cca6b783d2173830420a9f7d157b4125b54dd1 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 10:12:28 +0200 Subject: [PATCH 44/94] Allow indexing slicing --- src/rpc/methods/eth.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index ad6f1ffb17ee..5635481f8882 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -185,6 +185,7 @@ impl Address { self.0.as_bytes().starts_with(&MASKED_ID_PREFIX) } + #[allow(clippy::indexing_slicing)] fn from_actor_id(id: u64) -> Self { let mut payload = ethereum_types::H160::default(); payload.as_bytes_mut()[0] = 0xff; From 4df46e6c2126bb1f0eef8c650ad06f8795169b83 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 10:28:03 +0200 Subject: [PATCH 45/94] Allow indexing slicing --- src/rpc/methods/eth.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 5635481f8882..f4dd2f577e4a 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -63,6 +63,8 @@ const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES]; const ADDRESS_LENGTH: usize = 20; +const EVM_WORD_LENGTH: usize = 32; + /// Keccak-256 of an RLP of an empty array const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; @@ -917,11 +919,9 @@ fn encode_filecoin_returns_as_abi( encode_as_abi_helper(exit_code, codec, data) } -/// Format 2 numbers followed by an arbitrary byte array as solidity ABI. Both our native -/// inputs/outputs follow the same pattern, so we can reuse this code. +/// Format two numbers followed by an arbitrary byte array as solidity ABI. +#[allow(clippy::indexing_slicing)] fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { - const EVM_WORD_SIZE: usize = 32; - // The first two params are "static" numbers. Then, we record the offset of the "data" arg, // then, at that offset, we record the length of the data. // @@ -930,31 +930,30 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { let static_args = [ param1, param2, - (EVM_WORD_SIZE * 3) as u64, + (EVM_WORD_LENGTH * 3) as u64, data.len() as u64, ]; // We always pad out to the next EVM "word" (32 bytes). let total_words = static_args.len() - + (data.len() / EVM_WORD_SIZE) - + if (data.len() % EVM_WORD_SIZE) != 0 { + + (data.len() / EVM_WORD_LENGTH) + + if (data.len() % EVM_WORD_LENGTH) != 0 { 1 } else { 0 }; - let len = total_words * EVM_WORD_SIZE; + let len = total_words * EVM_WORD_LENGTH; let mut buf = vec![0u8; len]; let mut offset = 0; // Below, we use copy instead of "appending" to preserve all the zero padding. for arg in static_args.iter() { // Write each "arg" into the last 8 bytes of each 32 byte word. - offset += EVM_WORD_SIZE; + offset += EVM_WORD_LENGTH; let start = offset - 8; buf[start..offset].copy_from_slice(&arg.to_be_bytes()); } // Finally, we copy in the data. - let data_len = data.len(); - buf[offset..offset + data_len].copy_from_slice(data); + buf[offset..(offset + data.len())].copy_from_slice(data); buf } From baa0426d2f40bfcbb6ff980db9b99accdab761e3 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 10:44:17 +0200 Subject: [PATCH 46/94] Allow code hoarding --- src/rpc/methods/eth.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index f4dd2f577e4a..1caf49793da1 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -73,6 +73,7 @@ const EIP_1559_TX_TYPE: u64 = 2; /// The address used in messages to actors that have since been deleted. const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff"; +#[allow(dead_code)] #[repr(u64)] enum EAMMethod { Constructor = METHOD_CONSTRUCTOR, @@ -81,6 +82,7 @@ enum EAMMethod { CreateExternal = 4, } +#[allow(dead_code)] #[repr(u64)] enum EVMMethod { Constructor = METHOD_CONSTRUCTOR, From 7683c4e2446c34ab20c9f6fbbe9fbbac9d71422c Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 10:50:48 +0200 Subject: [PATCH 47/94] Remove dead code --- src/rpc/methods/eth.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 1caf49793da1..a5cd7eea435e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1062,7 +1062,6 @@ pub fn new_eth_tx_from_signed_message( chain_id: u32, ) -> Result { let mut tx: Tx = Tx::default(); - tx.chain_id = Uint64(1); if smsg.is_delegated() { // This is an eth tx From 024e9581bba8cfa56edaa8cfa0c56cbae723552f Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 11:23:15 +0200 Subject: [PATCH 48/94] Move to Option for the To address in eth::Tx --- src/rpc/methods/eth.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index a5cd7eea435e..7ce2b57953aa 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -388,7 +388,8 @@ pub struct Tx { pub block_number: Uint64, pub transaction_index: Uint64, pub from: Address, - pub to: Address, + #[serde(skip_serializing_if = "LotusJson::is_none", default)] + pub to: LotusJson>, pub value: BigInt, pub r#type: Uint64, pub input: Bytes, @@ -414,7 +415,7 @@ lotus_json_with_self!(Tx); struct TxArgs { pub chain_id: u64, pub nonce: u64, - pub to: Address, + pub to: Option
, pub value: BigInt, pub max_fee_per_gas: BigInt, pub max_priority_fee_per_gas: BigInt, @@ -430,7 +431,7 @@ impl From for TxArgs { Self { chain_id: tx.chain_id.0, nonce: tx.nonce.0, - to: tx.to, + to: tx.to.0, value: tx.value, max_fee_per_gas: tx.max_fee_per_gas, max_priority_fee_per_gas: tx.max_priority_fee_per_gas, @@ -469,8 +470,12 @@ fn format_bigint(value: &BigInt) -> BytesMut { } } -fn format_address(value: &Address) -> BytesMut { - value.0.as_bytes().into() +fn format_address(value: &Option
) -> BytesMut { + if let Some(addr) = value { + addr.0.as_bytes().into() + } else { + BytesMut::new() + } } impl TxArgs { @@ -757,7 +762,7 @@ fn is_eth_address(addr: &VmAddress) -> bool { } fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { - let mut to = Address::default(); + let mut to = Some(Address::default()); let mut params = vec![]; if msg.version != 0 { @@ -779,7 +784,7 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { } } else if msg.method_num() == EVMMethod::InvokeContract as u64 { let addr = Address::from_filecoin_address(&msg.to)?; - to = addr; + to = Some(addr); } else { bail!( "invalid methodnum {}: only allowed method is InvokeContract({})", @@ -853,7 +858,7 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage, chain_id: u32) -> Result Ok(Tx { nonce: Uint64(tx_args.nonce), chain_id: Uint64(chain_id as u64), - to: tx_args.to, + to: LotusJson(tx_args.to), from, value: tx_args.value, r#type: Uint64(EIP_1559_TX_TYPE), @@ -1003,10 +1008,12 @@ fn eth_tx_from_native_message( // Lookup the to address. If the recipient doesn't exist, we replace the address with a // known sentinel address. let mut to = match lookup_eth_address(&msg.to(), state) { - Ok(addr) => addr, + Ok(addr) => Some(addr), Err(_err) => { // TODO: bail in case of not "actor not found" errors - Address(ethereum_types::H160::from_str(REVERTED_ETH_ADDRESS)?) + Some(Address(ethereum_types::H160::from_str( + REVERTED_ETH_ADDRESS, + )?)) // bail!( // "failed to lookup receiver address {} when converting a native message to an eth txn", @@ -1030,7 +1037,7 @@ fn eth_tx_from_native_message( if let Ok(buffer) = decode_payload(msg.params(), codec) { // If this is a valid "create external", unset the "to" address. if msg.method_num() == EAMMethod::CreateExternal as MethodNum { - // to = None; + to = None; } break 'decode buffer; } @@ -1041,7 +1048,7 @@ fn eth_tx_from_native_message( }; let mut tx = Tx::default(); - tx.to = to; + tx.to = LotusJson(to); tx.from = from; tx.input = input; tx.nonce = Uint64(msg.sequence); @@ -1198,10 +1205,10 @@ mod test { let eth_tx_args = TxArgs { chain_id: 314159, nonce: 486, - to: Address( + to: Some(Address( ethereum_types::H160::from_str("0xeb4a9cdb9f42d3a503d580a39b6e3736eb21fffd") .unwrap(), - ), + )), value: BigInt(num_bigint::BigInt::from(0)), max_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000120)), max_priority_fee_per_gas: BigInt(num_bigint::BigInt::from(1500000000)), From cbaa298aed6d54fb602f962d0f9e21383f6d391d Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 11:54:27 +0200 Subject: [PATCH 49/94] Refactor to avoid mutation --- src/rpc/methods/eth.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 7ce2b57953aa..c900533b7bad 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1068,22 +1068,21 @@ pub fn new_eth_tx_from_signed_message( state: &StateTree, chain_id: u32, ) -> Result { - let mut tx: Tx = Tx::default(); - - if smsg.is_delegated() { + let (tx, hash) = if smsg.is_delegated() { // This is an eth tx - tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; - tx.hash = tx.eth_hash(); + let tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; + let hash = tx.eth_hash(); + (tx, hash) } else if smsg.is_secp256k1() { // Secp Filecoin Message - tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; - tx.hash = smsg.cid()?.into(); + let tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; + (tx, smsg.cid()?.into()) } else { // BLS Filecoin message - tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; - tx.hash = smsg.message().cid()?.into(); - } - Ok(tx) + let tx = eth_tx_from_native_message(smsg.message(), state, chain_id)?; + (tx, smsg.message().cid()?.into()) + }; + Ok(Tx { hash, ..tx }) } pub async fn block_from_filecoin_tipset( From fab11aa4fad4a24e61ef9cb7e7c83c6184f5fb3c Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 12:07:47 +0200 Subject: [PATCH 50/94] Refactor to avoid mutation --- src/rpc/methods/eth.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index c900533b7bad..82482d472c44 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1047,20 +1047,20 @@ fn eth_tx_from_native_message( encode_filecoin_params_as_abi(msg.method_num(), codec, msg.params())? }; - let mut tx = Tx::default(); - tx.to = LotusJson(to); - tx.from = from; - tx.input = input; - tx.nonce = Uint64(msg.sequence); - tx.chain_id = Uint64(chain_id as u64); - tx.value = msg.value.clone().into(); - tx.r#type = Uint64(EIP_1559_TX_TYPE); - tx.gas = Uint64(msg.gas_limit); - tx.max_fee_per_gas = msg.gas_fee_cap.clone().into(); - tx.max_priority_fee_per_gas = msg.gas_premium.clone().into(); - tx.access_list = vec![]; - - Ok(tx) + Ok(Tx { + to: LotusJson(to), + from: from, + input: input, + nonce: Uint64(msg.sequence), + chain_id: Uint64(chain_id as u64), + value: msg.value.clone().into(), + r#type: Uint64(EIP_1559_TX_TYPE), + gas: Uint64(msg.gas_limit), + max_fee_per_gas: msg.gas_fee_cap.clone().into(), + max_priority_fee_per_gas: msg.gas_premium.clone().into(), + access_list: vec![], + ..Tx::default() + }) } pub fn new_eth_tx_from_signed_message( From ba6b4a4873e346f5cf79804f9590578fbe8e242a Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 12:09:53 +0200 Subject: [PATCH 51/94] Remove redundant field names --- src/rpc/methods/eth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 82482d472c44..e1a24455d91a 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1049,8 +1049,8 @@ fn eth_tx_from_native_message( Ok(Tx { to: LotusJson(to), - from: from, - input: input, + from, + input, nonce: Uint64(msg.sequence), chain_id: Uint64(chain_id as u64), value: msg.value.clone().into(), From 0f56ca38282db03f0d6ca5c9b2c695c4aa21f3c7 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 12:35:38 +0200 Subject: [PATCH 52/94] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be57400703a5..f537098a539c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ - [#4184](https://github.com/ChainSafe/forest/pull/4184) Added `--no-healthcheck` flag to `forest` to disable the healthcheck endpoint. +- [#4183](https://github.com/ChainSafe/forest/issues/4183) Add support for the + `Filecoin.EthGetBlockByNumber` RPC method. + ### Changed - [#4170](https://github.com/ChainSafe/forest/pull/4170) Change the default From 357f22c6f7cb33e6e6b77890ac517ab98429ea9c Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 13:44:44 +0200 Subject: [PATCH 53/94] Remove dead code --- src/rpc/methods/eth.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index e1a24455d91a..398b09cf5117 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -19,7 +19,6 @@ use crate::shim::crypto::{Signature, SignatureType}; use crate::shim::econ::{TokenAmount, BLOCK_GAS_LIMIT}; use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; use crate::shim::fvm_shared_latest::MethodNum; -use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR; use crate::shim::message::Message; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; @@ -73,24 +72,13 @@ const EIP_1559_TX_TYPE: u64 = 2; /// The address used in messages to actors that have since been deleted. const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff"; -#[allow(dead_code)] #[repr(u64)] enum EAMMethod { - Constructor = METHOD_CONSTRUCTOR, - Create = 2, - Create2 = 3, CreateExternal = 4, } -#[allow(dead_code)] #[repr(u64)] enum EVMMethod { - Constructor = METHOD_CONSTRUCTOR, - Resurrect = 2, - GetBytecode = 3, - GetBytecodeHash = 4, - GetStorageAt = 5, - InvokeContractDelegate = 6, // it is very unfortunate but the hasher creates a circular dependency, so we use the raw // number. // InvokeContract = frc42_dispatch::method_hash!("InvokeEVM"), From 49fc5ab9f3245507cd0ffd939ed7289a4b0f13e3 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 14:05:53 +0200 Subject: [PATCH 54/94] Add link to Lotus implementation --- src/rpc/methods/eth.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 398b09cf5117..e18de4c08cfc 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -144,6 +144,7 @@ impl Address { } } + // See https://github.com/filecoin-project/lotus/blob/v1.26.2/chain/types/ethtypes/eth_types.go#L347-L375 for reference implementation pub fn from_filecoin_address(addr: &FilecoinAddress) -> Result { match addr.protocol() { Protocol::ID => Ok(Self::from_actor_id(addr.id()?)), From e2c3ec680d1ea1fb90010bb4ddc701f4b7444701 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 14:10:32 +0200 Subject: [PATCH 55/94] Shave some bytes off --- src/rpc/methods/chain.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 1869acf4107c..654798c04789 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -882,7 +882,7 @@ pub async fn get_parent_receipts( .map_err(|_| { ErrorObjectOwned::owned::<()>( 1, - format!("failed to root: ipld: could not find {}", message_receipts), + format!("failed to root: ipld: could not find {message_receipts}"), None, ) }) @@ -902,7 +902,7 @@ pub async fn get_parent_receipts( |_| { ErrorObjectOwned::owned::<()>( 1, - format!("failed to root: ipld: could not find {}", message_receipts), + format!("failed to root: ipld: could not find {message_receipts}"), None, ) }, From 9ed74f7343c994d5d99dfd8b5c49ebe3f8e37721 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 15:40:34 +0200 Subject: [PATCH 56/94] Avoid mutation --- src/rpc/methods/eth.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index e18de4c08cfc..2e9e22ae1396 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1121,25 +1121,25 @@ pub async fn block_from_filecoin_tipset( } } - let mut block = Block::new(); - block.hash = block_hash; - block.number = block_number; - block.parent_hash = parent_cid.into(); - block.timestamp = Uint64(tipset.block_headers().first().timestamp); - block.base_fee_per_gas = tipset - .block_headers() - .first() - .parent_base_fee - .clone() - .into(); - block.gas_used = Uint64(gas_used); - block.transactions = if full_tx_info { - Transactions::Full(full_transactions) - } else { - Transactions::Hash(hash_transactions) - }; - - Ok(block) + Ok(Block { + hash: block_hash, + number: block_number, + parent_hash: parent_cid.into(), + timestamp: Uint64(tipset.block_headers().first().timestamp), + base_fee_per_gas: tipset + .block_headers() + .first() + .parent_base_fee + .clone() + .into(), + gas_used: Uint64(gas_used), + transactions: if full_tx_info { + Transactions::Full(full_transactions) + } else { + Transactions::Hash(hash_transactions) + }, + ..Block::new() + }) } pub async fn eth_get_block_by_number( From ef7b0086b2509237745ad1c5a3fe2526dc263e33 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 18:03:43 +0200 Subject: [PATCH 57/94] Add quickcheck test --- src/rpc/methods/eth.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 2e9e22ae1396..a931be3ac761 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1171,7 +1171,7 @@ mod test { assert_eq!(r.0, decoded.0); } - pub fn decode_hex(s: &str) -> Result, ParseIntError> { + fn decode_hex(s: &str) -> Result, ParseIntError> { (0..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) @@ -1225,4 +1225,22 @@ mod test { ); assert_eq!(expected_hash, eth_tx_args.hash()); } + + #[quickcheck] + fn u64_roundtrip(i: u64) { + let bm = format_u64(i); + if i == 0 { + assert!(bm.is_empty()); + } else { + // check that buffer doesn't start with zero + let freezed = bm.freeze(); + assert!(!freezed.starts_with(&[0])); + + // roundtrip + let mut padded = [0u8; 8]; + let bytes: &[u8] = &freezed.slice(..); + padded[8 - bytes.len()..].copy_from_slice(bytes); + assert_eq!(i, u64::from_be_bytes(padded)); + } + } } From 9808c1f696e3eadeb4bcc6c20a7aa660d9ace579 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 18:25:06 +0200 Subject: [PATCH 58/94] Add quickcheck test for bigint_roundtrip --- src/rpc/methods/eth.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index a931be3ac761..d68f6d469ad0 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1159,6 +1159,7 @@ pub async fn eth_get_block_by_number( #[cfg(test)] mod test { use super::*; + use num_traits::FromBytes; use quickcheck_macros::quickcheck; use std::num::ParseIntError; @@ -1243,4 +1244,21 @@ mod test { assert_eq!(i, u64::from_be_bytes(padded)); } } + + #[quickcheck] + fn bigint_roundtrip(bi: num_bigint::BigUint) { + let eth_bi = BigInt(bi.clone().into()); + let bm = format_bigint(ð_bi); + + if eth_bi.0.is_zero() { + assert!(bm.is_empty()); + } else { + // check that buffer doesn't start with zero + let freezed = bm.freeze(); + assert!(!freezed.starts_with(&[0])); + + // roundtrip + assert_eq!(bi, num_bigint::BigUint::from_be_bytes(&freezed.slice(..))); + } + } } From b2bcf3c0d152d3ec2aeca98cd68fd05f3f8d7a71 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 12 Apr 2024 19:19:36 +0200 Subject: [PATCH 59/94] Remove comment --- src/rpc/methods/eth.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index d68f6d469ad0..cf7903cc4728 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -759,7 +759,6 @@ fn eth_tx_args_from_unsigned_eth_message(msg: &Message) -> Result { } if !msg.params().bytes().is_empty() { - // TODO: could we do better? let mut reader = SliceReader::new(msg.params().bytes()); match Value::decode(&mut reader) { Ok(Value::Bytes(bytes)) => params = bytes, From ee05dedb1fa315f95a3b9f15346da6faa6095808 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 11:12:32 +0200 Subject: [PATCH 60/94] Change format_bigint to fail on negative inputs --- src/rpc/methods/eth.rs | 71 ++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index cf7903cc4728..c6ee07ab618d 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -35,9 +35,8 @@ use itertools::Itertools; use jsonrpsee::types::Params; use keccak_hash::keccak; use nonempty::nonempty; -use num_bigint; -use num_bigint::Sign; -use num_traits::Zero as _; +use num_bigint::{self, Sign}; +use num_traits::{Signed as _, Zero as _}; use rlp::RlpStream; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; @@ -392,7 +391,7 @@ pub struct Tx { } impl Tx { - pub fn eth_hash(&self) -> Hash { + pub fn eth_hash(&self) -> Result { let eth_tx_args: TxArgs = self.clone().into(); eth_tx_args.hash() } @@ -446,17 +445,20 @@ fn format_u64(value: u64) -> BytesMut { } #[allow(clippy::indexing_slicing)] -fn format_bigint(value: &BigInt) -> BytesMut { +fn format_bigint(value: &BigInt) -> Result { + if value.0.is_negative() { + bail!("can't format a negative number"); + } let (_, bytes) = value.0.to_bytes_be(); let first_non_zero = bytes.iter().position(|&b| b != 0); - match first_non_zero { + Ok(match first_non_zero { Some(i) => bytes[i..].into(), None => { // If all bytes are zero, return an empty slice BytesMut::new() } - } + }) } fn format_address(value: &Option
) -> BytesMut { @@ -468,26 +470,26 @@ fn format_address(value: &Option
) -> BytesMut { } impl TxArgs { - pub fn hash(&self) -> Hash { - Hash(keccak(self.rlp_signed_message())) + pub fn hash(&self) -> Result { + Ok(Hash(keccak(self.rlp_signed_message()?))) } - pub fn rlp_signed_message(&self) -> Vec { + pub fn rlp_signed_message(&self) -> Result> { let mut stream = RlpStream::new_list(12); // THIS IS IMPORTANT stream.append(&format_u64(self.chain_id)); stream.append(&format_u64(self.nonce)); - stream.append(&format_bigint(&self.max_priority_fee_per_gas)); - stream.append(&format_bigint(&self.max_fee_per_gas)); + stream.append(&format_bigint(&self.max_priority_fee_per_gas)?); + stream.append(&format_bigint(&self.max_fee_per_gas)?); stream.append(&format_u64(self.gas_limit)); stream.append(&format_address(&self.to)); - stream.append(&format_bigint(&self.value)); + stream.append(&format_bigint(&self.value)?); stream.append(&self.input); let access_list: &[u8] = &[]; stream.append_list(access_list); - stream.append(&format_bigint(&self.v)); - stream.append(&format_bigint(&self.r)); - stream.append(&format_bigint(&self.s)); + stream.append(&format_bigint(&self.v)?); + stream.append(&format_bigint(&self.r)?); + stream.append(&format_bigint(&self.s)?); let mut rlp = stream.out()[..].to_vec(); let mut bytes: Vec = vec![0x02]; @@ -500,7 +502,7 @@ impl TxArgs { .join(""); tracing::trace!("rlp: {}", &hex); - bytes + Ok(bytes) } } @@ -1059,7 +1061,7 @@ pub fn new_eth_tx_from_signed_message( let (tx, hash) = if smsg.is_delegated() { // This is an eth tx let tx = eth_tx_from_signed_eth_message(smsg, chain_id)?; - let hash = tx.eth_hash(); + let hash = tx.eth_hash()?; (tx, hash) } else if smsg.is_secp256k1() { // Secp Filecoin Message @@ -1158,7 +1160,8 @@ pub async fn eth_get_block_by_number( #[cfg(test)] mod test { use super::*; - use num_traits::FromBytes; + use num_bigint; + use num_traits::{FromBytes, Signed}; use quickcheck_macros::quickcheck; use std::num::ParseIntError; @@ -1223,7 +1226,7 @@ mod test { ) .unwrap(), ); - assert_eq!(expected_hash, eth_tx_args.hash()); + assert_eq!(expected_hash, eth_tx_args.hash().unwrap()); } #[quickcheck] @@ -1245,19 +1248,27 @@ mod test { } #[quickcheck] - fn bigint_roundtrip(bi: num_bigint::BigUint) { + fn bigint_roundtrip(bi: num_bigint::BigInt) { let eth_bi = BigInt(bi.clone().into()); - let bm = format_bigint(ð_bi); - if eth_bi.0.is_zero() { - assert!(bm.is_empty()); - } else { - // check that buffer doesn't start with zero - let freezed = bm.freeze(); - assert!(!freezed.starts_with(&[0])); + match format_bigint(ð_bi) { + Ok(bm) => { + if eth_bi.0.is_zero() { + assert!(bm.is_empty()); + } else { + // check that buffer doesn't start with zero + let freezed = bm.freeze(); + assert!(!freezed.starts_with(&[0])); - // roundtrip - assert_eq!(bi, num_bigint::BigUint::from_be_bytes(&freezed.slice(..))); + // roundtrip + let unsigned = num_bigint::BigUint::from_be_bytes(&freezed.slice(..)); + assert_eq!(bi, unsigned.try_into().unwrap()); + } + } + Err(_) => { + // fails in case of negative number + assert!(eth_bi.0.is_negative()); + } } } } From 5d91a3ce218fa1083771217885b86938494323d3 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 11:33:10 +0200 Subject: [PATCH 61/94] Remove clippy directive, use get/except and strenghten len test --- src/lotus_json/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lotus_json/mod.rs b/src/lotus_json/mod.rs index fb7fdf8428e8..baca1ba28b61 100644 --- a/src/lotus_json/mod.rs +++ b/src/lotus_json/mod.rs @@ -341,11 +341,10 @@ pub mod hexify_vec_bytes { D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; - #[allow(clippy::indexing_slicing)] - if s.len() >= 2 && &s[..2] == "0x" { + if (s.len() >= 2 && s.len() % 2 == 0) && s.get(..2).expect("failed to get prefix") == "0x" { let result: Result, _> = (2..s.len()) .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .map(|i| u8::from_str_radix(s.get(i..i + 2).expect("failed to get slice"), 16)) .collect(); result.map_err(serde::de::Error::custom) } else { From 968c0ac0fca7fd760884b7bc4746c6076286d368 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 15:12:55 +0200 Subject: [PATCH 62/94] Rewrite from_actor_id to avoid indexing slicing and add unit test --- src/rpc/methods/eth.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index c6ee07ab618d..bf3826b09d68 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -177,13 +177,14 @@ impl Address { self.0.as_bytes().starts_with(&MASKED_ID_PREFIX) } - #[allow(clippy::indexing_slicing)] fn from_actor_id(id: u64) -> Self { - let mut payload = ethereum_types::H160::default(); - payload.as_bytes_mut()[0] = 0xff; - payload.as_bytes_mut()[12..20].copy_from_slice(&id.to_be_bytes()); + let arr = id.to_be_bytes(); + let payload = [ + 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], + ]; - Self(payload) + Self(ethereum_types::H160(payload)) } } @@ -1271,4 +1272,18 @@ mod test { } } } + + #[test] + fn test_id_address_roundtrip() { + let test_cases = [1u64, 2, 3, 100, 101]; + + for id in test_cases { + let addr = FilecoinAddress::new_id(id); + + // roundtrip + let eth_addr = Address::from_filecoin_address(&addr).unwrap(); + let fil_addr = eth_addr.to_filecoin_address().unwrap(); + assert_eq!(addr, fil_addr) + } + } } From cda520153c3b0abfacb5a0031c10db869fb661b2 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 15:47:05 +0200 Subject: [PATCH 63/94] Remove clippy directive and add comment --- src/rpc/methods/eth.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index bf3826b09d68..356d98f8f051 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -433,12 +433,14 @@ impl From for TxArgs { } } -#[allow(clippy::indexing_slicing)] fn format_u64(value: u64) -> BytesMut { if value != 0 { let i = (value.leading_zeros() / 8) as usize; let bytes = value.to_be_bytes(); - bytes[i..].into() + // `leading_zeros` for a positive `u64` returns a number in the range [1-63] + // `i` is in the range [1-7], and `bytes` is an array of size 8 + // therefore, getting the slice from `i` to end should never fail + bytes.get(i..).expect("failed to get slice").into() } else { // If all bytes are zero, return an empty slice BytesMut::new() From 14ab79dc85a39cf5ece676eefd132ddf4533aae3 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 16:19:09 +0200 Subject: [PATCH 64/94] Rewrite format_bigint to avoid indexing slicing --- src/rpc/methods/eth.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 356d98f8f051..ec22cfa326f2 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -447,20 +447,15 @@ fn format_u64(value: u64) -> BytesMut { } } -#[allow(clippy::indexing_slicing)] fn format_bigint(value: &BigInt) -> Result { - if value.0.is_negative() { - bail!("can't format a negative number"); - } - let (_, bytes) = value.0.to_bytes_be(); - let first_non_zero = bytes.iter().position(|&b| b != 0); - - Ok(match first_non_zero { - Some(i) => bytes[i..].into(), - None => { - // If all bytes are zero, return an empty slice - BytesMut::new() + Ok(if value.0.is_positive() { + BytesMut::from_iter(value.0.to_bytes_be().1.iter()) + } else { + if value.0.is_negative() { + bail!("can't format a negative number"); } + // If all bytes are zero, return an empty slice + BytesMut::new() }) } From f3d1592b87aae471a8af45563186f94608a51f6d Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 16:47:24 +0200 Subject: [PATCH 65/94] Remove clippy directive --- src/rpc/methods/eth.rs | 44 ++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index ec22cfa326f2..2297b65fbee5 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -803,18 +803,22 @@ fn recover_sig(sig: &Signature) -> Result<(BigInt, BigInt, BigInt)> { bail!("signature should be 65 bytes long, but got {len} bytes"); } - let bytes = sig.bytes(); - - #[allow(clippy::indexing_slicing)] - { - let r = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[0..32]); - - let s = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[32..64]); - - let v = num_bigint::BigInt::from_bytes_be(Sign::Plus, &bytes[64..65]); - - Ok((BigInt(r), BigInt(s), BigInt(v))) - } + let r = num_bigint::BigInt::from_bytes_be( + Sign::Plus, + sig.bytes().get(0..32).expect("failed to get slice"), + ); + + let s = num_bigint::BigInt::from_bytes_be( + Sign::Plus, + sig.bytes().get(32..64).expect("failed to get slice"), + ); + + let v = num_bigint::BigInt::from_bytes_be( + Sign::Plus, + sig.bytes().get(64..65).expect("failed to get slice"), + ); + + Ok((BigInt(r), BigInt(s), BigInt(v))) } /// `eth_tx_from_signed_eth_message` does NOT populate: @@ -1283,4 +1287,20 @@ mod test { assert_eq!(addr, fil_addr) } } + + // func TestEthAddr(t *testing.T) { + // testcases := []string{ + // strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), + // strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), + // strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), + // } + + // for _, addr := range testcases { + // var a EthAddress + // err := a.UnmarshalJSON([]byte(addr)) + + // require.Nil(t, err) + // require.Equal(t, a.String(), strings.Replace(addr, `"`, "", -1)) + // } + // } } From bafc4af1163469d74a5aebd13899c51306a17607 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 17:35:43 +0200 Subject: [PATCH 66/94] Remove clippy directive --- src/rpc/methods/eth.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 2297b65fbee5..5ad89c3c3ced 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -128,10 +128,19 @@ lotus_json_with_self!(Address); impl Address { pub fn to_filecoin_address(&self) -> Result { if self.is_masked_id() { + const PREFIX_LEN: usize = MASKED_ID_PREFIX.len(); // This is a masked ID address. - #[allow(clippy::indexing_slicing)] - let bytes: [u8; 8] = - core::array::from_fn(|i| self.0.as_fixed_bytes()[MASKED_ID_PREFIX.len() + i]); + let arr = self.0.as_fixed_bytes(); + let bytes = [ + arr[PREFIX_LEN + 0], + arr[PREFIX_LEN + 1], + arr[PREFIX_LEN + 2], + arr[PREFIX_LEN + 3], + arr[PREFIX_LEN + 4], + arr[PREFIX_LEN + 5], + arr[PREFIX_LEN + 6], + arr[PREFIX_LEN + 7], + ]; Ok(FilecoinAddress::new_id(u64::from_be_bytes(bytes))) } else { // Otherwise, translate the address into an address controlled by the From bb46648c5f13cbe8a72880f9c3b198c156bdd268 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 17:37:53 +0200 Subject: [PATCH 67/94] Reduce expression --- src/rpc/methods/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 5ad89c3c3ced..956f361127bf 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -132,7 +132,7 @@ impl Address { // This is a masked ID address. let arr = self.0.as_fixed_bytes(); let bytes = [ - arr[PREFIX_LEN + 0], + arr[PREFIX_LEN], arr[PREFIX_LEN + 1], arr[PREFIX_LEN + 2], arr[PREFIX_LEN + 3], From 01a3dc4a738b73dc885fd016fb6b19205d36fb89 Mon Sep 17 00:00:00 2001 From: elmattic Date: Mon, 15 Apr 2024 19:27:04 +0200 Subject: [PATCH 68/94] Add two more tests for eth abi --- src/rpc/methods/eth.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 956f361127bf..c5486d072bfb 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1202,6 +1202,26 @@ mod test { assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes)); } + #[test] + fn test_abi_encoding_empty_bytes() { + // Generated using https://abi.hashex.org/ + const EXPECTED: &str = "0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"; + let expected_bytes = decode_hex(EXPECTED).unwrap(); + let data_bytes = vec![]; + + assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes)); + } + + #[test] + fn test_abi_encoding_one_byte() { + // Generated using https://abi.hashex.org/ + const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; + let expected_bytes = decode_hex(EXPECTED).unwrap(); + let data_bytes = vec![253]; + + assert_eq!(expected_bytes, encode_as_abi_helper(22, 81, &data_bytes)); + } + #[test] fn test_rlp_encoding() { let eth_tx_args = TxArgs { From d46ece1c77d118bbf037813990b5d14c59b2c99e Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 10:34:57 +0200 Subject: [PATCH 69/94] Refactor get_parent_receipts internal to use a shimmed function --- src/rpc/methods/chain.rs | 62 ++++++++++++---------------------------- src/rpc/methods/eth.rs | 2 +- src/shim/executor.rs | 24 ++++++++++++++++ 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index d6e5656cc47f..873c8b968145 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -17,6 +17,7 @@ use crate::rpc::types::ApiTipsetKey; use crate::rpc::{ApiVersion, Ctx, RpcMethod, ServerError}; use crate::shim::clock::ChainEpoch; use crate::shim::error::ExitCode; +use crate::shim::executor::Receipt; use crate::shim::message::Message; use crate::utils::io::VoidAsyncWriter; use anyhow::{Context as _, Result}; @@ -890,56 +891,31 @@ quickcheck::quickcheck! { } } -pub async fn get_parent_receipts( +pub fn get_parent_receipts( data: Ctx, message_receipts: Cid, ) -> Result> { let store = data.state_manager.blockstore(); - let mut receipts = Vec::new(); - - // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here) - if let Ok(amt) = Amt::::load(&message_receipts, store) - .map_err(|_| { - ErrorObjectOwned::owned::<()>( - 1, - format!("failed to root: ipld: could not find {message_receipts}"), - None, - ) + let receipts = Receipt::get_parent_receipts(store, message_receipts).map_err(|_| { + ErrorObjectOwned::owned::<()>( + 1, + format!("failed to root: ipld: could not find {message_receipts}"), + None, + ) + })?; + + let api_receipts = receipts + .iter() + .map(|receipt| ApiReceipt { + exit_code: receipt.exit_code().into(), + return_data: LotusJson(receipt.return_data().clone()), + gas_used: receipt.gas_used(), + events_root: LotusJson(receipt.events_root()), }) - { - amt.for_each(|_, receipt| { - receipts.push(ApiReceipt { - exit_code: receipt.exit_code.into(), - return_data: LotusJson(receipt.return_data.clone()), - gas_used: receipt.gas_used, - events_root: LotusJson(receipt.events_root), - }); - Ok(()) - })?; - } else { - // Fallback to Receipt_v2. - let amt = Amt::::load(&message_receipts, store).map_err( - |_| { - ErrorObjectOwned::owned::<()>( - 1, - format!("failed to root: ipld: could not find {message_receipts}"), - None, - ) - }, - )?; - amt.for_each(|_, receipt| { - receipts.push(ApiReceipt { - exit_code: receipt.exit_code.into(), - return_data: LotusJson(receipt.return_data.clone()), - gas_used: receipt.gas_used as _, - events_root: LotusJson(None), - }); - Ok(()) - })?; - } + .collect(); - Ok(receipts) + Ok(api_receipts) } #[cfg(test)] diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index c5486d072bfb..7c215123132f 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -735,7 +735,7 @@ async fn execute_tipset( let (state_root, receipt_root) = data.state_manager.tipset_state(tipset).await?; - let receipts = get_parent_receipts(data, receipt_root).await?; + let receipts = get_parent_receipts(data, receipt_root)?; if msgs.len() != receipts.len() { bail!( diff --git a/src/shim/executor.rs b/src/shim/executor.rs index 7b7c9f250aa9..4fb668493dbc 100644 --- a/src/shim/executor.rs +++ b/src/shim/executor.rs @@ -167,6 +167,30 @@ impl Receipt { let receipts = amt.get(i)?; Ok(receipts.cloned().map(Receipt::V2)) } + + pub fn get_parent_receipts( + db: &impl Blockstore, + receipts: Cid, + ) -> anyhow::Result> { + let mut parent_receipts = Vec::new(); + + // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here) + if let Ok(amt) = Amtv0::::load(&receipts, db) { + amt.for_each(|_, receipt| { + parent_receipts.push(Receipt::V4(receipt.clone())); + Ok(()) + })?; + } else { + // Fallback to Receipt_v2. + let amt = Amtv0::::load(&receipts, db)?; + amt.for_each(|_, receipt| { + parent_receipts.push(Receipt::V2(receipt.clone())); + Ok(()) + })?; + } + + Ok(parent_receipts) + } } impl From for Receipt { From 76d9bc2afdaae891f0506adaaa3488df47be71d5 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 11:41:08 +0200 Subject: [PATCH 70/94] Add eth::Address serde roundtrip test --- src/rpc/methods/eth.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 7c215123132f..c3b9cffe43a5 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1317,19 +1317,22 @@ mod test { } } - // func TestEthAddr(t *testing.T) { - // testcases := []string{ - // strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), - // strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), - // strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), - // } - - // for _, addr := range testcases { - // var a EthAddress - // err := a.UnmarshalJSON([]byte(addr)) - - // require.Nil(t, err) - // require.Equal(t, a.String(), strings.Replace(addr, `"`, "", -1)) - // } - // } + #[test] + fn test_addr_serde_roundtrip() { + let test_cases = [ + r#""0xd4c5fb16488Aa48081296299d54b0c648C9333dA""#, + r#""0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d""#, + r#""0x01184F793982104363F9a8a5845743f452dE0586""#, + ]; + + for addr in test_cases { + let eth_addr: Address = serde_json::from_str(addr).unwrap(); + + let encoded = serde_json::to_string(ð_addr).unwrap(); + assert_eq!(encoded, addr.to_lowercase()); + + let decoded: Address = serde_json::from_str(&encoded).unwrap(); + assert_eq!(eth_addr, decoded); + } + } } From 8c108e21e88eb338accf46cb81b9dea34acfa44f Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 13:18:57 +0200 Subject: [PATCH 71/94] Use copy_from_slice instead --- src/rpc/methods/eth.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index c3b9cffe43a5..5f434e29f323 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -131,16 +131,8 @@ impl Address { const PREFIX_LEN: usize = MASKED_ID_PREFIX.len(); // This is a masked ID address. let arr = self.0.as_fixed_bytes(); - let bytes = [ - arr[PREFIX_LEN], - arr[PREFIX_LEN + 1], - arr[PREFIX_LEN + 2], - arr[PREFIX_LEN + 3], - arr[PREFIX_LEN + 4], - arr[PREFIX_LEN + 5], - arr[PREFIX_LEN + 6], - arr[PREFIX_LEN + 7], - ]; + let mut bytes = [0; 8]; + bytes.copy_from_slice(&arr[PREFIX_LEN..]); Ok(FilecoinAddress::new_id(u64::from_be_bytes(bytes))) } else { // Otherwise, translate the address into an address controlled by the From f6f502f090e88ad3805933a9e9de107c6515fcfd Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 14:38:52 +0200 Subject: [PATCH 72/94] Fix incorrect expected value (test still fiascoes) --- src/rpc/methods/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 5f434e29f323..5d4c6506af59 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1207,7 +1207,7 @@ mod test { #[test] fn test_abi_encoding_one_byte() { // Generated using https://abi.hashex.org/ - const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; + const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000fd"; let expected_bytes = decode_hex(EXPECTED).unwrap(); let data_bytes = vec![253]; From 2b876ea09ed79520c335e2747888e6b2fe80af85 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 15:16:33 +0200 Subject: [PATCH 73/94] Add comment --- src/rpc/methods/eth.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 5d4c6506af59..434a7e859d8b 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1207,6 +1207,9 @@ mod test { #[test] fn test_abi_encoding_one_byte() { // Generated using https://abi.hashex.org/ + // Uint64, Uint64, Bytes[] + // 22, 81, [253] + // TODO: find out if abi.hashex result is correct const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000fd"; let expected_bytes = decode_hex(EXPECTED).unwrap(); let data_bytes = vec![253]; From e971eec4f556ba347a7602a0fc57a728800516f2 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 15:33:12 +0200 Subject: [PATCH 74/94] Revert ref (the third type is bytes[]) --- src/rpc/methods/eth.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 434a7e859d8b..085c9c4997a3 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1209,8 +1209,7 @@ mod test { // Generated using https://abi.hashex.org/ // Uint64, Uint64, Bytes[] // 22, 81, [253] - // TODO: find out if abi.hashex result is correct - const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000fd"; + const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; let expected_bytes = decode_hex(EXPECTED).unwrap(); let data_bytes = vec![253]; From 3425fa5f4e500e7d7b40f8415dc8e237dc1ac39c Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 15:43:54 +0200 Subject: [PATCH 75/94] Fix expected value --- src/rpc/methods/eth.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 085c9c4997a3..91870a73f6da 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1209,7 +1209,11 @@ mod test { // Generated using https://abi.hashex.org/ // Uint64, Uint64, Bytes[] // 22, 81, [253] - const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; + //const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; + // Generated using https://adibas03.github.io/online-ethereum-abi-encoder-decoder/#/encode + //const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000fd"; + // According to https://docs.soliditylang.org/en/latest/abi-spec.html and handcrafted + const EXPECTED: &str = "0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; let expected_bytes = decode_hex(EXPECTED).unwrap(); let data_bytes = vec![253]; From bfb263c3f843b137842cb69bbc3f52e5e657a40d Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 18:21:38 +0200 Subject: [PATCH 76/94] Rewrite encode_as_abi_helper to remove indexing slicing --- src/rpc/methods/eth.rs | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 91870a73f6da..ca0d540c0c0e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -919,8 +919,12 @@ fn encode_filecoin_returns_as_abi( encode_as_abi_helper(exit_code, codec, data) } +/// Round to the next multiple of `EVM` word length. +fn round_up_word(value: usize) -> usize { + (value + (EVM_WORD_LENGTH - 1)) / EVM_WORD_LENGTH * EVM_WORD_LENGTH +} + /// Format two numbers followed by an arbitrary byte array as solidity ABI. -#[allow(clippy::indexing_slicing)] fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { // The first two params are "static" numbers. Then, we record the offset of the "data" arg, // then, at that offset, we record the length of the data. @@ -933,27 +937,20 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { (EVM_WORD_LENGTH * 3) as u64, data.len() as u64, ]; - // We always pad out to the next EVM "word" (32 bytes). - let total_words = static_args.len() - + (data.len() / EVM_WORD_LENGTH) - + if (data.len() % EVM_WORD_LENGTH) != 0 { - 1 - } else { - 0 - }; - let len = total_words * EVM_WORD_LENGTH; - let mut buf = vec![0u8; len]; - let mut offset = 0; - // Below, we use copy instead of "appending" to preserve all the zero padding. - for arg in static_args.iter() { - // Write each "arg" into the last 8 bytes of each 32 byte word. - offset += EVM_WORD_LENGTH; - let start = offset - 8; - buf[start..offset].copy_from_slice(&arg.to_be_bytes()); - } - - // Finally, we copy in the data. - buf[offset..(offset + data.len())].copy_from_slice(data); + let padding = [0u8; 24]; + let buf: Vec = padding + .iter() // Right pad + .chain(static_args[0].to_be_bytes().iter()) // Copy u64 + .chain(padding.iter()) + .chain(static_args[1].to_be_bytes().iter()) + .chain(padding.iter()) + .chain(static_args[2].to_be_bytes().iter()) + .chain(padding.iter()) + .chain(static_args[3].to_be_bytes().iter()) + .chain(data.iter()) // Finally, we copy in the data + .chain(std::iter::repeat(&0u8).take(round_up_word(data.len()) - data.len())) // Left pad + .cloned() + .collect(); buf } From 3cc89fcfd04e7e8c78f32d653e5502d61102ef42 Mon Sep 17 00:00:00 2001 From: elmattic Date: Tue, 16 Apr 2024 18:46:38 +0200 Subject: [PATCH 77/94] Fix clippy errors in test module --- src/rpc/methods/eth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index ca0d540c0c0e..29f6fa5a2d02 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -921,7 +921,7 @@ fn encode_filecoin_returns_as_abi( /// Round to the next multiple of `EVM` word length. fn round_up_word(value: usize) -> usize { - (value + (EVM_WORD_LENGTH - 1)) / EVM_WORD_LENGTH * EVM_WORD_LENGTH + ((value + (EVM_WORD_LENGTH - 1)) / EVM_WORD_LENGTH) * EVM_WORD_LENGTH } /// Format two numbers followed by an arbitrary byte array as solidity ABI. @@ -1275,7 +1275,7 @@ mod test { #[quickcheck] fn bigint_roundtrip(bi: num_bigint::BigInt) { - let eth_bi = BigInt(bi.clone().into()); + let eth_bi = BigInt(bi.clone()); match format_bigint(ð_bi) { Ok(bm) => { @@ -1288,7 +1288,7 @@ mod test { // roundtrip let unsigned = num_bigint::BigUint::from_be_bytes(&freezed.slice(..)); - assert_eq!(bi, unsigned.try_into().unwrap()); + assert_eq!(bi, unsigned.into()); } } Err(_) => { From 8eb87504c341045823225772fb4f830ddb5f7700 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 09:30:04 +0200 Subject: [PATCH 78/94] Use existing const binding --- src/rpc/methods/eth.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 29f6fa5a2d02..344602095397 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -179,9 +179,11 @@ impl Address { } fn from_actor_id(id: u64) -> Self { + let pfx = MASKED_ID_PREFIX; let arr = id.to_be_bytes(); let payload = [ - 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + pfx[0], pfx[1], pfx[2], pfx[3], pfx[4], pfx[5], pfx[6], pfx[7], // + pfx[8], pfx[9], pfx[10], pfx[11], // arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], ]; From 8421eb41af9072d544a49472bd296079b517fd02 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 10:03:37 +0200 Subject: [PATCH 79/94] Add some doc comments --- src/rpc/methods/eth.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 52728623ad36..21f393529938 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -51,17 +51,25 @@ pub const WEB3_CLIENT_VERSION: &str = "Filecoin.Web3ClientVersion"; const MASKED_ID_PREFIX: [u8; 12] = [0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +/// Ethereum Bloom filter size in bits. +/// Bloom filter is used in Ethereum to minimize the number of block queries. const BLOOM_SIZE: usize = 2048; +/// Ethereum Bloom filter size in bytes. const BLOOM_SIZE_IN_BYTES: usize = BLOOM_SIZE / 8; +/// Ethereum Bloom filter with all bits set to 1. const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES]; +/// Ethereum address size in bytes. const ADDRESS_LENGTH: usize = 20; +/// Ethereum Virtual Machine word size in bytes. const EVM_WORD_LENGTH: usize = 32; -/// Keccak-256 of an RLP of an empty array +/// Keccak-256 of an RLP of an empty array. +/// In Filecoin, we don't have the concept of uncle blocks but rather use tipsets to reward miners +/// who craft blocks. const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; const EIP_1559_TX_TYPE: u64 = 2; From 95d2fece71ccd726b186b663a6590b667e8cada3 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 10:12:30 +0200 Subject: [PATCH 80/94] Add some doc comment --- src/rpc/methods/eth.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 21f393529938..45de4c40b877 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -72,6 +72,11 @@ const EVM_WORD_LENGTH: usize = 32; /// who craft blocks. const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; +/// Ethereum Improvement Proposals 1559 transaction type. This EIP changed Ethereum’s fee market mechanism. +/// Transaction type can have 3 distinct values: +/// - 0 for legacy transactions +/// - 1 for transactions introduced in EIP-2930 +/// - 2 for transactions introduced in EIP-1559 const EIP_1559_TX_TYPE: u64 = 2; /// The address used in messages to actors that have since been deleted. From 3c6fe4d66bf83e78e9a22bdde4557a0f9d30e37f Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 10:19:06 +0200 Subject: [PATCH 81/94] Add link to solidity abi spec --- src/rpc/methods/eth.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 45de4c40b877..18d013031f90 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -916,6 +916,8 @@ fn lookup_eth_address( Ok(Address::from_actor_id(id_addr.unwrap())) } +/// See https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector-and-argument-encoding +/// for ABI specification fn encode_filecoin_params_as_abi( method: MethodNum, codec: u64, From 516fef6ed8b55183482130fb69a6a66406597b45 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 10:54:01 +0200 Subject: [PATCH 82/94] Add quickcheck test --- src/rpc/methods/eth.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 18d013031f90..1d47b6bc4504 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1347,4 +1347,15 @@ mod test { assert_eq!(eth_addr, decoded); } } + + #[quickcheck] + fn test_fil_address_roundtrip(addr: FilecoinAddress) { + if let Ok(eth_addr) = Address::from_filecoin_address(&addr) { + let fil_addr = eth_addr.to_filecoin_address().unwrap(); + + let protocol = addr.protocol(); + assert!(protocol == Protocol::ID || protocol == Protocol::Delegated); + assert_eq!(addr, fil_addr); + } + } } From b6f2676d7f4fc8b3155865cfba7c2249184ccb7c Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 10:56:37 +0200 Subject: [PATCH 83/94] Remove some comments --- src/rpc/methods/eth.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 1d47b6bc4504..ecf4de3c0ccf 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1220,13 +1220,9 @@ mod test { #[test] fn test_abi_encoding_one_byte() { - // Generated using https://abi.hashex.org/ + // According to https://docs.soliditylang.org/en/latest/abi-spec.html and handcrafted // Uint64, Uint64, Bytes[] // 22, 81, [253] - //const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; - // Generated using https://adibas03.github.io/online-ethereum-abi-encoder-decoder/#/encode - //const EXPECTED: &str = "000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000fd"; - // According to https://docs.soliditylang.org/en/latest/abi-spec.html and handcrafted const EXPECTED: &str = "0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fd00000000000000000000000000000000000000000000000000000000000000"; let expected_bytes = decode_hex(EXPECTED).unwrap(); let data_bytes = vec![253]; From dfb6f3160873b768878041c32994f2c3e514a857 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 11:14:19 +0200 Subject: [PATCH 84/94] Improve comment --- src/rpc/methods/eth.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index ecf4de3c0ccf..157988579ff2 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -487,7 +487,10 @@ impl TxArgs { } pub fn rlp_signed_message(&self) -> Result> { - let mut stream = RlpStream::new_list(12); // THIS IS IMPORTANT + // An item is either an item list or bytes. + const MSG_ITEMS: usize = 12; + + let mut stream = RlpStream::new_list(MSG_ITEMS); stream.append(&format_u64(self.chain_id)); stream.append(&format_u64(self.nonce)); stream.append(&format_bigint(&self.max_priority_fee_per_gas)?); From 1a52e2c4aa3dd10a8f26155d7f3e7512cf753316 Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 12:18:05 +0200 Subject: [PATCH 85/94] Remove unwrap and address TODO --- src/rpc/methods/eth.rs | 47 ++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 157988579ff2..20a7bb69ca7e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -888,35 +888,42 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage, chain_id: u32) -> Result fn lookup_eth_address( addr: &FilecoinAddress, state: &StateTree, -) -> Result
{ +) -> Result> { // Attempt to convert directly, if it's an f4 address. if let Ok(eth_addr) = Address::from_filecoin_address(addr) { if !eth_addr.is_masked_id() { - return Ok(eth_addr); + return Ok(Some(eth_addr)); } } // Otherwise, resolve the ID addr. - let id_addr = state.lookup_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. - if let Some(actor_state) = state.get_actor(addr)? { + 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) = Address::from_filecoin_address(&addr.into()) { if !eth_addr.is_masked_id() { // Conversable into an eth address, use it. - return Ok(eth_addr); + return Ok(Some(eth_addr)); } } } else { // No delegated address -> use a masked ID address } - } else { + } 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(Address::from_actor_id(id_addr.unwrap())) + Ok(Some(Address::from_actor_id(id_addr))) } /// See https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector-and-argument-encoding @@ -1009,26 +1016,22 @@ fn eth_tx_from_native_message( chain_id: u32, ) -> Result { // Lookup the from address. This must succeed. - let from = lookup_eth_address(&msg.from(), state).with_context(|| { - format!( + let from = match lookup_eth_address(&msg.from(), state) { + Ok(Some(from)) => from, + _ => bail!( "failed to lookup sender address {} when converting a native message to an eth txn", msg.from() - ) - })?; + ), + }; // Lookup the to address. If the recipient doesn't exist, we replace the address with a // known sentinel address. let mut to = match lookup_eth_address(&msg.to(), state) { - Ok(addr) => Some(addr), - Err(_err) => { - // TODO: bail in case of not "actor not found" errors - Some(Address(ethereum_types::H160::from_str( - REVERTED_ETH_ADDRESS, - )?)) - - // bail!( - // "failed to lookup receiver address {} when converting a native message to an eth txn", - // msg.to() - // ) + Ok(Some(addr)) => Some(addr), + Ok(None) => Some(Address( + ethereum_types::H160::from_str(REVERTED_ETH_ADDRESS).unwrap(), + )), + Err(err) => { + bail!(err) } }; From a2c30327b3f093ecebc1b88d0e79733edfb6f78d Mon Sep 17 00:00:00 2001 From: elmattic Date: Wed, 17 Apr 2024 12:39:06 +0200 Subject: [PATCH 86/94] Remove todo comment --- src/rpc/methods/eth.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 20a7bb69ca7e..89c8798a21dd 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -985,7 +985,6 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { /// Decodes the payload using the given codec. fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result { match codec { - // TODO: handle IDENTITY? DAG_CBOR | CBOR => { let result: Result, _> = serde_ipld_dagcbor::de::from_reader(payload.reader()); match result { From d3e8d04ee5c1e5c04139b5eeeb22243040abc957 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 18 Apr 2024 12:49:16 +0200 Subject: [PATCH 87/94] Fix doc comment --- src/rpc/methods/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 89c8798a21dd..1ab93905b6bc 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -926,7 +926,7 @@ fn lookup_eth_address( Ok(Some(Address::from_actor_id(id_addr))) } -/// See https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector-and-argument-encoding +/// See /// for ABI specification fn encode_filecoin_params_as_abi( method: MethodNum, From c541e5717e9d9950d49d8edd86e106e63070478f Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 18 Apr 2024 13:49:14 +0200 Subject: [PATCH 88/94] Fix build errors --- src/rpc/methods/chain.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index eb886d05d307..10a72b6797bf 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -880,9 +880,9 @@ pub fn get_parent_receipts( .iter() .map(|receipt| ApiReceipt { exit_code: receipt.exit_code().into(), - return_data: LotusJson(receipt.return_data().clone()), + return_data: receipt.return_data(), gas_used: receipt.gas_used(), - events_root: LotusJson(receipt.events_root()), + events_root: receipt.events_root(), }) .collect(); From 9d36be19c3d3bacda922d6168108b52d9fb7b2e0 Mon Sep 17 00:00:00 2001 From: elmattic Date: Thu, 18 Apr 2024 17:46:56 +0200 Subject: [PATCH 89/94] Refactor function name and remove useless code --- src/rpc/methods/chain.rs | 28 ---------------------------- src/rpc/methods/eth.rs | 8 ++++---- src/shim/executor.rs | 5 +---- 3 files changed, 5 insertions(+), 36 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 10a72b6797bf..23c81262e778 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -16,7 +16,6 @@ use crate::rpc::types::ApiTipsetKey; use crate::rpc::{ApiVersion, Ctx, RpcMethod, ServerError}; use crate::shim::clock::ChainEpoch; use crate::shim::error::ExitCode; -use crate::shim::executor::Receipt; use crate::shim::message::Message; use crate::utils::io::VoidAsyncWriter; use anyhow::{Context as _, Result}; @@ -862,33 +861,6 @@ quickcheck::quickcheck! { } } -pub fn get_parent_receipts( - data: Ctx, - message_receipts: Cid, -) -> Result> { - let store = data.state_manager.blockstore(); - - let receipts = Receipt::get_parent_receipts(store, message_receipts).map_err(|_| { - ErrorObjectOwned::owned::<()>( - 1, - format!("failed to root: ipld: could not find {message_receipts}"), - None, - ) - })?; - - let api_receipts = receipts - .iter() - .map(|receipt| ApiReceipt { - exit_code: receipt.exit_code().into(), - return_data: receipt.return_data(), - gas_used: receipt.gas_used(), - events_root: receipt.events_root(), - }) - .collect(); - - Ok(api_receipts) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 1ab93905b6bc..3f522c324b97 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -9,12 +9,12 @@ use crate::chain_sync::SyncStage; use crate::lotus_json::LotusJson; use crate::lotus_json::{lotus_json_with_self, HasLotusJson}; use crate::message::{ChainMessage, Message as _, SignedMessage}; -use crate::rpc::chain::{get_parent_receipts, ApiReceipt}; use crate::rpc::error::ServerError; use crate::rpc::{Ctx, RpcMethod}; use crate::shim::address::{Address as FilecoinAddress, Protocol}; use crate::shim::crypto::{Signature, SignatureType}; use crate::shim::econ::{TokenAmount, BLOCK_GAS_LIMIT}; +use crate::shim::executor::Receipt; use crate::shim::fvm_shared_latest::address::{Address as VmAddress, DelegatedAddress}; use crate::shim::fvm_shared_latest::MethodNum; use crate::shim::message::Message; @@ -740,12 +740,12 @@ fn tipset_by_block_number_or_hash( async fn execute_tipset( data: Ctx, tipset: &Arc, -) -> Result<(Cid, Vec<(ChainMessage, ApiReceipt)>)> { +) -> Result<(Cid, Vec<(ChainMessage, Receipt)>)> { let msgs = data.chain_store.messages_for_tipset(tipset)?; let (state_root, receipt_root) = data.state_manager.tipset_state(tipset).await?; - let receipts = get_parent_receipts(data, receipt_root)?; + let receipts = Receipt::get_receipts(data.state_manager.blockstore(), receipt_root)?; if msgs.len() != receipts.len() { bail!( @@ -1119,7 +1119,7 @@ pub async fn block_from_filecoin_tipset( let mut gas_used = 0; for (i, (msg, receipt)) in msgs_and_receipts.iter().enumerate() { let ti = Uint64(i as u64); - gas_used += receipt.gas_used; + gas_used += receipt.gas_used(); let smsg = match msg { ChainMessage::Signed(msg) => msg.clone(), ChainMessage::Unsigned(msg) => { diff --git a/src/shim/executor.rs b/src/shim/executor.rs index 4fb668493dbc..d868df2e47f0 100644 --- a/src/shim/executor.rs +++ b/src/shim/executor.rs @@ -168,10 +168,7 @@ impl Receipt { Ok(receipts.cloned().map(Receipt::V2)) } - pub fn get_parent_receipts( - db: &impl Blockstore, - receipts: Cid, - ) -> anyhow::Result> { + pub fn get_receipts(db: &impl Blockstore, receipts: Cid) -> anyhow::Result> { let mut parent_receipts = Vec::new(); // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here) From 780664ec3c1671c1d11793e58e984c9b129dc6d8 Mon Sep 17 00:00:00 2001 From: Guillaume Potier Date: Fri, 19 Apr 2024 09:51:33 +0200 Subject: [PATCH 90/94] Update src/shim/executor.rs Co-authored-by: David Himmelstrup --- src/shim/executor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shim/executor.rs b/src/shim/executor.rs index d868df2e47f0..c08a3c30fb3f 100644 --- a/src/shim/executor.rs +++ b/src/shim/executor.rs @@ -168,8 +168,8 @@ impl Receipt { Ok(receipts.cloned().map(Receipt::V2)) } - pub fn get_receipts(db: &impl Blockstore, receipts: Cid) -> anyhow::Result> { - let mut parent_receipts = Vec::new(); + pub fn get_receipts(db: &impl Blockstore, receipts_cid: Cid) -> anyhow::Result> { + let mut receipts = Vec::new(); // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here) if let Ok(amt) = Amtv0::::load(&receipts, db) { From 67d3f40441a650230fef4de4a417304b8246337d Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 19 Apr 2024 10:31:07 +0200 Subject: [PATCH 91/94] Fix build --- src/shim/executor.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/shim/executor.rs b/src/shim/executor.rs index c08a3c30fb3f..a43298eed42e 100644 --- a/src/shim/executor.rs +++ b/src/shim/executor.rs @@ -172,21 +172,21 @@ impl Receipt { let mut receipts = Vec::new(); // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here) - if let Ok(amt) = Amtv0::::load(&receipts, db) { + if let Ok(amt) = Amtv0::::load(&receipts_cid, db) { amt.for_each(|_, receipt| { - parent_receipts.push(Receipt::V4(receipt.clone())); + receipts.push(Receipt::V4(receipt.clone())); Ok(()) })?; } else { // Fallback to Receipt_v2. - let amt = Amtv0::::load(&receipts, db)?; + let amt = Amtv0::::load(&receipts_cid, db)?; amt.for_each(|_, receipt| { - parent_receipts.push(Receipt::V2(receipt.clone())); + receipts.push(Receipt::V2(receipt.clone())); Ok(()) })?; } - Ok(parent_receipts) + Ok(receipts) } } From 6b5905cfc936b80728fa5a924cd7044741a41fa1 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 19 Apr 2024 13:52:31 +0200 Subject: [PATCH 92/94] Fil Block not initialized properly --- src/rpc/methods/eth.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 5992052ef5b9..2dddfa684361 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -72,6 +72,9 @@ const EVM_WORD_LENGTH: usize = 32; /// who craft blocks. const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; +/// Keccak-256 hash of the RLP of null. +const EMPTY_ROOT_HASH: &str = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + /// Ethereum Improvement Proposals 1559 transaction type. This EIP changed Ethereum’s fee market mechanism. /// Transaction type can have 3 distinct values: /// - 0 for legacy transactions @@ -230,6 +233,10 @@ impl Hash { let mh = multihash::Code::Blake2b256.digest(self.0.as_bytes()); Cid::new_v1(fvm_ipld_encoding::DAG_CBOR, mh) } + + pub fn empty_root() -> Self { + Self(ethereum_types::H256::from_str(EMPTY_ROOT_HASH).unwrap()) + } } impl FromStr for Hash { @@ -369,11 +376,16 @@ pub struct Block { } impl Block { - pub fn new() -> Self { + pub fn new(has_transactions: bool) -> Self { Self { gas_limit: Uint64(BLOCK_GAS_LIMIT), logs_bloom: Bloom(ethereum_types::Bloom(FULL_BLOOM)), sha3_uncles: Hash(ethereum_types::H256::from_str(EMPTY_UNCLES).unwrap()), + transactions_root: if has_transactions { + Hash::default() + } else { + Hash::empty_root() + }, ..Default::default() } } @@ -1160,7 +1172,7 @@ pub async fn block_from_filecoin_tipset( } else { Transactions::Hash(hash_transactions) }, - ..Block::new() + ..Block::new(!msgs_and_receipts.is_empty()) }) } From 30296ba8d4d5238b9ff94636d33219a46a988280 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 19 Apr 2024 13:56:32 +0200 Subject: [PATCH 93/94] Add empty_uncles ctor --- src/rpc/methods/eth.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 2dddfa684361..a5a18b3d77aa 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -72,8 +72,8 @@ const EVM_WORD_LENGTH: usize = 32; /// who craft blocks. const EMPTY_UNCLES: &str = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; -/// Keccak-256 hash of the RLP of null. -const EMPTY_ROOT_HASH: &str = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; +/// Keccak-256 of the RLP of null. +const EMPTY_ROOT: &str = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; /// Ethereum Improvement Proposals 1559 transaction type. This EIP changed Ethereum’s fee market mechanism. /// Transaction type can have 3 distinct values: @@ -234,8 +234,12 @@ impl Hash { Cid::new_v1(fvm_ipld_encoding::DAG_CBOR, mh) } + pub fn empty_uncles() -> Self { + Self(ethereum_types::H256::from_str(EMPTY_UNCLES).unwrap()) + } + pub fn empty_root() -> Self { - Self(ethereum_types::H256::from_str(EMPTY_ROOT_HASH).unwrap()) + Self(ethereum_types::H256::from_str(EMPTY_ROOT).unwrap()) } } @@ -380,7 +384,7 @@ impl Block { Self { gas_limit: Uint64(BLOCK_GAS_LIMIT), logs_bloom: Bloom(ethereum_types::Bloom(FULL_BLOOM)), - sha3_uncles: Hash(ethereum_types::H256::from_str(EMPTY_UNCLES).unwrap()), + sha3_uncles: Hash::empty_uncles(), transactions_root: if has_transactions { Hash::default() } else { From b3996919e377b3cf8b248c67bd9002dd4dd9b778 Mon Sep 17 00:00:00 2001 From: elmattic Date: Fri, 19 Apr 2024 14:01:41 +0200 Subject: [PATCH 94/94] Add unit test for block ctor --- src/rpc/methods/eth.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index a5a18b3d77aa..12f49216fa81 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1374,4 +1374,13 @@ mod test { assert_eq!(addr, fil_addr); } } + + #[test] + fn test_block_constructor() { + let block = Block::new(false); + assert_eq!(block.transactions_root, Hash::empty_root()); + + let block = Block::new(true); + assert_eq!(block.transactions_root, Hash::default()); + } }