diff --git a/Cargo.lock b/Cargo.lock index d5b7479793..dcf9886e48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4727,6 +4727,7 @@ dependencies = [ "hash-db", "parking_lot 0.12.1", "plain_hasher", + "reth-consensus-common", "reth-db", "reth-interfaces", "reth-primitives", diff --git a/crates/consensus/common/src/calc.rs b/crates/consensus/common/src/calc.rs new file mode 100644 index 0000000000..f9cbe6dfd3 --- /dev/null +++ b/crates/consensus/common/src/calc.rs @@ -0,0 +1,143 @@ +use reth_primitives::{constants::ETH_TO_WEI, BlockNumber, ChainSpec, Hardfork, U256}; + +/// Calculates the base block reward. +/// +/// The base block reward is defined as: +/// +/// - For Paris and later: `None` +/// - For Petersburg and later: `Some(2 ETH)` +/// - For Byzantium and later: `Some(3 ETH)` +/// - Otherwise: `Some(5 ETH)` +/// +/// # Note +/// +/// This does not include the reward for including ommers. To calculate the full block reward, see +/// [`block_reward`]. +/// +/// # References +/// +/// - Definition: [Yellow Paper][yp] (page 15, 11.3) +/// +/// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf +pub fn base_block_reward( + chain_spec: &ChainSpec, + block_number: BlockNumber, + block_difficulty: U256, + total_difficulty: U256, +) -> Option { + if chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) { + None + } else if chain_spec.fork(Hardfork::Petersburg).active_at_block(block_number) { + Some(ETH_TO_WEI * 2) + } else if chain_spec.fork(Hardfork::Byzantium).active_at_block(block_number) { + Some(ETH_TO_WEI * 3) + } else { + Some(ETH_TO_WEI * 5) + } +} + +/// Calculates the reward for a block, including the reward for ommer inclusion. +/// +/// The base reward should be calculated using [`base_block_reward`]. `ommers` represents the number +/// of ommers included in the block. +/// +/// # Examples +/// +/// ``` +/// # use reth_consensus_common::calc::{base_block_reward, block_reward}; +/// # use reth_primitives::constants::ETH_TO_WEI; +/// # use reth_primitives::{MAINNET, U256}; +/// # +/// // This is block 126 on mainnet. +/// let block_number = 126; +/// let block_difficulty = U256::from(18_145_285_642usize); +/// let total_difficulty = U256::from(2_235_668_675_900usize); +/// let number_of_ommers = 1; +/// +/// let reward = base_block_reward(&MAINNET, block_number, block_difficulty, total_difficulty).map(|reward| block_reward(reward, 1)); +/// +/// // The base block reward is 5 ETH, and the ommer inclusion reward is 1/32th of 5 ETH. +/// assert_eq!( +/// reward.unwrap(), +/// U256::from(ETH_TO_WEI * 5 + ((ETH_TO_WEI * 5) >> 5)) +/// ); +/// ``` +/// +/// # References +/// +/// - Definition: [Yellow Paper][yp] (page 15, 11.3) +/// +/// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf +pub fn block_reward(base_block_reward: u128, ommers: usize) -> U256 { + U256::from(base_block_reward + (base_block_reward >> 5) * ommers as u128) +} + +/// Calculate the reward for an ommer. +/// +/// # Application +/// +/// Rewards are accumulative, so they should be added to the beneficiary addresses in addition to +/// any other rewards from the same block. +/// +/// From the yellow paper (page 15): +/// +/// > If there are collissions of the beneficiary addresses between ommers and the block (i.e. two +/// > ommers with the same beneficiary address or an ommer with the same beneficiary address as the +/// > present block), additions are applied cumulatively. +/// +/// # References +/// +/// - Implementation: [OpenEthereum][oe] +/// - Definition: [Yellow Paper][yp] (page 15, 11.3) +/// +/// [oe]: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333 +/// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf +pub fn ommer_reward( + base_block_reward: u128, + block_number: BlockNumber, + ommer_block_number: BlockNumber, +) -> U256 { + U256::from(((8 + ommer_block_number - block_number) as u128 * base_block_reward) >> 3) +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_primitives::{MAINNET, U256}; + + #[test] + fn calc_base_block_reward() { + // ((block number, td), reward) + let cases = [ + // Pre-byzantium + ((0, U256::ZERO), Some(ETH_TO_WEI * 5)), + // Byzantium + ((4370000, U256::ZERO), Some(ETH_TO_WEI * 3)), + // Petersburg + ((7280000, U256::ZERO), Some(ETH_TO_WEI * 2)), + // Merge + ((10000000, U256::from(58_750_000_000_000_000_000_000_u128)), None), + ]; + + for ((block_number, td), expected_reward) in cases { + assert_eq!(base_block_reward(&MAINNET, block_number, U256::ZERO, td), expected_reward); + } + } + + #[test] + fn calc_full_block_reward() { + let base_reward = ETH_TO_WEI * 1; + let one_thirty_twoth_reward = base_reward >> 5; + + // (num_ommers, reward) + let cases = [ + (0, base_reward), + (1, base_reward + one_thirty_twoth_reward), + (2, base_reward + one_thirty_twoth_reward * 2), + ]; + + for (num_ommers, expected_reward) in cases { + assert_eq!(block_reward(base_reward, num_ommers), U256::from(expected_reward)); + } + } +} diff --git a/crates/consensus/common/src/lib.rs b/crates/consensus/common/src/lib.rs index 4b1917d612..f8bc59b58d 100644 --- a/crates/consensus/common/src/lib.rs +++ b/crates/consensus/common/src/lib.rs @@ -9,3 +9,6 @@ /// Collection of consensus validation methods. pub mod validation; + +/// Various calculation methods (e.g. block rewards) +pub mod calc; diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml index 8731435414..f9877cc922 100644 --- a/crates/executor/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -21,6 +21,7 @@ reth-revm-inspectors = { path = "../revm/revm-inspectors" } reth-rlp = { path = "../rlp" } reth-db = { path = "../storage/db" } reth-provider = { path = "../storage/provider" } +reth-consensus-common = { path = "../consensus/common" } # revm revm = { version = "3.0.0" } diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index c32bba4cba..775bb60323 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -1,4 +1,5 @@ use crate::post_state::PostState; +use reth_consensus_common::calc; use reth_interfaces::executor::Error; use reth_primitives::{ Account, Address, Block, Bloom, Bytecode, ChainSpec, Hardfork, Header, Log, Receipt, @@ -6,7 +7,6 @@ use reth_primitives::{ }; use reth_provider::{BlockExecutor, StateProvider}; use reth_revm::{ - config::{WEI_2ETH, WEI_3ETH, WEI_5ETH}, database::SubState, env::{fill_cfg_and_block_env, fill_tx_env}, into_reth_log, to_reth_acc, @@ -216,26 +216,22 @@ where ) -> Result, Error> { let mut balance_increments = HashMap::::default(); - // Collect balance increments for block and uncle rewards. - if let Some(reward) = self.get_block_reward(block, td) { - // Calculate Uncle reward - // OpenEthereum code: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333 + // Add block rewards if they are enabled. + if let Some(base_block_reward) = + calc::base_block_reward(&self.chain_spec, block.number, block.difficulty, td) + { + // Ommer rewards for ommer in block.ommers.iter() { - let ommer_reward = - U256::from(((8 + ommer.number - block.number) as u128 * reward) >> 3); - // From yellowpaper Page 15: - // If there are collisions of the beneficiary addresses between ommers and the - // block (i.e. two ommers with the same beneficiary address - // or an ommer with the same beneficiary address as the - // present block), additions are applied cumulatively - *balance_increments.entry(ommer.beneficiary).or_default() += ommer_reward; + *balance_increments.entry(ommer.beneficiary).or_default() += + calc::ommer_reward(base_block_reward, block.number, ommer.number); } - // Increment balance for main block reward. - let block_reward = U256::from(reward + (reward >> 5) * block.ommers.len() as u128); - *balance_increments.entry(block.beneficiary).or_default() += block_reward; + // Full block reward + *balance_increments.entry(block.beneficiary).or_default() += + calc::block_reward(base_block_reward, block.ommers.len()); } + // Process withdrawals if self.chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block.timestamp) { if let Some(withdrawals) = block.withdrawals.as_ref() { for withdrawal in withdrawals { @@ -248,29 +244,6 @@ where Ok(balance_increments) } - /// From yellowpapper Page 15: - /// 11.3. Reward Application. The application of rewards to a block involves raising the - /// balance of the accounts of the beneficiary address of the block and each ommer by - /// a certain amount. We raise the block’s beneficiary account by Rblock; for each - /// ommer, we raise the block’s beneficiary by an additional 1/32 of the block reward - /// and the beneficiary of the ommer gets rewarded depending on the blocknumber. - /// Formally we define the function Ω. - /// - /// NOTE: Related to Ethereum reward change, for other network this is probably going to be - /// moved to config. - fn get_block_reward(&self, header: &Header, total_difficulty: U256) -> Option { - if self.chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, header.difficulty) - { - None - } else if self.chain_spec.fork(Hardfork::Petersburg).active_at_block(header.number) { - Some(WEI_2ETH) - } else if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(header.number) { - Some(WEI_3ETH) - } else { - Some(WEI_5ETH) - } - } - /// Irregular state change at Ethereum DAO hardfork fn apply_dao_fork_changes(&mut self, post_state: &mut PostState) -> Result<(), Error> { let db = self.db(); @@ -521,9 +494,10 @@ pub fn verify_receipt<'a>( #[cfg(test)] mod tests { use super::*; + use reth_consensus_common::calc; use reth_primitives::{ - hex_literal::hex, keccak256, Account, Address, BlockNumber, Bytecode, Bytes, - ChainSpecBuilder, ForkCondition, StorageKey, H256, MAINNET, U256, + constants::ETH_TO_WEI, hex_literal::hex, keccak256, Account, Address, BlockNumber, + Bytecode, Bytes, ChainSpecBuilder, ForkCondition, StorageKey, H256, MAINNET, U256, }; use reth_provider::{ post_state::{Change, Storage}, @@ -669,7 +643,8 @@ mod tests { "Should executed two transitions (1 tx and 1 block reward)" ); - let block_reward = U256::from(WEI_2ETH + (WEI_2ETH >> 5)); + let base_block_reward = ETH_TO_WEI * 2; + let block_reward = calc::block_reward(base_block_reward, 1); let account1_info = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None }; let account2_info = Account { @@ -688,8 +663,11 @@ mod tests { nonce: 0x01, bytecode_hash: None, }; - let ommer_beneficiary_info = - Account { nonce: 0, balance: U256::from((8 * WEI_2ETH) >> 3), bytecode_hash: None }; + let ommer_beneficiary_info = Account { + nonce: 0, + balance: calc::ommer_reward(base_block_reward, block.number, block.ommers[0].number), + bytecode_hash: None, + }; // Check if cache is set // account1 diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index 258c93a5ea..a45f641ac9 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -18,6 +18,12 @@ pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2; /// Multiplier for converting gwei to wei. pub const GWEI_TO_WEI: u64 = 1_000_000_000; +/// Multiplier for converting finney (milliether) to wei. +pub const FINNEY_TO_WEI: u128 = (GWEI_TO_WEI as u128) * 1_000_000; + +/// Multiplier for converting ether to wei. +pub const ETH_TO_WEI: u128 = FINNEY_TO_WEI * 1000; + /// The Ethereum mainnet genesis hash. pub const MAINNET_GENESIS: H256 = H256(hex!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")); diff --git a/crates/revm/revm-primitives/src/config.rs b/crates/revm/revm-primitives/src/config.rs index d95006fdd9..9786af3c49 100644 --- a/crates/revm/revm-primitives/src/config.rs +++ b/crates/revm/revm-primitives/src/config.rs @@ -2,13 +2,6 @@ use reth_primitives::{ChainSpec, Hardfork, Head}; -/// Two ethereum worth of wei -pub const WEI_2ETH: u128 = 2000000000000000000u128; -/// Three ethereum worth of wei -pub const WEI_3ETH: u128 = 3000000000000000000u128; -/// Five ethereum worth of wei -pub const WEI_5ETH: u128 = 5000000000000000000u128; - /// return revm_spec from spec configuration. pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm::primitives::SpecId { if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {