From 4449ad55abb397a09868481c054d6e8c9792482b Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:08:38 -0400 Subject: [PATCH] precompile attempt --- src/evm/precompiles.rs | 69 +++++++- src/node/evm/assembler.rs | 15 +- src/node/evm/config.rs | 192 +++++++++++++++++++++-- src/node/evm/executor.rs | 44 ++---- src/node/evm/factory.rs | 23 +-- src/node/evm/mod.rs | 7 +- src/node/network/block_import/service.rs | 1 + src/node/types.rs | 22 +++ 8 files changed, 295 insertions(+), 78 deletions(-) diff --git a/src/evm/precompiles.rs b/src/evm/precompiles.rs index 4bb2e4638..e6894709a 100644 --- a/src/evm/precompiles.rs +++ b/src/evm/precompiles.rs @@ -1,5 +1,12 @@ #![allow(unused)] +use crate::node::types::ReadPrecompileResult; +use crate::node::types::{ReadPrecompileInput, ReadPrecompileMap}; +use revm::interpreter::{Gas, InstructionResult}; +use revm::precompile::PrecompileError; +use revm::precompile::PrecompileOutput; +use revm::primitives::Bytes; + use super::spec::HlSpecId; use cfg_if::cfg_if; use once_cell::{race::OnceBox, sync::Lazy}; @@ -18,15 +25,23 @@ use std::boxed::Box; pub struct HlPrecompiles { /// Inner precompile provider is same as Ethereums. inner: EthPrecompiles, + /// Precompile calls + precompile_calls: ReadPrecompileMap, } impl HlPrecompiles { /// Create a new precompile provider with the given hl spec. #[inline] - pub fn new(spec: HlSpecId) -> Self { + pub fn new(spec: HlSpecId, precompile_calls: ReadPrecompileMap) -> Self { let precompiles = cancun(); - Self { inner: EthPrecompiles { precompiles, spec: spec.into_eth_spec() } } + Self { + inner: EthPrecompiles { + precompiles, + spec: spec.into_eth_spec(), + }, + precompile_calls, + } } #[inline] @@ -53,7 +68,7 @@ where #[inline] fn set_spec(&mut self, spec: ::Spec) -> bool { - *self = Self::new(spec); + *self = Self::new(spec, self.precompile_calls.clone()); true } @@ -66,7 +81,51 @@ where is_static: bool, gas_limit: u64, ) -> Result, String> { - self.inner.run(context, address, inputs, is_static, gas_limit) + let Some(precompile_calls) = self.precompile_calls.get(address) else { + return self + .inner + .run(context, address, inputs, is_static, gas_limit); + }; + + let input = ReadPrecompileInput { + input: inputs.input.bytes(context), + gas_limit, + }; + let mut result = InterpreterResult { + result: InstructionResult::Return, + gas: Gas::new(gas_limit), + output: Bytes::new(), + }; + + let Some(get) = precompile_calls.get(&input) else { + result.gas.spend_all(); + result.result = InstructionResult::PrecompileError; + return Ok(Some(result)); + }; + + return match *get { + ReadPrecompileResult::Ok { + gas_used, + ref bytes, + } => { + let underflow = result.gas.record_cost(gas_used); + assert!(underflow, "Gas underflow is not possible"); + result.output = bytes.clone(); + Ok(Some(result)) + } + ReadPrecompileResult::OutOfGas => { + // Use all the gas passed to this precompile + result.gas.spend_all(); + result.result = InstructionResult::OutOfGas; + Ok(Some(result)) + } + ReadPrecompileResult::Error => { + result.gas.spend_all(); + result.result = InstructionResult::PrecompileError; + Ok(Some(result)) + } + ReadPrecompileResult::UnexpectedError => panic!("unexpected precompile error"), + }; } #[inline] @@ -82,6 +141,6 @@ where impl Default for HlPrecompiles { fn default() -> Self { - Self::new(HlSpecId::default()) + Self::new(HlSpecId::default(), ReadPrecompileMap::default()) } } diff --git a/src/node/evm/assembler.rs b/src/node/evm/assembler.rs index 61864b949..a78658a68 100644 --- a/src/node/evm/assembler.rs +++ b/src/node/evm/assembler.rs @@ -15,18 +15,7 @@ impl BlockAssembler for HlEvmConfig { &self, input: BlockAssemblerInput<'_, '_, HlBlockExecutorFactory, Header>, ) -> Result { - let Block { header, body: inner } = self.block_assembler.assemble_block(input)?; - Ok(HlBlock { - header, - body: HlBlockBody { - inner, - // HACK: we're setting sidecars to `None` here but ideally we should somehow get - // them from the payload builder. - // - // Payload building is out of scope of reth-bsc for now, so this is not critical - sidecars: None, - read_precompile_calls: None, - }, - }) + let HlBlock { header, body } = self.block_assembler.assemble_block(input)?; + Ok(HlBlock { header, body }) } } diff --git a/src/node/evm/config.rs b/src/node/evm/config.rs index e13f1cbf8..4cc735f8c 100644 --- a/src/node/evm/config.rs +++ b/src/node/evm/config.rs @@ -3,20 +3,28 @@ use crate::{ chainspec::HlChainSpec, evm::{spec::HlSpecId, transaction::HlTxEnv}, hardforks::HlHardforks, - HlPrimitives, + node::types::ReadPrecompileMap, + HlBlock, HlBlockBody, HlPrimitives, }; -use alloy_consensus::{BlockHeader, Header, TxReceipt}; +use alloy_consensus::{BlockHeader, Header, Transaction as _, TxReceipt, EMPTY_OMMER_ROOT_HASH}; +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::{BlockExecutorFactory, BlockExecutorFor}, + block::{BlockExecutionError, BlockExecutorFactory, BlockExecutorFor}, eth::{receipt_builder::ReceiptBuilder, EthBlockExecutionCtx}, + execute::{BlockAssembler, BlockAssemblerInput}, ConfigureEvm, EvmEnv, EvmFactory, ExecutionCtxFor, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, NextBlockEnvAttributes, }; use reth_evm_ethereum::{EthBlockAssembler, RethReceiptBuilder}; -use reth_primitives::{BlockTy, HeaderTy, SealedBlock, SealedHeader, TransactionSigned}; +use reth_primitives::{ + logs_bloom, BlockTy, HeaderTy, Receipt, SealedBlock, SealedHeader, TransactionSigned, +}; +use reth_primitives_traits::proofs; +use reth_provider::BlockExecutionResult; use reth_revm::State; use revm::{ context::{BlockEnv, CfgEnv, TxEnv}, @@ -26,6 +34,139 @@ use revm::{ }; use std::{borrow::Cow, convert::Infallible, sync::Arc}; +#[derive(Debug, Clone)] +pub struct HlBlockAssembler { + inner: EthBlockAssembler, +} + +impl BlockAssembler for HlBlockAssembler +where + F: for<'a> BlockExecutorFactory< + ExecutionCtx<'a> = HlBlockExecutionCtx<'a>, + Transaction = TransactionSigned, + Receipt = Receipt, + >, +{ + type Block = HlBlock; + + fn assemble_block( + &self, + input: BlockAssemblerInput<'_, '_, F>, + ) -> Result { + // TODO: Copy of EthBlockAssembler::assemble_block + let inner = &self.inner; + let BlockAssemblerInput { + evm_env, + execution_ctx: ctx, + parent, + transactions, + output: + BlockExecutionResult { + receipts, + requests, + gas_used, + }, + state_root, + .. + } = input; + + let timestamp = evm_env.block_env.timestamp; + + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + let withdrawals = inner + .chain_spec + .is_shanghai_active_at_timestamp(timestamp) + .then(|| { + ctx.ctx + .withdrawals + .map(|w| w.into_owned()) + .unwrap_or_default() + }); + + let withdrawals_root = withdrawals + .as_deref() + .map(|w| proofs::calculate_withdrawals_root(w)); + let requests_hash = inner + .chain_spec + .is_prague_active_at_timestamp(timestamp) + .then(|| requests.requests_hash()); + + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + // only determine cancun fields when active + if inner.chain_spec.is_cancun_active_at_timestamp(timestamp) { + blob_gas_used = Some( + transactions + .iter() + .map(|tx| tx.blob_gas_used().unwrap_or_default()) + .sum(), + ); + excess_blob_gas = if inner + .chain_spec + .is_cancun_active_at_timestamp(parent.timestamp) + { + parent.maybe_next_block_excess_blob_gas( + inner.chain_spec.blob_params_at_timestamp(timestamp), + ) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(alloy_eips::eip7840::BlobParams::cancun().next_block_excess_blob_gas(0, 0)) + }; + } + + let header = Header { + parent_hash: ctx.ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: evm_env.block_env.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp, + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(evm_env.block_env.basefee), + number: evm_env.block_env.number, + gas_limit: evm_env.block_env.gas_limit, + difficulty: evm_env.block_env.difficulty, + gas_used: *gas_used, + extra_data: inner.extra_data.clone(), + parent_beacon_block_root: ctx.ctx.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + let read_precompile_calls = ctx.read_precompile_calls.clone().into(); + Ok(Self::Block { + header, + body: HlBlockBody { + inner: BlockBody { + transactions, + ommers: Default::default(), + withdrawals, + }, + sidecars: None, + read_precompile_calls: Some(read_precompile_calls), + }, + }) + } +} + +impl HlBlockAssembler { + pub fn new(chain_spec: Arc) -> Self { + Self { + inner: EthBlockAssembler::new(chain_spec), + } + } +} + /// Ethereum-related EVM configuration. #[derive(Debug, Clone)] pub struct HlEvmConfig { @@ -33,7 +174,7 @@ pub struct HlEvmConfig { pub executor_factory: HlBlockExecutorFactory, HlEvmFactory>, /// Ethereum block assembler. - pub block_assembler: EthBlockAssembler, + pub block_assembler: HlBlockAssembler, } impl HlEvmConfig { @@ -52,7 +193,7 @@ impl HlEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory. pub fn new_with_evm_factory(chain_spec: Arc, evm_factory: HlEvmFactory) -> Self { Self { - block_assembler: EthBlockAssembler::new(chain_spec.clone()), + block_assembler: HlBlockAssembler::new(chain_spec.clone()), executor_factory: HlBlockExecutorFactory::new( RethReceiptBuilder::default(), chain_spec, @@ -104,6 +245,12 @@ impl HlBlockExecutorFactory { } } +#[derive(Debug, Clone)] +pub struct HlBlockExecutionCtx<'a> { + ctx: EthBlockExecutionCtx<'a>, + read_precompile_calls: ReadPrecompileMap, +} + impl BlockExecutorFactory for HlBlockExecutorFactory where R: ReceiptBuilder>, @@ -114,7 +261,7 @@ where HlTxEnv: IntoTxEnv<::Tx>, { type EvmFactory = EvmF; - type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; + type ExecutionCtx<'a> = HlBlockExecutionCtx<'a>; type Transaction = TransactionSigned; type Receipt = R::Receipt; @@ -284,11 +431,18 @@ where &self, block: &'a SealedBlock>, ) -> ExecutionCtxFor<'a, Self> { - EthBlockExecutionCtx { - parent_hash: block.header().parent_hash, - parent_beacon_block_root: block.header().parent_beacon_block_root, - ommers: &block.body().ommers, - withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), + HlBlockExecutionCtx { + ctx: EthBlockExecutionCtx { + parent_hash: block.header().parent_hash, + parent_beacon_block_root: block.header().parent_beacon_block_root, + ommers: &block.body().ommers, + withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), + }, + read_precompile_calls: block + .body() + .read_precompile_calls + .clone() + .map_or(ReadPrecompileMap::default(), |calls| calls.into()), } } @@ -297,11 +451,15 @@ where parent: &SealedHeader>, attributes: Self::NextBlockEnvCtx, ) -> ExecutionCtxFor<'_, Self> { - EthBlockExecutionCtx { - parent_hash: parent.hash(), - parent_beacon_block_root: attributes.parent_beacon_block_root, - ommers: &[], - withdrawals: attributes.withdrawals.map(Cow::Owned), + HlBlockExecutionCtx { + ctx: EthBlockExecutionCtx { + parent_hash: parent.hash(), + parent_beacon_block_root: attributes.parent_beacon_block_root, + ommers: &[], + withdrawals: attributes.withdrawals.map(Cow::Owned), + }, + // TODO: hacky, double check if this is correct + read_precompile_calls: ReadPrecompileMap::default(), } } } diff --git a/src/node/evm/executor.rs b/src/node/evm/executor.rs index 66acc6a26..74c518459 100644 --- a/src/node/evm/executor.rs +++ b/src/node/evm/executor.rs @@ -1,13 +1,13 @@ +use super::config::HlBlockExecutionCtx; use super::patch::patch_mainnet_after_tx; -use crate::{evm::transaction::HlTxEnv, hardforks::HlHardforks}; +use crate::{evm::transaction::HlTxEnv, hardforks::HlHardforks, node::types::ReadPrecompileCalls}; use alloy_consensus::{Transaction, TxReceipt}; use alloy_eips::{eip7685::Requests, Encodable2718}; use alloy_evm::{block::ExecutableTx, eth::receipt_builder::ReceiptBuilderCtx}; -use alloy_primitives::Address; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_evm::{ block::{BlockValidationError, CommitChanges}, - eth::{receipt_builder::ReceiptBuilder, EthBlockExecutionCtx}, + eth::receipt_builder::ReceiptBuilder, execute::{BlockExecutionError, BlockExecutor}, Database, Evm, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, OnStateHook, RecoveredTx, }; @@ -19,8 +19,7 @@ use revm::{ result::{ExecutionResult, ResultAndState}, TxEnv, }, - state::Bytecode, - Database as _, DatabaseCommit, + DatabaseCommit, }; fn is_system_transaction(tx: &TransactionSigned) -> bool { @@ -44,12 +43,12 @@ where receipts: Vec, /// System txs system_txs: Vec, + /// Read precompile calls + read_precompile_calls: ReadPrecompileCalls, /// Receipt builder. receipt_builder: R, - /// System contracts used to trigger fork specific logic. - // system_contracts: SystemContract, /// Context for block execution. - _ctx: EthBlockExecutionCtx<'a>, + ctx: HlBlockExecutionCtx<'a>, } impl<'a, DB, EVM, Spec, R: ReceiptBuilder> HlBlockExecutor<'a, EVM, Spec, R> @@ -69,33 +68,19 @@ where R::Transaction: Into, { /// Creates a new HlBlockExecutor. - pub fn new( - evm: EVM, - _ctx: EthBlockExecutionCtx<'a>, - spec: Spec, - receipt_builder: R, - // system_contracts: SystemContract, - ) -> Self { + pub fn new(evm: EVM, ctx: HlBlockExecutionCtx<'a>, spec: Spec, receipt_builder: R) -> Self { Self { spec, evm, gas_used: 0, receipts: vec![], system_txs: vec![], + read_precompile_calls: ReadPrecompileCalls::default(), receipt_builder, // system_contracts, - _ctx, + ctx, } } - - /// Initializes the genesis contracts - fn deploy_genesis_contracts( - &mut self, - beneficiary: Address, - ) -> Result<(), BlockExecutionError> { - todo!("Deploy WETH, System contract"); - // Ok(()) - } } impl<'a, DB, E, Spec, R> BlockExecutor for HlBlockExecutor<'a, E, Spec, R> @@ -119,11 +104,6 @@ where type Evm = E; fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { - // If first block deploy genesis contracts - if self.evm.block().number == 1 { - self.deploy_genesis_contracts(self.evm.block().beneficiary)?; - } - Ok(()) } @@ -186,9 +166,7 @@ where Ok(gas_used) } - fn finish( - mut self, - ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { + fn finish(self) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { Ok(( self.evm, BlockExecutionResult { diff --git a/src/node/evm/factory.rs b/src/node/evm/factory.rs index 8bbbd21b5..bcee1ba2a 100644 --- a/src/node/evm/factory.rs +++ b/src/node/evm/factory.rs @@ -1,12 +1,15 @@ use super::HlEvm; -use crate::evm::{ - api::{ - builder::HlBuilder, - ctx::{HlContext, DefaultHl}, +use crate::{ + evm::{ + api::{ + builder::HlBuilder, + ctx::{DefaultHl, HlContext}, + }, + precompiles::HlPrecompiles, + spec::HlSpecId, + transaction::HlTxEnv, }, - precompiles::HlPrecompiles, - spec::HlSpecId, - transaction::HlTxEnv, + node::types::ReadPrecompileMap, }; use reth_evm::{precompiles::PrecompilesMap, EvmEnv, EvmFactory}; use reth_revm::{Context, Database}; @@ -39,7 +42,8 @@ impl EvmFactory for HlEvmFactory { db: DB, input: EvmEnv, ) -> Self::Evm { - let precompiles = HlPrecompiles::new(input.cfg_env.spec).precompiles(); + let precompiles = + HlPrecompiles::new(input.cfg_env.spec, ReadPrecompileMap::default()).precompiles(); HlEvm { inner: Context::hl() .with_block(input.block_env) @@ -60,7 +64,8 @@ impl EvmFactory for HlEvmFactory { input: EvmEnv, inspector: I, ) -> Self::Evm { - let precompiles = HlPrecompiles::new(input.cfg_env.spec).precompiles(); + let precompiles = + HlPrecompiles::new(input.cfg_env.spec, ReadPrecompileMap::default()).precompiles(); HlEvm { inner: Context::hl() .with_block(input.block_env) diff --git a/src/node/evm/mod.rs b/src/node/evm/mod.rs index 9ee32a7ea..038253cd8 100644 --- a/src/node/evm/mod.rs +++ b/src/node/evm/mod.rs @@ -121,7 +121,12 @@ where } fn finish(self) -> (Self::DB, EvmEnv) { - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx; + let Context { + block: block_env, + cfg: cfg_env, + journaled_state, + .. + } = self.inner.0.ctx; (journaled_state.database, EvmEnv { block_env, cfg_env }) } diff --git a/src/node/network/block_import/service.rs b/src/node/network/block_import/service.rs index 40f5e63ba..bd17fa9c2 100644 --- a/src/node/network/block_import/service.rs +++ b/src/node/network/block_import/service.rs @@ -379,6 +379,7 @@ mod tests { withdrawals: None, }, sidecars: None, + read_precompile_calls: None, }, }; let new_block = HlNewBlock(NewBlock { block, td: U128::from(1) }); diff --git a/src/node/types.rs b/src/node/types.rs index 3dd9a83a3..6cb29639f 100644 --- a/src/node/types.rs +++ b/src/node/types.rs @@ -6,12 +6,34 @@ use alloy_primitives::{Address, Bytes, Log}; use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable}; use bytes::BufMut; use reth_primitives::{SealedBlock, Transaction}; +use revm::primitives::HashMap; use serde::{Deserialize, Serialize}; pub type ReadPrecompileCall = (Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>); #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Default)] pub struct ReadPrecompileCalls(pub Vec); +pub type ReadPrecompileMap = HashMap>; + +impl From for ReadPrecompileMap { + fn from(calls: ReadPrecompileCalls) -> Self { + calls + .0 + .into_iter() + .map(|(address, calls)| (address, calls.into_iter().collect())) + .collect() + } +} + +impl From for ReadPrecompileCalls { + fn from(map: ReadPrecompileMap) -> Self { + Self( + map.into_iter() + .map(|(address, calls)| (address, calls.into_iter().collect())) + .collect(), + ) + } +} impl Encodable for ReadPrecompileCalls { fn encode(&self, out: &mut dyn BufMut) {