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"
version = "0.1.0"
dependencies = [
"assert_matches",
"reth-interfaces",
"reth-primitives",
"reth-provider",

View File

@ -1,6 +1,6 @@
use reth_primitives::{
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 std::collections::BTreeMap;
@ -71,6 +71,8 @@ pub struct Header {
pub uncle_hash: H256,
/// Base fee per gas.
pub base_fee_per_gas: Option<JsonU256>,
/// Withdrawals root.
pub withdrawals_root: Option<H256>,
}
impl From<Header> for SealedHeader {
@ -92,7 +94,8 @@ impl From<Header> for SealedHeader {
ommers_hash: value.uncle_hash,
state_root: value.state_root,
parent_hash: value.parent_hash,
logs_bloom: Bloom::default(), // TODO: ?
logs_bloom: value.bloom,
withdrawals_root: value.withdrawals_root,
},
value.hash,
)
@ -128,16 +131,6 @@ pub struct TransactionSequence {
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.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct State(pub BTreeMap<Address, Account>);

View File

@ -118,8 +118,6 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<TestOutcome> {
continue
}
// if matches!(suite.pre, State(RootOrState::Root(_))) {}
let pre_state = suite.pre.0;
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
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)?;
let mut last_block = None;
suite.blocks.iter().try_for_each(|block| -> eyre::Result<()> {
let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?;
last_block = Some(decoded.number);
reth_provider::insert_canonical_block(&tx, &decoded, has_block_reward)?;
last_block = Some(decoded.number);
Ok(())
})?;

View File

@ -18,3 +18,4 @@ tokio = { version = "1", features = ["sync"] }
[dev-dependencies]
reth-interfaces = { path = "../interfaces", 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> {
validation::validate_block_standalone(block)
validation::validate_block_standalone(block, &self.chain_spec)
}
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)
}
// 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(())
}
@ -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
/// - Pre-execution transaction validation
/// - (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
// TODO(onbjerg): This should probably be accessible directly on [Block]
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(())
}
@ -326,7 +362,7 @@ pub fn full_validation<Provider: HeaderProvider + AccountProvider>(
chain_spec: &ChainSpec,
) -> RethResult<()> {
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)?;
validate_header_regarding_parent(&parent, &block.header, chain_spec)?;
@ -348,15 +384,15 @@ pub fn full_validation<Provider: HeaderProvider + AccountProvider>(
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use reth_interfaces::Result;
use reth_primitives::{
hex_literal::hex, Account, Address, BlockHash, Bytes, Header, Signature, TransactionKind,
TransactionSigned, MAINNET, U256,
hex_literal::hex, proofs, Account, Address, BlockHash, Bytes, ChainSpecBuilder, Header,
Signature, TransactionKind, TransactionSigned, Withdrawal, MAINNET, U256,
};
use std::ops::RangeBounds;
use super::*;
#[test]
fn calculate_base_fee_success() {
let base_fee = [
@ -447,6 +483,7 @@ mod tests {
let signer = Address::zero();
TransactionSignedEcRecovered::from_signed_transaction(tx, signer)
}
/// got test block
fn mock_block() -> (SealedBlock, Header) {
// https://etherscan.io/block/15867168 where transaction root and receipts root are cleared
@ -469,6 +506,7 @@ mod tests {
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000,
base_fee_per_gas: 0x28f0001df.into(),
withdrawals_root: None
};
// size: 0x9b5
@ -481,7 +519,7 @@ mod tests {
let ommers = Vec::new();
let body = Vec::new();
(SealedBlock { header: header.seal(), body, ommers }, parent)
(SealedBlock { header: header.seal(), body, ommers, withdrawals: None }, parent)
}
#[test]
@ -557,4 +595,47 @@ mod tests {
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)]
pub struct ExecutionResult {
/// Transaction changeset containing [Receipt], changed [Accounts][Account] and Storages.
pub changesets: Vec<TransactionChangeSet>,
/// Block reward if present. It represent changeset for block reward slot in
/// [tables::AccountChangeSet] .
pub block_reward: Option<BTreeMap<Address, AccountInfoChangeSet>>,
pub tx_changesets: Vec<TransactionChangeSet>,
/// Post block account changesets. This might include block reward, uncle rewards, withdrawals
/// or irregular state changes (DAO fork).
pub block_changesets: BTreeMap<Address, AccountInfoChangeSet>,
}
/// 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_primitives::{
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 revm::{
@ -19,7 +19,7 @@ use revm::{
},
EVM,
};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
/// Main block executor
pub struct Executor<'a, DB>
@ -93,7 +93,7 @@ where
/// BTreeMap is used to have sorted values
fn commit_changes(
&mut self,
changes: hashbrown::HashMap<H160, RevmAccount>,
changes: hashbrown::HashMap<Address, RevmAccount>,
) -> (BTreeMap<Address, AccountChangeSet>, BTreeMap<H256, Bytecode>) {
let db = self.db();
@ -205,23 +205,58 @@ where
(change, new_bytecodes)
}
/// Calculate Block reward changeset
pub fn block_reward_changeset(
/// Collect all balance changes at the end of the block. Balance changes might include block
/// reward, uncle rewards, withdrawals or irregular state changes (DAO fork).
fn post_block_balance_increments(
&mut self,
header: &Header,
total_difficulty: U256,
ommers: &[Header],
) -> Result<Option<BTreeMap<H160, AccountInfoChangeSet>>, Error> {
// NOTE: Related to Ethereum reward change, for other network this is probably going to be
// moved to config.
block: &Block,
td: U256,
) -> Result<HashMap<Address, U256>, Error> {
let mut balance_increments = HashMap::<Address, U256>::default();
// 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 Ω:
// Collect balance increments for block and uncle rewards.
if let Some(reward) = self.get_block_reward(block, td) {
// Calculate Uncle reward
// OpenEthereum code: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333
for ommer in block.ommers.iter() {
let ommer_reward =
U256::from(((8 + ommer.number - block.number) as u128 * reward) >> 3);
// From yellowpaper Page 15:
// If there are collisions of the beneficiary addresses between ommers and the
// block (i.e. two ommers with the same beneficiary address
// or an ommer with the same beneficiary address as the
// present block), additions are applied cumulatively
*balance_increments.entry(ommer.beneficiary).or_default() += ommer_reward;
}
// 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)
{
None
@ -232,78 +267,10 @@ where
} else {
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
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 mut drained_balance = U256::ZERO;
@ -320,7 +287,7 @@ where
// assume it is changeset as it is irregular state change
Ok((address, AccountInfoChangeSet::Changed { new, old }))
})
.collect::<Result<BTreeMap<H160, AccountInfoChangeSet>, _>>()?;
.collect::<Result<BTreeMap<Address, AccountInfoChangeSet>, _>>()?;
// add drained ether to beneficiary.
let beneficiary = crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY;
@ -338,6 +305,43 @@ where
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>
@ -350,19 +354,18 @@ where
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<ExecutionResult, Error> {
let Block { header, body, ommers } = block;
let senders = self.recover_senders(body, senders)?;
let senders = self.recover_senders(&block.body, senders)?;
self.init_block_env(header, total_difficulty);
self.init_block_env(&block.header, total_difficulty);
let mut cumulative_gas_used = 0;
// 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,
// 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 {
return Err(Error::TransactionGasLimitMoreThenAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
@ -376,13 +379,13 @@ where
// Execute transaction.
let out = if self.use_printer_tracer {
// execution with inspector.
let out = self.evm.inspect(revm::inspectors::CustomPrintTracer::default());
tracing::trace!(target:"evm","Executed transaction: {:?},
\nExecution output:{out:?}:
\nTransaction data:{transaction:?}
\nenv data:{:?}",transaction.hash(),self.evm.env);
out
let output = self.evm.inspect(revm::inspectors::CustomPrintTracer::default());
tracing::trace!(
target: "evm",
hash = ?transaction.hash(), ?output, ?transaction, env = ?self.evm.env,
"Executed transaction"
);
output
} else {
// main execution.
self.evm.transact()
@ -401,7 +404,7 @@ where
let logs: Vec<Log> = result.logs().into_iter().map(into_reth_log).collect();
// Push transaction changeset and calculate header bloom filter for receipt.
changesets.push(TransactionChangeSet {
tx_changesets.push(TransactionChangeSet {
receipt: Receipt {
tx_type: transaction.tx_type(),
// 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.
if header.gas_used != cumulative_gas_used {
return Err(Error::BlockGasUsed { got: cumulative_gas_used, expected: header.gas_used })
if block.gas_used != cumulative_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)?;
if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(header.number) {
let mut irregular_state_changeset = self.dao_fork_changeset()?;
irregular_state_changeset.extend(block_reward.take().unwrap_or_default().into_iter());
block_reward = Some(irregular_state_changeset);
let mut block_changesets = BTreeMap::default();
let balance_increments = self.post_block_balance_increments(block, total_difficulty)?;
for (address, increment) in balance_increments {
let changeset = self.account_balance_increment_changeset(address, increment)?;
block_changesets.insert(address, 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> {
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) {
verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts_iter)?;
@ -497,17 +506,15 @@ pub fn execute<DB: StateProvider>(
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::revm_wrap::State;
use reth_primitives::{
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_rlp::Decodable;
use super::*;
use std::{collections::HashMap, str::FromStr};
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct StateProviderTest {
@ -569,23 +576,22 @@ mod tests {
// Got rlp block from: src/GeneralStateTestsFiller/stChainId/chainIdGasCostFiller.json
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 ommer_beneficiary = H160(hex!("3000000000000000000000000000000000000000"));
let ommer_beneficiary =
Address::from_str("3000000000000000000000000000000000000000").unwrap();
ommer.beneficiary = ommer_beneficiary;
ommer.number = block.number;
let ommers = vec![ommer];
let block = Block { header: block.header.unseal(), body: block.body, ommers };
block.ommers = vec![ommer];
let mut db = StateProviderTest::default();
let account1 = H160(hex!("1000000000000000000000000000000000000000"));
let account2 = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"));
let account3 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
let account1 = Address::from_str("1000000000000000000000000000000000000000").unwrap();
let account2 = Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap();
let account3 = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();
// pre staet
// pre state
db.insert_account(
account1,
Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None },
@ -619,9 +625,9 @@ mod tests {
let out =
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");
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();
assert_eq!(cached_acc1.info.balance, account1_info.balance);
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.get(&U256::from(1)), Some(&U256::from(2)));
@ -659,7 +665,7 @@ mod tests {
let cached_acc3 = db.accounts.get(&account3).unwrap();
assert_eq!(cached_acc3.info.balance, account3_info.balance);
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!(
@ -685,8 +691,8 @@ mod tests {
// check block reward changeset
assert_eq!(
out.block_reward,
Some(BTreeMap::from([
out.block_changesets,
BTreeMap::from([
(
account2,
AccountInfoChangeSet::Changed {
@ -704,7 +710,7 @@ mod tests {
}
}
)
]))
])
);
assert_eq!(changesets.new_bytecodes.len(), 0, "No new bytecodes");
@ -744,14 +750,14 @@ mod tests {
let mut db = SubState::new(State::new(db));
// execute chain and verify receipts
let out = execute_and_verify_receipt(
&Block { header, body: vec![], ommers: vec![] },
&Block { header, body: vec![], ommers: vec![], withdrawals: None },
U256::ZERO,
None,
&chain_spec,
&mut db,
)
.unwrap();
assert_eq!(out.changesets.len(), 0, "No tx");
assert_eq!(out.tx_changesets.len(), 0, "No tx");
// Check if cache is set
// beneficiary
@ -765,8 +771,8 @@ mod tests {
}
// check changesets
let block_reward = out.block_reward.unwrap();
let change_set = block_reward.get(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap();
let change_set =
out.block_changesets.get(&crate::eth_dao_fork::DAO_HARDFORK_BENEFICIARY).unwrap();
assert_eq!(
*change_set,
AccountInfoChangeSet::Changed {
@ -775,7 +781,7 @@ mod tests {
}
);
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!(
*change_set,
AccountInfoChangeSet::Changed {
@ -793,11 +799,12 @@ mod tests {
// Got rlp block from: src/GeneralStateTestsFiller/stArgsZeroOneBalance/suicideNonConst.json
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 address_caller = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
let address_selfdestruct = H160(hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"));
let address_caller = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();
let address_selfdestruct =
Address::from_str("095e7baea6a6c7c4c2dfeb977efac326af552d87").unwrap();
// pre state
let pre_account_caller = Account {
@ -834,12 +841,11 @@ mod tests {
// execute chain and verify receipts
let out =
execute_and_verify_receipt(&block.unseal(), 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");
let post_account_caller = Account {
@ -865,4 +871,58 @@ mod tests {
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 {
#[error("Block used gas ({gas_used:?}) is greater than gas limit ({gas_limit:?}).")]
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 },
#[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 },
#[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 },
#[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 },
#[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.")]
BlockKnown { hash: BlockHash, number: BlockNumber },
#[error("Block parent [hash:{hash:?}] is not known.")]
@ -120,4 +122,12 @@ pub enum Error {
TheMergeOmmerRootIsNotEmpty,
#[error("Mix hash after merge is not zero")]
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(),
body: transactions,
ommers: ommers.into_iter().map(|ommer| ommer.seal()).collect(),
withdrawals: None,
}
}

View File

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

View File

@ -171,6 +171,7 @@ where
header: next_header,
body: next_body.transactions,
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) {

View File

@ -26,6 +26,7 @@ pub(crate) fn zip_blocks<'a>(
header: header.clone(),
body: body.transactions,
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
headers.insert(block.header.number, block.header.clone());
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");

View File

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

View File

@ -2,7 +2,7 @@
//! types.
use reth_codecs::derive_arbitrary;
use reth_primitives::{
Block, BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, H256,
Block, BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, Withdrawal, H256,
};
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
/// 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(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
#[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[rlp(trailing)]
pub struct BlockBody {
/// Transactions in the block
pub transactions: Vec<TransactionSigned>,
/// Uncle headers for the given block
pub ommers: Vec<Header>,
/// Withdrawals in the block.
pub withdrawals: Option<Vec<Withdrawal>>,
}
impl BlockBody {
@ -87,6 +92,7 @@ impl BlockBody {
header: header.clone(),
body: self.transactions.clone(),
ommers: self.ommers.clone(),
withdrawals: self.withdrawals.clone(),
}
}
}
@ -276,6 +282,7 @@ mod test {
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
},
]),
}.encode(&mut data);
@ -306,6 +313,7 @@ mod test {
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
},
]),
};
@ -417,8 +425,10 @@ mod test {
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
},
],
withdrawals: None,
}
]),
};
@ -499,8 +509,10 @@ mod test {
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
},
],
withdrawals: None,
}
]),
};

View File

@ -164,7 +164,11 @@ where
if let Some(block) =
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);

View File

@ -70,7 +70,8 @@ async fn test_get_body() {
let blocks = res.unwrap().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);
}
}

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

View File

@ -426,6 +426,13 @@ impl ChainSpecBuilder {
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`].
///
/// # 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)
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.
pub const MAINNET_GENESIS: H256 =
H256(hex!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"));

View File

@ -3,10 +3,10 @@ use crate::{
proofs::{EMPTY_LIST_HASH, EMPTY_ROOT},
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 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 std::ops::Deref;
@ -48,13 +48,14 @@ pub struct Header {
/// executed and finalisations applied; formally Hr.
pub state_root: H256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// transaction in the transactions list portion of the
/// block; formally Ht.
/// transaction in the transactions list portion of the block; formally Ht.
pub transactions_root: H256,
/// The Keccak 256-bit hash of the root
/// node of the trie structure populated with the receipts of each transaction in the
/// transactions list portion of the block; formally He.
/// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
/// of each transaction in the transactions list portion of the block; formally He.
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)
/// contained in each log entry from the receipt of each transaction in the transactions list;
/// formally Hb.
@ -110,6 +111,7 @@ impl Default for Header {
mix_hash: Default::default(),
nonce: 0,
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
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.
@ -161,7 +168,16 @@ impl Header {
length += self.extra_data.length();
length += self.mix_hash.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
}
}
@ -186,8 +202,17 @@ impl Encodable for Header {
self.extra_data.encode(out);
self.mix_hash.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 {
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,11 +248,18 @@ impl Decodable for Header {
mix_hash: Decodable::decode(buf)?,
nonce: H64::decode(buf)?.to_low_u64_be(),
base_fee_per_gas: None,
withdrawals_root: None,
};
let consumed = started_len - buf.len();
if consumed < rlp_head.payload_length {
if started_len - buf.len() < rlp_head.payload_length {
if buf.first().map(|b| *b == EMPTY_STRING_CODE).unwrap_or_default() {
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();
if consumed != rlp_head.payload_length {
return Err(reth_rlp::DecodeError::ListLengthMismatch {
@ -493,6 +525,7 @@ mod tests {
mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
nonce: 0,
base_fee_per_gas: Some(0x036b_u64),
withdrawals_root: None,
};
assert_eq!(header.hash_slow(), expected_hash);
}
@ -518,10 +551,56 @@ mod tests {
assert_eq!(header, expected);
// make sure the hash matches
let expected_hash = H256::from_slice(
&hex::decode("8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9")
let expected_hash =
H256::from_str("8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9")
.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);
}

View File

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

View File

@ -1,7 +1,8 @@
use std::collections::HashMap;
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 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.
pub fn calculate_receipt_root<'a>(receipts: impl Iterator<Item = &'a Receipt>) -> H256 {
ordered_trie_root::<KeccakHasher, _>(receipts.into_iter().map(|receipt| {
@ -96,7 +108,7 @@ mod tests {
};
use reth_rlp::Decodable;
use super::EMPTY_ROOT;
use super::{calculate_withdrawals_root, EMPTY_ROOT};
#[test]
fn check_transaction_root() {
@ -105,7 +117,7 @@ mod tests {
let block: Block = Block::decode(block_rlp).unwrap();
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]
@ -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]
fn check_empty_state_root() {
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()))
.collect::<std::result::Result<Vec<_>, _>>()?;
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 {
parent_hash: payload.parent_hash,
beneficiary: payload.fee_recipient,
state_root: payload.state_root,
transactions_root,
receipts_root: payload.receipts_root,
withdrawals_root,
logs_bloom: payload.logs_bloom,
number: payload.block_number.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
@ -333,6 +343,7 @@ mod tests {
header: transformed.header.seal(),
body: transformed.body,
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
use crate::Transaction;
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 serde::{ser::Error, Deserialize, Serialize, Serializer};
@ -43,6 +44,8 @@ pub struct Block {
/// Base Fee for post-EIP1559 blocks.
#[serde(skip_serializing_if = "Option::is_none")]
pub base_fee_per_gas: Option<U256>,
/// Withdrawals
pub withdrawals: Option<Vec<Withdrawal>>,
}
impl Block {
@ -74,6 +77,7 @@ impl Block {
nonce,
base_fee_per_gas,
extra_data,
withdrawals_root,
} = block.header;
let header = Header {
@ -85,6 +89,7 @@ impl Block {
state_root,
transactions_root,
receipts_root,
withdrawals_root,
number: Some(U256::from(number)),
gas_used: U256::from(gas_used),
gas_limit: U256::from(gas_limit),
@ -115,6 +120,7 @@ impl Block {
base_fee_per_gas: base_fee_per_gas.map(U256::from),
total_difficulty,
size: Some(U256::from(block_length)),
withdrawals: block.withdrawals,
})
}
}
@ -140,6 +146,8 @@ pub struct Header {
pub transactions_root: H256,
/// Transactions receipts root hash
pub receipts_root: H256,
/// Withdrawals root hash
pub withdrawals_root: Option<H256>,
/// Block number
pub number: Option<U256>,
/// Gas Used

View File

@ -3,7 +3,7 @@
#![allow(missing_docs)]
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 serde::{Deserialize, Serialize};
@ -30,7 +30,7 @@ pub struct ExecutionPayload {
/// Array of [`Withdrawal`] enabled with V2
/// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawal: Option<Withdrawal>,
pub withdrawals: Option<Vec<Withdrawal>>,
}
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()),
block_hash: value.hash(),
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
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -99,7 +84,7 @@ pub struct PayloadAttributes {
/// Array of [`Withdrawal`] enabled with V2
/// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
#[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

View File

@ -6,7 +6,7 @@ use futures_util::TryStreamExt;
use reth_db::{
cursor::{DbCursorRO, DbCursorRW},
database::Database,
models::{StoredBlockBody, StoredBlockOmmers},
models::{StoredBlockBody, StoredBlockOmmers, StoredBlockWithdrawals},
tables,
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.
///
/// # Empty blocks
///
/// 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
@ -88,8 +89,9 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
// Cursors used to write bodies, ommers and transactions
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 ommers_cursor = tx.cursor_write::<tables::BlockOmmers>()?;
let mut withdrawals_cursor = tx.cursor_write::<tables::BlockWithdrawals>()?;
// Cursors used to write state transition mapping
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 difficulty = response.difficulty();
let mut has_withdrawals = false;
match response {
BlockResponse::Full(block) => {
body_cursor.append(
@ -133,6 +136,7 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
transition_id += 1;
}
// Write ommers if any
if !block.ommers.is_empty() {
ommers_cursor.append(
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(_) => {
body_cursor.append(
@ -163,7 +176,8 @@ impl<DB: Database, D: BodyDownloader> Stage<DB> for BodyStage<D> {
.ok_or(ProviderError::TotalDifficulty { number: block_number })?
.1;
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;
}
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");
// Cursors to unwind bodies, ommers
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 ommers_cursor = tx.cursor_write::<tables::BlockOmmers>()?;
let mut withdrawals_cursor = tx.cursor_write::<tables::BlockWithdrawals>()?;
// Cursors to unwind transitions
let mut block_transition_cursor = tx.cursor_write::<tables::BlockTransitionIndex>()?;
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
}
// Delete the ommers value if any
// Delete the ommers entry if any
if ommers_cursor.seek_exact(number)?.is_some() {
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
if block_transition_cursor.seek_exact(number)?.is_some() {
block_transition_cursor.delete_current()?;
@ -453,6 +473,7 @@ mod tests {
BlockBody {
transactions: block.body.clone(),
ommers: block.ommers.iter().cloned().map(|ommer| ommer.unseal()).collect(),
withdrawals: block.withdrawals.clone(),
},
)
}
@ -772,6 +793,7 @@ mod tests {
header,
body: body.transactions,
ommers: body.ommers.into_iter().map(|h| h.seal()).collect(),
withdrawals: body.withdrawals,
}));
}

View File

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

View File

@ -278,11 +278,11 @@ mod tests {
let n_accounts = 31;
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);
let mut header = header.unseal();
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 mut blocks = vec![sealed_head];

View File

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

View File

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

View File

@ -9,7 +9,7 @@ use crate::{
};
use bytes::Bytes;
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};
/// Total number of transactions.
@ -51,13 +51,21 @@ impl StoredBlockBody {
///
/// It is stored as the headers of the block's uncles.
/// tx_amount)`.
#[derive(Debug, Default, Eq, PartialEq, Clone)]
#[main_codec]
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct StoredBlockOmmers {
/// The block headers of this block's uncles.
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]
pub type HeaderHash = H256;

View File

@ -1,5 +1,5 @@
use reth_db::{
models::{StoredBlockBody, StoredBlockOmmers},
models::{StoredBlockBody, StoredBlockOmmers, StoredBlockWithdrawals},
tables,
transaction::{DbTx, DbTxMut},
};
@ -72,7 +72,18 @@ pub fn insert_block<'a, TX: DbTxMut<'a> + DbTx<'a>>(
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;
}
tx.put::<tables::BlockTransitionIndex>(block.number, transition_id)?;