reth_blockchain_tree_api/
lib.rs

1//! Interfaces and types for interacting with the blockchain tree.
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
4    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
5    issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
6)]
7#![cfg_attr(not(test), warn(unused_crate_dependencies))]
8#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
9
10use self::error::CanonicalError;
11use crate::error::InsertBlockError;
12use alloy_eips::BlockNumHash;
13use alloy_primitives::{BlockHash, BlockNumber};
14use reth_primitives::{Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader};
15use reth_storage_errors::provider::{ProviderError, ProviderResult};
16use std::collections::BTreeMap;
17
18pub mod error;
19
20/// * [`BlockchainTreeEngine::insert_block`]: Connect block to chain, execute it and if valid insert
21///   block inside tree.
22/// * [`BlockchainTreeEngine::finalize_block`]: Remove chains that join to now finalized block, as
23///   chain becomes invalid.
24/// * [`BlockchainTreeEngine::make_canonical`]: Check if we have the hash of block that we want to
25///   finalize and commit it to db. If we don't have the block, syncing should start to fetch the
26///   blocks from p2p. Do reorg in tables if canonical chain if needed.
27pub trait BlockchainTreeEngine: BlockchainTreeViewer + Send + Sync {
28    /// Recover senders and call [`BlockchainTreeEngine::insert_block`].
29    ///
30    /// This will recover all senders of the transactions in the block first, and then try to insert
31    /// the block.
32    fn insert_block_without_senders(
33        &self,
34        block: SealedBlock,
35        validation_kind: BlockValidationKind,
36    ) -> Result<InsertPayloadOk, InsertBlockError> {
37        match block.try_seal_with_senders() {
38            Ok(block) => self.insert_block(block, validation_kind),
39            Err(block) => Err(InsertBlockError::sender_recovery_error(block)),
40        }
41    }
42
43    /// Recover senders and call [`BlockchainTreeEngine::buffer_block`].
44    ///
45    /// This will recover all senders of the transactions in the block first, and then try to buffer
46    /// the block.
47    fn buffer_block_without_senders(&self, block: SealedBlock) -> Result<(), InsertBlockError> {
48        match block.try_seal_with_senders() {
49            Ok(block) => self.buffer_block(block),
50            Err(block) => Err(InsertBlockError::sender_recovery_error(block)),
51        }
52    }
53
54    /// Buffer block with senders
55    fn buffer_block(&self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError>;
56
57    /// Inserts block with senders
58    ///
59    /// The `validation_kind` parameter controls which validation checks are performed.
60    ///
61    /// Caution: If the block was received from the consensus layer, this should always be called
62    /// with [`BlockValidationKind::Exhaustive`] to validate the state root, if possible to adhere
63    /// to the engine API spec.
64    fn insert_block(
65        &self,
66        block: SealedBlockWithSenders,
67        validation_kind: BlockValidationKind,
68    ) -> Result<InsertPayloadOk, InsertBlockError>;
69
70    /// Finalize blocks up until and including `finalized_block`, and remove them from the tree.
71    fn finalize_block(&self, finalized_block: BlockNumber) -> ProviderResult<()>;
72
73    /// Reads the last `N` canonical hashes from the database and updates the block indices of the
74    /// tree by attempting to connect the buffered blocks to canonical hashes.
75    ///
76    ///
77    /// `N` is the maximum of `max_reorg_depth` and the number of block hashes needed to satisfy the
78    /// `BLOCKHASH` opcode in the EVM.
79    ///
80    /// # Note
81    ///
82    /// This finalizes `last_finalized_block` prior to reading the canonical hashes (using
83    /// [`BlockchainTreeEngine::finalize_block`]).
84    fn connect_buffered_blocks_to_canonical_hashes_and_finalize(
85        &self,
86        last_finalized_block: BlockNumber,
87    ) -> Result<(), CanonicalError>;
88
89    /// Update all block hashes. iterate over present and new list of canonical hashes and compare
90    /// them. Remove all mismatches, disconnect them, removes all chains and clears all buffered
91    /// blocks before the tip.
92    fn update_block_hashes_and_clear_buffered(
93        &self,
94    ) -> Result<BTreeMap<BlockNumber, BlockHash>, CanonicalError>;
95
96    /// Reads the last `N` canonical hashes from the database and updates the block indices of the
97    /// tree by attempting to connect the buffered blocks to canonical hashes.
98    ///
99    /// `N` is the maximum of `max_reorg_depth` and the number of block hashes needed to satisfy the
100    /// `BLOCKHASH` opcode in the EVM.
101    fn connect_buffered_blocks_to_canonical_hashes(&self) -> Result<(), CanonicalError>;
102
103    /// Make a block and its parent chain part of the canonical chain by committing it to the
104    /// database.
105    ///
106    /// # Note
107    ///
108    /// This unwinds the database if necessary, i.e. if parts of the canonical chain have been
109    /// re-orged.
110    ///
111    /// # Returns
112    ///
113    /// Returns `Ok` if the blocks were canonicalized, or if the blocks were already canonical.
114    fn make_canonical(&self, block_hash: BlockHash) -> Result<CanonicalOutcome, CanonicalError>;
115}
116
117/// Represents the kind of validation that should be performed when inserting a block.
118///
119/// The motivation for this is that the engine API spec requires that block's state root is
120/// validated when received from the CL.
121///
122/// This step is very expensive due to how changesets are stored in the database, so we want to
123/// avoid doing it if not necessary. Blocks can also originate from the network where this step is
124/// not required.
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub enum BlockValidationKind {
127    /// All validation checks that can be performed.
128    ///
129    /// This includes validating the state root, if possible.
130    ///
131    /// Note: This should always be used when inserting blocks that originate from the consensus
132    /// layer.
133    #[default]
134    Exhaustive,
135    /// Perform all validation checks except for state root validation.
136    SkipStateRootValidation,
137}
138
139impl BlockValidationKind {
140    /// Returns true if the state root should be validated if possible.
141    pub const fn is_exhaustive(&self) -> bool {
142        matches!(self, Self::Exhaustive)
143    }
144}
145
146impl std::fmt::Display for BlockValidationKind {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match self {
149            Self::Exhaustive => {
150                write!(f, "Exhaustive")
151            }
152            Self::SkipStateRootValidation => {
153                write!(f, "SkipStateRootValidation")
154            }
155        }
156    }
157}
158
159/// All possible outcomes of a canonicalization attempt of [`BlockchainTreeEngine::make_canonical`].
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub enum CanonicalOutcome {
162    /// The block is already canonical.
163    AlreadyCanonical {
164        /// Block number and hash of current head.
165        head: BlockNumHash,
166        /// The corresponding [`SealedHeader`] that was attempted to be made a current head and
167        /// is already canonical.
168        header: SealedHeader,
169    },
170    /// Committed the block to the database.
171    Committed {
172        /// The new corresponding canonical head
173        head: SealedHeader,
174    },
175}
176
177impl CanonicalOutcome {
178    /// Returns the header of the block that was made canonical.
179    pub const fn header(&self) -> &SealedHeader {
180        match self {
181            Self::AlreadyCanonical { header, .. } => header,
182            Self::Committed { head } => head,
183        }
184    }
185
186    /// Consumes the outcome and returns the header of the block that was made canonical.
187    pub fn into_header(self) -> SealedHeader {
188        match self {
189            Self::AlreadyCanonical { header, .. } => header,
190            Self::Committed { head } => head,
191        }
192    }
193
194    /// Returns true if the block was already canonical.
195    pub const fn is_already_canonical(&self) -> bool {
196        matches!(self, Self::AlreadyCanonical { .. })
197    }
198}
199
200/// Block inclusion can be valid, accepted, or invalid. Invalid blocks are returned as an error
201/// variant.
202///
203/// If we don't know the block's parent, we return `Disconnected`, as we can't claim that the block
204/// is valid or not.
205#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub enum BlockStatus2 {
207    /// The block is valid and block extends canonical chain.
208    Valid,
209    /// The block may be valid and has an unknown missing ancestor.
210    Disconnected {
211        /// Current canonical head.
212        head: BlockNumHash,
213        /// The lowest ancestor block that is not connected to the canonical chain.
214        missing_ancestor: BlockNumHash,
215    },
216}
217
218/// How a payload was inserted if it was valid.
219///
220/// If the payload was valid, but has already been seen, [`InsertPayloadOk2::AlreadySeen(_)`] is
221/// returned, otherwise [`InsertPayloadOk2::Inserted(_)`] is returned.
222#[derive(Clone, Copy, Debug, Eq, PartialEq)]
223pub enum InsertPayloadOk2 {
224    /// The payload was valid, but we have already seen it.
225    AlreadySeen(BlockStatus2),
226    /// The payload was valid and inserted into the tree.
227    Inserted(BlockStatus2),
228}
229
230/// From Engine API spec, block inclusion can be valid, accepted or invalid.
231/// Invalid case is already covered by error, but we need to make distinction
232/// between valid blocks that extend canonical chain and the ones that fork off
233/// into side chains (see [`BlockAttachment`]). If we don't know the block
234/// parent we are returning Disconnected status as we can't make a claim if
235/// block is valid or not.
236#[derive(Clone, Copy, Debug, Eq, PartialEq)]
237pub enum BlockStatus {
238    /// If block is valid and block extends canonical chain.
239    /// In `BlockchainTree` terms, it forks off canonical tip.
240    Valid(BlockAttachment),
241    /// If block is valid and block forks off canonical chain.
242    /// If blocks is not connected to canonical chain.
243    Disconnected {
244        /// Current canonical head.
245        head: BlockNumHash,
246        /// The lowest ancestor block that is not connected to the canonical chain.
247        missing_ancestor: BlockNumHash,
248    },
249}
250
251/// Represents what kind of block is being executed and validated.
252///
253/// This is required to:
254/// - differentiate whether trie state updates should be cached.
255/// - inform other
256///
257/// This is required because the state root check can only be performed if the targeted block can be
258/// traced back to the canonical __head__.
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub enum BlockAttachment {
261    /// The `block` is canonical or a descendant of the canonical head.
262    /// ([`head..(block.parent)*,block`])
263    Canonical,
264    /// The block can be traced back to an ancestor of the canonical head: a historical block, but
265    /// this chain does __not__ include the canonical head.
266    HistoricalFork,
267}
268
269impl BlockAttachment {
270    /// Returns `true` if the block is canonical or a descendant of the canonical head.
271    #[inline]
272    pub const fn is_canonical(&self) -> bool {
273        matches!(self, Self::Canonical)
274    }
275}
276
277/// How a payload was inserted if it was valid.
278///
279/// If the payload was valid, but has already been seen, [`InsertPayloadOk::AlreadySeen(_)`] is
280/// returned, otherwise [`InsertPayloadOk::Inserted(_)`] is returned.
281#[derive(Clone, Copy, Debug, Eq, PartialEq)]
282pub enum InsertPayloadOk {
283    /// The payload was valid, but we have already seen it.
284    AlreadySeen(BlockStatus),
285    /// The payload was valid and inserted into the tree.
286    Inserted(BlockStatus),
287}
288
289/// Allows read only functionality on the blockchain tree.
290///
291/// Tree contains all blocks that are not canonical that can potentially be included
292/// as canonical chain. For better explanation we can group blocks into four groups:
293/// * Canonical chain blocks
294/// * Side chain blocks. Side chain are block that forks from canonical chain but not its tip.
295/// * Pending blocks that extend the canonical chain but are not yet included.
296/// * Future pending blocks that extend the pending blocks.
297pub trait BlockchainTreeViewer: Send + Sync {
298    /// Returns the header with matching hash from the tree, if it exists.
299    ///
300    /// Caution: This will not return headers from the canonical chain.
301    fn header_by_hash(&self, hash: BlockHash) -> Option<SealedHeader>;
302
303    /// Returns the block with matching hash from the tree, if it exists.
304    ///
305    /// Caution: This will not return blocks from the canonical chain or buffered blocks that are
306    /// disconnected from the canonical chain.
307    fn block_by_hash(&self, hash: BlockHash) -> Option<SealedBlock>;
308
309    /// Returns the block with matching hash from the tree, if it exists.
310    ///
311    /// Caution: This will not return blocks from the canonical chain or buffered blocks that are
312    /// disconnected from the canonical chain.
313    fn block_with_senders_by_hash(&self, hash: BlockHash) -> Option<SealedBlockWithSenders>;
314
315    /// Returns the _buffered_ (disconnected) header with matching hash from the internal buffer if
316    /// it exists.
317    ///
318    /// Caution: Unlike [`Self::block_by_hash`] this will only return headers that are currently
319    /// disconnected from the canonical chain.
320    fn buffered_header_by_hash(&self, block_hash: BlockHash) -> Option<SealedHeader>;
321
322    /// Returns true if the tree contains the block with matching hash.
323    fn contains(&self, hash: BlockHash) -> bool {
324        self.block_by_hash(hash).is_some()
325    }
326
327    /// Return whether or not the block is known and in the canonical chain.
328    fn is_canonical(&self, hash: BlockHash) -> Result<bool, ProviderError>;
329
330    /// Given the hash of a block, this checks the buffered blocks for the lowest ancestor in the
331    /// buffer.
332    ///
333    /// If there is a buffered block with the given hash, this returns the block itself.
334    fn lowest_buffered_ancestor(&self, hash: BlockHash) -> Option<SealedBlockWithSenders>;
335
336    /// Return `BlockchainTree` best known canonical chain tip (`BlockHash`, `BlockNumber`)
337    fn canonical_tip(&self) -> BlockNumHash;
338
339    /// Return block number and hash that extends the canonical chain tip by one.
340    ///
341    /// If there is no such block, this returns `None`.
342    fn pending_block_num_hash(&self) -> Option<BlockNumHash>;
343
344    /// Returns the pending block if there is one.
345    fn pending_block(&self) -> Option<SealedBlock> {
346        self.block_by_hash(self.pending_block_num_hash()?.hash)
347    }
348
349    /// Returns the pending block if there is one.
350    fn pending_block_with_senders(&self) -> Option<SealedBlockWithSenders> {
351        self.block_with_senders_by_hash(self.pending_block_num_hash()?.hash)
352    }
353
354    /// Returns the pending block and its receipts in one call.
355    ///
356    /// This exists to prevent a potential data race if the pending block changes in between
357    /// [`Self::pending_block`] and [`Self::pending_receipts`] calls.
358    fn pending_block_and_receipts(&self) -> Option<(SealedBlock, Vec<Receipt>)>;
359
360    /// Returns the pending receipts if there is one.
361    fn pending_receipts(&self) -> Option<Vec<Receipt>> {
362        self.receipts_by_block_hash(self.pending_block_num_hash()?.hash)
363    }
364
365    /// Returns the pending receipts if there is one.
366    fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<Receipt>>;
367
368    /// Returns the pending block if there is one.
369    fn pending_header(&self) -> Option<SealedHeader> {
370        self.header_by_hash(self.pending_block_num_hash()?.hash)
371    }
372}