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, NodePrimitives, 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: NodePrimitives>:
44    BlockBodyWriter<Provider, <Primitives::Block as Block>::Body>
45{
46}
47impl<T, Provider, Primitives: NodePrimitives> 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: NodePrimitives>:
77    BlockBodyReader<Provider, Block = Primitives::Block>
78{
79}
80impl<T, Provider, Primitives: NodePrimitives> 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
117                    .append(block_number, &StoredBlockOmmers { ommers: body.ommers.clone() })?;
118            }
119
120            // Write withdrawals if any
121            if let Some(withdrawals) = body.withdrawals.clone() &&
122                !withdrawals.is_empty()
123            {
124                withdrawals_cursor.append(block_number, &StoredBlockWithdrawals { withdrawals })?;
125            }
126        }
127
128        Ok(())
129    }
130
131    fn remove_block_bodies_above(
132        &self,
133        provider: &Provider,
134        block: BlockNumber,
135    ) -> ProviderResult<()> {
136        provider.tx_ref().unwind_table_by_num::<tables::BlockWithdrawals>(block)?;
137        provider.tx_ref().unwind_table_by_num::<tables::BlockOmmers<H>>(block)?;
138
139        Ok(())
140    }
141}
142
143impl<Provider, T, H> BlockBodyReader<Provider> for EthStorage<T, H>
144where
145    Provider: DBProvider + ChainSpecProvider<ChainSpec: EthereumHardforks>,
146    T: SignedTransaction,
147    H: FullBlockHeader,
148{
149    type Block = alloy_consensus::Block<T, H>;
150
151    fn read_block_bodies(
152        &self,
153        provider: &Provider,
154        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
155    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
156        // TODO: Ideally storage should hold its own copy of chain spec
157        let chain_spec = provider.chain_spec();
158
159        let mut withdrawals_cursor = provider.tx_ref().cursor_read::<tables::BlockWithdrawals>()?;
160
161        let mut bodies = Vec::with_capacity(inputs.len());
162
163        for (header, transactions) in inputs {
164            // If we are past shanghai, then all blocks should have a withdrawal list,
165            // even if empty
166            let withdrawals = if chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) {
167                withdrawals_cursor
168                    .seek_exact(header.number())?
169                    .map(|(_, w)| w.withdrawals)
170                    .unwrap_or_default()
171                    .into()
172            } else {
173                None
174            };
175            let ommers = if chain_spec.is_paris_active_at_block(header.number()) {
176                Vec::new()
177            } else {
178                // Pre-merge: fetch ommers from database using direct database access
179                provider
180                    .tx_ref()
181                    .cursor_read::<tables::BlockOmmers<H>>()?
182                    .seek_exact(header.number())?
183                    .map(|(_, stored_ommers)| stored_ommers.ommers)
184                    .unwrap_or_default()
185            };
186            bodies.push(alloy_consensus::BlockBody { transactions, ommers, withdrawals });
187        }
188
189        Ok(bodies)
190    }
191}
192
193/// A noop storage for chains that don’t have custom body storage.
194///
195/// This will never read nor write additional body content such as withdrawals or ommers.
196/// But will respect the optionality of withdrawals if activated and fill them if the corresponding
197/// hardfork is activated.
198#[derive(Debug, Clone, Copy)]
199pub struct EmptyBodyStorage<T, H>(PhantomData<(T, H)>);
200
201impl<T, H> Default for EmptyBodyStorage<T, H> {
202    fn default() -> Self {
203        Self(PhantomData)
204    }
205}
206
207impl<Provider, T, H> BlockBodyWriter<Provider, alloy_consensus::BlockBody<T, H>>
208    for EmptyBodyStorage<T, H>
209where
210    T: SignedTransaction,
211    H: FullBlockHeader,
212{
213    fn write_block_bodies(
214        &self,
215        _provider: &Provider,
216        _bodies: Vec<(u64, Option<&alloy_consensus::BlockBody<T, H>>)>,
217    ) -> ProviderResult<()> {
218        // noop
219        Ok(())
220    }
221
222    fn remove_block_bodies_above(
223        &self,
224        _provider: &Provider,
225        _block: BlockNumber,
226    ) -> ProviderResult<()> {
227        // noop
228        Ok(())
229    }
230}
231
232impl<Provider, T, H> BlockBodyReader<Provider> for EmptyBodyStorage<T, H>
233where
234    Provider: ChainSpecProvider<ChainSpec: EthereumHardforks>,
235    T: SignedTransaction,
236    H: FullBlockHeader,
237{
238    type Block = alloy_consensus::Block<T, H>;
239
240    fn read_block_bodies(
241        &self,
242        provider: &Provider,
243        inputs: Vec<ReadBodyInput<'_, Self::Block>>,
244    ) -> ProviderResult<Vec<<Self::Block as Block>::Body>> {
245        let chain_spec = provider.chain_spec();
246
247        Ok(inputs
248            .into_iter()
249            .map(|(header, transactions)| {
250                alloy_consensus::BlockBody {
251                    transactions,
252                    ommers: vec![], // Empty storage never has ommers
253                    withdrawals: chain_spec
254                        .is_shanghai_active_at_timestamp(header.timestamp())
255                        .then(Default::default),
256                }
257            })
258            .collect())
259    }
260}