reth_storage_api/
chain.rs

1use crate::{DBProvider, OmmersProvider, StorageLocation};
2use alloc::vec::Vec;
3use alloy_consensus::Header;
4use alloy_primitives::BlockNumber;
5use core::marker::PhantomData;
6use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
7use reth_db_api::{
8    cursor::{DbCursorRO, DbCursorRW},
9    models::StoredBlockOmmers,
10    tables,
11    transaction::{DbTx, DbTxMut},
12    DbTxUnwindExt,
13};
14use reth_db_models::StoredBlockWithdrawals;
15use reth_ethereum_primitives::TransactionSigned;
16use reth_primitives_traits::{
17    Block, BlockBody, FullBlockHeader, FullNodePrimitives, SignedTransaction,
18};
19use reth_storage_errors::provider::ProviderResult;
20
21/// Trait that implements how block bodies are written to the storage.
22///
23/// Note: Within the current abstraction, this should only write to tables unrelated to
24/// transactions. Writing of transactions is handled separately.
25#[auto_impl::auto_impl(&, Arc)]
26pub trait BlockBodyWriter<Provider, Body: BlockBody> {
27    /// Writes a set of block bodies to the storage.
28    fn write_block_bodies(
29        &self,
30        provider: &Provider,
31        bodies: Vec<(BlockNumber, Option<Body>)>,
32        write_to: StorageLocation,
33    ) -> ProviderResult<()>;
34
35    /// Removes all block bodies above the given block number from the database.
36    fn remove_block_bodies_above(
37        &self,
38        provider: &Provider,
39        block: BlockNumber,
40        remove_from: StorageLocation,
41    ) -> ProviderResult<()>;
42}
43
44/// Trait that implements how chain-specific types are written to the storage.
45pub trait ChainStorageWriter<Provider, Primitives: FullNodePrimitives>:
46    BlockBodyWriter<Provider, <Primitives::Block as Block>::Body>
47{
48}
49impl<T, Provider, Primitives: FullNodePrimitives> ChainStorageWriter<Provider, Primitives> for T where
50    T: BlockBodyWriter<Provider, <Primitives::Block as Block>::Body>
51{
52}
53
54/// Input for reading a block body. Contains a header of block being read and a list of pre-fetched
55/// transactions.
56pub type ReadBodyInput<'a, B> =
57    (&'a <B as Block>::Header, Vec<<<B as Block>::Body as BlockBody>::Transaction>);
58
59/// Trait that implements how block bodies are read from the storage.
60///
61/// Note: Within the current abstraction, transactions persistence is handled separately, thus this
62/// trait is provided with transactions read beforehand and is expected to construct the block body
63/// from those transactions and additional data read from elsewhere.
64#[auto_impl::auto_impl(&, Arc)]
65pub trait BlockBodyReader<Provider> {
66    /// The block type.
67    type Block: Block;
68
69    /// Receives a list of block headers along with block transactions and returns the block bodies.
70    fn read_block_bodies(
71        &self,
72        provider: &Provider,
73        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
74    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>>;
75}
76
77/// Trait that implements how chain-specific types are read from storage.
78pub trait ChainStorageReader<Provider, Primitives: FullNodePrimitives>:
79    BlockBodyReader<Provider, Block = Primitives::Block>
80{
81}
82impl<T, Provider, Primitives: FullNodePrimitives> ChainStorageReader<Provider, Primitives> for T where
83    T: BlockBodyReader<Provider, Block = Primitives::Block>
84{
85}
86
87/// Ethereum storage implementation.
88#[derive(Debug, Clone, Copy)]
89pub struct EthStorage<T = TransactionSigned, H = Header>(PhantomData<(T, H)>);
90
91impl<T, H> Default for EthStorage<T, H> {
92    fn default() -> Self {
93        Self(Default::default())
94    }
95}
96
97impl<Provider, T, H> BlockBodyWriter<Provider, alloy_consensus::BlockBody<T, H>>
98    for EthStorage<T, H>
99where
100    Provider: DBProvider<Tx: DbTxMut>,
101    T: SignedTransaction,
102    H: FullBlockHeader,
103{
104    fn write_block_bodies(
105        &self,
106        provider: &Provider,
107        bodies: Vec<(u64, Option<alloy_consensus::BlockBody<T, H>>)>,
108        _write_to: StorageLocation,
109    ) -> ProviderResult<()> {
110        let mut ommers_cursor = provider.tx_ref().cursor_write::<tables::BlockOmmers<H>>()?;
111        let mut withdrawals_cursor =
112            provider.tx_ref().cursor_write::<tables::BlockWithdrawals>()?;
113
114        for (block_number, body) in bodies {
115            let Some(body) = body else { continue };
116
117            // Write ommers if any
118            if !body.ommers.is_empty() {
119                ommers_cursor.append(block_number, &StoredBlockOmmers { ommers: body.ommers })?;
120            }
121
122            // Write withdrawals if any
123            if let Some(withdrawals) = body.withdrawals {
124                if !withdrawals.is_empty() {
125                    withdrawals_cursor
126                        .append(block_number, &StoredBlockWithdrawals { withdrawals })?;
127                }
128            }
129        }
130
131        Ok(())
132    }
133
134    fn remove_block_bodies_above(
135        &self,
136        provider: &Provider,
137        block: BlockNumber,
138        _remove_from: StorageLocation,
139    ) -> ProviderResult<()> {
140        provider.tx_ref().unwind_table_by_num::<tables::BlockWithdrawals>(block)?;
141        provider.tx_ref().unwind_table_by_num::<tables::BlockOmmers>(block)?;
142
143        Ok(())
144    }
145}
146
147impl<Provider, T, H> BlockBodyReader<Provider> for EthStorage<T, H>
148where
149    Provider:
150        DBProvider + ChainSpecProvider<ChainSpec: EthereumHardforks> + OmmersProvider<Header = H>,
151    T: SignedTransaction,
152    H: FullBlockHeader,
153{
154    type Block = alloy_consensus::Block<T, H>;
155
156    fn read_block_bodies(
157        &self,
158        provider: &Provider,
159        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
160    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
161        // TODO: Ideally storage should hold its own copy of chain spec
162        let chain_spec = provider.chain_spec();
163
164        let mut withdrawals_cursor = provider.tx_ref().cursor_read::<tables::BlockWithdrawals>()?;
165
166        let mut bodies = Vec::with_capacity(inputs.len());
167
168        for (header, transactions) in inputs {
169            // If we are past shanghai, then all blocks should have a withdrawal list,
170            // even if empty
171            let withdrawals = if chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) {
172                withdrawals_cursor
173                    .seek_exact(header.number())?
174                    .map(|(_, w)| w.withdrawals)
175                    .unwrap_or_default()
176                    .into()
177            } else {
178                None
179            };
180            let ommers = if chain_spec.is_paris_active_at_block(header.number()) {
181                Vec::new()
182            } else {
183                provider.ommers(header.number().into())?.unwrap_or_default()
184            };
185
186            bodies.push(alloy_consensus::BlockBody { transactions, ommers, withdrawals });
187        }
188
189        Ok(bodies)
190    }
191}