refactor: EmptyBodyStorage block reader logic (#18508)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Dharm Singh
2025-09-18 05:21:27 +05:30
committed by GitHub
parent d357d2acb3
commit 870389c5d6
7 changed files with 107 additions and 170 deletions

6
Cargo.lock generated
View File

@@ -9518,14 +9518,8 @@ name = "reth-optimism-storage"
version = "1.7.0"
dependencies = [
"alloy-consensus",
"alloy-primitives",
"reth-chainspec",
"reth-codecs",
"reth-db-api",
"reth-node-api",
"reth-optimism-primitives",
"reth-primitives-traits",
"reth-provider",
"reth-prune-types",
"reth-stages-types",
"reth-storage-api",

View File

@@ -12,16 +12,10 @@ workspace = true
[dependencies]
# reth
reth-node-api.workspace = true
reth-chainspec.workspace = true
reth-primitives-traits.workspace = true
reth-optimism-primitives = { workspace = true, features = ["serde", "reth-codec"] }
reth-storage-api = { workspace = true, features = ["db-api"] }
reth-db-api.workspace = true
reth-provider.workspace = true
# ethereum
alloy-primitives.workspace = true
alloy-consensus.workspace = true
[dev-dependencies]
@@ -33,11 +27,8 @@ reth-stages-types.workspace = true
default = ["std"]
std = [
"reth-storage-api/std",
"alloy-primitives/std",
"reth-prune-types/std",
"reth-stages-types/std",
"alloy-consensus/std",
"reth-chainspec/std",
"reth-optimism-primitives/std",
"reth-primitives-traits/std",
]

View File

@@ -1,111 +1,6 @@
use alloc::{vec, vec::Vec};
use alloy_consensus::{BlockBody, Header};
use alloy_primitives::BlockNumber;
use core::marker::PhantomData;
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
use reth_db_api::transaction::{DbTx, DbTxMut};
use reth_node_api::{FullNodePrimitives, FullSignedTx};
use alloy_consensus::Header;
use reth_optimism_primitives::OpTransactionSigned;
use reth_primitives_traits::{Block, FullBlockHeader, SignedTransaction};
use reth_provider::{
providers::{ChainStorage, NodeTypesForProvider},
DatabaseProvider,
};
use reth_storage_api::{
errors::ProviderResult, BlockBodyReader, BlockBodyWriter, ChainStorageReader,
ChainStorageWriter, DBProvider, ReadBodyInput, StorageLocation,
};
use reth_storage_api::EmptyBodyStorage;
/// Optimism storage implementation.
#[derive(Debug, Clone, Copy)]
pub struct OpStorage<T = OpTransactionSigned, H = Header>(PhantomData<(T, H)>);
impl<T, H> Default for OpStorage<T, H> {
fn default() -> Self {
Self(Default::default())
}
}
impl<N, T, H> ChainStorage<N> for OpStorage<T, H>
where
T: FullSignedTx,
H: FullBlockHeader,
N: FullNodePrimitives<
Block = alloy_consensus::Block<T, H>,
BlockHeader = H,
BlockBody = alloy_consensus::BlockBody<T, H>,
SignedTx = T,
>,
{
fn reader<TX, Types>(&self) -> impl ChainStorageReader<DatabaseProvider<TX, Types>, N>
where
TX: DbTx + 'static,
Types: NodeTypesForProvider<Primitives = N>,
{
self
}
fn writer<TX, Types>(&self) -> impl ChainStorageWriter<DatabaseProvider<TX, Types>, N>
where
TX: DbTxMut + DbTx + 'static,
Types: NodeTypesForProvider<Primitives = N>,
{
self
}
}
impl<Provider, T, H> BlockBodyWriter<Provider, alloy_consensus::BlockBody<T, H>> for OpStorage<T, H>
where
Provider: DBProvider<Tx: DbTxMut>,
T: SignedTransaction,
H: FullBlockHeader,
{
fn write_block_bodies(
&self,
_provider: &Provider,
_bodies: Vec<(u64, Option<alloy_consensus::BlockBody<T, H>>)>,
_write_to: StorageLocation,
) -> ProviderResult<()> {
// noop
Ok(())
}
fn remove_block_bodies_above(
&self,
_provider: &Provider,
_block: BlockNumber,
_remove_from: StorageLocation,
) -> ProviderResult<()> {
// noop
Ok(())
}
}
impl<Provider, T, H> BlockBodyReader<Provider> for OpStorage<T, H>
where
Provider: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + DBProvider,
T: SignedTransaction,
H: FullBlockHeader,
{
type Block = alloy_consensus::Block<T, H>;
fn read_block_bodies(
&self,
provider: &Provider,
inputs: Vec<ReadBodyInput<'_, Self::Block>>,
) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
let chain_spec = provider.chain_spec();
Ok(inputs
.into_iter()
.map(|(header, transactions)| BlockBody {
transactions,
ommers: vec![],
// after shanghai the body should have an empty withdrawals list
withdrawals: chain_spec
.is_shanghai_active_at_timestamp(header.timestamp())
.then(Default::default),
})
.collect())
}
}
pub type OpStorage<T = OpTransactionSigned, H = Header> = EmptyBodyStorage<T, H>;

View File

@@ -9,67 +9,26 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
extern crate alloc;
mod chain;
pub use chain::OpStorage;
#[cfg(test)]
mod tests {
use reth_codecs::{test_utils::UnusedBits, validate_bitflag_backwards_compat};
use reth_db_api::models::{
CompactClientVersion, CompactU256, CompactU64, StoredBlockBodyIndices,
StoredBlockWithdrawals,
};
use reth_primitives_traits::Account;
use reth_prune_types::{PruneCheckpoint, PruneMode, PruneSegment};
use reth_stages_types::{
AccountHashingCheckpoint, CheckpointBlockRange, EntitiesCheckpoint, ExecutionCheckpoint,
HeadersCheckpoint, IndexHistoryCheckpoint, StageCheckpoint, StageUnitCheckpoint,
StorageHashingCheckpoint,
};
#[test]
fn test_ensure_backwards_compatibility() {
assert_eq!(Account::bitflag_encoded_bytes(), 2);
assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1);
assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0);
assert_eq!(CompactU256::bitflag_encoded_bytes(), 1);
assert_eq!(CompactU64::bitflag_encoded_bytes(), 1);
assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(PruneMode::bitflag_encoded_bytes(), 1);
assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1);
assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1);
assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0);
assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1);
// In case of failure, refer to the documentation of the
// [`validate_bitflag_backwards_compat`] macro for detailed instructions on handling
// it.
validate_bitflag_backwards_compat!(Account, UnusedBits::NotZero);
validate_bitflag_backwards_compat!(AccountHashingCheckpoint, UnusedBits::NotZero);
validate_bitflag_backwards_compat!(CheckpointBlockRange, UnusedBits::Zero);
validate_bitflag_backwards_compat!(CompactClientVersion, UnusedBits::Zero);
validate_bitflag_backwards_compat!(CompactU256, UnusedBits::NotZero);
validate_bitflag_backwards_compat!(CompactU64, UnusedBits::NotZero);
validate_bitflag_backwards_compat!(EntitiesCheckpoint, UnusedBits::Zero);
validate_bitflag_backwards_compat!(ExecutionCheckpoint, UnusedBits::Zero);
validate_bitflag_backwards_compat!(HeadersCheckpoint, UnusedBits::Zero);
validate_bitflag_backwards_compat!(IndexHistoryCheckpoint, UnusedBits::Zero);
validate_bitflag_backwards_compat!(PruneCheckpoint, UnusedBits::NotZero);
validate_bitflag_backwards_compat!(PruneMode, UnusedBits::Zero);
validate_bitflag_backwards_compat!(PruneSegment, UnusedBits::Zero);
validate_bitflag_backwards_compat!(StageCheckpoint, UnusedBits::NotZero);
validate_bitflag_backwards_compat!(StageUnitCheckpoint, UnusedBits::Zero);
validate_bitflag_backwards_compat!(StoredBlockBodyIndices, UnusedBits::Zero);
validate_bitflag_backwards_compat!(StoredBlockWithdrawals, UnusedBits::Zero);
validate_bitflag_backwards_compat!(StorageHashingCheckpoint, UnusedBits::NotZero);
}
}

View File

@@ -3,7 +3,7 @@ use reth_db_api::transaction::{DbTx, DbTxMut};
use reth_node_types::FullNodePrimitives;
use reth_primitives_traits::{FullBlockHeader, FullSignedTx};
use reth_storage_api::{ChainStorageReader, ChainStorageWriter, EthStorage};
use reth_storage_api::{ChainStorageReader, ChainStorageWriter, EmptyBodyStorage, EthStorage};
/// Trait that provides access to implementations of [`ChainStorage`]
pub trait ChainStorage<Primitives: FullNodePrimitives>: Send + Sync {
@@ -47,3 +47,31 @@ where
self
}
}
impl<N, T, H> ChainStorage<N> for EmptyBodyStorage<T, H>
where
T: FullSignedTx,
H: FullBlockHeader,
N: FullNodePrimitives<
Block = alloy_consensus::Block<T, H>,
BlockHeader = H,
BlockBody = alloy_consensus::BlockBody<T, H>,
SignedTx = T,
>,
{
fn reader<TX, Types>(&self) -> impl ChainStorageReader<DatabaseProvider<TX, Types>, N>
where
TX: DbTx + 'static,
Types: NodeTypesForProvider<Primitives = N>,
{
self
}
fn writer<TX, Types>(&self) -> impl ChainStorageWriter<DatabaseProvider<TX, Types>, N>
where
TX: DbTxMut + DbTx + 'static,
Types: NodeTypesForProvider<Primitives = N>,
{
self
}
}

View File

@@ -65,7 +65,6 @@ serde = [
"reth-stages-types/serde",
"reth-trie-common/serde",
"revm-database/serde",
"reth-ethereum-primitives/serde",
"alloy-eips/serde",
"alloy-primitives/serde",
"alloy-consensus/serde",
@@ -73,7 +72,6 @@ serde = [
]
serde-bincode-compat = [
"reth-ethereum-primitives/serde-bincode-compat",
"reth-execution-types/serde-bincode-compat",
"reth-primitives-traits/serde-bincode-compat",
"reth-trie-common/serde-bincode-compat",

View File

@@ -3,7 +3,7 @@ use alloc::vec::Vec;
use alloy_consensus::Header;
use alloy_primitives::BlockNumber;
use core::marker::PhantomData;
use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
use reth_db_api::{
cursor::{DbCursorRO, DbCursorRW},
models::StoredBlockOmmers,
@@ -193,3 +193,75 @@ where
Ok(bodies)
}
}
/// A noop storage for chains that dont have custom body storage.
///
/// This will never read nor write additional body content such as withdrawals or ommers.
/// But will respect the optionality of withdrawals if activated and fill them if the corresponding
/// hardfork is activated.
#[derive(Debug, Clone, Copy)]
pub struct EmptyBodyStorage<T, H>(PhantomData<(T, H)>);
impl<T, H> Default for EmptyBodyStorage<T, H> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<Provider, T, H> BlockBodyWriter<Provider, alloy_consensus::BlockBody<T, H>>
for EmptyBodyStorage<T, H>
where
Provider: DBProvider<Tx: DbTxMut>,
T: SignedTransaction,
H: FullBlockHeader,
{
fn write_block_bodies(
&self,
_provider: &Provider,
_bodies: Vec<(u64, Option<alloy_consensus::BlockBody<T, H>>)>,
_write_to: StorageLocation,
) -> ProviderResult<()> {
// noop
Ok(())
}
fn remove_block_bodies_above(
&self,
_provider: &Provider,
_block: BlockNumber,
_remove_from: StorageLocation,
) -> ProviderResult<()> {
// noop
Ok(())
}
}
impl<Provider, T, H> BlockBodyReader<Provider> for EmptyBodyStorage<T, H>
where
Provider: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + DBProvider,
T: SignedTransaction,
H: FullBlockHeader,
{
type Block = alloy_consensus::Block<T, H>;
fn read_block_bodies(
&self,
provider: &Provider,
inputs: Vec<ReadBodyInput<'_, Self::Block>>,
) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
let chain_spec = provider.chain_spec();
Ok(inputs
.into_iter()
.map(|(header, transactions)| {
alloy_consensus::BlockBody {
transactions,
ommers: vec![], // Empty storage never has ommers
withdrawals: chain_spec
.is_shanghai_active_at_timestamp(header.timestamp())
.then(Default::default),
}
})
.collect())
}
}