diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 2cff15528c..c9f41a010c 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -21,16 +21,26 @@ //! be mined. use reth_beacon_consensus::BeaconEngineMessage; -use reth_interfaces::consensus::{Consensus, ConsensusError}; -use reth_primitives::{ - BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Header, SealedBlock, - SealedHeader, H256, U256, +use reth_interfaces::{ + consensus::{Consensus, ConsensusError}, + executor::{BlockExecutionError, BlockValidationError}, }; -use reth_provider::{BlockReaderIdExt, CanonStateNotificationSender}; +use reth_primitives::{ + constants::{EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, + proofs, Address, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, + Header, ReceiptWithBloom, SealedBlock, SealedHeader, TransactionSigned, EMPTY_OMMER_ROOT, H256, + U256, +}; +use reth_provider::{BlockReaderIdExt, CanonStateNotificationSender, PostState, StateProvider}; +use reth_revm::executor::Executor; use reth_transaction_pool::TransactionPool; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; use tokio::sync::{mpsc::UnboundedSender, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use tracing::trace; +use tracing::{trace, warn}; mod client; mod mode; @@ -182,6 +192,7 @@ impl Storage { } } +/// In-memory storage for the chain the auto seal engine is building. #[derive(Default, Debug)] pub(crate) struct StorageInner { /// Headers buffered for download. @@ -232,4 +243,127 @@ impl StorageInner { self.bodies.insert(self.best_hash, body); self.hash_to_number.insert(self.best_hash, self.best_block); } + + /// Fills in pre-execution header fields based on the current best block and given + /// transactions. + pub(crate) fn build_header_template(&self, transactions: &Vec) -> Header { + // check previous block for base fee + let base_fee_per_gas = + self.headers.get(&self.best_block).and_then(|parent| parent.next_block_base_fee()); + + let mut header = Header { + parent_hash: self.best_hash, + ommers_hash: EMPTY_OMMER_ROOT, + beneficiary: Default::default(), + state_root: Default::default(), + transactions_root: Default::default(), + receipts_root: Default::default(), + withdrawals_root: None, + logs_bloom: Default::default(), + difficulty: U256::from(2), + number: self.best_block + 1, + gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, + gas_used: 0, + timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(), + mix_hash: Default::default(), + nonce: 0, + base_fee_per_gas, + extra_data: Default::default(), + }; + + header.transactions_root = if transactions.is_empty() { + EMPTY_TRANSACTIONS + } else { + proofs::calculate_transaction_root(transactions) + }; + + header + } + + /// Executes the block with the given block and senders, on the provided [Executor]. + /// + /// This returns the poststate from execution and post-block changes, as well as the gas used. + pub(crate) fn execute( + &mut self, + block: &Block, + executor: &mut Executor, + senders: Vec
, + ) -> Result<(PostState, u64), BlockExecutionError> { + trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions"); + + let (post_state, gas_used) = + executor.execute_transactions(block, U256::ZERO, Some(senders.clone()))?; + + // apply post block changes + let post_state = executor.apply_post_block_changes(block, U256::ZERO, post_state)?; + + Ok((post_state, gas_used)) + } + + /// Fills in the post-execution header fields based on the given PostState and gas used. + /// In doing this, the state root is calculated and the final header is returned. + pub(crate) fn complete_header( + &self, + mut header: Header, + post_state: &PostState, + executor: &mut Executor, + gas_used: u64, + ) -> Header { + let receipts = post_state.receipts(header.number); + header.receipts_root = if receipts.is_empty() { + EMPTY_RECEIPTS + } else { + let receipts_with_bloom = + receipts.iter().map(|r| r.clone().into()).collect::>(); + proofs::calculate_receipt_root(&receipts_with_bloom) + }; + + header.gas_used = gas_used; + + // calculate the state root + let state_root = executor.db().db.0.state_root(post_state.clone()).unwrap(); + header.state_root = state_root; + header + } + + /// Builds and executes a new block with the given transactions, on the provided [Executor]. + /// + /// This returns the header of the executed block, as well as the poststate from execution. + pub(crate) fn build_and_execute( + &mut self, + transactions: Vec, + executor: &mut Executor, + ) -> Result<(SealedHeader, PostState), BlockExecutionError> { + let header = self.build_header_template(&transactions); + + let block = Block { header, body: transactions, ommers: vec![], withdrawals: None }; + + let senders = + block.body.iter().map(|tx| tx.recover_signer()).collect::>>().ok_or( + BlockExecutionError::Validation(BlockValidationError::SenderRecoveryError), + )?; + + trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions"); + + // now execute the block + let (post_state, gas_used) = self.execute(&block, executor, senders)?; + + let Block { header, body, .. } = block; + let body = BlockBody { transactions: body, ommers: vec![], withdrawals: None }; + + trace!(target: "consensus::auto", ?post_state, ?header, ?body, "executed block, calculating state root and completing header"); + + // fill in the rest of the fields + let header = self.complete_header(header, &post_state, executor, gas_used); + + trace!(target: "consensus::auto", root=?header.state_root, ?body, "calculated root"); + + // finally insert into storage + self.insert_new_block(header.clone(), body); + + // set new header with hash that should have been updated by insert_new_block + let new_header = header.seal(self.best_hash); + + Ok((new_header, post_state)) + } } diff --git a/crates/consensus/auto-seal/src/task.rs b/crates/consensus/auto-seal/src/task.rs index 5217661c4c..6d77784b1a 100644 --- a/crates/consensus/auto-seal/src/task.rs +++ b/crates/consensus/auto-seal/src/task.rs @@ -2,11 +2,7 @@ use crate::{mode::MiningMode, Storage}; use futures_util::{future::BoxFuture, FutureExt}; use reth_beacon_consensus::{BeaconEngineMessage, ForkchoiceStatus}; use reth_interfaces::consensus::ForkchoiceState; -use reth_primitives::{ - constants::{EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, - proofs, Block, BlockBody, ChainSpec, Header, IntoRecoveredTransaction, ReceiptWithBloom, - SealedBlockWithSenders, EMPTY_OMMER_ROOT, U256, -}; +use reth_primitives::{Block, ChainSpec, IntoRecoveredTransaction, SealedBlockWithSenders}; use reth_provider::{CanonChainTracker, CanonStateNotificationSender, Chain, StateProviderFactory}; use reth_revm::{ database::{State, SubState}, @@ -20,11 +16,10 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::{SystemTime, UNIX_EPOCH}, }; use tokio::sync::{mpsc::UnboundedSender, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; -use tracing::{debug, error, trace, warn}; +use tracing::{debug, error, warn}; /// A Future that listens for new ready transactions and puts new blocks into storage pub struct MiningTask { @@ -123,104 +118,28 @@ where this.insert_task = Some(Box::pin(async move { let mut storage = storage.write().await; - // check previous block for base fee - let base_fee_per_gas = storage - .headers - .get(&storage.best_block) - .and_then(|parent| parent.next_block_base_fee()); - - let mut header = Header { - parent_hash: storage.best_hash, - ommers_hash: EMPTY_OMMER_ROOT, - beneficiary: Default::default(), - state_root: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), - withdrawals_root: None, - logs_bloom: Default::default(), - difficulty: U256::from(2), - number: storage.best_block + 1, - gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, - gas_used: 0, - timestamp: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(), - mix_hash: Default::default(), - nonce: 0, - base_fee_per_gas, - extra_data: Default::default(), - }; - - let transactions = transactions + let (transactions, senders): (Vec<_>, Vec<_>) = transactions .into_iter() - .map(|tx| tx.to_recovered_transaction().into_signed()) - .collect::>(); - - header.transactions_root = if transactions.is_empty() { - EMPTY_TRANSACTIONS - } else { - proofs::calculate_transaction_root(&transactions) - }; - - let block = - Block { header, body: transactions, ommers: vec![], withdrawals: None }; + .map(|tx| { + let recovered = tx.to_recovered_transaction(); + let signer = recovered.signer(); + (recovered.into_signed(), signer) + }) + .unzip(); // execute the new block let substate = SubState::new(State::new(client.latest().unwrap())); let mut executor = Executor::new(chain_spec, substate); - trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions"); - - let senders = block - .body - .iter() - .map(|tx| tx.recover_signer()) - .collect::>>()?; - - match executor.execute_transactions(&block, U256::ZERO, Some(senders.clone())) { - Ok((post_state, gas_used)) => { - // apply post block changes - let post_state = executor - .apply_post_block_changes(&block, U256::ZERO, post_state) - .unwrap(); - - let Block { mut header, body, .. } = block; - + match storage.build_and_execute(transactions.clone(), &mut executor) { + Ok((new_header, post_state)) => { // clear all transactions from pool - pool.remove_transactions(body.iter().map(|tx| tx.hash())); + pool.remove_transactions(transactions.iter().map(|tx| tx.hash())); - let receipts = post_state.receipts(header.number); - header.receipts_root = if receipts.is_empty() { - EMPTY_RECEIPTS - } else { - let receipts_with_bloom = receipts - .iter() - .map(|r| r.clone().into()) - .collect::>(); - proofs::calculate_receipt_root(&receipts_with_bloom) - }; - let transactions = body.clone(); - let body = - BlockBody { transactions: body, ommers: vec![], withdrawals: None }; - header.gas_used = gas_used; - - trace!(target: "consensus::auto", ?post_state, ?header, ?body, "executed block, calculating root"); - - // calculate the state root - let state_root = - executor.db().db.0.state_root(post_state.clone()).unwrap(); - header.state_root = state_root; - - trace!(target: "consensus::auto", root=?header.state_root, ?body, "calculated root"); - - storage.insert_new_block(header.clone(), body); - - let new_hash = storage.best_hash; let state = ForkchoiceState { - head_block_hash: new_hash, - finalized_block_hash: new_hash, - safe_block_hash: new_hash, + head_block_hash: new_header.hash, + finalized_block_hash: new_header.hash, + safe_block_hash: new_header.hash, }; drop(storage); @@ -261,7 +180,7 @@ where // seal the block let block = Block { - header: header.clone(), + header: new_header.clone().unseal(), body: transactions, ommers: vec![], withdrawals: None, @@ -273,9 +192,9 @@ where .expect("senders are valid"); // update canon chain for rpc - client.set_canonical_head(header.clone().seal(new_hash)); - client.set_safe(header.clone().seal(new_hash)); - client.set_finalized(header.clone().seal(new_hash)); + client.set_canonical_head(new_header.clone()); + client.set_safe(new_header.clone()); + client.set_finalized(new_header.clone()); debug!(target: "consensus::auto", header=?sealed_block_with_senders.hash(), "sending block notification");