use crate::precompile::HashMap; use alloy_eips::eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE}; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus_common::calc; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; use reth_primitives::{Address, Block, Withdrawal, Withdrawals, B256, U256}; use reth_storage_errors::provider::ProviderError; use revm::{ primitives::{Account, AccountInfo, Bytecode, EvmStorageSlot, BLOCKHASH_SERVE_WINDOW}, Database, DatabaseCommit, }; /// Collect all balance changes at the end of the block. /// /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular /// state changes (DAO fork). #[inline] pub fn post_block_balance_increments( chain_spec: &ChainSpec, block: &Block, total_difficulty: U256, ) -> HashMap { let mut balance_increments = HashMap::new(); // Add block rewards if they are enabled. if let Some(base_block_reward) = calc::base_block_reward(chain_spec, block.number, block.difficulty, total_difficulty) { // Ommer rewards for ommer in &block.ommers { *balance_increments.entry(ommer.beneficiary).or_default() += calc::ommer_reward(base_block_reward, block.number, ommer.number); } // Full block reward *balance_increments.entry(block.beneficiary).or_default() += calc::block_reward(base_block_reward, block.ommers.len()); } // process withdrawals insert_post_block_withdrawals_balance_increments( chain_spec, block.timestamp, block.withdrawals.as_ref().map(Withdrawals::as_ref), &mut balance_increments, ); balance_increments } /// Applies the pre-block state change outlined in [EIP-2935] to store historical blockhashes in a /// system contract. /// /// If Prague is not activated, or the block is the genesis block, then this is a no-op, and no /// state changes are made. /// /// If the provided block is after Prague has been activated, the parent hash will be inserted. /// /// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935 #[inline] pub fn apply_blockhashes_update> + DatabaseCommit>( db: &mut DB, chain_spec: &ChainSpec, block_timestamp: u64, block_number: u64, parent_block_hash: B256, ) -> Result<(), BlockExecutionError> where DB::Error: core::fmt::Display, { // If Prague is not activated or this is the genesis block, no hashes are added. if !chain_spec.is_prague_active_at_timestamp(block_timestamp) || block_number == 0 { return Ok(()) } assert!(block_number > 0); // Account is expected to exist either in genesis (for tests) or deployed on mainnet or // testnets. // If the account for any reason does not exist, we create it with the EIP-2935 bytecode and a // nonce of 1, so it does not get deleted. let mut account: Account = db .basic(HISTORY_STORAGE_ADDRESS) .map_err(|err| BlockValidationError::BlockHashAccountLoadingFailed(err.into()))? .unwrap_or_else(|| AccountInfo { nonce: 1, code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), ..Default::default() }) .into(); // Insert the state change for the slot let (slot, value) = eip2935_block_hash_slot(db, block_number - 1, parent_block_hash)?; account.storage.insert(slot, value); // Mark the account as touched and commit the state change account.mark_touch(); db.commit(HashMap::from([(HISTORY_STORAGE_ADDRESS, account)])); Ok(()) } /// Helper function to create a [`EvmStorageSlot`] for [EIP-2935] state transitions for a given /// block number. /// /// This calculates the correct storage slot in the `BLOCKHASH` history storage address, fetches the /// blockhash and creates a [`EvmStorageSlot`] with appropriate previous and new values. fn eip2935_block_hash_slot>>( db: &mut DB, block_number: u64, block_hash: B256, ) -> Result<(U256, EvmStorageSlot), BlockValidationError> { let slot = U256::from(block_number % BLOCKHASH_SERVE_WINDOW as u64); let current_hash = db .storage(HISTORY_STORAGE_ADDRESS, slot) .map_err(|err| BlockValidationError::BlockHashAccountLoadingFailed(err.into()))?; Ok((slot, EvmStorageSlot::new_changed(current_hash, block_hash.into()))) } /// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the /// given timestamp. /// /// Zero-valued withdrawals are filtered out. #[inline] pub fn post_block_withdrawals_balance_increments( chain_spec: &ChainSpec, block_timestamp: u64, withdrawals: &[Withdrawal], ) -> HashMap { let mut balance_increments = HashMap::with_capacity(withdrawals.len()); insert_post_block_withdrawals_balance_increments( chain_spec, block_timestamp, Some(withdrawals), &mut balance_increments, ); balance_increments } /// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the /// given `balance_increments` map. /// /// Zero-valued withdrawals are filtered out. #[inline] pub fn insert_post_block_withdrawals_balance_increments( chain_spec: &ChainSpec, block_timestamp: u64, withdrawals: Option<&[Withdrawal]>, balance_increments: &mut HashMap, ) { // Process withdrawals if chain_spec.is_shanghai_active_at_timestamp(block_timestamp) { if let Some(withdrawals) = withdrawals { for withdrawal in withdrawals { if withdrawal.amount > 0 { *balance_increments.entry(withdrawal.address).or_default() += withdrawal.amount_wei().to::(); } } } } } #[cfg(test)] mod tests { use super::*; use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition}; use reth_primitives::constants::GWEI_TO_WEI; /// Tests that the function correctly inserts balance increments when the Shanghai hardfork is /// active and there are withdrawals. #[test] fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_with_withdrawals() { // Arrange // Create a ChainSpec with the Shanghai hardfork active at timestamp 100 let chain_spec = ChainSpec { hardforks: ChainHardforks::new(vec![( Box::new(EthereumHardfork::Shanghai), ForkCondition::Timestamp(100), )]), ..Default::default() }; // Define the block timestamp and withdrawals let block_timestamp = 1000; let withdrawals = vec![ Withdrawal { address: Address::from([1; 20]), amount: 1000, index: 45, validator_index: 12, }, Withdrawal { address: Address::from([2; 20]), amount: 500, index: 412, validator_index: 123, }, ]; // Create an empty HashMap to hold the balance increments let mut balance_increments = HashMap::new(); // Act // Call the function with the prepared inputs insert_post_block_withdrawals_balance_increments( &chain_spec, block_timestamp, Some(&withdrawals), &mut balance_increments, ); // Assert // Verify that the balance increments map has the correct number of entries assert_eq!(balance_increments.len(), 2); // Verify that the balance increments map contains the correct values for each address assert_eq!( *balance_increments.get(&Address::from([1; 20])).unwrap(), (1000 * GWEI_TO_WEI).into() ); assert_eq!( *balance_increments.get(&Address::from([2; 20])).unwrap(), (500 * GWEI_TO_WEI).into() ); } /// Tests that the function correctly handles the case when Shanghai is active but there are no /// withdrawals. #[test] fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_no_withdrawals() { // Arrange // Create a ChainSpec with the Shanghai hardfork active let chain_spec = ChainSpec { hardforks: ChainHardforks::new(vec![( Box::new(EthereumHardfork::Shanghai), ForkCondition::Timestamp(100), )]), ..Default::default() }; // Define the block timestamp and an empty list of withdrawals let block_timestamp = 1000; let withdrawals = Vec::::new(); // Create an empty HashMap to hold the balance increments let mut balance_increments = HashMap::new(); // Act // Call the function with the prepared inputs insert_post_block_withdrawals_balance_increments( &chain_spec, block_timestamp, Some(&withdrawals), &mut balance_increments, ); // Assert // Verify that the balance increments map is empty assert!(balance_increments.is_empty()); } /// Tests that the function correctly handles the case when Shanghai is not active even if there /// are withdrawals. #[test] fn test_insert_post_block_withdrawals_balance_increments_shanghai_not_active_with_withdrawals() { // Arrange // Create a ChainSpec without the Shanghai hardfork active let chain_spec = ChainSpec::default(); // Mock chain spec with Shanghai not active // Define the block timestamp and withdrawals let block_timestamp = 1000; let withdrawals = vec![ Withdrawal { address: Address::from([1; 20]), amount: 1000, index: 45, validator_index: 12, }, Withdrawal { address: Address::from([2; 20]), amount: 500, index: 412, validator_index: 123, }, ]; // Create an empty HashMap to hold the balance increments let mut balance_increments = HashMap::new(); // Act // Call the function with the prepared inputs insert_post_block_withdrawals_balance_increments( &chain_spec, block_timestamp, Some(&withdrawals), &mut balance_increments, ); // Assert // Verify that the balance increments map is empty assert!(balance_increments.is_empty()); } /// Tests that the function correctly handles the case when Shanghai is active but all /// withdrawals have zero amounts. #[test] fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_with_zero_withdrawals() { // Arrange // Create a ChainSpec with the Shanghai hardfork active let chain_spec = ChainSpec { hardforks: ChainHardforks::new(vec![( Box::new(EthereumHardfork::Shanghai), ForkCondition::Timestamp(100), )]), ..Default::default() }; // Define the block timestamp and withdrawals with zero amounts let block_timestamp = 1000; let withdrawals = vec![ Withdrawal { address: Address::from([1; 20]), amount: 0, // Zero withdrawal amount index: 45, validator_index: 12, }, Withdrawal { address: Address::from([2; 20]), amount: 0, // Zero withdrawal amount index: 412, validator_index: 123, }, ]; // Create an empty HashMap to hold the balance increments let mut balance_increments = HashMap::new(); // Act // Call the function with the prepared inputs insert_post_block_withdrawals_balance_increments( &chain_spec, block_timestamp, Some(&withdrawals), &mut balance_increments, ); // Assert // Verify that the balance increments map is empty assert!(balance_increments.is_empty()); } /// Tests that the function correctly handles the case when Shanghai is active but there are no /// withdrawals provided. #[test] fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_with_empty_withdrawals( ) { // Arrange // Create a ChainSpec with the Shanghai hardfork active let chain_spec = ChainSpec { hardforks: ChainHardforks::new(vec![( Box::new(EthereumHardfork::Shanghai), ForkCondition::Timestamp(100), )]), ..Default::default() }; // Define the block timestamp and no withdrawals let block_timestamp = 1000; let withdrawals = None; // No withdrawals provided // Create an empty HashMap to hold the balance increments let mut balance_increments = HashMap::new(); // Act // Call the function with the prepared inputs insert_post_block_withdrawals_balance_increments( &chain_spec, block_timestamp, withdrawals, &mut balance_increments, ); // Assert // Verify that the balance increments map is empty assert!(balance_increments.is_empty()); } }