reth_storage_api/
chain.rs

1use crate::DBProvider;
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    ) -> ProviderResult<()>;
33
34    /// Removes all block bodies above the given block number from the database.
35    fn remove_block_bodies_above(
36        &self,
37        provider: &Provider,
38        block: BlockNumber,
39    ) -> ProviderResult<()>;
40}
41
42/// Trait that implements how chain-specific types are written to the storage.
43pub trait ChainStorageWriter<Provider, Primitives: FullNodePrimitives>:
44    BlockBodyWriter<Provider, <Primitives::Block as Block>::Body>
45{
46}
47impl<T, Provider, Primitives: FullNodePrimitives> ChainStorageWriter<Provider, Primitives> for T where
48    T: BlockBodyWriter<Provider, <Primitives::Block as Block>::Body>
49{
50}
51
52/// Input for reading a block body. Contains a header of block being read and a list of pre-fetched
53/// transactions.
54pub type ReadBodyInput<'a, B> =
55    (&'a <B as Block>::Header, Vec<<<B as Block>::Body as BlockBody>::Transaction>);
56
57/// Trait that implements how block bodies are read from the storage.
58///
59/// Note: Within the current abstraction, transactions persistence is handled separately, thus this
60/// trait is provided with transactions read beforehand and is expected to construct the block body
61/// from those transactions and additional data read from elsewhere.
62#[auto_impl::auto_impl(&, Arc)]
63pub trait BlockBodyReader<Provider> {
64    /// The block type.
65    type Block: Block;
66
67    /// Receives a list of block headers along with block transactions and returns the block bodies.
68    fn read_block_bodies(
69        &self,
70        provider: &Provider,
71        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
72    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>>;
73}
74
75/// Trait that implements how chain-specific types are read from storage.
76pub trait ChainStorageReader<Provider, Primitives: FullNodePrimitives>:
77    BlockBodyReader<Provider, Block = Primitives::Block>
78{
79}
80impl<T, Provider, Primitives: FullNodePrimitives> ChainStorageReader<Provider, Primitives> for T where
81    T: BlockBodyReader<Provider, Block = Primitives::Block>
82{
83}
84
85/// Ethereum storage implementation.
86#[derive(Debug, Clone, Copy)]
87pub struct EthStorage<T = TransactionSigned, H = Header>(PhantomData<(T, H)>);
88
89impl<T, H> Default for EthStorage<T, H> {
90    fn default() -> Self {
91        Self(Default::default())
92    }
93}
94
95impl<Provider, T, H> BlockBodyWriter<Provider, alloy_consensus::BlockBody<T, H>>
96    for EthStorage<T, H>
97where
98    Provider: DBProvider<Tx: DbTxMut>,
99    T: SignedTransaction,
100    H: FullBlockHeader,
101{
102    fn write_block_bodies(
103        &self,
104        provider: &Provider,
105        bodies: Vec<(u64, Option<alloy_consensus::BlockBody<T, H>>)>,
106    ) -> ProviderResult<()> {
107        let mut ommers_cursor = provider.tx_ref().cursor_write::<tables::BlockOmmers<H>>()?;
108        let mut withdrawals_cursor =
109            provider.tx_ref().cursor_write::<tables::BlockWithdrawals>()?;
110
111        for (block_number, body) in bodies {
112            let Some(body) = body else { continue };
113
114            // Write ommers if any
115            if !body.ommers.is_empty() {
116                ommers_cursor.append(block_number, &StoredBlockOmmers { ommers: body.ommers })?;
117            }
118
119            // Write withdrawals if any
120            if let Some(withdrawals) = body.withdrawals &&
121                !withdrawals.is_empty()
122            {
123                withdrawals_cursor.append(block_number, &StoredBlockWithdrawals { withdrawals })?;
124            }
125        }
126
127        Ok(())
128    }
129
130    fn remove_block_bodies_above(
131        &self,
132        provider: &Provider,
133        block: BlockNumber,
134    ) -> ProviderResult<()> {
135        provider.tx_ref().unwind_table_by_num::<tables::BlockWithdrawals>(block)?;
136        provider.tx_ref().unwind_table_by_num::<tables::BlockOmmers<H>>(block)?;
137
138        Ok(())
139    }
140}
141
142impl<Provider, T, H> BlockBodyReader<Provider> for EthStorage<T, H>
143where
144    Provider: DBProvider + ChainSpecProvider<ChainSpec: EthereumHardforks>,
145    T: SignedTransaction,
146    H: FullBlockHeader,
147{
148    type Block = alloy_consensus::Block<T, H>;
149
150    fn read_block_bodies(
151        &self,
152        provider: &Provider,
153        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
154    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
155        // TODO: Ideally storage should hold its own copy of chain spec
156        let chain_spec = provider.chain_spec();
157
158        let mut withdrawals_cursor = provider.tx_ref().cursor_read::<tables::BlockWithdrawals>()?;
159
160        let mut bodies = Vec::with_capacity(inputs.len());
161
162        for (header, transactions) in inputs {
163            // If we are past shanghai, then all blocks should have a withdrawal list,
164            // even if empty
165            let withdrawals = if chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) {
166                withdrawals_cursor
167                    .seek_exact(header.number())?
168                    .map(|(_, w)| w.withdrawals)
169                    .unwrap_or_default()
170                    .into()
171            } else {
172                None
173            };
174            let ommers = if chain_spec.is_paris_active_at_block(header.number()) {
175                Vec::new()
176            } else {
177                // Pre-merge: fetch ommers from database using direct database access
178                provider
179                    .tx_ref()
180                    .cursor_read::<tables::BlockOmmers<H>>()?
181                    .seek_exact(header.number())?
182                    .map(|(_, stored_ommers)| stored_ommers.ommers)
183                    .unwrap_or_default()
184            };
185            bodies.push(alloy_consensus::BlockBody { transactions, ommers, withdrawals });
186        }
187
188        Ok(bodies)
189    }
190}
191
192/// A noop storage for chains that don’t have custom body storage.
193///
194/// This will never read nor write additional body content such as withdrawals or ommers.
195/// But will respect the optionality of withdrawals if activated and fill them if the corresponding
196/// hardfork is activated.
197#[derive(Debug, Clone, Copy)]
198pub struct EmptyBodyStorage<T, H>(PhantomData<(T, H)>);
199
200impl<T, H> Default for EmptyBodyStorage<T, H> {
201    fn default() -> Self {
202        Self(PhantomData)
203    }
204}
205
206impl<Provider, T, H> BlockBodyWriter<Provider, alloy_consensus::BlockBody<T, H>>
207    for EmptyBodyStorage<T, H>
208where
209    T: SignedTransaction,
210    H: FullBlockHeader,
211{
212    fn write_block_bodies(
213        &self,
214        _provider: &Provider,
215        _bodies: Vec<(u64, Option<alloy_consensus::BlockBody<T, H>>)>,
216    ) -> ProviderResult<()> {
217        // noop
218        Ok(())
219    }
220
221    fn remove_block_bodies_above(
222        &self,
223        _provider: &Provider,
224        _block: BlockNumber,
225    ) -> ProviderResult<()> {
226        // noop
227        Ok(())
228    }
229}
230
231impl<Provider, T, H> BlockBodyReader<Provider> for EmptyBodyStorage<T, H>
232where
233    Provider: ChainSpecProvider<ChainSpec: EthereumHardforks>,
234    T: SignedTransaction,
235    H: FullBlockHeader,
236{
237    type Block = alloy_consensus::Block<T, H>;
238
239    fn read_block_bodies(
240        &self,
241        provider: &Provider,
242        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
243    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
244        let chain_spec = provider.chain_spec();
245
246        Ok(inputs
247            .into_iter()
248            .map(|(header, transactions)| {
249                alloy_consensus::BlockBody {
250                    transactions,
251                    ommers: vec![], // Empty storage never has ommers
252                    withdrawals: chain_spec
253                        .is_shanghai_active_at_timestamp(header.timestamp())
254                        .then(Default::default),
255                }
256            })
257            .collect())
258    }
259}