From 2c6e989ad011935d803de70f3c5275530babef52 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 27 Jun 2025 06:32:39 +0000 Subject: [PATCH] feat: Custom TransactionSigned Like OpTransactionSigned, introduce a new tx type. Not a trivial change but might be beneficial for potential system tx changes e.g., in case it's separated into another tx type --- Cargo.lock | 2 + Cargo.toml | 2 + src/evm/transaction.rs | 23 +- src/node/{consensus.rs => consensus/mod.rs} | 83 +--- src/node/consensus/reth_copy.rs | 68 ++- src/node/evm/config.rs | 13 +- src/node/evm/executor.rs | 50 +- src/node/evm/mod.rs | 1 + src/node/evm/receipt_builder.rs | 30 ++ src/node/mod.rs | 15 +- src/node/network/block_import/service.rs | 22 +- src/node/network/mod.rs | 14 +- src/node/pool.rs | 436 ++++++++++++++++++ src/node/{primitives.rs => primitives/mod.rs} | 27 +- src/node/primitives/tx_wrapper.rs | 398 ++++++++++++++++ src/node/rpc/block.rs | 9 +- src/node/rpc/transaction.rs | 18 +- src/node/storage/mod.rs | 22 +- src/node/{types.rs => types/mod.rs} | 12 +- src/node/types/reth_compat.rs | 55 ++- 20 files changed, 1102 insertions(+), 198 deletions(-) rename src/node/{consensus.rs => consensus/mod.rs} (61%) create mode 100644 src/node/evm/receipt_builder.rs create mode 100644 src/node/pool.rs rename src/node/{primitives.rs => primitives/mod.rs} (94%) create mode 100644 src/node/primitives/tx_wrapper.rs rename src/node/{types.rs => types/mod.rs} (91%) diff --git a/Cargo.lock b/Cargo.lock index c98771364..fbbc39edb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8738,6 +8738,7 @@ dependencies = [ "reth-cli", "reth-cli-commands", "reth-cli-util", + "reth-codecs", "reth-db", "reth-discv4", "reth-engine-primitives", @@ -8763,6 +8764,7 @@ dependencies = [ "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-tracing", + "reth-transaction-pool", "reth-trie-common", "reth-trie-db", "revm", diff --git a/Cargo.toml b/Cargo.toml index e7eee0a73..3361a30dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,8 @@ reth-rpc-engine-api = { path = "../reth/crates/rpc/rpc-engine-api" } reth-tracing = { path = "../reth/crates/tracing" } reth-trie-common = { path = "../reth/crates/trie/common", default-features = false } reth-trie-db = { path = "../reth/crates/trie/db" } +reth-codecs = { path = "../reth/crates/storage/codecs" } +reth-transaction-pool = { path = "../reth/crates/transaction-pool" } revm = { version = "24.0.1" } # alloy dependencies diff --git a/src/evm/transaction.rs b/src/evm/transaction.rs index 4045b0986..bd2379dc6 100644 --- a/src/evm/transaction.rs +++ b/src/evm/transaction.rs @@ -1,9 +1,9 @@ -use alloy_consensus::Transaction as AlloyTransaction; -use alloy_primitives::address; +use crate::node::primitives::TransactionSigned; +use alloy_consensus::Transaction as _; use alloy_rpc_types::AccessList; use auto_impl::auto_impl; use reth_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, TransactionEnv}; -use reth_primitives::TransactionSigned; +use reth_primitives_traits::SignerRecoverable; use revm::{ context::TxEnv, context_interface::transaction::Transaction, @@ -118,21 +118,10 @@ impl IntoTxEnv for HlTxEnv { } } -fn s_to_address(s: U256) -> Address { - if s == U256::ONE { - return address!("2222222222222222222222222222222222222222"); - } - let mut buf = [0u8; 20]; - buf.copy_from_slice(&s.to_be_bytes::<32>()[12..]); - Address::from_slice(&buf) -} - impl FromRecoveredTx for HlTxEnv { fn from_recovered_tx(tx: &TransactionSigned, sender: Address) -> Self { - if let Some(gas_price) = tx.gas_price() { - if gas_price == 0 { - return Self::new(TxEnv::from_recovered_tx(tx, s_to_address(tx.signature().s()))); - } + if tx.gas_price().is_some() && tx.gas_price().unwrap() == 0 { + return Self::new(TxEnv::from_recovered_tx(tx, tx.recover_signer().unwrap())); } Self::new(TxEnv::from_recovered_tx(tx, sender)) @@ -141,7 +130,7 @@ impl FromRecoveredTx for HlTxEnv { impl FromTxWithEncoded for HlTxEnv { fn from_encoded_tx(tx: &TransactionSigned, sender: Address, _encoded: Bytes) -> Self { - let base = match tx.clone().into_typed_transaction() { + let base = match tx.clone().0.into_typed_transaction() { reth_primitives::Transaction::Legacy(tx) => TxEnv::from_recovered_tx(&tx, sender), reth_primitives::Transaction::Eip2930(tx) => TxEnv::from_recovered_tx(&tx, sender), reth_primitives::Transaction::Eip1559(tx) => TxEnv::from_recovered_tx(&tx, sender), diff --git a/src/node/consensus.rs b/src/node/consensus/mod.rs similarity index 61% rename from src/node/consensus.rs rename to src/node/consensus/mod.rs index 22c71dce7..bc4588283 100644 --- a/src/node/consensus.rs +++ b/src/node/consensus/mod.rs @@ -1,6 +1,4 @@ use crate::{hardforks::HlHardforks, node::HlNode, HlBlock, HlBlockBody, HlPrimitives}; -use alloy_consensus::BlockHeader as _; -use alloy_eips::eip7685::Requests; use reth::{ api::FullNodeTypes, beacon_consensus::EthBeaconConsensus, @@ -10,11 +8,9 @@ use reth::{ validate_against_parent_4844, validate_against_parent_hash_number, }, }; -use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_primitives::{ - gas_spent_by_transactions, GotExpected, Receipt, RecoveredBlock, SealedBlock, SealedHeader, -}; -use reth_primitives_traits::{Block, BlockHeader, Receipt as ReceiptTrait}; +use reth_chainspec::EthChainSpec; +use reth_primitives::{Receipt, RecoveredBlock, SealedBlock, SealedHeader}; +use reth_primitives_traits::BlockHeader; use reth_provider::BlockExecutionResult; use std::sync::Arc; @@ -67,11 +63,8 @@ pub fn validate_against_parent_timestamp( } impl HeaderValidator for HlConsensus { - fn validate_header(&self, _header: &SealedHeader) -> Result<(), ConsensusError> { - // TODO: doesn't work because of extradata check - // self.inner.validate_header(header) - - Ok(()) + fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { + self.inner.validate_header(header) } fn validate_header_against_parent( @@ -142,71 +135,17 @@ impl Consensus for HlConsensus( - block: &RecoveredBlock, - chain_spec: &ChainSpec, - receipts: &[R], - requests: &Requests, -) -> Result<(), ConsensusError> -where - B: Block, - R: ReceiptTrait, - ChainSpec: EthereumHardforks, -{ - use reth_copy::verify_receipts; - // Copy of reth's validate_block_post_execution - // Differences: - // - Filter out system transactions for receipts check - - // Check if gas used matches the value set in header. - let cumulative_gas_used = - receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0); - if block.header().gas_used() != cumulative_gas_used { - return Err(ConsensusError::BlockGasUsed { - gas: GotExpected { got: cumulative_gas_used, expected: block.header().gas_used() }, - gas_spent_by_tx: gas_spent_by_transactions(receipts), - }); - } - - // Before Byzantium, receipts contained state root that would mean that expensive - // operation as hashing that is required for state root got calculated in every - // transaction This was replaced with is_success flag. - // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 - if chain_spec.is_byzantium_active_at_block(block.header().number()) { - let receipts_for_root = - receipts.iter().filter(|&r| r.cumulative_gas_used() != 0).cloned().collect::>(); - if let Err(error) = verify_receipts( - block.header().receipts_root(), - block.header().logs_bloom(), - &receipts_for_root, - ) { - tracing::debug!(%error, ?receipts, "receipts verification failed"); - return Err(error); - } - } - - // Validate that the header requests hash matches the calculated requests hash - if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) { - let Some(header_requests_hash) = block.header().requests_hash() else { - return Err(ConsensusError::RequestsHashMissing); - }; - let requests_hash = requests.requests_hash(); - if requests_hash != header_requests_hash { - return Err(ConsensusError::BodyRequestsHashDiff( - GotExpected::new(requests_hash, header_requests_hash).into(), - )); - } - } - - Ok(()) -} - impl FullConsensus for HlConsensus { fn validate_block_post_execution( &self, block: &RecoveredBlock, result: &BlockExecutionResult, ) -> Result<(), ConsensusError> { - validate_block_post_execution(block, &self.chain_spec, &result.receipts, &result.requests) + reth_copy::validate_block_post_execution( + block, + &self.chain_spec, + &result.receipts, + &result.requests, + ) } } diff --git a/src/node/consensus/reth_copy.rs b/src/node/consensus/reth_copy.rs index 88d172fbd..b1930b0dd 100644 --- a/src/node/consensus/reth_copy.rs +++ b/src/node/consensus/reth_copy.rs @@ -1,14 +1,74 @@ //! Copy of reth codebase. -use alloy_consensus::{proofs::calculate_receipt_root, TxReceipt}; +use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt}; +use alloy_eips::eip7685::Requests; use alloy_primitives::{Bloom, B256}; use reth::consensus::ConsensusError; -use reth_primitives::GotExpected; -use reth_primitives_traits::Receipt; +use reth_chainspec::EthereumHardforks; +use reth_primitives::{gas_spent_by_transactions, GotExpected, RecoveredBlock}; +use reth_primitives_traits::{Block, Receipt as ReceiptTrait}; + +pub fn validate_block_post_execution( + block: &RecoveredBlock, + chain_spec: &ChainSpec, + receipts: &[R], + requests: &Requests, +) -> Result<(), ConsensusError> +where + B: Block, + R: ReceiptTrait, + ChainSpec: EthereumHardforks, +{ + // Copy of reth's validate_block_post_execution + // Differences: + // - Filter out system transactions for receipts check + + // Check if gas used matches the value set in header. + let cumulative_gas_used = + receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0); + if block.header().gas_used() != cumulative_gas_used { + return Err(ConsensusError::BlockGasUsed { + gas: GotExpected { got: cumulative_gas_used, expected: block.header().gas_used() }, + gas_spent_by_tx: gas_spent_by_transactions(receipts), + }); + } + + // Before Byzantium, receipts contained state root that would mean that expensive + // operation as hashing that is required for state root got calculated in every + // transaction This was replaced with is_success flag. + // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 + if chain_spec.is_byzantium_active_at_block(block.header().number()) { + let receipts_for_root = + receipts.iter().filter(|&r| r.cumulative_gas_used() != 0).cloned().collect::>(); + if let Err(error) = verify_receipts( + block.header().receipts_root(), + block.header().logs_bloom(), + &receipts_for_root, + ) { + tracing::debug!(%error, ?receipts, "receipts verification failed"); + return Err(error); + } + } + + // Validate that the header requests hash matches the calculated requests hash + if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) { + let Some(header_requests_hash) = block.header().requests_hash() else { + return Err(ConsensusError::RequestsHashMissing); + }; + let requests_hash = requests.requests_hash(); + if requests_hash != header_requests_hash { + return Err(ConsensusError::BodyRequestsHashDiff( + GotExpected::new(requests_hash, header_requests_hash).into(), + )); + } + } + + Ok(()) +} /// Calculate the receipts root, and compare it against the expected receipts root and logs /// bloom. -pub(super) fn verify_receipts( +pub(super) fn verify_receipts( expected_receipts_root: B256, expected_logs_bloom: Bloom, receipts: &[R], diff --git a/src/node/evm/config.rs b/src/node/evm/config.rs index 2d1492abd..d95ff27ca 100644 --- a/src/node/evm/config.rs +++ b/src/node/evm/config.rs @@ -3,7 +3,11 @@ use crate::{ chainspec::HlChainSpec, evm::{spec::HlSpecId, transaction::HlTxEnv}, hardforks::HlHardforks, - node::{evm::executor::is_system_transaction, types::ReadPrecompileMap}, + node::{ + evm::{executor::is_system_transaction, receipt_builder::RethReceiptBuilder}, + primitives::{BlockBody, TransactionSigned}, + types::ReadPrecompileMap, + }, HlBlock, HlBlockBody, HlPrimitives, }; use alloy_consensus::{BlockHeader, Header, Transaction as _, TxReceipt, EMPTY_OMMER_ROOT_HASH}; @@ -11,7 +15,6 @@ use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::{Log, U256}; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_ethereum_forks::EthereumHardfork; -use reth_ethereum_primitives::BlockBody; use reth_evm::{ block::{BlockExecutionError, BlockExecutorFactory, BlockExecutorFor}, eth::{receipt_builder::ReceiptBuilder, EthBlockExecutionCtx}, @@ -20,10 +23,8 @@ use reth_evm::{ ConfigureEvm, EvmEnv, EvmFactory, ExecutionCtxFor, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, NextBlockEnvAttributes, }; -use reth_evm_ethereum::{EthBlockAssembler, RethReceiptBuilder}; -use reth_primitives::{ - logs_bloom, BlockTy, HeaderTy, Receipt, SealedBlock, SealedHeader, TransactionSigned, -}; +use reth_evm_ethereum::EthBlockAssembler; +use reth_primitives::{logs_bloom, BlockTy, HeaderTy, Receipt, SealedBlock, SealedHeader}; use reth_primitives_traits::proofs; use reth_provider::BlockExecutionResult; use reth_revm::State; diff --git a/src/node/evm/executor.rs b/src/node/evm/executor.rs index 42b985457..efa592a2f 100644 --- a/src/node/evm/executor.rs +++ b/src/node/evm/executor.rs @@ -2,7 +2,10 @@ use super::{config::HlBlockExecutionCtx, patch::patch_mainnet_after_tx}; use crate::{ evm::transaction::HlTxEnv, hardforks::HlHardforks, - node::types::{ReadPrecompileInput, ReadPrecompileResult}, + node::{ + primitives::TransactionSigned, + types::{ReadPrecompileInput, ReadPrecompileResult}, + }, }; use alloy_consensus::{Transaction, TxReceipt}; use alloy_eips::{eip7685::Requests, Encodable2718}; @@ -16,7 +19,6 @@ use reth_evm::{ precompiles::{DynPrecompile, PrecompilesMap}, Database, Evm, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, OnStateHook, RecoveredTx, }; -use reth_primitives::TransactionSigned; use reth_provider::BlockExecutionResult; use reth_revm::State; use revm::{ @@ -98,23 +100,7 @@ where { /// Creates a new HlBlockExecutor. pub fn new(mut evm: EVM, ctx: HlBlockExecutionCtx<'a>, spec: Spec, receipt_builder: R) -> Self { - let precompiles_mut = evm.precompiles_mut(); - // For all precompile addresses just in case it's populated and not cleared - // Clear 0x00...08xx addresses - let addresses = precompiles_mut.addresses().cloned().collect::>(); - for address in addresses { - if address.starts_with(&[0u8; 18]) && address[19] == 8 { - precompiles_mut.apply_precompile(&address, |_| None); - } - } - for (address, precompile) in ctx.read_precompile_calls.iter() { - let precompile = precompile.clone(); - precompiles_mut.apply_precompile(address, |_| { - Some(DynPrecompile::from(move |data: &[u8], gas: u64| { - run_precompile(&precompile, data, gas) - })) - }); - } + apply_precompiles(&mut evm, &ctx); Self { spec, evm, gas_used: 0, receipts: vec![], receipt_builder, ctx } } } @@ -127,6 +113,7 @@ where Tx: FromRecoveredTx + FromRecoveredTx + FromTxWithEncoded, + Precompiles = PrecompilesMap, >, Spec: EthereumHardforks + HlHardforks + EthChainSpec + Hardforks, R: ReceiptBuilder, @@ -140,6 +127,7 @@ where type Evm = E; fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + apply_precompiles(&mut self.evm, &self.ctx); Ok(()) } @@ -223,3 +211,27 @@ where &self.evm } } + +fn apply_precompiles<'a, DB, EVM>(evm: &mut EVM, ctx: &HlBlockExecutionCtx<'a>) +where + EVM: Evm, Precompiles = PrecompilesMap>, + DB: Database + 'a, +{ + let precompiles_mut = evm.precompiles_mut(); + // For all precompile addresses just in case it's populated and not cleared + // Clear 0x00...08xx addresses + let addresses = precompiles_mut.addresses().cloned().collect::>(); + for address in addresses { + if address.starts_with(&[0u8; 18]) && address[19] == 8 { + precompiles_mut.apply_precompile(&address, |_| None); + } + } + for (address, precompile) in ctx.read_precompile_calls.iter() { + let precompile = precompile.clone(); + precompiles_mut.apply_precompile(address, |_| { + Some(DynPrecompile::from(move |data: &[u8], gas: u64| { + run_precompile(&precompile, data, gas) + })) + }); + } +} diff --git a/src/node/evm/mod.rs b/src/node/evm/mod.rs index cc92056e0..b1ce8ee08 100644 --- a/src/node/evm/mod.rs +++ b/src/node/evm/mod.rs @@ -29,6 +29,7 @@ pub mod config; mod executor; mod factory; mod patch; +pub mod receipt_builder; /// HL EVM implementation. /// diff --git a/src/node/evm/receipt_builder.rs b/src/node/evm/receipt_builder.rs new file mode 100644 index 000000000..d6a758b68 --- /dev/null +++ b/src/node/evm/receipt_builder.rs @@ -0,0 +1,30 @@ +use crate::node::primitives::TransactionSigned; +use alloy_evm::eth::receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx}; +use reth_evm::Evm; +use reth_primitives::Receipt; + +/// A builder that operates on Reth primitive types, specifically [`TransactionSigned`] and +/// [`Receipt`]. +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct RethReceiptBuilder; + +impl ReceiptBuilder for RethReceiptBuilder { + type Transaction = TransactionSigned; + type Receipt = Receipt; + + fn build_receipt( + &self, + ctx: ReceiptBuilderCtx<'_, Self::Transaction, E>, + ) -> Self::Receipt { + let ReceiptBuilderCtx { tx, result, cumulative_gas_used, .. } = ctx; + Receipt { + tx_type: tx.tx_type(), + // Success flag was added in `EIP-658: Embedding transaction status code in + // receipts`. + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs(), + } + } +} diff --git a/src/node/mod.rs b/src/node/mod.rs index a9595649b..450783415 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,7 +1,8 @@ use crate::{ chainspec::HlChainSpec, node::{ - primitives::{HlBlock, HlBlockBody, HlPrimitives}, + pool::HlPoolBuilder, + primitives::{BlockBody, HlBlock, HlBlockBody, HlPrimitives, TransactionSigned}, rpc::{ engine_api::{ builder::HlEngineApiBuilder, payload::HlPayloadTypes, @@ -24,8 +25,6 @@ use reth::{ }, }; use reth_engine_primitives::BeaconConsensusEngineHandle; -use reth_node_ethereum::node::EthereumPoolBuilder; -use reth_primitives::BlockBody; use reth_trie_db::MerklePatriciaTrie; use std::sync::Arc; use tokio::sync::{oneshot, Mutex}; @@ -59,12 +58,14 @@ impl HlNode { } } +mod pool; + impl HlNode { pub fn components( &self, ) -> ComponentsBuilder< Node, - EthereumPoolBuilder, + HlPoolBuilder, HlPayloadServiceBuilder, HlNetworkBuilder, HlExecutorBuilder, @@ -75,7 +76,7 @@ impl HlNode { { ComponentsBuilder::default() .node_types::() - .pool(EthereumPoolBuilder::default()) + .pool(HlPoolBuilder) .executor(HlExecutorBuilder::default()) .payload(HlPayloadServiceBuilder::default()) .network(HlNetworkBuilder { engine_handle_rx: self.engine_handle_rx.clone() }) @@ -97,7 +98,7 @@ where { type ComponentsBuilder = ComponentsBuilder< N, - EthereumPoolBuilder, + HlPoolBuilder, HlPayloadServiceBuilder, HlNetworkBuilder, HlExecutorBuilder, @@ -131,7 +132,7 @@ where inner: BlockBody { transactions: transactions .into_transactions() - .map(|tx| tx.inner.into_inner().into()) + .map(|tx| TransactionSigned(tx.inner.into_inner().into())) .collect(), ommers: Default::default(), withdrawals, diff --git a/src/node/network/block_import/service.rs b/src/node/network/block_import/service.rs index 511382c8c..eb82f0aec 100644 --- a/src/node/network/block_import/service.rs +++ b/src/node/network/block_import/service.rs @@ -64,7 +64,6 @@ where to_network: UnboundedSender, /// Pending block imports. pending_imports: FuturesUnordered, - height: u64, } impl ImportService @@ -77,7 +76,6 @@ where engine: BeaconConsensusEngineHandle, from_network: UnboundedReceiver, to_network: UnboundedSender, - height: u64, ) -> Self { Self { engine, @@ -85,7 +83,6 @@ where from_network, to_network, pending_imports: FuturesUnordered::new(), - height, } } @@ -172,23 +169,10 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - let prev_height = this.height; // Receive new blocks from network - while let Some(block) = collect_block(this.height) { - if this.height > prev_height + 1000 { - break; - } - - let peer_id = PeerId::random(); - let reth_block = block.to_reth_block(); - let td = U128::from(reth_block.header().difficulty()); - let msg = NewBlockMessage { - hash: reth_block.header().hash_slow(), - block: Arc::new(HlNewBlock(NewBlock { block: reth_block, td })), - }; - this.on_new_block(msg, peer_id); - this.height += 1; + while let Poll::Ready(Some((block, peer_id))) = this.from_network.poll_recv(cx) { + this.on_new_block(block, peer_id); } // Process completed imports and send events to network @@ -366,7 +350,7 @@ mod tests { let handle = ImportHandle::new(to_import, import_outcome); - let service = ImportService::new(consensus, engine_handle, from_network, to_network, 1); + let service = ImportService::new(consensus, engine_handle, from_network, to_network); tokio::spawn(Box::pin(async move { service.await.unwrap(); })); diff --git a/src/node/network/mod.rs b/src/node/network/mod.rs index fa6bea32d..a3fd8d80c 100644 --- a/src/node/network/mod.rs +++ b/src/node/network/mod.rs @@ -11,7 +11,6 @@ use crate::{ HlBlock, }; use alloy_rlp::{Decodable, Encodable}; -use reth_provider::BlockNumReader; // use handshake::HlHandshake; use reth::{ api::{FullNodeTypes, TxTy}, @@ -37,12 +36,14 @@ pub struct HlNewBlock(pub NewBlock); mod rlp { use super::*; - use crate::HlBlockBody; - use alloy_consensus::{BlobTransactionSidecar, BlockBody, Header}; + use crate::{ + node::primitives::{BlockBody, TransactionSigned}, + HlBlockBody, + }; + use alloy_consensus::{BlobTransactionSidecar, Header}; use alloy_primitives::U128; use alloy_rlp::{RlpDecodable, RlpEncodable}; use alloy_rpc_types::Withdrawals; - use reth_primitives::TransactionSigned; use std::borrow::Cow; #[derive(RlpEncodable, RlpDecodable)] @@ -170,8 +171,6 @@ impl HlNetworkBuilder { let handle = ImportHandle::new(to_import, import_outcome); let consensus = Arc::new(HlConsensus { provider: ctx.provider().clone() }); - let number = ctx.provider().last_block_number().unwrap_or(1); - let number = std::cmp::max(number, 1); ctx.task_executor().spawn_critical("block import", async move { let handle = engine_handle_rx @@ -182,12 +181,13 @@ impl HlNetworkBuilder { .await .unwrap(); - ImportService::new(consensus, handle, from_network, to_network, number).await.unwrap(); + ImportService::new(consensus, handle, from_network, to_network).await.unwrap(); }); let network_builder = network_builder .boot_nodes(boot_nodes()) .set_head(ctx.head()) + .with_pow() .block_import(Box::new(HlBlockImport::new(handle))); // .discovery(discv4) // .eth_rlpx_handshake(Arc::new(HlHandshake::default())); diff --git a/src/node/pool.rs b/src/node/pool.rs new file mode 100644 index 000000000..961661bec --- /dev/null +++ b/src/node/pool.rs @@ -0,0 +1,436 @@ +//! A placeholder for the transaction pool. +//! +//! We'll not use transaction pool in HL, since this node does not write to HL consensus; +//! just a minimum implementation to make the node compile would suffice. +//! +//! Ethereum transaction pool only supports TransactionSigned (EthereumTxEnvelope), +//! hence this placeholder for the transaction pool. + +use crate::node::{primitives::TransactionSigned, HlNode}; +use alloy_consensus::{ + error::ValueError, EthereumTxEnvelope, Transaction as TransactionTrait, TxEip4844, +}; +use alloy_eips::{ + eip4844::BlobAndProofV2, eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, + Typed2718, +}; +use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, B256, U256}; +use alloy_rpc_types::AccessList; +use alloy_rpc_types_engine::BlobAndProofV1; +use reth::{ + api::FullNodeTypes, + builder::components::PoolBuilder, + transaction_pool::{PoolResult, PoolSize, PoolTransaction, TransactionOrigin, TransactionPool}, +}; +use reth_eth_wire::HandleMempoolData; +use reth_ethereum_primitives::PooledTransactionVariant; +use reth_primitives::Recovered; +use reth_primitives_traits::InMemorySize; +use reth_transaction_pool::{ + error::InvalidPoolTransactionError, AllPoolTransactions, AllTransactionsEvents, BestTransactions, BestTransactionsAttributes, BlobStoreError, BlockInfo, EthPoolTransaction, GetPooledTransactionLimit, NewBlobSidecar, NewTransactionEvent, PropagatedTransactions, TransactionEvents, TransactionListenerKind, ValidPoolTransaction +}; +use std::{collections::HashSet, sync::Arc}; +use tokio::sync::mpsc::{self, Receiver}; + +pub struct HlPoolBuilder; +impl PoolBuilder for HlPoolBuilder +where + Node: FullNodeTypes, +{ + type Pool = HlTransactionPool; + + async fn build_pool( + self, + _ctx: &reth::builder::BuilderContext, + ) -> eyre::Result { + Ok(HlTransactionPool) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct HlPooledTransaction; + +impl InMemorySize for HlPooledTransaction { + fn size(&self) -> usize { + 0 + } +} + +impl TransactionTrait for HlPooledTransaction { + fn chain_id(&self) -> Option { + unreachable!() + } + fn nonce(&self) -> u64 { + unreachable!() + } + fn gas_limit(&self) -> u64 { + unreachable!() + } + fn gas_price(&self) -> Option { + unreachable!() + } + fn max_fee_per_gas(&self) -> u128 { + unreachable!() + } + fn max_priority_fee_per_gas(&self) -> Option { + unreachable!() + } + fn max_fee_per_blob_gas(&self) -> Option { + unreachable!() + } + fn priority_fee_or_price(&self) -> u128 { + unreachable!() + } + fn effective_gas_price(&self, _base_fee: Option) -> u128 { + unreachable!() + } + fn is_dynamic_fee(&self) -> bool { + unreachable!() + } + fn kind(&self) -> TxKind { + unreachable!() + } + fn is_create(&self) -> bool { + unreachable!() + } + fn value(&self) -> U256 { + unreachable!() + } + fn input(&self) -> &Bytes { + unreachable!() + } + fn access_list(&self) -> Option<&AccessList> { + unreachable!() + } + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + unreachable!() + } + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + unreachable!() + } +} + +impl Typed2718 for HlPooledTransaction { + fn ty(&self) -> u8 { + unreachable!() + } +} + +impl PoolTransaction for HlPooledTransaction { + type TryFromConsensusError = ValueError>; + type Consensus = TransactionSigned; + type Pooled = PooledTransactionVariant; + + fn try_from_consensus( + _tx: Recovered, + ) -> Result { + unreachable!() + } + + fn clone_into_consensus(&self) -> Recovered { + unreachable!() + } + + fn into_consensus(self) -> Recovered { + unreachable!() + } + + fn from_pooled(_tx: Recovered) -> Self { + unreachable!() + } + + fn hash(&self) -> &TxHash { + unreachable!() + } + + fn sender(&self) -> Address { + unreachable!() + } + + fn sender_ref(&self) -> &Address { + unreachable!() + } + + fn cost(&self) -> &U256 { + unreachable!() + } + + fn encoded_length(&self) -> usize { + 0 + } + + fn ensure_max_init_code_size( + &self, + _max_init_code_size: usize, + ) -> Result<(), InvalidPoolTransactionError> { + Ok(()) + } +} + +impl EthPoolTransaction for HlPooledTransaction { + fn take_blob(&mut self) -> reth_transaction_pool::EthBlobTransactionSidecar { + unreachable!() + } + + fn try_into_pooled_eip4844( + self, + _sidecar: Arc, + ) -> Option> { + None + } + + fn try_from_eip4844( + _tx: Recovered, + _sidecar: alloy_eips::eip7594::BlobTransactionSidecarVariant, + ) -> Option { + None + } + + fn validate_blob( + &self, + _blob: &alloy_eips::eip7594::BlobTransactionSidecarVariant, + _settings: &alloy_eips::eip4844::env_settings::KzgSettings, + ) -> Result<(), alloy_consensus::BlobTransactionValidationError> { + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct HlTransactionPool; +impl TransactionPool for HlTransactionPool { + type Transaction = HlPooledTransaction; + + fn pool_size(&self) -> PoolSize { + PoolSize::default() + } + + fn block_info(&self) -> BlockInfo { + BlockInfo::default() + } + + async fn add_transaction_and_subscribe( + &self, + _origin: TransactionOrigin, + _transaction: Self::Transaction, + ) -> PoolResult { + unreachable!() + } + + async fn add_transaction( + &self, + _origin: TransactionOrigin, + _transaction: Self::Transaction, + ) -> PoolResult { + Ok(TxHash::default()) + } + + async fn add_transactions( + &self, + _origin: TransactionOrigin, + _transactions: Vec, + ) -> Vec> { + vec![] + } + + fn transaction_event_listener(&self, _tx_hash: TxHash) -> Option { + None + } + + fn all_transactions_event_listener(&self) -> AllTransactionsEvents { + unreachable!() + } + + fn pending_transactions_listener_for( + &self, + _kind: TransactionListenerKind, + ) -> Receiver { + mpsc::channel(1).1 + } + + fn blob_transaction_sidecars_listener(&self) -> Receiver { + mpsc::channel(1).1 + } + + fn new_transactions_listener_for( + &self, + _kind: TransactionListenerKind, + ) -> Receiver> { + mpsc::channel(1).1 + } + fn pooled_transaction_hashes(&self) -> Vec { + vec![] + } + fn pooled_transaction_hashes_max(&self, _max: usize) -> Vec { + vec![] + } + fn pooled_transactions(&self) -> Vec>> { + vec![] + } + fn pooled_transactions_max( + &self, + _max: usize, + ) -> Vec>> { + vec![] + } + fn get_pooled_transaction_elements( + &self, + _tx_hashes: Vec, + _limit: GetPooledTransactionLimit, + ) -> Vec<::Pooled> { + vec![] + } + fn get_pooled_transaction_element( + &self, + _tx_hash: TxHash, + ) -> Option::Pooled>> { + None + } + fn best_transactions( + &self, + ) -> Box>>> { + Box::new(std::iter::empty()) + } + fn best_transactions_with_attributes( + &self, + _best_transactions_attributes: BestTransactionsAttributes, + ) -> Box>>> { + Box::new(std::iter::empty()) + } + fn pending_transactions(&self) -> Vec>> { + vec![] + } + fn pending_transactions_max( + &self, + _max: usize, + ) -> Vec>> { + vec![] + } + fn queued_transactions(&self) -> Vec>> { + vec![] + } + fn all_transactions(&self) -> AllPoolTransactions { + AllPoolTransactions::default() + } + fn remove_transactions( + &self, + _hashes: Vec, + ) -> Vec>> { + vec![] + } + fn remove_transactions_and_descendants( + &self, + _hashes: Vec, + ) -> Vec>> { + vec![] + } + fn remove_transactions_by_sender( + &self, + _sender: Address, + ) -> Vec>> { + vec![] + } + fn retain_unknown(&self, _announcement: &mut A) + where + A: HandleMempoolData, + { + // do nothing + } + fn get(&self, _tx_hash: &TxHash) -> Option>> { + None + } + fn get_all(&self, _txs: Vec) -> Vec>> { + vec![] + } + fn on_propagated(&self, _txs: PropagatedTransactions) { + // do nothing + } + fn get_transactions_by_sender( + &self, + _sender: Address, + ) -> Vec>> { + vec![] + } + fn get_pending_transactions_with_predicate( + &self, + _predicate: impl FnMut(&ValidPoolTransaction) -> bool, + ) -> Vec>> { + vec![] + } + fn get_pending_transactions_by_sender( + &self, + _sender: Address, + ) -> Vec>> { + vec![] + } + fn get_queued_transactions_by_sender( + &self, + _sender: Address, + ) -> Vec>> { + unreachable!() + } + fn get_highest_transaction_by_sender( + &self, + _sender: Address, + ) -> Option>> { + None + } + fn get_highest_consecutive_transaction_by_sender( + &self, + _sender: Address, + _on_chain_nonce: u64, + ) -> Option>> { + None + } + fn get_transaction_by_sender_and_nonce( + &self, + _sender: Address, + _nonce: u64, + ) -> Option>> { + None + } + fn get_transactions_by_origin( + &self, + _origin: TransactionOrigin, + ) -> Vec>> { + unreachable!() + } + fn get_pending_transactions_by_origin( + &self, + _origin: TransactionOrigin, + ) -> Vec>> { + unreachable!() + } + fn unique_senders(&self) -> HashSet
{ + unreachable!() + } + fn get_blob( + &self, + _tx_hash: TxHash, + ) -> Result>, BlobStoreError> { + unreachable!() + } + fn get_all_blobs( + &self, + _tx_hashes: Vec, + ) -> Result)>, BlobStoreError> { + unreachable!() + } + fn get_all_blobs_exact( + &self, + _tx_hashes: Vec, + ) -> Result>, BlobStoreError> { + unreachable!() + } + fn get_blobs_for_versioned_hashes_v1( + &self, + _versioned_hashes: &[B256], + ) -> Result>, BlobStoreError> { + unreachable!() + } + fn get_blobs_for_versioned_hashes_v2( + &self, + _versioned_hashes: &[B256], + ) -> Result>, BlobStoreError> { + unreachable!() + } +} diff --git a/src/node/primitives.rs b/src/node/primitives/mod.rs similarity index 94% rename from src/node/primitives.rs rename to src/node/primitives/mod.rs index ef06be8f0..1bc38383a 100644 --- a/src/node/primitives.rs +++ b/src/node/primitives/mod.rs @@ -1,14 +1,17 @@ #![allow(clippy::owned_cow)] use alloy_consensus::{BlobTransactionSidecar, Header}; use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable}; -use reth_ethereum_primitives::{BlockBody, Receipt}; -use reth_primitives::{NodePrimitives, TransactionSigned}; +use reth_ethereum_primitives::Receipt; +use reth_primitives::NodePrimitives; use reth_primitives_traits::{Block, BlockBody as BlockBodyTrait, InMemorySize}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use crate::node::types::{ReadPrecompileCall, ReadPrecompileCalls}; +pub mod tx_wrapper; +pub use tx_wrapper::{BlockBody, TransactionSigned}; + /// Primitive types for HyperEVM. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[non_exhaustive] @@ -46,11 +49,13 @@ pub struct HlBlockBody { impl InMemorySize for HlBlockBody { fn size(&self) -> usize { - self.inner.size() + - self.sidecars + self.inner.size() + + self + .sidecars .as_ref() - .map_or(0, |s| s.capacity() * core::mem::size_of::()) + - self.read_precompile_calls + .map_or(0, |s| s.capacity() * core::mem::size_of::()) + + self + .read_precompile_calls .as_ref() .map_or(0, |s| s.0.capacity() * core::mem::size_of::()) } @@ -79,6 +84,16 @@ impl BlockBodyTrait for HlBlockBody { fn ommers(&self) -> Option<&[Self::OmmerHeader]> { self.inner.ommers() } + + fn calculate_tx_root(&self) -> alloy_primitives::B256 { + alloy_consensus::proofs::calculate_transaction_root( + &self + .transactions() + .iter() + .filter(|tx| !tx.is_system_transaction()) + .collect::>(), + ) + } } /// Block for HL diff --git a/src/node/primitives/tx_wrapper.rs b/src/node/primitives/tx_wrapper.rs new file mode 100644 index 000000000..223ba5726 --- /dev/null +++ b/src/node/primitives/tx_wrapper.rs @@ -0,0 +1,398 @@ +//! HlNodePrimitives::TransactionSigned; it's the same as ethereum transaction type, +//! except that it supports pseudo signer for system transactions. +use std::hash::Hasher; + +use alloy_consensus::{ + crypto::RecoveryError, EthereumTxEnvelope, Signed, Transaction as TransactionTrait, TxEip1559, + TxEip2930, TxEip4844, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxLegacy, TxType, + TypedTransaction, +}; +use alloy_eips::{ + eip2718::Eip2718Result, eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, + Decodable2718, Encodable2718, Typed2718, +}; +use alloy_primitives::{address, Address, Bytes, TxHash, TxKind, Uint, B256, U256}; +use alloy_rpc_types::AccessList; +use alloy_signer::Signature; +use reth_codecs::alloy::transaction::FromTxCompact; +use reth_db::{ + table::{Compress, Decompress}, + DatabaseError, +}; +use reth_evm::FromRecoveredTx; +use reth_primitives::Recovered; +use reth_primitives_traits::{ + serde_bincode_compat::SerdeBincodeCompat, InMemorySize, SignedTransaction, SignerRecoverable, +}; +use revm::context::TxEnv; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +type InnerType = alloy_consensus::EthereumTxEnvelope; + +#[derive(Debug, Clone, Eq)] +pub struct TransactionSigned(pub InnerType); + +fn s_to_address(s: U256) -> Address { + if s == U256::ONE { + return address!("2222222222222222222222222222222222222222"); + } + let mut buf = [0u8; 20]; + buf[0..20].copy_from_slice(&s.to_be_bytes::<32>()[12..32]); + Address::from_slice(&buf) +} + +impl SignerRecoverable for TransactionSigned { + fn recover_signer(&self) -> Result { + if self.is_system_transaction() { + return Ok(s_to_address(self.signature().s())); + } + self.0.recover_signer() + } + + fn recover_signer_unchecked(&self) -> Result { + if self.is_system_transaction() { + return Ok(s_to_address(self.signature().s())); + } + self.0.recover_signer_unchecked() + } +} + +impl SignedTransaction for TransactionSigned { + fn tx_hash(&self) -> &TxHash { + self.0.tx_hash() + } + + fn recover_signer_unchecked_with_buf( + &self, + buf: &mut Vec, + ) -> Result { + if self.is_system_transaction() { + return Ok(s_to_address(self.signature().s())); + } + + self.0.recover_signer_unchecked_with_buf(buf) + } +} + +// ------------------------------------------------------------ +// NOTE: All lines below are just wrappers for the inner type. +// ------------------------------------------------------------ + +impl Serialize for TransactionSigned { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TransactionSigned { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self(InnerType::deserialize(deserializer)?)) + } +} + +macro_rules! impl_from_signed { + ($($tx:ident),*) => { + $( + impl From> for TransactionSigned { + fn from(value: Signed<$tx>) -> Self { + Self(value.into()) + } + } + )* + }; +} + +impl_from_signed!(TxLegacy, TxEip2930, TxEip1559, TxEip7702, TypedTransaction); + +impl InMemorySize for TransactionSigned { + #[inline] + fn size(&self) -> usize { + self.0.size() + } +} + +impl alloy_rlp::Encodable for TransactionSigned { + fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) { + self.0.encode(out); + } + + fn length(&self) -> usize { + self.0.length() + } +} + +impl alloy_rlp::Decodable for TransactionSigned { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self(TxEnvelope::decode(buf)?.into())) + } +} + +impl Encodable2718 for TransactionSigned { + fn type_flag(&self) -> Option { + self.0.type_flag() + } + + fn encode_2718_len(&self) -> usize { + self.0.encode_2718_len() + } + + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + self.0.encode_2718(out) + } +} + +impl Decodable2718 for TransactionSigned { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result { + Ok(Self(TxEnvelope::typed_decode(ty, buf)?.into())) + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result { + Ok(Self(TxEnvelope::fallback_decode(buf)?.into())) + } +} + +impl Typed2718 for TransactionSigned { + fn ty(&self) -> u8 { + self.0.ty() + } +} + +impl PartialEq for TransactionSigned { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl core::hash::Hash for TransactionSigned { + fn hash(&self, state: &mut H) { + core::hash::Hash::hash(&self.0, state); + } +} + +impl TransactionTrait for TransactionSigned { + fn chain_id(&self) -> Option { + self.0.chain_id() + } + + fn nonce(&self) -> u64 { + self.0.nonce() + } + + fn gas_limit(&self) -> u64 { + self.0.gas_limit() + } + + fn gas_price(&self) -> Option { + self.0.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.0.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.0.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.0.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.0.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + self.0.effective_gas_price(base_fee) + } + + fn effective_tip_per_gas(&self, base_fee: u64) -> Option { + self.0.effective_tip_per_gas(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.0.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.0.kind() + } + + fn is_create(&self) -> bool { + self.0.is_create() + } + + fn value(&self) -> Uint<256, 4> { + self.0.value() + } + + fn input(&self) -> &Bytes { + self.0.input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.0.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.0.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.0.authorization_list() + } +} + +impl reth_codecs::Compact for TransactionSigned { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + self.0.to_compact(buf) + } + + fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) { + let (tx, hash) = InnerType::from_compact(buf, _len); + (Self(tx), hash) + } +} + +pub fn convert_recovered(value: Recovered) -> Recovered { + let (tx, signer) = value.into_parts(); + Recovered::new_unchecked(tx.0, signer) +} + +impl FromRecoveredTx for TxEnv { + fn from_recovered_tx(tx: &TransactionSigned, sender: Address) -> Self { + TxEnv::from_recovered_tx(&tx.0, sender) + } +} + +impl FromTxCompact for TransactionSigned { + type TxType = TxType; + + fn from_tx_compact(buf: &[u8], tx_type: Self::TxType, signature: Signature) -> (Self, &[u8]) + where + Self: Sized, + { + let (tx, buf) = InnerType::from_tx_compact(buf, tx_type, signature); + (Self(tx), buf) + } +} + +impl reth_codecs::alloy::transaction::Envelope for TransactionSigned { + fn signature(&self) -> &Signature { + self.0.signature() + } + + fn tx_type(&self) -> Self::TxType { + self.0.tx_type() + } +} + +impl TransactionSigned { + pub const fn signature(&self) -> &Signature { + self.0.signature() + } + + pub const fn tx_type(&self) -> TxType { + self.0.tx_type() + } + + pub fn is_system_transaction(&self) -> bool { + self.gas_price().is_some() && self.gas_price().unwrap() == 0 + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct BincodeCompatTxCustom(pub TransactionSigned); + +impl SerdeBincodeCompat for TransactionSigned { + type BincodeRepr<'a> = BincodeCompatTxCustom; + + fn as_repr(&self) -> Self::BincodeRepr<'_> { + BincodeCompatTxCustom(self.clone()) + } + + fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { + repr.0 + } +} + +pub type BlockBody = alloy_consensus::BlockBody; + +impl From for EthereumTxEnvelope { + fn from(value: TransactionSigned) -> Self { + value.0 + } +} + +impl TryFrom for EthereumTxEnvelope { + type Error = >>::Error; + + fn try_from(value: TransactionSigned) -> Result { + value.0.try_into() + } +} + +impl TryFrom + for EthereumTxEnvelope> +{ + type Error = >, + >>::Error; + + fn try_from(value: TransactionSigned) -> Result { + value.0.try_into() + } +} + +impl From>> + for TransactionSigned +{ + fn from( + value: EthereumTxEnvelope>, + ) -> Self { + Self(value.into()) + } +} + +impl Compress for TransactionSigned { + type Compressed = Vec; + + fn compress(self) -> Self::Compressed { + self.0.compress() + } + + fn compress_to_buf>(&self, buf: &mut B) { + self.0.compress_to_buf(buf); + } +} + +impl Decompress for TransactionSigned { + fn decompress(value: &[u8]) -> Result { + Ok(Self(InnerType::decompress(value)?)) + } +} + +pub fn convert_to_eth_block_body(value: BlockBody) -> alloy_consensus::BlockBody { + alloy_consensus::BlockBody { + transactions: value.transactions.into_iter().map(|tx| tx.0).collect(), + ommers: value.ommers, + withdrawals: value.withdrawals, + } +} + +pub fn convert_to_hl_block_body(value: alloy_consensus::BlockBody) -> BlockBody { + BlockBody { + transactions: value.transactions.into_iter().map(TransactionSigned).collect(), + ommers: value.ommers, + withdrawals: value.withdrawals, + } +} diff --git a/src/node/rpc/block.rs b/src/node/rpc/block.rs index d04815441..44af38643 100644 --- a/src/node/rpc/block.rs +++ b/src/node/rpc/block.rs @@ -1,6 +1,7 @@ use crate::{ chainspec::HlChainSpec, node::{ + primitives::TransactionSigned, rpc::{HlEthApi, HlNodeCore}, HlBlock, HlPrimitives, }, @@ -10,7 +11,7 @@ use alloy_primitives::B256; use reth::{ api::NodeTypes, builder::FullNodeComponents, - primitives::{Receipt, SealedHeader, TransactionMeta, TransactionSigned}, + primitives::{Receipt, SealedHeader, TransactionMeta}, providers::{BlockReaderIdExt, ProviderHeader, ReceiptProvider, TransactionsProvider}, rpc::{ eth::EthApiTypes, @@ -64,7 +65,7 @@ where .enumerate() .map(|(idx, (tx, receipt))| { let meta = TransactionMeta { - tx_hash: *tx.tx_hash(), + tx_hash: *tx.0.tx_hash(), index: idx as u64, block_hash, block_number, @@ -72,7 +73,7 @@ where excess_blob_gas, timestamp, }; - EthReceiptBuilder::new(tx, meta, receipt, &receipts, blob_params) + EthReceiptBuilder::new(&tx.0, meta, receipt, &receipts, blob_params) .map(|builder| builder.build()) }) .collect::, Self::Error>>() @@ -164,6 +165,6 @@ where .ok_or(EthApiError::HeaderNotFound(hash.into()))?; let blob_params = self.provider().chain_spec().blob_params_at_timestamp(meta.timestamp); - Ok(EthReceiptBuilder::new(&tx, meta, &receipt, &all_receipts, blob_params)?.build()) + Ok(EthReceiptBuilder::new(&tx.0, meta, &receipt, &all_receipts, blob_params)?.build()) } } diff --git a/src/node/rpc/transaction.rs b/src/node/rpc/transaction.rs index 24e960a22..4b9ed4b84 100644 --- a/src/node/rpc/transaction.rs +++ b/src/node/rpc/transaction.rs @@ -1,10 +1,16 @@ use super::HlNodeCore; -use crate::{node::rpc::HlEthApi, HlPrimitives}; +use crate::{ + node::{ + primitives::{tx_wrapper::convert_recovered, TransactionSigned}, + rpc::HlEthApi, + }, + HlPrimitives, +}; use alloy_network::{Ethereum, Network}; use alloy_primitives::{Bytes, Signature, B256}; use reth::{ builder::FullNodeComponents, - primitives::{Receipt, Recovered, TransactionSigned}, + primitives::{Receipt, Recovered}, providers::ReceiptProvider, rpc::{ eth::helpers::types::EthRpcConverter, @@ -18,6 +24,7 @@ use reth_rpc_eth_api::{ helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking}, FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat, }; + impl LoadTransaction for HlEthApi where Self: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt, @@ -41,7 +48,7 @@ where tx_info: TransactionInfo, ) -> Result { let builder = EthRpcConverter::default(); - builder.fill(tx, tx_info) + builder.fill(convert_recovered(tx), tx_info) } fn build_simulate_v1_transaction( @@ -54,7 +61,10 @@ where // Create an empty signature for the transaction. let signature = Signature::new(Default::default(), Default::default(), false); - Ok(TransactionSigned::new_unhashed(tx.into(), signature)) + Ok(TransactionSigned(reth_primitives::TransactionSigned::new_unhashed( + tx.into(), + signature, + ))) } } diff --git a/src/node/storage/mod.rs b/src/node/storage/mod.rs index 4df8c7028..eab40d5b5 100644 --- a/src/node/storage/mod.rs +++ b/src/node/storage/mod.rs @@ -1,4 +1,10 @@ -use crate::{node::types::ReadPrecompileCalls, HlBlock, HlBlockBody, HlPrimitives}; +use crate::{ + node::{ + primitives::tx_wrapper::{convert_to_eth_block_body, convert_to_hl_block_body}, + types::ReadPrecompileCalls, + }, + HlBlock, HlBlockBody, HlPrimitives, +}; use alloy_consensus::BlockHeader; use alloy_primitives::Bytes; use reth_chainspec::EthereumHardforks; @@ -87,7 +93,7 @@ where for (block_number, body) in bodies { match body { Some(HlBlockBody { inner, sidecars: _, read_precompile_calls: rpc }) => { - eth_bodies.push((block_number, Some(inner))); + eth_bodies.push((block_number, Some(convert_to_eth_block_body(inner)))); read_precompile_calls.push((block_number, rpc)); } None => { @@ -128,14 +134,22 @@ where inputs: Vec>, ) -> ProviderResult> { let read_precompile_calls = self.read_precompile_calls(provider, &inputs)?; - let eth_bodies = self.0.read_block_bodies(provider, inputs)?; + let eth_bodies = self.0.read_block_bodies( + provider, + inputs + .into_iter() + .map(|(header, transactions)| { + (header, transactions.into_iter().map(|tx| tx.0).collect()) + }) + .collect(), + )?; // NOTE: sidecars are not used in HyperEVM yet. Ok(eth_bodies .into_iter() .zip(read_precompile_calls) .map(|(inner, read_precompile_calls)| HlBlockBody { - inner, + inner: convert_to_hl_block_body(inner), sidecars: None, read_precompile_calls, }) diff --git a/src/node/types.rs b/src/node/types/mod.rs similarity index 91% rename from src/node/types.rs rename to src/node/types/mod.rs index 55890eabb..ed4f05220 100644 --- a/src/node/types.rs +++ b/src/node/types/mod.rs @@ -2,7 +2,7 @@ //! //! Changes: //! - ReadPrecompileCalls supports RLP encoding / decoding -use alloy_primitives::{Address, Bytes, Log}; +use alloy_primitives::{Address, Bytes, Log, B256}; use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable}; use bytes::BufMut; use revm::primitives::HashMap; @@ -69,6 +69,16 @@ impl BlockAndReceipts { MAINNET_CHAIN_ID, ) } + + pub fn hash(&self) -> B256 { + let EvmBlock::Reth115(block) = &self.block; + block.header.hash + } + + pub fn number(&self) -> u64 { + let EvmBlock::Reth115(block) = &self.block; + block.header.header.number + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/node/types/reth_compat.rs b/src/node/types/reth_compat.rs index ff60d02f2..faa5a67f0 100644 --- a/src/node/types/reth_compat.rs +++ b/src/node/types/reth_compat.rs @@ -1,15 +1,17 @@ //! Copy of reth codebase to preserve serialization compatibility use alloy_consensus::{Header, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip7702, TxLegacy}; use alloy_primitives::{Address, BlockHash, Signature, TxKind, U256}; +use reth_primitives::TransactionSigned as RethTxSigned; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, - sync::{Arc, LazyLock, Mutex}, + sync::{Arc, LazyLock, RwLock}, }; use tracing::info; use crate::{ node::{ + primitives::TransactionSigned as TxSigned, spot_meta::{erc20_contract_to_spot_token, SpotId}, types::{ReadPrecompileCalls, SystemTx}, }, @@ -42,23 +44,23 @@ pub struct TransactionSigned { transaction: Transaction, } impl TransactionSigned { - fn to_reth_transaction(&self) -> reth_primitives::TransactionSigned { + fn to_reth_transaction(&self) -> TxSigned { match self.transaction.clone() { Transaction::Legacy(tx) => { - reth_primitives::TransactionSigned::Legacy(Signed::new_unhashed(tx, self.signature)) + TxSigned(RethTxSigned::Legacy(Signed::new_unhashed(tx, self.signature))) + } + Transaction::Eip2930(tx) => { + TxSigned(RethTxSigned::Eip2930(Signed::new_unhashed(tx, self.signature))) + } + Transaction::Eip1559(tx) => { + TxSigned(RethTxSigned::Eip1559(Signed::new_unhashed(tx, self.signature))) + } + Transaction::Eip4844(tx) => { + TxSigned(RethTxSigned::Eip4844(Signed::new_unhashed(tx, self.signature))) + } + Transaction::Eip7702(tx) => { + TxSigned(RethTxSigned::Eip7702(Signed::new_unhashed(tx, self.signature))) } - Transaction::Eip2930(tx) => reth_primitives::TransactionSigned::Eip2930( - Signed::new_unhashed(tx, self.signature), - ), - Transaction::Eip1559(tx) => reth_primitives::TransactionSigned::Eip1559( - Signed::new_unhashed(tx, self.signature), - ), - Transaction::Eip4844(tx) => reth_primitives::TransactionSigned::Eip4844( - Signed::new_unhashed(tx, self.signature), - ), - Transaction::Eip7702(tx) => reth_primitives::TransactionSigned::Eip7702( - Signed::new_unhashed(tx, self.signature), - ), } } } @@ -67,24 +69,21 @@ type BlockBody = alloy_consensus::BlockBody; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SealedHeader { - hash: BlockHash, - header: Header, + pub hash: BlockHash, + pub header: Header, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SealedBlock { /// Sealed Header. - header: SealedHeader, + pub header: SealedHeader, /// the block's body. - body: BlockBody, + pub body: BlockBody, } -fn system_tx_to_reth_transaction( - transaction: &SystemTx, - chain_id: u64, -) -> reth_primitives::TransactionSigned { - static EVM_MAP: LazyLock>>> = - LazyLock::new(|| Arc::new(Mutex::new(BTreeMap::new()))); +fn system_tx_to_reth_transaction(transaction: &SystemTx, chain_id: u64) -> TxSigned { + static EVM_MAP: LazyLock>>> = + LazyLock::new(|| Arc::new(RwLock::new(BTreeMap::new()))); { let Transaction::Legacy(tx) = &transaction.tx else { panic!("Unexpected transaction type"); @@ -96,16 +95,16 @@ fn system_tx_to_reth_transaction( U256::from(0x1) } else { loop { - if let Some(spot) = EVM_MAP.lock().unwrap().get(&to) { + if let Some(spot) = EVM_MAP.read().unwrap().get(&to) { break spot.to_s(); } info!("Contract not found: {:?} from spot mapping, fetching again...", to); - *EVM_MAP.lock().unwrap() = erc20_contract_to_spot_token(chain_id).unwrap(); + *EVM_MAP.write().unwrap() = erc20_contract_to_spot_token(chain_id).unwrap(); } }; let signature = Signature::new(U256::from(0x1), s, true); - reth_primitives::TransactionSigned::Legacy(Signed::new_unhashed(tx.clone(), signature)) + TxSigned(RethTxSigned::Legacy(Signed::new_unhashed(tx.clone(), signature))) } }