mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: implement EIP-2935 (#8431)
Co-authored-by: Oliver Nordbjerg <onbjerg@users.noreply.github.com> Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
@ -19,7 +19,9 @@ use reth_primitives::{
|
||||
use reth_revm::{
|
||||
batch::{BlockBatchRecord, BlockExecutorStats},
|
||||
db::states::bundle_state::BundleRetention,
|
||||
state_change::{apply_beacon_root_contract_call, post_block_balance_increments},
|
||||
state_change::{
|
||||
apply_beacon_root_contract_call, apply_blockhashes_update, post_block_balance_increments,
|
||||
},
|
||||
Evm, State,
|
||||
};
|
||||
use revm_primitives::{
|
||||
@ -142,6 +144,13 @@ where
|
||||
block.parent_beacon_block_root,
|
||||
&mut evm,
|
||||
)?;
|
||||
apply_blockhashes_update(
|
||||
evm.db_mut(),
|
||||
&self.chain_spec,
|
||||
block.timestamp,
|
||||
block.number,
|
||||
block.parent_hash,
|
||||
)?;
|
||||
|
||||
// execute transactions
|
||||
let mut cumulative_gas_used = 0;
|
||||
@ -429,10 +438,16 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_eips::eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS};
|
||||
use reth_primitives::{keccak256, Account, Block, ChainSpecBuilder, ForkCondition, B256};
|
||||
use alloy_eips::{
|
||||
eip2935::HISTORY_STORAGE_ADDRESS,
|
||||
eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS},
|
||||
};
|
||||
use reth_primitives::{
|
||||
keccak256, trie::EMPTY_ROOT_HASH, Account, Block, ChainSpecBuilder, ForkCondition, B256,
|
||||
};
|
||||
use reth_revm::{
|
||||
database::StateProviderDatabase, test_utils::StateProviderTest, TransitionState,
|
||||
database::StateProviderDatabase, state_change::HISTORY_SERVE_WINDOW,
|
||||
test_utils::StateProviderTest, TransitionState,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -574,8 +589,8 @@ mod tests {
|
||||
|
||||
// attempt to execute an empty block with parent beacon block root, this should not fail
|
||||
provider
|
||||
.executor(StateProviderDatabase::new(&db))
|
||||
.execute(
|
||||
.batch_executor(StateProviderDatabase::new(&db), PruneModes::none())
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
@ -624,22 +639,26 @@ mod tests {
|
||||
..Header::default()
|
||||
};
|
||||
|
||||
let mut executor = provider.executor(StateProviderDatabase::new(&db));
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
// attempt to execute an empty block with parent beacon block root, this should not fail
|
||||
executor
|
||||
.execute_without_verification(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while cancun is active should not fail",
|
||||
@ -663,9 +682,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut header = chain_spec.genesis_header();
|
||||
|
||||
let provider = executor_provider(chain_spec);
|
||||
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
@ -801,4 +818,378 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
|
||||
}
|
||||
|
||||
fn create_state_provider_with_block_hashes(latest_block: u64) -> StateProviderTest {
|
||||
let mut db = StateProviderTest::default();
|
||||
for block_number in 0..=latest_block {
|
||||
db.insert_block_hash(block_number, keccak256(block_number.to_string()));
|
||||
}
|
||||
db
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eip_2935_pre_fork() {
|
||||
let db = create_state_provider_with_block_hashes(1);
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::from(&*MAINNET)
|
||||
.shanghai_activated()
|
||||
.with_fork(Hardfork::Prague, ForkCondition::Never)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let provider = executor_provider(chain_spec);
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
// construct the header for block one
|
||||
let header = Header { timestamp: 1, number: 1, ..Header::default() };
|
||||
|
||||
// attempt to execute an empty block, this should not fail
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// ensure that the block hash was *not* written to storage, since this is before the fork
|
||||
// was activated
|
||||
//
|
||||
// we load the account first, which should also not exist, because revm expects it to be
|
||||
// loaded
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_none());
|
||||
assert!(executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
|
||||
.unwrap()
|
||||
.is_zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eip_2935_fork_activation_genesis() {
|
||||
let db = create_state_provider_with_block_hashes(0);
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::from(&*MAINNET)
|
||||
.shanghai_activated()
|
||||
.with_fork(Hardfork::Prague, ForkCondition::Timestamp(0))
|
||||
.build(),
|
||||
);
|
||||
|
||||
let header = chain_spec.genesis_header();
|
||||
let provider = executor_provider(chain_spec);
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
// attempt to execute genesis block, this should not fail
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// ensure that the block hash was *not* written to storage, since there are no blocks
|
||||
// preceding genesis
|
||||
//
|
||||
// we load the account first, which should also not exist, because revm expects it to be
|
||||
// loaded
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_none());
|
||||
assert!(executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
|
||||
.unwrap()
|
||||
.is_zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eip_2935_fork_activation_within_window_bounds() {
|
||||
let fork_activation_block = HISTORY_SERVE_WINDOW - 10;
|
||||
let db = create_state_provider_with_block_hashes(fork_activation_block);
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::from(&*MAINNET)
|
||||
.shanghai_activated()
|
||||
.with_fork(Hardfork::Prague, ForkCondition::Timestamp(1))
|
||||
.build(),
|
||||
);
|
||||
|
||||
let header = Header {
|
||||
parent_hash: B256::random(),
|
||||
timestamp: 1,
|
||||
number: fork_activation_block,
|
||||
requests_root: Some(EMPTY_ROOT_HASH),
|
||||
..Header::default()
|
||||
};
|
||||
let provider = executor_provider(chain_spec);
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
// attempt to execute the fork activation block, this should not fail
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// the hash for the ancestor of the fork activation block should be present
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
|
||||
assert_ne!(
|
||||
executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block - 1))
|
||||
.unwrap(),
|
||||
U256::ZERO
|
||||
);
|
||||
|
||||
// the hash of the block itself should not be in storage
|
||||
assert!(executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block))
|
||||
.unwrap()
|
||||
.is_zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eip_2935_fork_activation_outside_window_bounds() {
|
||||
let fork_activation_block = HISTORY_SERVE_WINDOW + 256;
|
||||
let db = create_state_provider_with_block_hashes(fork_activation_block);
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::from(&*MAINNET)
|
||||
.shanghai_activated()
|
||||
.with_fork(Hardfork::Prague, ForkCondition::Timestamp(1))
|
||||
.build(),
|
||||
);
|
||||
|
||||
let provider = executor_provider(chain_spec);
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
let header = Header {
|
||||
parent_hash: B256::random(),
|
||||
timestamp: 1,
|
||||
number: fork_activation_block,
|
||||
requests_root: Some(EMPTY_ROOT_HASH),
|
||||
..Header::default()
|
||||
};
|
||||
|
||||
// attempt to execute the fork activation block, this should not fail
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// the hash for the ancestor of the fork activation block should be present
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
|
||||
assert_ne!(
|
||||
executor
|
||||
.state_mut()
|
||||
.storage(
|
||||
HISTORY_STORAGE_ADDRESS,
|
||||
U256::from(fork_activation_block % HISTORY_SERVE_WINDOW - 1)
|
||||
)
|
||||
.unwrap(),
|
||||
U256::ZERO
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eip_2935_state_transition_inside_fork() {
|
||||
let db = create_state_provider_with_block_hashes(2);
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::from(&*MAINNET)
|
||||
.shanghai_activated()
|
||||
.with_fork(Hardfork::Prague, ForkCondition::Timestamp(0))
|
||||
.build(),
|
||||
);
|
||||
|
||||
let mut header = chain_spec.genesis_header();
|
||||
header.requests_root = Some(EMPTY_ROOT_HASH);
|
||||
let header_hash = header.hash_slow();
|
||||
|
||||
let provider = executor_provider(chain_spec);
|
||||
let mut executor =
|
||||
provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none());
|
||||
|
||||
// attempt to execute the genesis block, this should not fail
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// nothing should be written as the genesis has no ancestors
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_none());
|
||||
assert!(executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
|
||||
.unwrap()
|
||||
.is_zero());
|
||||
|
||||
// attempt to execute block 1, this should not fail
|
||||
let header = Header {
|
||||
parent_hash: header_hash,
|
||||
timestamp: 1,
|
||||
number: 1,
|
||||
requests_root: Some(EMPTY_ROOT_HASH),
|
||||
..Header::default()
|
||||
};
|
||||
let header_hash = header.hash_slow();
|
||||
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// the block hash of genesis should now be in storage, but not block 1
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
|
||||
assert_ne!(
|
||||
executor.state_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(),
|
||||
U256::ZERO
|
||||
);
|
||||
assert!(executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::from(1))
|
||||
.unwrap()
|
||||
.is_zero());
|
||||
|
||||
// attempt to execute block 2, this should not fail
|
||||
let header = Header {
|
||||
parent_hash: header_hash,
|
||||
timestamp: 1,
|
||||
number: 2,
|
||||
requests_root: Some(EMPTY_ROOT_HASH),
|
||||
..Header::default()
|
||||
};
|
||||
|
||||
executor
|
||||
.execute_and_verify_one(
|
||||
(
|
||||
&BlockWithSenders {
|
||||
block: Block {
|
||||
header,
|
||||
body: vec![],
|
||||
ommers: vec![],
|
||||
withdrawals: None,
|
||||
requests: None,
|
||||
},
|
||||
senders: vec![],
|
||||
},
|
||||
U256::ZERO,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.expect(
|
||||
"Executing a block with no transactions while Prague is active should not fail",
|
||||
);
|
||||
|
||||
// the block hash of genesis and block 1 should now be in storage, but not block 2
|
||||
assert!(executor.state_mut().basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some());
|
||||
assert_ne!(
|
||||
executor.state_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(),
|
||||
U256::ZERO
|
||||
);
|
||||
assert_ne!(
|
||||
executor.state_mut().storage(HISTORY_STORAGE_ADDRESS, U256::from(1)).unwrap(),
|
||||
U256::ZERO
|
||||
);
|
||||
assert!(executor
|
||||
.state_mut()
|
||||
.storage(HISTORY_STORAGE_ADDRESS, U256::from(2))
|
||||
.unwrap()
|
||||
.is_zero());
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,9 @@ pub enum BlockValidationError {
|
||||
/// The beacon block root
|
||||
parent_beacon_block_root: B256,
|
||||
},
|
||||
/// EVM error during beacon root contract call
|
||||
/// EVM error during [EIP-4788] beacon root contract call.
|
||||
///
|
||||
/// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788
|
||||
#[error("failed to apply beacon root contract call at {parent_beacon_block_root}: {message}")]
|
||||
BeaconRootContractCall {
|
||||
/// The beacon block root
|
||||
@ -75,6 +77,9 @@ pub enum BlockValidationError {
|
||||
/// The error message.
|
||||
message: String,
|
||||
},
|
||||
/// Provider error during the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) block hash account loading.
|
||||
#[error(transparent)]
|
||||
BlockHashAccountLoadingFailed(#[from] ProviderError),
|
||||
}
|
||||
|
||||
/// BlockExecutor Errors
|
||||
|
||||
@ -30,7 +30,7 @@ use reth_primitives::{
|
||||
U256,
|
||||
};
|
||||
use reth_provider::{BundleStateWithReceipts, StateProviderFactory};
|
||||
use reth_revm::database::StateProviderDatabase;
|
||||
use reth_revm::{database::StateProviderDatabase, state_change::apply_blockhashes_update};
|
||||
use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool};
|
||||
use revm::{
|
||||
db::states::bundle_state::BundleRetention,
|
||||
@ -127,6 +127,18 @@ where
|
||||
err
|
||||
})?;
|
||||
|
||||
// apply eip-2935 blockhashes update
|
||||
apply_blockhashes_update(
|
||||
&mut db,
|
||||
&chain_spec,
|
||||
initialized_block_env.timestamp.to::<u64>(),
|
||||
block_number,
|
||||
parent_block.hash(),
|
||||
).map_err(|err| {
|
||||
warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to update blockhashes for empty payload");
|
||||
PayloadBuilderError::Internal(err.into())
|
||||
})?;
|
||||
|
||||
let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals(
|
||||
&mut db,
|
||||
&chain_spec,
|
||||
@ -271,6 +283,16 @@ where
|
||||
&attributes,
|
||||
)?;
|
||||
|
||||
// apply eip-2935 blockhashes update
|
||||
apply_blockhashes_update(
|
||||
&mut db,
|
||||
&chain_spec,
|
||||
initialized_block_env.timestamp.to::<u64>(),
|
||||
block_number,
|
||||
parent_block.hash(),
|
||||
)
|
||||
.map_err(|err| PayloadBuilderError::Internal(err.into()))?;
|
||||
|
||||
let mut receipts = Vec::new();
|
||||
while let Some(pool_tx) = best_txs.next() {
|
||||
// ensure we still have capacity for this transaction
|
||||
|
||||
@ -21,7 +21,9 @@ pub fn revm_spec_by_timestamp_after_merge(
|
||||
}
|
||||
}
|
||||
|
||||
if chain_spec.is_cancun_active_at_timestamp(timestamp) {
|
||||
if chain_spec.is_prague_active_at_timestamp(timestamp) {
|
||||
revm_primitives::PRAGUE
|
||||
} else if chain_spec.is_cancun_active_at_timestamp(timestamp) {
|
||||
revm_primitives::CANCUN
|
||||
} else if chain_spec.is_shanghai_active_at_timestamp(timestamp) {
|
||||
revm_primitives::SHANGHAI
|
||||
@ -45,7 +47,9 @@ pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId
|
||||
}
|
||||
}
|
||||
|
||||
if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
|
||||
if chain_spec.fork(Hardfork::Prague).active_at_head(&block) {
|
||||
revm_primitives::PRAGUE
|
||||
} else if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
|
||||
revm_primitives::CANCUN
|
||||
} else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
|
||||
revm_primitives::SHANGHAI
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
use alloy_eips::eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE};
|
||||
use reth_consensus_common::calc;
|
||||
use reth_execution_errors::{BlockExecutionError, BlockValidationError};
|
||||
use reth_primitives::{
|
||||
revm::env::fill_tx_env_with_beacon_root_contract_call, Address, ChainSpec, Header, Withdrawal,
|
||||
B256, U256,
|
||||
};
|
||||
use revm::{interpreter::Host, Database, DatabaseCommit, Evm};
|
||||
use reth_storage_errors::provider::ProviderError;
|
||||
use revm::{
|
||||
interpreter::Host,
|
||||
primitives::{Account, AccountInfo, Bytecode, EvmStorageSlot},
|
||||
Database, DatabaseCommit, Evm,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Collect all balance changes at the end of the block.
|
||||
@ -51,11 +57,85 @@ pub fn post_block_balance_increments(
|
||||
balance_increments
|
||||
}
|
||||
|
||||
/// Applies the pre-block call to the EIP-4788 beacon block root contract, using the given block,
|
||||
/// todo: temporary move over of constants from revm until we've migrated to the latest version
|
||||
pub const HISTORY_SERVE_WINDOW: u64 = 8192;
|
||||
|
||||
/// Applies the pre-block state change outlined in [EIP-2935] to store historical blockhashes in a
|
||||
/// system contract.
|
||||
///
|
||||
/// If Prague is not activated, or the block is the genesis block, then this is a no-op, and no
|
||||
/// state changes are made.
|
||||
///
|
||||
/// If the provided block is after Prague has been activated, the parent hash will be inserted.
|
||||
///
|
||||
/// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935
|
||||
#[inline]
|
||||
pub fn apply_blockhashes_update<DB: Database<Error = ProviderError> + DatabaseCommit>(
|
||||
db: &mut DB,
|
||||
chain_spec: &ChainSpec,
|
||||
block_timestamp: u64,
|
||||
block_number: u64,
|
||||
parent_block_hash: B256,
|
||||
) -> Result<(), BlockExecutionError>
|
||||
where
|
||||
DB::Error: std::fmt::Display,
|
||||
{
|
||||
// If Prague is not activated or this is the genesis block, no hashes are added.
|
||||
if !chain_spec.is_prague_active_at_timestamp(block_timestamp) || block_number == 0 {
|
||||
return Ok(())
|
||||
}
|
||||
assert!(block_number > 0);
|
||||
|
||||
// Account is expected to exist either in genesis (for tests) or deployed on mainnet or
|
||||
// testnets.
|
||||
// If the account for any reason does not exist, we create it with the EIP-2935 bytecode and a
|
||||
// nonce of 1, so it does not get deleted.
|
||||
let mut account: Account = db
|
||||
.basic(HISTORY_STORAGE_ADDRESS)
|
||||
.map_err(BlockValidationError::BlockHashAccountLoadingFailed)?
|
||||
.unwrap_or_else(|| AccountInfo {
|
||||
nonce: 1,
|
||||
code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
|
||||
// Insert the state change for the slot
|
||||
let (slot, value) = eip2935_block_hash_slot(db, block_number - 1, parent_block_hash)?;
|
||||
account.storage.insert(slot, value);
|
||||
|
||||
// Mark the account as touched and commit the state change
|
||||
account.mark_touch();
|
||||
db.commit(HashMap::from([(HISTORY_STORAGE_ADDRESS, account)]));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to create a [`EvmStorageSlot`] for [EIP-2935] state transitions for a given
|
||||
/// block number.
|
||||
///
|
||||
/// This calculates the correct storage slot in the `BLOCKHASH` history storage address, fetches the
|
||||
/// blockhash and creates a [`EvmStorageSlot`] with appropriate previous and new values.
|
||||
fn eip2935_block_hash_slot<DB: Database<Error = ProviderError>>(
|
||||
db: &mut DB,
|
||||
block_number: u64,
|
||||
block_hash: B256,
|
||||
) -> Result<(U256, EvmStorageSlot), BlockValidationError> {
|
||||
let slot = U256::from(block_number % HISTORY_SERVE_WINDOW);
|
||||
let current_hash = db
|
||||
.storage(HISTORY_STORAGE_ADDRESS, slot)
|
||||
.map_err(BlockValidationError::BlockHashAccountLoadingFailed)?;
|
||||
|
||||
Ok((slot, EvmStorageSlot::new_changed(current_hash, block_hash.into())))
|
||||
}
|
||||
|
||||
/// Applies the pre-block call to the [EIP-4788] beacon block root contract, using the given block,
|
||||
/// [ChainSpec], EVM.
|
||||
///
|
||||
/// If cancun is not activated or the block is the genesis block, then this is a no-op, and no
|
||||
/// If Cancun is not activated or the block is the genesis block, then this is a no-op, and no
|
||||
/// state changes are made.
|
||||
///
|
||||
/// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788
|
||||
#[inline]
|
||||
pub fn apply_beacon_root_contract_call<EXT, DB: Database + DatabaseCommit>(
|
||||
chain_spec: &ChainSpec,
|
||||
|
||||
@ -32,6 +32,11 @@ impl StateProviderTest {
|
||||
}
|
||||
self.accounts.insert(address, (storage, account));
|
||||
}
|
||||
|
||||
/// Insert a block hash.
|
||||
pub fn insert_block_hash(&mut self, block_number: u64, block_hash: B256) {
|
||||
self.block_hash.insert(block_number, block_hash);
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountReader for StateProviderTest {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! Support for building a pending block via local txpool.
|
||||
|
||||
use crate::eth::error::{EthApiError, EthResult};
|
||||
use reth_errors::ProviderError;
|
||||
use reth_primitives::{
|
||||
constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE},
|
||||
proofs,
|
||||
@ -15,7 +16,10 @@ use reth_primitives::{
|
||||
use reth_provider::{BundleStateWithReceipts, ChainSpecProvider, StateProviderFactory};
|
||||
use reth_revm::{
|
||||
database::StateProviderDatabase,
|
||||
state_change::{apply_beacon_root_contract_call, post_block_withdrawals_balance_increments},
|
||||
state_change::{
|
||||
apply_beacon_root_contract_call, apply_blockhashes_update,
|
||||
post_block_withdrawals_balance_increments,
|
||||
},
|
||||
};
|
||||
use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool};
|
||||
use revm::{db::states::bundle_state::BundleRetention, Database, DatabaseCommit, State};
|
||||
@ -93,6 +97,13 @@ impl PendingBlockEnv {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
pre_block_blockhashes_update(
|
||||
&mut db,
|
||||
chain_spec.as_ref(),
|
||||
&block_env,
|
||||
block_number,
|
||||
parent_hash,
|
||||
)?;
|
||||
|
||||
let mut receipts = Vec::new();
|
||||
|
||||
@ -283,7 +294,7 @@ impl PendingBlockEnv {
|
||||
/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call.
|
||||
///
|
||||
/// This constructs a new [Evm](revm::Evm) with the given DB, and environment [CfgEnvWithHandlerCfg]
|
||||
/// and [BlockEnv]) to execute the pre block contract call.
|
||||
/// and [BlockEnv] to execute the pre block contract call.
|
||||
///
|
||||
/// This uses [apply_beacon_root_contract_call] to ultimately apply the beacon root contract state
|
||||
/// change.
|
||||
@ -319,6 +330,32 @@ where
|
||||
.map_err(|err| EthApiError::Internal(err.into()))
|
||||
}
|
||||
|
||||
/// Apply the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) pre block state transitions.
|
||||
///
|
||||
/// This constructs a new [Evm](revm::Evm) with the given DB, and environment [CfgEnvWithHandlerCfg]
|
||||
/// and [BlockEnv].
|
||||
///
|
||||
/// This uses [apply_blockhashes_update].
|
||||
fn pre_block_blockhashes_update<DB: Database<Error = ProviderError> + DatabaseCommit>(
|
||||
db: &mut DB,
|
||||
chain_spec: &ChainSpec,
|
||||
initialized_block_env: &BlockEnv,
|
||||
block_number: u64,
|
||||
parent_block_hash: B256,
|
||||
) -> EthResult<()>
|
||||
where
|
||||
DB::Error: std::fmt::Display,
|
||||
{
|
||||
apply_blockhashes_update(
|
||||
db,
|
||||
chain_spec,
|
||||
initialized_block_env.timestamp.to::<u64>(),
|
||||
block_number,
|
||||
parent_block_hash,
|
||||
)
|
||||
.map_err(|err| EthApiError::Internal(err.into()))
|
||||
}
|
||||
|
||||
/// The origin for a configured [PendingBlockEnv]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum PendingBlockEnvOrigin {
|
||||
|
||||
Reference in New Issue
Block a user