From f21a4020b190f6e4f880636595342c56bc7aa7fe Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 26 Dec 2022 15:44:51 +0100 Subject: [PATCH] feat(execution): Add ommers reward (#611) * feat(execution): Add ommers reward * Refactor and add comments --- crates/executor/src/executor.rs | 126 +++++++++++++++++++------- crates/stages/src/error.rs | 6 ++ crates/stages/src/stages/execution.rs | 16 +++- 3 files changed, 109 insertions(+), 39 deletions(-) diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index d385d80f30..877b6d9675 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -255,10 +255,11 @@ pub struct TransactionChangeSet { pub fn execute_and_verify_receipt( header: &Header, transactions: &[TransactionSignedEcRecovered], + ommers: &[Header], config: &Config, db: SubState, ) -> Result { - let transaction_change_set = execute(header, transactions, config, db)?; + let transaction_change_set = execute(header, transactions, ommers, config, db)?; let receipts_iter = transaction_change_set.changesets.iter().map(|changeset| &changeset.receipt); @@ -266,7 +267,7 @@ pub fn execute_and_verify_receipt( if header.number >= config.spec_upgrades.byzantium { verify_receipt(header.receipts_root, header.logs_bloom, receipts_iter)?; } - // TODO Before Byzantium receipts contained state root that would mean that expensive operation + // TODO Before Byzantium, receipts contained state root that would mean that expensive operation // as hashing that is needed 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 @@ -304,6 +305,7 @@ pub fn verify_receipt<'a>( pub fn execute( header: &Header, transactions: &[TransactionSignedEcRecovered], + ommers: &[Header], config: &Config, db: SubState, ) -> Result { @@ -394,41 +396,74 @@ pub fn execute( return Err(Error::BlockGasUsed { got: cumulative_gas_used, expected: header.gas_used }) } - // it is okay to unwrap the db. - let beneficiary = evm - .db - .expect("It is set at the start of the function") - .basic(B160(header.beneficiary.0)) - .map_err(|_| Error::ProviderError)?; + let mut db = evm.db.expect("It is set at the start of the function"); + let block_reward = block_reward_changeset(header, ommers, &mut db, config)?; + Ok(ExecutionResult { changesets, block_reward }) +} + +/// Calculate Block reward changeset +pub fn block_reward_changeset( + header: &Header, + ommers: &[Header], + db: &mut SubState, + config: &Config, +) -> Result>, Error> { // NOTE: Related to Ethereum reward change, for other network this is probably going to be moved // to config. - let block_reward = match header.number { + + // 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 Ω: + match header.number { n if n >= config.spec_upgrades.paris => None, n if n >= config.spec_upgrades.petersburg => Some(WEI_2ETH), n if n >= config.spec_upgrades.byzantium => Some(WEI_3ETH), _ => Some(WEI_5ETH), } - .map(|reward| { - // add block reward to beneficiary/miner - if let Some(beneficiary) = beneficiary { - // if account is present append `Changed` changeset for block reward - let old = to_reth_acc(&beneficiary); - let mut new = old; - new.balance += U256::from(reward); - BTreeMap::from([(header.beneficiary, AccountInfoChangeSet::Changed { new, old })]) - } else { - // if account is not present append `Created` changeset - BTreeMap::from([( - header.beneficiary, - AccountInfoChangeSet::Created { - new: Account { nonce: 0, balance: reward.into(), bytecode_hash: None }, - }, - )]) + .map(|reward| -> Result<_, _> { + let mut reward_beneficiaries: BTreeMap = BTreeMap::new(); + // Calculate Uncle reward + // OpenEthereum code: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333 + for ommer in ommers { + let ommer_reward = ((8 + ommer.number - header.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 + *reward_beneficiaries.entry(ommer.beneficiary).or_default() += ommer_reward; } - }); + // insert main block reward + *reward_beneficiaries.entry(header.beneficiary).or_default() += + reward + (reward >> 5) * ommers.len() as u128; - Ok(ExecutionResult { changesets, block_reward }) + // apply block rewards to beneficiaries (Main block and ommers); + reward_beneficiaries + .into_iter() + .map(|(beneficiary, reward)| -> Result<_, _> { + let changeset = db + .basic(B160(beneficiary.0)) + .map_err(|_| Error::ProviderError)? + // if account is present append `Changed` changeset for block reward + .map(|acc| { + let old = to_reth_acc(&acc); + let mut new = old; + new.balance += U256::from(reward); + AccountInfoChangeSet::Changed { new, old } + }) + // if account is not present append `Created` changeset + .unwrap_or(AccountInfoChangeSet::Created { + new: Account { nonce: 0, balance: reward.into(), bytecode_hash: None }, + }); + Ok((beneficiary, changeset)) + }) + .collect::, _>>() + }) + .transpose() } #[cfg(test)] @@ -510,6 +545,11 @@ mod tests { let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); + let mut ommer = Header::default(); + let ommer_beneficiary = H160(hex!("3000000000000000000000000000000000000000")); + ommer.beneficiary = ommer_beneficiary; + ommer.number = block.number; + let ommers = vec![ommer]; let mut db = StateProviderTest::default(); @@ -540,7 +580,8 @@ mod tests { block.body.iter().map(|tx| tx.try_ecrecovered().unwrap()).collect(); // execute chain and verify receipts - let out = execute_and_verify_receipt(&block.header, &transactions, &config, db).unwrap(); + let out = + execute_and_verify_receipt(&block.header, &transactions, &ommers, &config, db).unwrap(); assert_eq!(out.changesets.len(), 1, "Should executed one transaction"); @@ -576,16 +617,31 @@ mod tests { "Change to account state" ); - // check block rewards changeset + // check block rewards changeset. let mut block_rewarded_acc_info = account2_info; - // add Blocks 2 eth reward - block_rewarded_acc_info.balance += 0x1bc16d674ec80000u128.into(); + // add Blocks 2 eth reward and 2>>5 for one ommer + block_rewarded_acc_info.balance += (WEI_2ETH + (WEI_2ETH >> 5) * 1).into(); assert_eq!( out.block_reward, - Some(BTreeMap::from([( - account2, - AccountInfoChangeSet::Changed { new: block_rewarded_acc_info, old: account2_info } - )])) + Some(BTreeMap::from([ + ( + account2, + AccountInfoChangeSet::Changed { + new: block_rewarded_acc_info, + old: account2_info + } + ), + ( + ommer_beneficiary, + AccountInfoChangeSet::Created { + new: Account { + nonce: 0, + balance: ((8 * WEI_2ETH) >> 3).into(), + bytecode_hash: None + } + } + ) + ])) ); assert_eq!(changesets.new_bytecodes.len(), 0, "No new bytecodes"); diff --git a/crates/stages/src/error.rs b/crates/stages/src/error.rs index 3b3ec20f18..b4c93f847a 100644 --- a/crates/stages/src/error.rs +++ b/crates/stages/src/error.rs @@ -83,6 +83,12 @@ pub enum DatabaseIntegrityError { /// The block hash key hash: H256, }, + /// A ommers are missing. + #[error("Block ommers not found for block #{number}")] + Ommers { + /// The block number key + number: BlockNumber, + }, /// A block body is missing. #[error("Block body not found for block #{number}")] BlockBody { diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 6f60e64198..61d1295595 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -95,6 +95,8 @@ impl Stage for ExecutionStage { let mut headers = tx.cursor::()?; // Get bodies with canonical hashes. let mut bodies_cursor = tx.cursor::()?; + // Get ommers with canonical hashes. + let mut ommers_cursor = tx.cursor::()?; // Get transaction of the block that we are executing. let mut tx_cursor = tx.cursor::()?; // Skip sender recovery and load signer from database. @@ -116,8 +118,10 @@ impl Stage for ExecutionStage { // Get block headers and bodies from canonical hashes let block_batch = canonical_batch .iter() - .map(|key| -> Result<(Header, StoredBlockBody), StageError> { - // TODO see if walker next has better performance then seek_exact calls. + .map(|key| -> Result<(Header, StoredBlockBody, Vec
), StageError> { + // NOTE: It probably will be faster to fetch all items from one table with cursor, + // but to reduce complexity we are using `seek_exact` to skip some + // edge cases that can happen. let (_, header) = headers.seek_exact(*key)?.ok_or(DatabaseIntegrityError::Header { number: key.number(), @@ -126,13 +130,16 @@ impl Stage for ExecutionStage { let (_, body) = bodies_cursor .seek_exact(*key)? .ok_or(DatabaseIntegrityError::BlockBody { number: key.number() })?; - Ok((header, body)) + let (_, stored_ommers) = ommers_cursor + .seek_exact(*key)? + .ok_or(DatabaseIntegrityError::Ommers { number: key.number() })?; + Ok((header, body, stored_ommers.ommers)) }) .collect::, _>>()?; // Fetch transactions, execute them and generate results let mut block_change_patches = Vec::with_capacity(canonical_batch.len()); - for (header, body) in block_batch.iter() { + for (header, body, ommers) in block_batch.iter() { let num = header.number; tracing::trace!(target: "sync::stages::execution", ?num, "Execute block."); // iterate over all transactions @@ -192,6 +199,7 @@ impl Stage for ExecutionStage { reth_executor::executor::execute_and_verify_receipt( header, &recovered_transactions, + ommers, &self.config, state_provider, )