mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: withdrawals (#1322)
Co-authored-by: rakita <rakita@users.noreply.github.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4116,6 +4116,7 @@ dependencies = [
|
||||
name = "reth-consensus"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"reth-interfaces",
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
|
||||
@ -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>);
|
||||
|
||||
@ -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(())
|
||||
})?;
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 { .. })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 block’s beneficiary account by Rblock; for each
|
||||
// ommer, we raise the block’s beneficiary by an additional 1/32 of the block reward
|
||||
// and the beneficiary of the ommer gets rewarded depending on the blocknumber.
|
||||
// Formally we define the function Ω:
|
||||
// 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 block’s beneficiary account by Rblock; for each
|
||||
/// ommer, we raise the block’s beneficiary by an additional 1/32 of the block reward
|
||||
/// and the beneficiary of the ommer gets rewarded depending on the blocknumber.
|
||||
/// Formally we define the function Ω.
|
||||
///
|
||||
/// NOTE: Related to Ethereum reward change, for other network this is probably going to be
|
||||
/// moved to config.
|
||||
fn get_block_reward(&self, header: &Header, total_difficulty: U256) -> Option<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 transaction’s gas limit, Tg, and the gas utilised in this block prior,
|
||||
// must be no greater than the block’s 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
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -136,6 +136,7 @@ pub fn random_block(
|
||||
.seal(),
|
||||
body: transactions,
|
||||
ommers: ommers.into_iter().map(|ommer| ommer.seal()).collect(),
|
||||
withdrawals: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -585,6 +585,7 @@ mod tests {
|
||||
BlockBody {
|
||||
transactions: block.body,
|
||||
ommers: block.ommers.into_iter().map(|header| header.unseal()).collect(),
|
||||
withdrawals: None,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@ -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,
|
||||
}
|
||||
]),
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
24
crates/primitives/src/withdrawal.rs
Normal file
24
crates/primitives/src/withdrawal.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user