reth_storage_api/
chain.rs

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