feat: withdrawals (#1322)

Co-authored-by: rakita <rakita@users.noreply.github.com>
This commit is contained in:
Roman Krasiuk
2023-02-16 14:44:05 +02:00
committed by GitHub
parent 94674f9c16
commit e97753c768
35 changed files with 648 additions and 264 deletions

1
Cargo.lock generated
View File

@ -4116,6 +4116,7 @@ dependencies = [
name = "reth-consensus" name = "reth-consensus"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"assert_matches",
"reth-interfaces", "reth-interfaces",
"reth-primitives", "reth-primitives",
"reth-provider", "reth-provider",

View File

@ -1,6 +1,6 @@
use reth_primitives::{ use reth_primitives::{
Address, BigEndianHash, Bloom, Bytes, ChainSpec, ChainSpecBuilder, Header as RethHeader, Address, BigEndianHash, Bloom, Bytes, ChainSpec, ChainSpecBuilder, Header as RethHeader,
JsonU256, SealedHeader, H160, H256, H64, U256, U64, JsonU256, SealedHeader, Withdrawal, H160, H256, H64,
}; };
use serde::{self, Deserialize}; use serde::{self, Deserialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -71,6 +71,8 @@ pub struct Header {
pub uncle_hash: H256, pub uncle_hash: H256,
/// Base fee per gas. /// Base fee per gas.
pub base_fee_per_gas: Option<JsonU256>, pub base_fee_per_gas: Option<JsonU256>,
/// Withdrawals root.
pub withdrawals_root: Option<H256>,
} }
impl From<Header> for SealedHeader { impl From<Header> for SealedHeader {
@ -92,7 +94,8 @@ impl From<Header> for SealedHeader {
ommers_hash: value.uncle_hash, ommers_hash: value.uncle_hash,
state_root: value.state_root, state_root: value.state_root,
parent_hash: value.parent_hash, parent_hash: value.parent_hash,
logs_bloom: Bloom::default(), // TODO: ? logs_bloom: value.bloom,
withdrawals_root: value.withdrawals_root,
}, },
value.hash, value.hash,
) )
@ -128,16 +131,6 @@ pub struct TransactionSequence {
valid: String, valid: String,
} }
/// Withdrawal in block
#[derive(Default, Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Withdrawal {
index: U64,
validator_index: U64,
address: Address,
amount: U256,
}
/// Ethereum blockchain test data state. /// Ethereum blockchain test data state.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct State(pub BTreeMap<Address, Account>); pub struct State(pub BTreeMap<Address, Account>);

View File

@ -118,8 +118,6 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<TestOutcome> {
continue continue
} }
// if matches!(suite.pre, State(RootOrState::Root(_))) {}
let pre_state = suite.pre.0; let pre_state = suite.pre.0;
debug!(target: "reth::cli", name, network = ?suite.network, "Running test"); debug!(target: "reth::cli", name, network = ?suite.network, "Running test");
@ -134,14 +132,14 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<TestOutcome> {
// insert genesis // insert genesis
let header: SealedHeader = suite.genesis_block_header.into(); let header: SealedHeader = suite.genesis_block_header.into();
let genesis_block = SealedBlock { header, body: vec![], ommers: vec![] }; let genesis_block = SealedBlock { header, body: vec![], ommers: vec![], withdrawals: None };
reth_provider::insert_canonical_block(&tx, &genesis_block, has_block_reward)?; reth_provider::insert_canonical_block(&tx, &genesis_block, has_block_reward)?;
let mut last_block = None; let mut last_block = None;
suite.blocks.iter().try_for_each(|block| -> eyre::Result<()> { suite.blocks.iter().try_for_each(|block| -> eyre::Result<()> {
let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?; let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?;
last_block = Some(decoded.number);
reth_provider::insert_canonical_block(&tx, &decoded, has_block_reward)?; reth_provider::insert_canonical_block(&tx, &decoded, has_block_reward)?;
last_block = Some(decoded.number);
Ok(()) Ok(())
})?; })?;

View File

@ -18,3 +18,4 @@ tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
reth-interfaces = { path = "../interfaces", features = ["test-utils"] } reth-interfaces = { path = "../interfaces", features = ["test-utils"] }
reth-provider = { path = "../storage/provider", features = ["test-utils"] } reth-provider = { path = "../storage/provider", features = ["test-utils"] }
assert_matches = "1.5.0"

View File

@ -78,7 +78,7 @@ impl Consensus for BeaconConsensus {
} }
fn pre_validate_block(&self, block: &SealedBlock) -> Result<(), Error> { fn pre_validate_block(&self, block: &SealedBlock) -> Result<(), Error> {
validation::validate_block_standalone(block) validation::validate_block_standalone(block, &self.chain_spec)
} }
fn has_block_reward(&self, total_difficulty: U256, difficulty: U256) -> bool { fn has_block_reward(&self, total_difficulty: U256, difficulty: U256) -> bool {

View File

@ -45,6 +45,15 @@ pub fn validate_header_standalone(
return Err(Error::BaseFeeMissing) return Err(Error::BaseFeeMissing)
} }
// EIP-4895: Beacon chain push withdrawals as operations
if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp) &&
header.withdrawals_root.is_none()
{
return Err(Error::WithdrawalsRootMissing)
} else if header.withdrawals_root.is_some() {
return Err(Error::WithdrawalsRootUnexpected)
}
Ok(()) Ok(())
} }
@ -167,7 +176,7 @@ pub fn validate_all_transaction_regarding_block_and_nonces<
/// - Compares the transactions root in the block header to the block body /// - Compares the transactions root in the block header to the block body
/// - Pre-execution transaction validation /// - Pre-execution transaction validation
/// - (Optionally) Compares the receipts root in the block header to the block body /// - (Optionally) Compares the receipts root in the block header to the block body
pub fn validate_block_standalone(block: &SealedBlock) -> Result<(), Error> { pub fn validate_block_standalone(block: &SealedBlock, chain_spec: &ChainSpec) -> Result<(), Error> {
// Check ommers hash // Check ommers hash
// TODO(onbjerg): This should probably be accessible directly on [Block] // TODO(onbjerg): This should probably be accessible directly on [Block]
let ommers_hash = let ommers_hash =
@ -189,6 +198,33 @@ pub fn validate_block_standalone(block: &SealedBlock) -> Result<(), Error> {
}) })
} }
// EIP-4895: Beacon chain push withdrawals as operations
if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block.timestamp) {
let withdrawals = block.withdrawals.as_ref().ok_or(Error::BodyWithdrawalsMissing)?;
let withdrawals_root =
reth_primitives::proofs::calculate_withdrawals_root(withdrawals.iter());
let header_withdrawals_root =
block.withdrawals_root.as_ref().ok_or(Error::WithdrawalsRootMissing)?;
if withdrawals_root != *header_withdrawals_root {
return Err(Error::BodyWithdrawalsRootDiff {
got: withdrawals_root,
expected: *header_withdrawals_root,
})
}
// Validate that withdrawal index is monotonically increasing within a block.
if let Some(first) = withdrawals.first() {
let mut prev_index = first.index;
for withdrawal in withdrawals.iter().skip(1) {
let expected = prev_index + 1;
if expected != withdrawal.index {
return Err(Error::WithdrawalIndexInvalid { got: withdrawal.index, expected })
}
prev_index = withdrawal.index;
}
}
}
Ok(()) Ok(())
} }
@ -326,7 +362,7 @@ pub fn full_validation<Provider: HeaderProvider + AccountProvider>(
chain_spec: &ChainSpec, chain_spec: &ChainSpec,
) -> RethResult<()> { ) -> RethResult<()> {
validate_header_standalone(&block.header, chain_spec)?; validate_header_standalone(&block.header, chain_spec)?;
validate_block_standalone(block)?; validate_block_standalone(block, chain_spec)?;
let parent = validate_block_regarding_chain(block, &provider)?; let parent = validate_block_regarding_chain(block, &provider)?;
validate_header_regarding_parent(&parent, &block.header, chain_spec)?; validate_header_regarding_parent(&parent, &block.header, chain_spec)?;
@ -348,15 +384,15 @@ pub fn full_validation<Provider: HeaderProvider + AccountProvider>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use assert_matches::assert_matches;
use reth_interfaces::Result; use reth_interfaces::Result;
use reth_primitives::{ use reth_primitives::{
hex_literal::hex, Account, Address, BlockHash, Bytes, Header, Signature, TransactionKind, hex_literal::hex, proofs, Account, Address, BlockHash, Bytes, ChainSpecBuilder, Header,
TransactionSigned, MAINNET, U256, Signature, TransactionKind, TransactionSigned, Withdrawal, MAINNET, U256,
}; };
use std::ops::RangeBounds; use std::ops::RangeBounds;
use super::*;
#[test] #[test]
fn calculate_base_fee_success() { fn calculate_base_fee_success() {
let base_fee = [ let base_fee = [
@ -447,6 +483,7 @@ mod tests {
let signer = Address::zero(); let signer = Address::zero();
TransactionSignedEcRecovered::from_signed_transaction(tx, signer) TransactionSignedEcRecovered::from_signed_transaction(tx, signer)
} }
/// got test block /// got test block
fn mock_block() -> (SealedBlock, Header) { fn mock_block() -> (SealedBlock, Header) {
// https://etherscan.io/block/15867168 where transaction root and receipts root are cleared // https://etherscan.io/block/15867168 where transaction root and receipts root are cleared
@ -469,6 +506,7 @@ mod tests {
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000, nonce: 0x0000000000000000,
base_fee_per_gas: 0x28f0001df.into(), base_fee_per_gas: 0x28f0001df.into(),
withdrawals_root: None
}; };
// size: 0x9b5 // size: 0x9b5
@ -481,7 +519,7 @@ mod tests {
let ommers = Vec::new(); let ommers = Vec::new();
let body = Vec::new(); let body = Vec::new();
(SealedBlock { header: header.seal(), body, ommers }, parent) (SealedBlock { header: header.seal(), body, ommers, withdrawals: None }, parent)
} }
#[test] #[test]
@ -557,4 +595,47 @@ mod tests {
Err(Error::TransactionNonceNotConsistent.into()) Err(Error::TransactionNonceNotConsistent.into())
); );
} }
#[test]
fn valid_withdrawal_index() {
let chain_spec = ChainSpecBuilder::mainnet().shanghai_activated().build();
let create_block_with_withdrawals = |indexes: &[u64]| {
let withdrawals = indexes
.iter()
.map(|idx| Withdrawal { index: *idx, ..Default::default() })
.collect::<Vec<_>>();
SealedBlock {
header: Header {
withdrawals_root: Some(proofs::calculate_withdrawals_root(withdrawals.iter())),
..Default::default()
}
.seal(),
withdrawals: Some(withdrawals),
..Default::default()
}
};
// Single withdrawal
let block = create_block_with_withdrawals(&[1]);
assert_eq!(validate_block_standalone(&block, &chain_spec), Ok(()));
// Multiple increasing withdrawals
let block = create_block_with_withdrawals(&[1, 2, 3]);
assert_eq!(validate_block_standalone(&block, &chain_spec), Ok(()));
let block = create_block_with_withdrawals(&[5, 6, 7, 8, 9]);
assert_eq!(validate_block_standalone(&block, &chain_spec), Ok(()));
// Invalid withdrawal index
let block = create_block_with_withdrawals(&[100, 102]);
assert_matches!(
validate_block_standalone(&block, &chain_spec),
Err(Error::WithdrawalIndexInvalid { .. })
);
let block = create_block_with_withdrawals(&[5, 6, 7, 9]);
assert_matches!(
validate_block_standalone(&block, &chain_spec),
Err(Error::WithdrawalIndexInvalid { .. })
);
}
} }

View File

@ -8,10 +8,10 @@ use std::collections::BTreeMap;
#[derive(Debug)] #[derive(Debug)]
pub struct ExecutionResult { pub struct ExecutionResult {
/// Transaction changeset containing [Receipt], changed [Accounts][Account] and Storages. /// Transaction changeset containing [Receipt], changed [Accounts][Account] and Storages.
pub changesets: Vec<TransactionChangeSet>, pub tx_changesets: Vec<TransactionChangeSet>,
/// Block reward if present. It represent changeset for block reward slot in /// Post block account changesets. This might include block reward, uncle rewards, withdrawals
/// [tables::AccountChangeSet] . /// or irregular state changes (DAO fork).
pub block_reward: Option<BTreeMap<Address, AccountInfoChangeSet>>, pub block_changesets: BTreeMap<Address, AccountInfoChangeSet>,
} }
/// After transaction is executed this structure contain /// After transaction is executed this structure contain

View File

@ -9,7 +9,7 @@ use hashbrown::hash_map::Entry;
use reth_interfaces::executor::{BlockExecutor, Error}; use reth_interfaces::executor::{BlockExecutor, Error};
use reth_primitives::{ use reth_primitives::{
bloom::logs_bloom, Account, Address, Block, Bloom, ChainSpec, Hardfork, Head, Header, Log, bloom::logs_bloom, Account, Address, Block, Bloom, ChainSpec, Hardfork, Head, Header, Log,
Receipt, TransactionSigned, H160, H256, U256, Receipt, TransactionSigned, H256, U256,
}; };
use reth_provider::StateProvider; use reth_provider::StateProvider;
use revm::{ use revm::{
@ -19,7 +19,7 @@ use revm::{
}, },
EVM, EVM,
}; };
use std::collections::BTreeMap; use std::collections::{BTreeMap, HashMap};
/// Main block executor /// Main block executor
pub struct Executor<'a, DB> pub struct Executor<'a, DB>
@ -93,7 +93,7 @@ where
/// BTreeMap is used to have sorted values /// BTreeMap is used to have sorted values
fn commit_changes( fn commit_changes(
&mut self, &mut self,
changes: hashbrown::HashMap<H160, RevmAccount>, changes: hashbrown::HashMap<Address, RevmAccount>,
) -> (BTreeMap<Address, AccountChangeSet>, BTreeMap<H256, Bytecode>) { ) -> (BTreeMap<Address, AccountChangeSet>, BTreeMap<H256, Bytecode>) {
let db = self.db(); let db = self.db();
@ -205,23 +205,58 @@ where
(change, new_bytecodes) (change, new_bytecodes)
} }
/// Calculate Block reward changeset /// Collect all balance changes at the end of the block. Balance changes might include block
pub fn block_reward_changeset( /// reward, uncle rewards, withdrawals or irregular state changes (DAO fork).
fn post_block_balance_increments(
&mut self, &mut self,
header: &Header, block: &Block,
total_difficulty: U256, td: U256,
ommers: &[Header], ) -> Result<HashMap<Address, U256>, Error> {
) -> Result<Option<BTreeMap<H160, AccountInfoChangeSet>>, Error> { let mut balance_increments = HashMap::<Address, U256>::default();
// NOTE: Related to Ethereum reward change, for other network this is probably going to be
// moved to config.
// From yellowpapper Page 15: // Collect balance increments for block and uncle rewards.
// 11.3. Reward Application. The application of rewards to a block involves raising the if let Some(reward) = self.get_block_reward(block, td) {
// balance of the accounts of the beneficiary address of the block and each ommer by // Calculate Uncle reward
// a certain amount. We raise the blocks beneficiary account by Rblock; for each // OpenEthereum code: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333
// ommer, we raise the blocks beneficiary by an additional 1/32 of the block reward for ommer in block.ommers.iter() {
// and the beneficiary of the ommer gets rewarded depending on the blocknumber. let ommer_reward =
// Formally we define the function Ω: 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;
}
// 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;
}
if self.chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block.timestamp) {
if let Some(withdrawals) = block.withdrawals.as_ref() {
for withdrawal in withdrawals {
*balance_increments.entry(withdrawal.address).or_default() +=
withdrawal.amount_wei();
}
}
}
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 blocks beneficiary account by Rblock; for each
/// ommer, we raise the blocks 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<u128> {
if self.chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, header.difficulty) if self.chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, header.difficulty)
{ {
None None
@ -232,78 +267,10 @@ where
} else { } else {
Some(WEI_5ETH) Some(WEI_5ETH)
} }
.map(|reward| -> Result<_, _> {
let mut reward_beneficiaries: BTreeMap<H160, u128> = 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;
//
let db = self.db();
// create changesets for beneficiaries rewards (Main block and ommers);
reward_beneficiaries
.into_iter()
.map(|(beneficiary, reward)| -> Result<_, _> {
let changeset = db
.load_account(beneficiary)
.map_err(|_| Error::ProviderError)
// if account is present append `Changed` changeset for block reward
.map(|db_acc| {
let old = to_reth_acc(&db_acc.info);
let mut new = old;
new.balance += U256::from(reward);
db_acc.info.balance = new.balance;
match db_acc.account_state {
AccountState::NotExisting => {
// if account was not existing that means that storage is not
// present.
db_acc.account_state = AccountState::StorageCleared;
// if account was not present append `Created` changeset
AccountInfoChangeSet::Created {
new: Account {
nonce: 0,
balance: new.balance,
bytecode_hash: None,
},
}
}
AccountState::StorageCleared |
AccountState::Touched |
AccountState::None => {
// If account is None that means that EVM didn't touch it.
// we are changing the state to Touched as account can have
// storage in db.
if db_acc.account_state == AccountState::None {
db_acc.account_state = AccountState::Touched;
}
// if account was present, append changed changeset.
AccountInfoChangeSet::Changed { new, old }
}
}
})?;
Ok((beneficiary, changeset))
})
.collect::<Result<BTreeMap<_, _>, _>>()
})
.transpose()
} }
/// Irregular state change at Ethereum DAO hardfork /// Irregular state change at Ethereum DAO hardfork
pub fn dao_fork_changeset(&mut self) -> Result<BTreeMap<H160, AccountInfoChangeSet>, Error> { fn dao_fork_changeset(&mut self) -> Result<BTreeMap<Address, AccountInfoChangeSet>, Error> {
let db = self.db(); let db = self.db();
let mut drained_balance = U256::ZERO; let mut drained_balance = U256::ZERO;
@ -320,7 +287,7 @@ where
// assume it is changeset as it is irregular state change // assume it is changeset as it is irregular state change
Ok((address, AccountInfoChangeSet::Changed { new, old })) Ok((address, AccountInfoChangeSet::Changed { new, old }))
}) })
.collect::<Result<BTreeMap<H160, AccountInfoChangeSet>, _>>()?; .collect::<Result<BTreeMap<Address, AccountInfoChangeSet>, _>>()?;
// add drained ether to beneficiary. // add drained ether to beneficiary.
let beneficiary = crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY; let beneficiary = crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY;
@ -338,6 +305,43 @@ where
Ok(changesets) Ok(changesets)
} }
/// Generate balance increment account changeset and mutate account database entry in place.
fn account_balance_increment_changeset(
&mut self,
address: Address,
increment: U256,
) -> Result<AccountInfoChangeSet, Error> {
let db = self.db();
let beneficiary = db.load_account(address).map_err(|_| Error::ProviderError)?;
let old = to_reth_acc(&beneficiary.info);
// Increment beneficiary balance by mutating db entry in place.
beneficiary.info.balance += increment;
let new = to_reth_acc(&beneficiary.info);
match beneficiary.account_state {
AccountState::NotExisting => {
// if account was not existing that means that storage is not
// present.
beneficiary.account_state = AccountState::StorageCleared;
// if account was not present append `Created` changeset
Ok(AccountInfoChangeSet::Created {
new: Account { nonce: 0, balance: new.balance, bytecode_hash: None },
})
}
AccountState::StorageCleared | AccountState::Touched | AccountState::None => {
// If account is None that means that EVM didn't touch it.
// we are changing the state to Touched as account can have
// storage in db.
if beneficiary.account_state == AccountState::None {
beneficiary.account_state = AccountState::Touched;
}
// if account was present, append changed changeset.
Ok(AccountInfoChangeSet::Changed { new, old })
}
}
}
} }
impl<'a, DB> BlockExecutor<ExecutionResult> for Executor<'a, DB> impl<'a, DB> BlockExecutor<ExecutionResult> for Executor<'a, DB>
@ -350,19 +354,18 @@ where
total_difficulty: U256, total_difficulty: U256,
senders: Option<Vec<Address>>, senders: Option<Vec<Address>>,
) -> Result<ExecutionResult, Error> { ) -> Result<ExecutionResult, Error> {
let Block { header, body, ommers } = block; let senders = self.recover_senders(&block.body, senders)?;
let senders = self.recover_senders(body, senders)?;
self.init_block_env(header, total_difficulty); self.init_block_env(&block.header, total_difficulty);
let mut cumulative_gas_used = 0; let mut cumulative_gas_used = 0;
// output of execution // output of execution
let mut changesets = Vec::with_capacity(body.len()); let mut tx_changesets = Vec::with_capacity(block.body.len());
for (transaction, sender) in body.iter().zip(senders.into_iter()) { for (transaction, sender) in block.body.iter().zip(senders.into_iter()) {
// The sum of the transactions gas limit, Tg, and the gas utilised in this block prior, // The sum of the transactions gas limit, Tg, and the gas utilised in this block prior,
// must be no greater than the blocks gasLimit. // must be no greater than the blocks gasLimit.
let block_available_gas = header.gas_limit - cumulative_gas_used; let block_available_gas = block.header.gas_limit - cumulative_gas_used;
if transaction.gas_limit() > block_available_gas { if transaction.gas_limit() > block_available_gas {
return Err(Error::TransactionGasLimitMoreThenAvailableBlockGas { return Err(Error::TransactionGasLimitMoreThenAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(), transaction_gas_limit: transaction.gas_limit(),
@ -376,13 +379,13 @@ where
// Execute transaction. // Execute transaction.
let out = if self.use_printer_tracer { let out = if self.use_printer_tracer {
// execution with inspector. // execution with inspector.
let out = self.evm.inspect(revm::inspectors::CustomPrintTracer::default()); let output = self.evm.inspect(revm::inspectors::CustomPrintTracer::default());
tracing::trace!(
tracing::trace!(target:"evm","Executed transaction: {:?}, target: "evm",
\nExecution output:{out:?}: hash = ?transaction.hash(), ?output, ?transaction, env = ?self.evm.env,
\nTransaction data:{transaction:?} "Executed transaction"
\nenv data:{:?}",transaction.hash(),self.evm.env); );
out output
} else { } else {
// main execution. // main execution.
self.evm.transact() self.evm.transact()
@ -401,7 +404,7 @@ where
let logs: Vec<Log> = result.logs().into_iter().map(into_reth_log).collect(); let logs: Vec<Log> = result.logs().into_iter().map(into_reth_log).collect();
// Push transaction changeset and calculate header bloom filter for receipt. // Push transaction changeset and calculate header bloom filter for receipt.
changesets.push(TransactionChangeSet { tx_changesets.push(TransactionChangeSet {
receipt: Receipt { receipt: Receipt {
tx_type: transaction.tx_type(), tx_type: transaction.tx_type(),
// Success flag was added in `EIP-658: Embedding transaction status code in // Success flag was added in `EIP-658: Embedding transaction status code in
@ -417,19 +420,25 @@ where
} }
// Check if gas used matches the value set in header. // Check if gas used matches the value set in header.
if header.gas_used != cumulative_gas_used { if block.gas_used != cumulative_gas_used {
return Err(Error::BlockGasUsed { got: cumulative_gas_used, expected: header.gas_used }) return Err(Error::BlockGasUsed { got: cumulative_gas_used, expected: block.gas_used })
} }
let mut block_reward = self.block_reward_changeset(header, total_difficulty, ommers)?; let mut block_changesets = BTreeMap::default();
let balance_increments = self.post_block_balance_increments(block, total_difficulty)?;
if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(header.number) { for (address, increment) in balance_increments {
let mut irregular_state_changeset = self.dao_fork_changeset()?; let changeset = self.account_balance_increment_changeset(address, increment)?;
irregular_state_changeset.extend(block_reward.take().unwrap_or_default().into_iter()); block_changesets.insert(address, changeset);
block_reward = Some(irregular_state_changeset);
} }
Ok(ExecutionResult { changesets, block_reward }) if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(block.number) {
for (address, changeset) in self.dao_fork_changeset()? {
// No account collision between rewarded accounts and DAO fork related accounts.
block_changesets.insert(address, changeset);
}
}
Ok(ExecutionResult { tx_changesets, block_changesets })
} }
} }
@ -443,7 +452,7 @@ pub fn execute_and_verify_receipt<DB: StateProvider>(
) -> Result<ExecutionResult, Error> { ) -> Result<ExecutionResult, Error> {
let execution_result = execute(block, total_difficulty, senders, chain_spec, db)?; let execution_result = execute(block, total_difficulty, senders, chain_spec, db)?;
let receipts_iter = execution_result.changesets.iter().map(|changeset| &changeset.receipt); let receipts_iter = execution_result.tx_changesets.iter().map(|changeset| &changeset.receipt);
if chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) { if chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) {
verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts_iter)?; verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts_iter)?;
@ -497,17 +506,15 @@ pub fn execute<DB: StateProvider>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashMap; use super::*;
use crate::revm_wrap::State; use crate::revm_wrap::State;
use reth_primitives::{ use reth_primitives::{
hex_literal::hex, keccak256, Account, Address, Bytes, ChainSpecBuilder, ForkCondition, hex_literal::hex, keccak256, Account, Address, Bytes, ChainSpecBuilder, ForkCondition,
SealedBlock, StorageKey, H160, H256, MAINNET, U256, StorageKey, H256, MAINNET, U256,
}; };
use reth_provider::{AccountProvider, BlockHashProvider, StateProvider}; use reth_provider::{AccountProvider, BlockHashProvider, StateProvider};
use reth_rlp::Decodable; use reth_rlp::Decodable;
use std::{collections::HashMap, str::FromStr};
use super::*;
#[derive(Debug, Default, Clone, Eq, PartialEq)] #[derive(Debug, Default, Clone, Eq, PartialEq)]
struct StateProviderTest { struct StateProviderTest {
@ -569,23 +576,22 @@ mod tests {
// Got rlp block from: src/GeneralStateTestsFiller/stChainId/chainIdGasCostFiller.json // Got rlp block from: src/GeneralStateTestsFiller/stChainId/chainIdGasCostFiller.json
let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
let block = SealedBlock::decode(&mut block_rlp).unwrap(); let mut block = Block::decode(&mut block_rlp).unwrap();
let mut ommer = Header::default(); let mut ommer = Header::default();
let ommer_beneficiary = H160(hex!("3000000000000000000000000000000000000000")); let ommer_beneficiary =
Address::from_str("3000000000000000000000000000000000000000").unwrap();
ommer.beneficiary = ommer_beneficiary; ommer.beneficiary = ommer_beneficiary;
ommer.number = block.number; ommer.number = block.number;
let ommers = vec![ommer]; block.ommers = vec![ommer];
let block = Block { header: block.header.unseal(), body: block.body, ommers };
let mut db = StateProviderTest::default(); let mut db = StateProviderTest::default();
let account1 = H160(hex!("1000000000000000000000000000000000000000")); let account1 = Address::from_str("1000000000000000000000000000000000000000").unwrap();
let account2 = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba")); let account2 = Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap();
let account3 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); let account3 = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();
// pre staet // pre state
db.insert_account( db.insert_account(
account1, account1,
Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None }, Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None },
@ -619,9 +625,9 @@ mod tests {
let out = let out =
execute_and_verify_receipt(&block, U256::ZERO, None, &chain_spec, &mut db).unwrap(); execute_and_verify_receipt(&block, U256::ZERO, None, &chain_spec, &mut db).unwrap();
assert_eq!(out.changesets.len(), 1, "Should executed one transaction"); assert_eq!(out.tx_changesets.len(), 1, "Should executed one transaction");
let changesets = out.changesets[0].clone(); let changesets = out.tx_changesets[0].clone();
assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes"); assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes");
let account1_info = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None }; let account1_info = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None };
@ -644,7 +650,7 @@ mod tests {
let cached_acc1 = db.accounts.get(&account1).unwrap(); let cached_acc1 = db.accounts.get(&account1).unwrap();
assert_eq!(cached_acc1.info.balance, account1_info.balance); assert_eq!(cached_acc1.info.balance, account1_info.balance);
assert_eq!(cached_acc1.info.nonce, account1_info.nonce); assert_eq!(cached_acc1.info.nonce, account1_info.nonce);
assert!(matches!(cached_acc1.account_state, AccountState::Touched)); assert_eq!(cached_acc1.account_state, AccountState::Touched);
assert_eq!(cached_acc1.storage.len(), 1); assert_eq!(cached_acc1.storage.len(), 1);
assert_eq!(cached_acc1.storage.get(&U256::from(1)), Some(&U256::from(2))); assert_eq!(cached_acc1.storage.get(&U256::from(1)), Some(&U256::from(2)));
@ -659,7 +665,7 @@ mod tests {
let cached_acc3 = db.accounts.get(&account3).unwrap(); let cached_acc3 = db.accounts.get(&account3).unwrap();
assert_eq!(cached_acc3.info.balance, account3_info.balance); assert_eq!(cached_acc3.info.balance, account3_info.balance);
assert_eq!(cached_acc3.info.nonce, account3_info.nonce); assert_eq!(cached_acc3.info.nonce, account3_info.nonce);
assert!(matches!(cached_acc3.account_state, AccountState::Touched)); assert_eq!(cached_acc3.account_state, AccountState::Touched);
assert_eq!(cached_acc3.storage.len(), 0); assert_eq!(cached_acc3.storage.len(), 0);
assert_eq!( assert_eq!(
@ -685,8 +691,8 @@ mod tests {
// check block reward changeset // check block reward changeset
assert_eq!( assert_eq!(
out.block_reward, out.block_changesets,
Some(BTreeMap::from([ BTreeMap::from([
( (
account2, account2,
AccountInfoChangeSet::Changed { AccountInfoChangeSet::Changed {
@ -704,7 +710,7 @@ mod tests {
} }
} }
) )
])) ])
); );
assert_eq!(changesets.new_bytecodes.len(), 0, "No new bytecodes"); assert_eq!(changesets.new_bytecodes.len(), 0, "No new bytecodes");
@ -744,14 +750,14 @@ mod tests {
let mut db = SubState::new(State::new(db)); let mut db = SubState::new(State::new(db));
// execute chain and verify receipts // execute chain and verify receipts
let out = execute_and_verify_receipt( let out = execute_and_verify_receipt(
&Block { header, body: vec![], ommers: vec![] }, &Block { header, body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO, U256::ZERO,
None, None,
&chain_spec, &chain_spec,
&mut db, &mut db,
) )
.unwrap(); .unwrap();
assert_eq!(out.changesets.len(), 0, "No tx"); assert_eq!(out.tx_changesets.len(), 0, "No tx");
// Check if cache is set // Check if cache is set
// beneficiary // beneficiary
@ -765,8 +771,8 @@ mod tests {
} }
// check changesets // check changesets
let block_reward = out.block_reward.unwrap(); let change_set =
let change_set = block_reward.get(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap(); out.block_changesets.get(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap();
assert_eq!( assert_eq!(
*change_set, *change_set,
AccountInfoChangeSet::Changed { AccountInfoChangeSet::Changed {
@ -775,7 +781,7 @@ mod tests {
} }
); );
for (i, address) in crate::eth_dao_fork::DAO_HARDKFORK_ACCOUNTS.iter().enumerate() { for (i, address) in crate::eth_dao_fork::DAO_HARDKFORK_ACCOUNTS.iter().enumerate() {
let change_set = block_reward.get(address).unwrap(); let change_set = out.block_changesets.get(address).unwrap();
assert_eq!( assert_eq!(
*change_set, *change_set,
AccountInfoChangeSet::Changed { AccountInfoChangeSet::Changed {
@ -793,11 +799,12 @@ mod tests {
// Got rlp block from: src/GeneralStateTestsFiller/stArgsZeroOneBalance/suicideNonConst.json // Got rlp block from: src/GeneralStateTestsFiller/stArgsZeroOneBalance/suicideNonConst.json
let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice(); let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice();
let block = SealedBlock::decode(&mut block_rlp).unwrap(); let block = Block::decode(&mut block_rlp).unwrap();
let mut db = StateProviderTest::default(); let mut db = StateProviderTest::default();
let address_caller = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); let address_caller = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();
let address_selfdestruct = H160(hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87")); let address_selfdestruct =
Address::from_str("095e7baea6a6c7c4c2dfeb977efac326af552d87").unwrap();
// pre state // pre state
let pre_account_caller = Account { let pre_account_caller = Account {
@ -834,12 +841,11 @@ mod tests {
// execute chain and verify receipts // execute chain and verify receipts
let out = let out =
execute_and_verify_receipt(&block.unseal(), U256::ZERO, None, &chain_spec, &mut db) execute_and_verify_receipt(&block, U256::ZERO, None, &chain_spec, &mut db).unwrap();
.unwrap();
assert_eq!(out.changesets.len(), 1, "Should executed one transaction"); assert_eq!(out.tx_changesets.len(), 1, "Should executed one transaction");
let changesets = out.changesets[0].clone(); let changesets = out.tx_changesets[0].clone();
assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes"); assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes");
let post_account_caller = Account { let post_account_caller = Account {
@ -865,4 +871,58 @@ mod tests {
assert!(selfdestroyer_changeset.wipe_storage); assert!(selfdestroyer_changeset.wipe_storage);
} }
// Test vector from https://github.com/ethereum/tests/blob/3156db5389921125bb9e04142d18e0e7b0cf8d64/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndexDifferentValidator.json
#[test]
fn test_withdrawals() {
let block_rlp = hex!("f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa048cd9a5957e45beebf80278a5208b0cbe975ab4b4adb0da1509c67b26f2be3ffa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a04a220ebe55034d51f8a58175bb504b6ebf883105010a1f6d42e557c18bbd5d69c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da020194c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710");
let block = Block::decode(&mut block_rlp.as_slice()).unwrap();
let withdrawals = block.withdrawals.as_ref().unwrap();
assert_eq!(withdrawals.len(), 4);
let withdrawal_beneficiary =
Address::from_str("c94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();
// spec at shanghai fork
let chain_spec = ChainSpecBuilder::mainnet().shanghai_activated().build();
let mut db = SubState::new(State::new(StateProviderTest::default()));
// execute chain and verify receipts
let out =
execute_and_verify_receipt(&block, U256::ZERO, None, &chain_spec, &mut db).unwrap();
assert_eq!(out.tx_changesets.len(), 0, "No tx");
let withdrawal_sum = withdrawals.iter().fold(U256::ZERO, |sum, w| sum + w.amount_wei());
let beneficiary_account = db.accounts.get(&withdrawal_beneficiary).unwrap();
assert_eq!(beneficiary_account.info.balance, withdrawal_sum);
assert_eq!(beneficiary_account.info.nonce, 0);
assert_eq!(beneficiary_account.account_state, AccountState::StorageCleared);
assert_eq!(out.block_changesets.len(), 1);
assert_eq!(
out.block_changesets.get(&withdrawal_beneficiary),
Some(&AccountInfoChangeSet::Created {
new: Account { nonce: 0, balance: withdrawal_sum, bytecode_hash: None },
})
);
// Execute same block again
let out =
execute_and_verify_receipt(&block, U256::ZERO, None, &chain_spec, &mut db).unwrap();
assert_eq!(out.tx_changesets.len(), 0, "No tx");
assert_eq!(out.block_changesets.len(), 1);
assert_eq!(
out.block_changesets.get(&withdrawal_beneficiary),
Some(&AccountInfoChangeSet::Changed {
old: Account { nonce: 0, balance: withdrawal_sum, bytecode_hash: None },
new: Account {
nonce: 0,
balance: withdrawal_sum + withdrawal_sum,
bytecode_hash: None
},
})
);
}
} }

View File

@ -54,14 +54,16 @@ pub trait Consensus: Debug + Send + Sync {
pub enum Error { pub enum Error {
#[error("Block used gas ({gas_used:?}) is greater than gas limit ({gas_limit:?}).")] #[error("Block used gas ({gas_used:?}) is greater than gas limit ({gas_limit:?}).")]
HeaderGasUsedExceedsGasLimit { gas_used: u64, gas_limit: u64 }, HeaderGasUsedExceedsGasLimit { gas_used: u64, gas_limit: u64 },
#[error("Block ommer hash ({got:?}) is different then expected: ({expected:?})")] #[error("Block ommer hash ({got:?}) is different from expected: ({expected:?})")]
BodyOmmersHashDiff { got: H256, expected: H256 }, BodyOmmersHashDiff { got: H256, expected: H256 },
#[error("Block state root ({got:?}) is different then expected: ({expected:?})")] #[error("Block state root ({got:?}) is different from expected: ({expected:?})")]
BodyStateRootDiff { got: H256, expected: H256 }, BodyStateRootDiff { got: H256, expected: H256 },
#[error("Block transaction root ({got:?}) is different then expected: ({expected:?})")] #[error("Block transaction root ({got:?}) is different from expected ({expected:?})")]
BodyTransactionRootDiff { got: H256, expected: H256 }, BodyTransactionRootDiff { got: H256, expected: H256 },
#[error("Block receipts root ({got:?}) is different then expected: ({expected:?}).")] #[error("Block receipts root ({got:?}) is different from expected ({expected:?})")]
BodyReceiptsRootDiff { got: H256, expected: H256 }, BodyReceiptsRootDiff { got: H256, expected: H256 },
#[error("Block withdrawals root ({got:?}) is different from expected ({expected:?})")]
BodyWithdrawalsRootDiff { got: H256, expected: H256 },
#[error("Block with [hash:{hash:?},number: {number:}] is already known.")] #[error("Block with [hash:{hash:?},number: {number:}] is already known.")]
BlockKnown { hash: BlockHash, number: BlockNumber }, BlockKnown { hash: BlockHash, number: BlockNumber },
#[error("Block parent [hash:{hash:?}] is not known.")] #[error("Block parent [hash:{hash:?}] is not known.")]
@ -120,4 +122,12 @@ pub enum Error {
TheMergeOmmerRootIsNotEmpty, TheMergeOmmerRootIsNotEmpty,
#[error("Mix hash after merge is not zero")] #[error("Mix hash after merge is not zero")]
TheMergeMixHashIsNotZero, TheMergeMixHashIsNotZero,
#[error("Missing withdrawals root")]
WithdrawalsRootMissing,
#[error("Unexpected withdrawals root")]
WithdrawalsRootUnexpected,
#[error("Withdrawal index #{got} is invalid. Expected: #{expected}.")]
WithdrawalIndexInvalid { got: u64, expected: u64 },
#[error("Missing withdrawals")]
BodyWithdrawalsMissing,
} }

View File

@ -136,6 +136,7 @@ pub fn random_block(
.seal(), .seal(),
body: transactions, body: transactions,
ommers: ommers.into_iter().map(|ommer| ommer.seal()).collect(), ommers: ommers.into_iter().map(|ommer| ommer.seal()).collect(),
withdrawals: None,
} }
} }

View File

@ -585,6 +585,7 @@ mod tests {
BlockBody { BlockBody {
transactions: block.body, transactions: block.body,
ommers: block.ommers.into_iter().map(|header| header.unseal()).collect(), ommers: block.ommers.into_iter().map(|header| header.unseal()).collect(),
withdrawals: None,
}, },
) )
}) })

View File

@ -171,6 +171,7 @@ where
header: next_header, header: next_header,
body: next_body.transactions, body: next_body.transactions,
ommers: next_body.ommers.into_iter().map(|header| header.seal()).collect(), ommers: next_body.ommers.into_iter().map(|header| header.seal()).collect(),
withdrawals: next_body.withdrawals,
}; };
if let Err(error) = self.consensus.pre_validate_block(&block) { if let Err(error) = self.consensus.pre_validate_block(&block) {

View File

@ -26,6 +26,7 @@ pub(crate) fn zip_blocks<'a>(
header: header.clone(), header: header.clone(),
body: body.transactions, body: body.transactions,
ommers: body.ommers.into_iter().map(|o| o.seal()).collect(), ommers: body.ommers.into_iter().map(|o| o.seal()).collect(),
withdrawals: body.withdrawals,
}) })
} }
}) })

View File

@ -102,7 +102,14 @@ impl FileClient {
// add to the internal maps // add to the internal maps
headers.insert(block.header.number, block.header.clone()); headers.insert(block.header.number, block.header.clone());
hash_to_number.insert(block_hash, block.header.number); hash_to_number.insert(block_hash, block.header.number);
bodies.insert(block_hash, BlockBody { transactions: block.body, ommers: block.ommers }); bodies.insert(
block_hash,
BlockBody {
transactions: block.body,
ommers: block.ommers,
withdrawals: block.withdrawals,
},
);
} }
trace!(blocks = headers.len(), "Initialized file client"); trace!(blocks = headers.len(), "Initialized file client");

View File

@ -30,6 +30,7 @@ pub(crate) fn generate_bodies(
BlockBody { BlockBody {
transactions: block.body, transactions: block.body,
ommers: block.ommers.into_iter().map(|header| header.unseal()).collect(), ommers: block.ommers.into_iter().map(|header| header.unseal()).collect(),
withdrawals: block.withdrawals,
}, },
) )
}) })

View File

@ -2,7 +2,7 @@
//! types. //! types.
use reth_codecs::derive_arbitrary; use reth_codecs::derive_arbitrary;
use reth_primitives::{ use reth_primitives::{
Block, BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, H256, Block, BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, Withdrawal, H256,
}; };
use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
@ -70,14 +70,19 @@ impl From<Vec<H256>> for GetBlockBodies {
// TODO(onbjerg): We should have this type in primitives // TODO(onbjerg): We should have this type in primitives
/// A response to [`GetBlockBodies`], containing bodies if any bodies were found. /// A response to [`GetBlockBodies`], containing bodies if any bodies were found.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
#[derive_arbitrary(rlp, 10)] #[derive_arbitrary(rlp, 10)]
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)] #[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[rlp(trailing)]
pub struct BlockBody { pub struct BlockBody {
/// Transactions in the block /// Transactions in the block
pub transactions: Vec<TransactionSigned>, pub transactions: Vec<TransactionSigned>,
/// Uncle headers for the given block /// Uncle headers for the given block
pub ommers: Vec<Header>, pub ommers: Vec<Header>,
/// Withdrawals in the block.
pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl BlockBody { impl BlockBody {
@ -87,6 +92,7 @@ impl BlockBody {
header: header.clone(), header: header.clone(),
body: self.transactions.clone(), body: self.transactions.clone(),
ommers: self.ommers.clone(), ommers: self.ommers.clone(),
withdrawals: self.withdrawals.clone(),
} }
} }
} }
@ -276,6 +282,7 @@ mod test {
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64, nonce: 0x0000000000000000u64,
base_fee_per_gas: None, base_fee_per_gas: None,
withdrawals_root: None,
}, },
]), ]),
}.encode(&mut data); }.encode(&mut data);
@ -306,6 +313,7 @@ mod test {
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64, nonce: 0x0000000000000000u64,
base_fee_per_gas: None, base_fee_per_gas: None,
withdrawals_root: None,
}, },
]), ]),
}; };
@ -417,8 +425,10 @@ mod test {
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64, nonce: 0x0000000000000000u64,
base_fee_per_gas: None, base_fee_per_gas: None,
withdrawals_root: None,
}, },
], ],
withdrawals: None,
} }
]), ]),
}; };
@ -499,8 +509,10 @@ mod test {
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64, nonce: 0x0000000000000000u64,
base_fee_per_gas: None, base_fee_per_gas: None,
withdrawals_root: None,
}, },
], ],
withdrawals: None,
} }
]), ]),
}; };

View File

@ -164,7 +164,11 @@ where
if let Some(block) = if let Some(block) =
self.client.block(rpc::BlockId::Hash(rpc::H256(hash.0))).unwrap_or_default() self.client.block(rpc::BlockId::Hash(rpc::H256(hash.0))).unwrap_or_default()
{ {
let body = BlockBody { transactions: block.body, ommers: block.ommers }; let body = BlockBody {
transactions: block.body,
ommers: block.ommers,
withdrawals: block.withdrawals,
};
bodies.push(body); bodies.push(body);

View File

@ -70,7 +70,8 @@ async fn test_get_body() {
let blocks = res.unwrap().1; let blocks = res.unwrap().1;
assert_eq!(blocks.len(), 1); assert_eq!(blocks.len(), 1);
let expected = BlockBody { transactions: block.body, ommers: block.ommers }; let expected =
BlockBody { transactions: block.body, ommers: block.ommers, withdrawals: None };
assert_eq!(blocks[0], expected); assert_eq!(blocks[0], expected);
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{Header, SealedHeader, TransactionSigned, H256}; use crate::{Header, SealedHeader, TransactionSigned, Withdrawal, H256};
use reth_codecs::derive_arbitrary; use reth_codecs::derive_arbitrary;
use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable};
use serde::{ use serde::{
@ -9,17 +9,22 @@ use serde::{
use std::{fmt, fmt::Formatter, ops::Deref, str::FromStr}; use std::{fmt, fmt::Formatter, ops::Deref, str::FromStr};
/// Ethereum full block. /// Ethereum full block.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
#[derive_arbitrary(rlp, 25)] #[derive_arbitrary(rlp, 25)]
#[derive( #[derive(
Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable, RlpDecodable,
)] )]
#[rlp(trailing)]
pub struct Block { pub struct Block {
/// Block header. /// Block header.
pub header: Header, pub header: Header,
/// Transactions in this block. /// Transactions in this block.
pub body: Vec<TransactionSigned>, pub body: Vec<TransactionSigned>,
/// Ommers/uncles header /// Ommers/uncles header.
pub ommers: Vec<Header>, pub ommers: Vec<Header>,
/// Block withdrawals.
pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl Deref for Block { impl Deref for Block {
@ -30,10 +35,13 @@ impl Deref for Block {
} }
/// Sealed Ethereum full block. /// Sealed Ethereum full block.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
#[derive_arbitrary(rlp, 10)] #[derive_arbitrary(rlp, 10)]
#[derive( #[derive(
Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable, RlpDecodable,
)] )]
#[rlp(trailing)]
pub struct SealedBlock { pub struct SealedBlock {
/// Locked block header. /// Locked block header.
pub header: SealedHeader, pub header: SealedHeader,
@ -41,6 +49,8 @@ pub struct SealedBlock {
pub body: Vec<TransactionSigned>, pub body: Vec<TransactionSigned>,
/// Ommer/uncle headers /// Ommer/uncle headers
pub ommers: Vec<SealedHeader>, pub ommers: Vec<SealedHeader>,
/// Block withdrawals.
pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl SealedBlock { impl SealedBlock {
@ -60,6 +70,7 @@ impl SealedBlock {
header: self.header.unseal(), header: self.header.unseal(),
body: self.body, body: self.body,
ommers: self.ommers.into_iter().map(|o| o.unseal()).collect(), ommers: self.ommers.into_iter().map(|o| o.unseal()).collect(),
withdrawals: self.withdrawals,
} }
} }
} }

View File

@ -426,6 +426,13 @@ impl ChainSpecBuilder {
self self
} }
/// Enable Shanghai at genesis.
pub fn shanghai_activated(mut self) -> Self {
self = self.paris_activated();
self.hardforks.insert(Hardfork::Shanghai, ForkCondition::Timestamp(0));
self
}
/// Build the resulting [`ChainSpec`]. /// Build the resulting [`ChainSpec`].
/// ///
/// # Panics /// # Panics

View File

@ -12,6 +12,9 @@ pub const EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8;
/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) /// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2; pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2;
/// Multiplier for converting gwei to wei.
pub const GWEI_TO_WEI: u64 = 1_000_000_000;
/// The Ethereum mainnet genesis hash. /// The Ethereum mainnet genesis hash.
pub const MAINNET_GENESIS: H256 = pub const MAINNET_GENESIS: H256 =
H256(hex!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")); H256(hex!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"));

View File

@ -3,10 +3,10 @@ use crate::{
proofs::{EMPTY_LIST_HASH, EMPTY_ROOT}, proofs::{EMPTY_LIST_HASH, EMPTY_ROOT},
BlockHash, BlockNumber, Bloom, Bytes, H160, H256, U256, BlockHash, BlockNumber, Bloom, Bytes, H160, H256, U256,
}; };
use bytes::{BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use ethers_core::types::{Block, H256 as EthersH256, H64}; use ethers_core::types::{Block, H256 as EthersH256, H64};
use reth_codecs::{add_arbitrary_tests, derive_arbitrary, main_codec, Compact}; use reth_codecs::{add_arbitrary_tests, derive_arbitrary, main_codec, Compact};
use reth_rlp::{length_of_length, Decodable, Encodable}; use reth_rlp::{length_of_length, Decodable, Encodable, EMPTY_STRING_CODE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
@ -48,13 +48,14 @@ pub struct Header {
/// executed and finalisations applied; formally Hr. /// executed and finalisations applied; formally Hr.
pub state_root: H256, pub state_root: H256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each /// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// transaction in the transactions list portion of the /// transaction in the transactions list portion of the block; formally Ht.
/// block; formally Ht.
pub transactions_root: H256, pub transactions_root: H256,
/// The Keccak 256-bit hash of the root /// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
/// node of the trie structure populated with the receipts of each transaction in the /// of each transaction in the transactions list portion of the block; formally He.
/// transactions list portion of the block; formally He.
pub receipts_root: H256, pub receipts_root: H256,
/// The Keccak 256-bit hash of the withdrawals list portion of this block.
/// <https://eips.ethereum.org/EIPS/eip-4895>
pub withdrawals_root: Option<H256>,
/// The Bloom filter composed from indexable information (logger address and log topics) /// The Bloom filter composed from indexable information (logger address and log topics)
/// contained in each log entry from the receipt of each transaction in the transactions list; /// contained in each log entry from the receipt of each transaction in the transactions list;
/// formally Hb. /// formally Hb.
@ -110,6 +111,7 @@ impl Default for Header {
mix_hash: Default::default(), mix_hash: Default::default(),
nonce: 0, nonce: 0,
base_fee_per_gas: None, base_fee_per_gas: None,
withdrawals_root: None,
} }
} }
} }
@ -125,7 +127,12 @@ impl Header {
/// Checks if the header is empty - has no transactions and no ommers /// Checks if the header is empty - has no transactions and no ommers
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.transaction_root_is_empty() && self.ommers_hash_is_empty() let txs_and_ommers_empty = self.transaction_root_is_empty() && self.ommers_hash_is_empty();
if let Some(withdrawals_root) = self.withdrawals_root {
txs_and_ommers_empty && withdrawals_root == EMPTY_ROOT
} else {
txs_and_ommers_empty
}
} }
/// Check if the ommers hash equals to empty hash list. /// Check if the ommers hash equals to empty hash list.
@ -161,7 +168,16 @@ impl Header {
length += self.extra_data.length(); length += self.extra_data.length();
length += self.mix_hash.length(); length += self.mix_hash.length();
length += H64::from_low_u64_be(self.nonce).length(); length += H64::from_low_u64_be(self.nonce).length();
length += self.base_fee_per_gas.map(|fee| U256::from(fee).length()).unwrap_or_default();
if let Some(base_fee) = self.base_fee_per_gas {
length += U256::from(base_fee).length();
} else if self.withdrawals_root.is_some() {
length += 1; // EMTY STRING CODE
}
if let Some(root) = self.withdrawals_root {
length += root.length();
}
length length
} }
} }
@ -186,8 +202,17 @@ impl Encodable for Header {
self.extra_data.encode(out); self.extra_data.encode(out);
self.mix_hash.encode(out); self.mix_hash.encode(out);
H64::from_low_u64_be(self.nonce).encode(out); H64::from_low_u64_be(self.nonce).encode(out);
// Encode base fee. Put empty string if base fee is missing,
// but withdrawals root is present.
if let Some(ref base_fee) = self.base_fee_per_gas { if let Some(ref base_fee) = self.base_fee_per_gas {
U256::from(*base_fee).encode(out); U256::from(*base_fee).encode(out);
} else if self.withdrawals_root.is_some() {
out.put_u8(EMPTY_STRING_CODE);
}
if let Some(ref root) = self.withdrawals_root {
root.encode(out);
} }
} }
@ -223,10 +248,17 @@ impl Decodable for Header {
mix_hash: Decodable::decode(buf)?, mix_hash: Decodable::decode(buf)?,
nonce: H64::decode(buf)?.to_low_u64_be(), nonce: H64::decode(buf)?.to_low_u64_be(),
base_fee_per_gas: None, base_fee_per_gas: None,
withdrawals_root: None,
}; };
let consumed = started_len - buf.len(); if started_len - buf.len() < rlp_head.payload_length {
if consumed < rlp_head.payload_length { if buf.first().map(|b| *b == EMPTY_STRING_CODE).unwrap_or_default() {
this.base_fee_per_gas = Some(U256::decode(buf)?.to::<u64>()); buf.advance(1)
} else {
this.base_fee_per_gas = Some(U256::decode(buf)?.to::<u64>());
}
}
if started_len - buf.len() < rlp_head.payload_length {
this.withdrawals_root = Some(Decodable::decode(buf)?);
} }
let consumed = started_len - buf.len(); let consumed = started_len - buf.len();
if consumed != rlp_head.payload_length { if consumed != rlp_head.payload_length {
@ -493,6 +525,7 @@ mod tests {
mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
nonce: 0, nonce: 0,
base_fee_per_gas: Some(0x036b_u64), base_fee_per_gas: Some(0x036b_u64),
withdrawals_root: None,
}; };
assert_eq!(header.hash_slow(), expected_hash); assert_eq!(header.hash_slow(), expected_hash);
} }
@ -518,10 +551,56 @@ mod tests {
assert_eq!(header, expected); assert_eq!(header, expected);
// make sure the hash matches // make sure the hash matches
let expected_hash = H256::from_slice( let expected_hash =
&hex::decode("8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9") H256::from_str("8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9")
.unwrap(), .unwrap();
); assert_eq!(header.hash_slow(), expected_hash);
}
// Test vector from: https://github.com/ethereum/tests/blob/970503935aeb76f59adfa3b3224aabf25e77b83d/BlockchainTests/ValidBlocks/bcExample/shanghaiExample.json#L15-L34
#[test]
fn test_decode_block_header_with_withdrawals() {
let data = hex::decode("f9021ca018db39e19931515b30b16b3a92c292398039e31d6c267111529c3f2ba0a26c17a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa095efce3d6972874ca8b531b233b7a1d1ff0a56f08b20c8f1b89bef1b001194a5a071e515dd89e8a7973402c2e11646081b4e2209b2d3a1550df5095289dabcb3fba0ed9c51ea52c968e552e370a77a41dac98606e98b915092fb5f949d6452fce1c4b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff830125b882079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a027f166f1d7c789251299535cb176ba34116e44894476a7886fe5d73d9be5c973").unwrap();
let expected = Header {
parent_hash: H256::from_str(
"18db39e19931515b30b16b3a92c292398039e31d6c267111529c3f2ba0a26c17",
)
.unwrap(),
beneficiary: Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(),
state_root: H256::from_str(
"95efce3d6972874ca8b531b233b7a1d1ff0a56f08b20c8f1b89bef1b001194a5",
)
.unwrap(),
transactions_root: H256::from_str(
"71e515dd89e8a7973402c2e11646081b4e2209b2d3a1550df5095289dabcb3fb",
)
.unwrap(),
receipts_root: H256::from_str(
"ed9c51ea52c968e552e370a77a41dac98606e98b915092fb5f949d6452fce1c4",
)
.unwrap(),
number: 0x01,
gas_limit: 0x7fffffffffffffff,
gas_used: 0x0125b8,
timestamp: 0x079e,
extra_data: Bytes::from_str("42").unwrap(),
mix_hash: H256::from_str(
"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
)
.unwrap(),
base_fee_per_gas: Some(0x09),
withdrawals_root: Some(
H256::from_str("27f166f1d7c789251299535cb176ba34116e44894476a7886fe5d73d9be5c973")
.unwrap(),
),
..Default::default()
};
let header = <Header as Decodable>::decode(&mut data.as_slice()).unwrap();
assert_eq!(header, expected);
let expected_hash =
H256::from_str("85fdec94c534fa0a1534720f167b899d1fc268925c71c0cbf5aaa213483f5a69")
.unwrap();
assert_eq!(header.hash_slow(), expected_hash); assert_eq!(header.hash_slow(), expected_hash);
} }

View File

@ -29,6 +29,7 @@ mod peer;
mod receipt; mod receipt;
mod storage; mod storage;
mod transaction; mod transaction;
mod withdrawal;
/// Helper function for calculating Merkle proofs and hashes /// Helper function for calculating Merkle proofs and hashes
pub mod proofs; pub mod proofs;
@ -61,6 +62,7 @@ pub use transaction::{
Transaction, TransactionKind, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, Transaction, TransactionKind, TransactionSigned, TransactionSignedEcRecovered, TxEip1559,
TxEip2930, TxLegacy, TxType, TxEip2930, TxLegacy, TxType,
}; };
pub use withdrawal::Withdrawal;
/// A block hash. /// A block hash.
pub type BlockHash = H256; pub type BlockHash = H256;

View File

@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
keccak256, Address, Bytes, GenesisAccount, Header, Log, Receipt, TransactionSigned, H256, keccak256, Address, Bytes, GenesisAccount, Header, Log, Receipt, TransactionSigned, Withdrawal,
H256,
}; };
use bytes::BytesMut; use bytes::BytesMut;
use hash_db::Hasher; use hash_db::Hasher;
@ -47,6 +48,17 @@ pub fn calculate_transaction_root<'a>(
})) }))
} }
/// Calculates the root hash of the withdrawals.
pub fn calculate_withdrawals_root<'a>(
withdrawals: impl IntoIterator<Item = &'a Withdrawal>,
) -> H256 {
ordered_trie_root::<KeccakHasher, _>(withdrawals.into_iter().map(|withdrawal| {
let mut withdrawal_rlp = Vec::new();
withdrawal.encode(&mut withdrawal_rlp);
withdrawal_rlp
}))
}
/// Calculates the receipt root for a header. /// Calculates the receipt root for a header.
pub fn calculate_receipt_root<'a>(receipts: impl Iterator<Item = &'a Receipt>) -> H256 { pub fn calculate_receipt_root<'a>(receipts: impl Iterator<Item = &'a Receipt>) -> H256 {
ordered_trie_root::<KeccakHasher, _>(receipts.into_iter().map(|receipt| { ordered_trie_root::<KeccakHasher, _>(receipts.into_iter().map(|receipt| {
@ -96,7 +108,7 @@ mod tests {
}; };
use reth_rlp::Decodable; use reth_rlp::Decodable;
use super::EMPTY_ROOT; use super::{calculate_withdrawals_root, EMPTY_ROOT};
#[test] #[test]
fn check_transaction_root() { fn check_transaction_root() {
@ -105,7 +117,7 @@ mod tests {
let block: Block = Block::decode(block_rlp).unwrap(); let block: Block = Block::decode(block_rlp).unwrap();
let tx_root = calculate_transaction_root(block.body.iter()); let tx_root = calculate_transaction_root(block.body.iter());
assert_eq!(block.transactions_root, tx_root, "Should be same"); assert_eq!(block.transactions_root, tx_root, "Must be the same");
} }
#[test] #[test]
@ -127,6 +139,29 @@ mod tests {
); );
} }
#[test]
fn check_withdrawals_root() {
// Single withdrawal, amount 0
// https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/amountIs0.json
let data = &hex!("f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80");
let block: Block = Block::decode(&mut data.as_slice()).unwrap();
assert!(block.withdrawals.is_some());
let withdrawals = block.withdrawals.as_ref().unwrap();
assert_eq!(withdrawals.len(), 1);
let withdrawals_root = calculate_withdrawals_root(withdrawals.iter());
assert_eq!(block.withdrawals_root, Some(withdrawals_root));
// 4 withdrawals, identical indices
// https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndex.json
let data = &hex!("f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710");
let block: Block = Block::decode(&mut data.as_slice()).unwrap();
assert!(block.withdrawals.is_some());
let withdrawals = block.withdrawals.as_ref().unwrap();
assert_eq!(withdrawals.len(), 4);
let withdrawals_root = calculate_withdrawals_root(withdrawals.iter());
assert_eq!(block.withdrawals_root, Some(withdrawals_root));
}
#[test] #[test]
fn check_empty_state_root() { fn check_empty_state_root() {
let genesis_alloc = HashMap::new(); let genesis_alloc = HashMap::new();

View File

@ -0,0 +1,24 @@
use crate::{constants::GWEI_TO_WEI, Address, U256};
use reth_codecs::{main_codec, Compact};
use reth_rlp::{RlpDecodable, RlpEncodable};
/// Withdrawal represents a validator withdrawal from the consensus layer.
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
pub struct Withdrawal {
/// Monotonically increasing identifier issued by consensus layer.
pub index: u64,
/// Index of validator associated with withdrawal.
pub validator_index: u64,
/// Target address for withdrawn ether.
pub address: Address,
/// Value of the withdrawal in gwei.
pub amount: u64,
}
impl Withdrawal {
/// Return the withdrawal amount in wei.
pub fn amount_wei(&self) -> U256 {
U256::from(self.amount) * U256::from(GWEI_TO_WEI)
}
}

View File

@ -85,12 +85,17 @@ impl<Client: HeaderProvider + BlockProvider + StateProvider> EngineApi<Client> {
.map(|tx| TransactionSigned::decode(&mut tx.as_ref())) .map(|tx| TransactionSigned::decode(&mut tx.as_ref()))
.collect::<std::result::Result<Vec<_>, _>>()?; .collect::<std::result::Result<Vec<_>, _>>()?;
let transactions_root = proofs::calculate_transaction_root(transactions.iter()); let transactions_root = proofs::calculate_transaction_root(transactions.iter());
let withdrawals_root =
payload.withdrawals.as_ref().map(|w| proofs::calculate_withdrawals_root(w.iter()));
let header = Header { let header = Header {
parent_hash: payload.parent_hash, parent_hash: payload.parent_hash,
beneficiary: payload.fee_recipient, beneficiary: payload.fee_recipient,
state_root: payload.state_root, state_root: payload.state_root,
transactions_root, transactions_root,
receipts_root: payload.receipts_root, receipts_root: payload.receipts_root,
withdrawals_root,
logs_bloom: payload.logs_bloom, logs_bloom: payload.logs_bloom,
number: payload.block_number.as_u64(), number: payload.block_number.as_u64(),
gas_limit: payload.gas_limit.as_u64(), gas_limit: payload.gas_limit.as_u64(),
@ -113,7 +118,12 @@ impl<Client: HeaderProvider + BlockProvider + StateProvider> EngineApi<Client> {
}) })
} }
Ok(SealedBlock { header, body: transactions, ommers: Default::default() }) Ok(SealedBlock {
header,
body: transactions,
withdrawals: payload.withdrawals,
ommers: Default::default(),
})
} }
/// Called to retrieve the latest state of the network, validate new blocks, and maintain /// Called to retrieve the latest state of the network, validate new blocks, and maintain
@ -333,6 +343,7 @@ mod tests {
header: transformed.header.seal(), header: transformed.header.seal(),
body: transformed.body, body: transformed.body,
ommers: transformed.ommers.into_iter().map(Header::seal).collect(), ommers: transformed.ommers.into_iter().map(Header::seal).collect(),
withdrawals: transformed.withdrawals,
} }
} }

View File

@ -1,7 +1,8 @@
//! Contains types that represent ethereum types in [reth_primitives] when used in RPC //! Contains types that represent ethereum types in [reth_primitives] when used in RPC
use crate::Transaction; use crate::Transaction;
use reth_primitives::{ use reth_primitives::{
Address, Block as PrimitiveBlock, Bloom, Bytes, Header as RethHeader, H256, H64, U256, Address, Block as PrimitiveBlock, Bloom, Bytes, Header as RethHeader, Withdrawal, H256, H64,
U256,
}; };
use reth_rlp::Encodable; use reth_rlp::Encodable;
use serde::{ser::Error, Deserialize, Serialize, Serializer}; use serde::{ser::Error, Deserialize, Serialize, Serializer};
@ -43,6 +44,8 @@ pub struct Block {
/// Base Fee for post-EIP1559 blocks. /// Base Fee for post-EIP1559 blocks.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub base_fee_per_gas: Option<U256>, pub base_fee_per_gas: Option<U256>,
/// Withdrawals
pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl Block { impl Block {
@ -74,6 +77,7 @@ impl Block {
nonce, nonce,
base_fee_per_gas, base_fee_per_gas,
extra_data, extra_data,
withdrawals_root,
} = block.header; } = block.header;
let header = Header { let header = Header {
@ -85,6 +89,7 @@ impl Block {
state_root, state_root,
transactions_root, transactions_root,
receipts_root, receipts_root,
withdrawals_root,
number: Some(U256::from(number)), number: Some(U256::from(number)),
gas_used: U256::from(gas_used), gas_used: U256::from(gas_used),
gas_limit: U256::from(gas_limit), gas_limit: U256::from(gas_limit),
@ -115,6 +120,7 @@ impl Block {
base_fee_per_gas: base_fee_per_gas.map(U256::from), base_fee_per_gas: base_fee_per_gas.map(U256::from),
total_difficulty, total_difficulty,
size: Some(U256::from(block_length)), size: Some(U256::from(block_length)),
withdrawals: block.withdrawals,
}) })
} }
} }
@ -140,6 +146,8 @@ pub struct Header {
pub transactions_root: H256, pub transactions_root: H256,
/// Transactions receipts root hash /// Transactions receipts root hash
pub receipts_root: H256, pub receipts_root: H256,
/// Withdrawals root hash
pub withdrawals_root: Option<H256>,
/// Block number /// Block number
pub number: Option<U256>, pub number: Option<U256>,
/// Gas Used /// Gas Used

View File

@ -3,7 +3,7 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use bytes::BytesMut; use bytes::BytesMut;
use reth_primitives::{Address, Bloom, Bytes, SealedBlock, H256, H64, U256, U64}; use reth_primitives::{Address, Bloom, Bytes, SealedBlock, Withdrawal, H256, H64, U256, U64};
use reth_rlp::Encodable; use reth_rlp::Encodable;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -30,7 +30,7 @@ pub struct ExecutionPayload {
/// Array of [`Withdrawal`] enabled with V2 /// Array of [`Withdrawal`] enabled with V2
/// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2> /// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawal: Option<Withdrawal>, pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl From<SealedBlock> for ExecutionPayload { impl From<SealedBlock> for ExecutionPayload {
@ -59,26 +59,11 @@ impl From<SealedBlock> for ExecutionPayload {
base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()), base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()),
block_hash: value.hash(), block_hash: value.hash(),
transactions, transactions,
withdrawal: None, withdrawals: value.withdrawals,
} }
} }
} }
/// This structure maps onto the validator withdrawal object from the beacon chain spec.
///
/// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#withdrawalv1>
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Withdrawal {
pub index: U64,
pub validator_index: U64,
pub address: Address,
/// Note: the amount value is represented on the beacon chain as a little-endian value in units
/// of Gwei, whereas the amount in this structure MUST be converted to a big-endian value in
/// units of Wei.
pub amount: U256,
}
/// This structure encapsulates the fork choice state /// This structure encapsulates the fork choice state
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -99,7 +84,7 @@ pub struct PayloadAttributes {
/// Array of [`Withdrawal`] enabled with V2 /// Array of [`Withdrawal`] enabled with V2
/// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2> /// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawal: Option<Withdrawal>, // TODO: should be a vec pub withdrawals: Option<Vec<Withdrawal>>,
} }
/// This structure contains the result of processing a payload /// This structure contains the result of processing a payload

View File

@ -6,7 +6,7 @@ use futures_util::TryStreamExt;
use reth_db::{ use reth_db::{
cursor::{DbCursorRO, DbCursorRW}, cursor::{DbCursorRO, DbCursorRW},
database::Database, database::Database,
models::{StoredBlockBody, StoredBlockOmmers}, models::{StoredBlockBody, StoredBlockOmmers, StoredBlockWithdrawals},
tables, tables,
transaction::{DbTx, DbTxMut}, transaction::{DbTx, DbTxMut},
}; };
@ -28,6 +28,7 @@ pub const BODIES: StageId = StageId("Bodies");
/// The body stage downloads block bodies for all block headers stored locally in the database. /// The body stage downloads block bodies for all block headers stored locally in the database.
/// ///
/// # Empty blocks /// # Empty blocks
/// ///
/// Blocks with an ommers hash corresponding to no ommers *and* a transaction root corresponding to /// Blocks with an ommers hash corresponding to no ommers *and* a transaction root corresponding to
/// no transactions will not have a block body downloaded for them, since it would be meaningless to /// no transactions will not have a block body downloaded for them, since it would be meaningless to
@ -88,8 +89,9 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
// Cursors used to write bodies, ommers and transactions // Cursors used to write bodies, ommers and transactions
let mut body_cursor = tx.cursor_write::<tables::BlockBodies>()?; let mut body_cursor = tx.cursor_write::<tables::BlockBodies>()?;
let mut ommers_cursor = tx.cursor_write::<tables::BlockOmmers>()?;
let mut tx_cursor = tx.cursor_write::<tables::Transactions>()?; let mut tx_cursor = tx.cursor_write::<tables::Transactions>()?;
let mut ommers_cursor = tx.cursor_write::<tables::BlockOmmers>()?;
let mut withdrawals_cursor = tx.cursor_write::<tables::BlockWithdrawals>()?;
// Cursors used to write state transition mapping // Cursors used to write state transition mapping
let mut block_transition_cursor = tx.cursor_write::<tables::BlockTransitionIndex>()?; let mut block_transition_cursor = tx.cursor_write::<tables::BlockTransitionIndex>()?;
@ -112,6 +114,7 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
let block_number = response.block_number(); let block_number = response.block_number();
let difficulty = response.difficulty(); let difficulty = response.difficulty();
let mut has_withdrawals = false;
match response { match response {
BlockResponse::Full(block) => { BlockResponse::Full(block) => {
body_cursor.append( body_cursor.append(
@ -133,6 +136,7 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
transition_id += 1; transition_id += 1;
} }
// Write ommers if any
if !block.ommers.is_empty() { if !block.ommers.is_empty() {
ommers_cursor.append( ommers_cursor.append(
block_number, block_number,
@ -145,6 +149,15 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
}, },
)?; )?;
} }
// Write withdrawals if any
if let Some(withdrawals) = block.withdrawals {
if !withdrawals.is_empty() {
has_withdrawals = true;
withdrawals_cursor
.append(block_number, StoredBlockWithdrawals { withdrawals })?;
}
}
} }
BlockResponse::Empty(_) => { BlockResponse::Empty(_) => {
body_cursor.append( body_cursor.append(
@ -163,7 +176,8 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
.ok_or(ProviderError::TotalDifficulty { number: block_number })? .ok_or(ProviderError::TotalDifficulty { number: block_number })?
.1; .1;
let has_reward = self.consensus.has_block_reward(td.into(), difficulty); let has_reward = self.consensus.has_block_reward(td.into(), difficulty);
if has_reward { let has_post_block_transition = has_reward || has_withdrawals;
if has_post_block_transition {
transition_id += 1; transition_id += 1;
} }
block_transition_cursor.append(block_number, transition_id)?; block_transition_cursor.append(block_number, transition_id)?;
@ -188,8 +202,9 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
info!(target: "sync::stages::bodies", to_block = input.unwind_to, "Unwinding"); info!(target: "sync::stages::bodies", to_block = input.unwind_to, "Unwinding");
// Cursors to unwind bodies, ommers // Cursors to unwind bodies, ommers
let mut body_cursor = tx.cursor_write::<tables::BlockBodies>()?; let mut body_cursor = tx.cursor_write::<tables::BlockBodies>()?;
let mut ommers_cursor = tx.cursor_write::<tables::BlockOmmers>()?;
let mut transaction_cursor = tx.cursor_write::<tables::Transactions>()?; let mut transaction_cursor = tx.cursor_write::<tables::Transactions>()?;
let mut ommers_cursor = tx.cursor_write::<tables::BlockOmmers>()?;
let mut withdrawals_cursor = tx.cursor_write::<tables::BlockWithdrawals>()?;
// Cursors to unwind transitions // Cursors to unwind transitions
let mut block_transition_cursor = tx.cursor_write::<tables::BlockTransitionIndex>()?; let mut block_transition_cursor = tx.cursor_write::<tables::BlockTransitionIndex>()?;
let mut tx_transition_cursor = tx.cursor_write::<tables::TxTransitionIndex>()?; let mut tx_transition_cursor = tx.cursor_write::<tables::TxTransitionIndex>()?;
@ -200,11 +215,16 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
break break
} }
// Delete the ommers value if any // Delete the ommers entry if any
if ommers_cursor.seek_exact(number)?.is_some() { if ommers_cursor.seek_exact(number)?.is_some() {
ommers_cursor.delete_current()?; ommers_cursor.delete_current()?;
} }
// Delete the withdrawals entry if any
if withdrawals_cursor.seek_exact(number)?.is_some() {
withdrawals_cursor.delete_current()?;
}
// Delete the block transition if any // Delete the block transition if any
if block_transition_cursor.seek_exact(number)?.is_some() { if block_transition_cursor.seek_exact(number)?.is_some() {
block_transition_cursor.delete_current()?; block_transition_cursor.delete_current()?;
@ -453,6 +473,7 @@ mod tests {
BlockBody { BlockBody {
transactions: block.body.clone(), transactions: block.body.clone(),
ommers: block.ommers.iter().cloned().map(|ommer| ommer.unseal()).collect(), ommers: block.ommers.iter().cloned().map(|ommer| ommer.unseal()).collect(),
withdrawals: block.withdrawals.clone(),
}, },
) )
} }
@ -772,6 +793,7 @@ mod tests {
header, header,
body: body.transactions, body: body.transactions,
ommers: body.ommers.into_iter().map(|h| h.seal()).collect(), ommers: body.ommers.into_iter().map(|h| h.seal()).collect(),
withdrawals: body.withdrawals,
})); }));
} }

View File

@ -5,7 +5,7 @@ use crate::{
use reth_db::{ use reth_db::{
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO},
database::Database, database::Database,
models::{StoredBlockBody, TransitionIdAddress}, models::TransitionIdAddress,
tables, tables,
transaction::{DbTx, DbTxMut}, transaction::{DbTx, DbTxMut},
}; };
@ -14,9 +14,7 @@ use reth_executor::{
revm_wrap::{State, SubState}, revm_wrap::{State, SubState},
}; };
use reth_interfaces::provider::Error as ProviderError; use reth_interfaces::provider::Error as ProviderError;
use reth_primitives::{ use reth_primitives::{Address, Block, ChainSpec, Hardfork, StorageEntry, H256, MAINNET, U256};
Address, Block, ChainSpec, Hardfork, Header, StorageEntry, H256, MAINNET, U256,
};
use reth_provider::{LatestStateProviderRef, Transaction}; use reth_provider::{LatestStateProviderRef, Transaction};
use std::fmt::Debug; use std::fmt::Debug;
use tracing::*; use tracing::*;
@ -85,6 +83,8 @@ impl ExecutionStage {
let mut bodies_cursor = tx.cursor_read::<tables::BlockBodies>()?; let mut bodies_cursor = tx.cursor_read::<tables::BlockBodies>()?;
// Get ommers with canonical hashes. // Get ommers with canonical hashes.
let mut ommers_cursor = tx.cursor_read::<tables::BlockOmmers>()?; let mut ommers_cursor = tx.cursor_read::<tables::BlockOmmers>()?;
// Get block withdrawals.
let mut withdrawals_cursor = tx.cursor_read::<tables::BlockWithdrawals>()?;
// Get transaction of the block that we are executing. // Get transaction of the block that we are executing.
let mut tx_cursor = tx.cursor_read::<tables::Transactions>()?; let mut tx_cursor = tx.cursor_read::<tables::Transactions>()?;
// Skip sender recovery and load signer from database. // Skip sender recovery and load signer from database.
@ -93,7 +93,7 @@ impl ExecutionStage {
// Get block headers and bodies // Get block headers and bodies
let block_batch = headers_cursor let block_batch = headers_cursor
.walk_range(start_block..=end_block)? .walk_range(start_block..=end_block)?
.map(|entry| -> Result<(Header, U256, StoredBlockBody, Vec<Header>), StageError> { .map(|entry| -> Result<_, StageError> {
let (number, header) = entry?; let (number, header) = entry?;
let (_, td) = td_cursor let (_, td) = td_cursor
.seek_exact(number)? .seek_exact(number)?
@ -101,7 +101,9 @@ impl ExecutionStage {
let (_, body) = let (_, body) =
bodies_cursor.seek_exact(number)?.ok_or(ProviderError::BlockBody { number })?; bodies_cursor.seek_exact(number)?.ok_or(ProviderError::BlockBody { number })?;
let (_, stored_ommers) = ommers_cursor.seek_exact(number)?.unwrap_or_default(); let (_, stored_ommers) = ommers_cursor.seek_exact(number)?.unwrap_or_default();
Ok((header, td.into(), body, stored_ommers.ommers)) let withdrawals =
withdrawals_cursor.seek_exact(number)?.map(|(_, w)| w.withdrawals);
Ok((header, td.into(), body, stored_ommers.ommers, withdrawals))
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
@ -110,7 +112,7 @@ impl ExecutionStage {
// Fetch transactions, execute them and generate results // Fetch transactions, execute them and generate results
let mut block_change_patches = Vec::with_capacity(block_batch.len()); let mut block_change_patches = Vec::with_capacity(block_batch.len());
for (header, td, body, ommers) in block_batch.into_iter() { for (header, td, body, ommers, withdrawals) in block_batch.into_iter() {
let block_number = header.number; let block_number = header.number;
tracing::trace!(target: "sync::stages::execution", ?block_number, "Execute block."); tracing::trace!(target: "sync::stages::execution", ?block_number, "Execute block.");
@ -144,7 +146,7 @@ impl ExecutionStage {
trace!(target: "sync::stages::execution", number = block_number, txs = transactions.len(), "Executing block"); trace!(target: "sync::stages::execution", number = block_number, txs = transactions.len(), "Executing block");
let changeset = reth_executor::executor::execute_and_verify_receipt( let changeset = reth_executor::executor::execute_and_verify_receipt(
&Block { header, body: transactions, ommers }, &Block { header, body: transactions, ommers, withdrawals },
td, td,
Some(signers), Some(signers),
&self.chain_spec, &self.chain_spec,
@ -163,7 +165,7 @@ impl ExecutionStage {
let spurious_dragon_active = let spurious_dragon_active =
self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block_number); self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block_number);
// insert state change set // insert state change set
for result in results.changesets.into_iter() { for result in results.tx_changesets.into_iter() {
for (address, account_change_set) in result.changeset.into_iter() { for (address, account_change_set) in result.changeset.into_iter() {
let AccountChangeSet { account, wipe_storage, storage } = account_change_set; let AccountChangeSet { account, wipe_storage, storage } = account_change_set;
// apply account change to db. Updates AccountChangeSet and PlainAccountState // apply account change to db. Updates AccountChangeSet and PlainAccountState
@ -247,20 +249,17 @@ impl ExecutionStage {
current_transition_id += 1; current_transition_id += 1;
} }
// If there is block reward we will add account changeset to db // If there are any post block changes, we will add account changesets to db.
if let Some(block_reward_changeset) = results.block_reward { for (address, changeset) in results.block_changesets.into_iter() {
// we are sure that block reward index is present. trace!(target: "sync::stages::execution", ?address, current_transition_id, "Applying block reward");
for (address, changeset) in block_reward_changeset.into_iter() { changeset.apply_to_db(
trace!(target: "sync::stages::execution", ?address, current_transition_id, "Applying block reward"); &**tx,
changeset.apply_to_db( address,
&**tx, current_transition_id,
address, spurious_dragon_active,
current_transition_id, )?;
spurious_dragon_active,
)?;
}
current_transition_id += 1;
} }
current_transition_id += 1;
} }
let done = !capped; let done = !capped;

View File

@ -278,11 +278,11 @@ mod tests {
let n_accounts = 31; let n_accounts = 31;
let mut accounts = random_contract_account_range(&mut (0..n_accounts)); let mut accounts = random_contract_account_range(&mut (0..n_accounts));
let SealedBlock { header, body, ommers } = let SealedBlock { header, body, ommers, withdrawals } =
random_block(stage_progress, None, Some(0), None); random_block(stage_progress, None, Some(0), None);
let mut header = header.unseal(); let mut header = header.unseal();
header.state_root = self.generate_initial_trie(&accounts)?; header.state_root = self.generate_initial_trie(&accounts)?;
let sealed_head = SealedBlock { header: header.seal(), body, ommers }; let sealed_head = SealedBlock { header: header.seal(), body, ommers, withdrawals };
let head_hash = sealed_head.hash(); let head_hash = sealed_head.hash();
let mut blocks = vec![sealed_head]; let mut blocks = vec![sealed_head];

View File

@ -42,7 +42,8 @@ impl_compression_for_compact!(
StorageEntry, StorageEntry,
StorageTrieEntry, StorageTrieEntry,
StoredBlockBody, StoredBlockBody,
StoredBlockOmmers StoredBlockOmmers,
StoredBlockWithdrawals
); );
impl_compression_for_compact!(AccountBeforeTx, TransactionSigned); impl_compression_for_compact!(AccountBeforeTx, TransactionSigned);
impl_compression_for_compact!(CompactU256); impl_compression_for_compact!(CompactU256);

View File

@ -12,7 +12,8 @@ use crate::{
models::{ models::{
accounts::{AccountBeforeTx, TransitionIdAddress}, accounts::{AccountBeforeTx, TransitionIdAddress},
blocks::{HeaderHash, StoredBlockOmmers}, blocks::{HeaderHash, StoredBlockOmmers},
ShardedKey, storage_sharded_key::StorageShardedKey,
ShardedKey, StoredBlockBody, StoredBlockWithdrawals,
}, },
}, },
}; };
@ -21,8 +22,6 @@ use reth_primitives::{
StorageTrieEntry, TransactionSigned, TransitionId, TxHash, TxNumber, H256, StorageTrieEntry, TransactionSigned, TransitionId, TxHash, TxNumber, H256,
}; };
use self::models::{storage_sharded_key::StorageShardedKey, StoredBlockBody};
/// Enum for the types of tables present in libmdbx. /// Enum for the types of tables present in libmdbx.
#[derive(Debug)] #[derive(Debug)]
pub enum TableType { pub enum TableType {
@ -33,13 +32,14 @@ pub enum TableType {
} }
/// Default tables that should be present inside database. /// Default tables that should be present inside database.
pub const TABLES: [(TableType, &str); 26] = [ pub const TABLES: [(TableType, &str); 27] = [
(TableType::Table, CanonicalHeaders::const_name()), (TableType::Table, CanonicalHeaders::const_name()),
(TableType::Table, HeaderTD::const_name()), (TableType::Table, HeaderTD::const_name()),
(TableType::Table, HeaderNumbers::const_name()), (TableType::Table, HeaderNumbers::const_name()),
(TableType::Table, Headers::const_name()), (TableType::Table, Headers::const_name()),
(TableType::Table, BlockBodies::const_name()), (TableType::Table, BlockBodies::const_name()),
(TableType::Table, BlockOmmers::const_name()), (TableType::Table, BlockOmmers::const_name()),
(TableType::Table, BlockWithdrawals::const_name()),
(TableType::Table, Transactions::const_name()), (TableType::Table, Transactions::const_name()),
(TableType::Table, TxHashNumber::const_name()), (TableType::Table, TxHashNumber::const_name()),
(TableType::Table, Receipts::const_name()), (TableType::Table, Receipts::const_name()),
@ -141,6 +141,11 @@ table!(
( BlockOmmers ) BlockNumber | StoredBlockOmmers ( BlockOmmers ) BlockNumber | StoredBlockOmmers
); );
table!(
/// Stores the block withdrawals.
( BlockWithdrawals ) BlockNumber | StoredBlockWithdrawals
);
table!( table!(
/// (Canonical only) Stores the transaction body for canonical transactions. /// (Canonical only) Stores the transaction body for canonical transactions.
( Transactions ) TxNumber | TransactionSigned ( Transactions ) TxNumber | TransactionSigned

View File

@ -9,7 +9,7 @@ use crate::{
}; };
use bytes::Bytes; use bytes::Bytes;
use reth_codecs::{main_codec, Compact}; use reth_codecs::{main_codec, Compact};
use reth_primitives::{BlockHash, BlockNumber, Header, TxNumber, H256}; use reth_primitives::{BlockHash, BlockNumber, Header, TxNumber, Withdrawal, H256};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Total number of transactions. /// Total number of transactions.
@ -51,13 +51,21 @@ impl StoredBlockBody {
/// ///
/// It is stored as the headers of the block's uncles. /// It is stored as the headers of the block's uncles.
/// tx_amount)`. /// tx_amount)`.
#[derive(Debug, Default, Eq, PartialEq, Clone)]
#[main_codec] #[main_codec]
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct StoredBlockOmmers { pub struct StoredBlockOmmers {
/// The block headers of this block's uncles. /// The block headers of this block's uncles.
pub ommers: Vec<Header>, pub ommers: Vec<Header>,
} }
/// The storage representation of block withdrawals.
#[main_codec]
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct StoredBlockWithdrawals {
/// The block withdrawals.
pub withdrawals: Vec<Withdrawal>,
}
/// Hash of the block header. Value for [`CanonicalHeaders`][crate::tables::CanonicalHeaders] /// Hash of the block header. Value for [`CanonicalHeaders`][crate::tables::CanonicalHeaders]
pub type HeaderHash = H256; pub type HeaderHash = H256;

View File

@ -1,5 +1,5 @@
use reth_db::{ use reth_db::{
models::{StoredBlockBody, StoredBlockOmmers}, models::{StoredBlockBody, StoredBlockOmmers, StoredBlockWithdrawals},
tables, tables,
transaction::{DbTx, DbTxMut}, transaction::{DbTx, DbTxMut},
}; };
@ -72,7 +72,18 @@ pub fn insert_block<'a, TX: DbTxMut<'a> + DbTx<'a>>(
current_tx_id += 1; current_tx_id += 1;
} }
if has_block_reward { let mut has_withdrawals = false;
if let Some(withdrawals) = block.withdrawals.clone() {
if !withdrawals.is_empty() {
has_withdrawals = true;
tx.put::<tables::BlockWithdrawals>(
block.number,
StoredBlockWithdrawals { withdrawals },
)?;
}
}
if has_block_reward || has_withdrawals {
transition_id += 1; transition_id += 1;
} }
tx.put::<tables::BlockTransitionIndex>(block.number, transition_id)?; tx.put::<tables::BlockTransitionIndex>(block.number, transition_id)?;