Skip to main content

ab_client_database/
lib.rs

1//! Client database.
2//!
3//! ## High-level architecture overview
4//!
5//! The database operates on [`ClientDatabaseStorageBackend`], which is backed by [`AlignedPage`]s
6//! that can be read or written. Pages contain `StorageItem`s, one storage item can occupy one or
7//! more pages, but pages always belong to a single storage item. Pages are the smallest unit and
8//! align nicely with the hardware architecture of modern SSDs. Each page starts with a prefix that
9//! describes the contents of the page. `StorageItem` always starts at the multiple of the
10//! `u128`/16 bytes, allowing for direct memory mapping onto target data structures.
11//!
12//! [`AlignedPage`]: crate::storage_backend::AlignedPage
13//!
14//! Individual pages are grouped into page groups (configurable via [`ClientDatabaseOptions`]). Page
15//! groups can be permanent and ephemeral. Permanent page groups store information that is never
16//! going to be deleted, like segment headers. Ephemeral page groups store the majority of the
17//! information about blocks, blockchain state and other things that are being created all the time.
18//! Once information in an ephemeral page group is too old and no longer needed, it can be
19//! repurposed for a new permanent or ephemeral page group. There are different kinds of page groups
20//! defined in `PageGroupKind`, and each variant has independent sequence numbers.
21//!
22//! Page groups are append-only, there is only one active permanent and one ephemeral page group.
23//! They are appended with more pages containing storage items until there is no space to add a
24//! complete storage item, after which the next page group is started.
25//!
26//! Ephemeral page groups can be freed only when they contain 100% outdated storage items.
27//! Individual pages can't be freed.
28//!
29//! Each storage item has a sequence number and checksums that help to define the global ordering
30//! and check whether a storage item was written fully. Upon restart, the page group containing the
31//! latest storage items is found, and the latest fully written storage item is identified to
32//! reconstruct the database state.
33//!
34//! Each page group starts with a `StorageItemPageGroupHeader` storage item for easier
35//! identification.
36//!
37//! The database is typically contained in a single file (though in principle could be contained in
38//! multiple if necessary). Before the database can be used, it needs to be formatted with a
39//! specific size (it is possible to increase the size afterward) before it can be used. It is
40//! expected (but depends on the storage backend) that the whole file size is pre-allocated on disk
41//! and no writes will fail due to lack of disk space (which could be the case with a sparse file).
42
43#![feature(
44    const_block_items,
45    const_convert,
46    const_trait_impl,
47    default_field_values,
48    iter_collect_into,
49    maybe_uninit_as_bytes,
50    maybe_uninit_fill
51)]
52
53mod page_group;
54pub mod storage_backend;
55mod storage_backend_adapter;
56#[cfg(test)]
57mod test_utils;
58#[cfg(test)]
59mod tests;
60
61use crate::page_group::temporary::StorageItemTemporary;
62use crate::page_group::temporary::block::StorageItemTemporaryBlock;
63use crate::page_group::temporary::segment_headers::StorageItemTemporarySegmentHeaders;
64use crate::page_group::temporary::super_segment_headers::StorageItemTemporarySuperSegmentHeaders;
65use crate::storage_backend::ClientDatabaseStorageBackend;
66use crate::storage_backend_adapter::{
67    StorageBackendAdapter, StorageItemHandlerArg, StorageItemHandlers, WriteLocation,
68};
69use ab_client_api::{
70    BeaconChainInfo, BeaconChainInfoWrite, BlockDetails, ChainInfo, ChainInfoWrite,
71    ContractSlotState, PersistBlockError, PersistSegmentHeadersError,
72    PersistSuperSegmentHeadersError, ReadBlockError, ShardSegmentRoot, ShardSegmentRootsError,
73};
74use ab_core_primitives::block::body::BeaconChainBody;
75use ab_core_primitives::block::body::owned::{GenericOwnedBlockBody, OwnedBeaconChainBody};
76use ab_core_primitives::block::header::GenericBlockHeader;
77use ab_core_primitives::block::header::owned::GenericOwnedBlockHeader;
78use ab_core_primitives::block::owned::{GenericOwnedBlock, OwnedBeaconChainBlock};
79use ab_core_primitives::block::{BlockNumber, BlockRoot, GenericBlock};
80use ab_core_primitives::segments::{
81    LocalSegmentIndex, SegmentHeader, SegmentIndex, SuperSegmentHeader, SuperSegmentIndex,
82};
83use ab_core_primitives::shard::RealShardKind;
84use ab_core_primitives::trees::BlockMerkleMountainRange;
85use ab_io_type::trivial_type::TrivialType;
86use async_lock::{
87    RwLock as AsyncRwLock, RwLockUpgradableReadGuard, RwLockWriteGuard as AsyncRwLockWriteGuard,
88};
89use rand::rngs::SysError;
90use replace_with::replace_with_or_abort;
91use smallvec::{SmallVec, smallvec};
92use std::any::Any;
93use std::collections::{HashMap, VecDeque};
94use std::hash::{BuildHasherDefault, Hasher};
95use std::num::{NonZeroU32, NonZeroUsize};
96use std::ops::Deref;
97use std::{fmt, io};
98use tracing::error;
99use triomphe::Arc;
100
101/// Unique identifier for a database
102#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
103#[repr(C)]
104pub struct DatabaseId([u8; 32]);
105
106impl Deref for DatabaseId {
107    type Target = [u8; 32];
108
109    #[inline(always)]
110    fn deref(&self) -> &Self::Target {
111        &self.0
112    }
113}
114
115impl AsRef<[u8]> for DatabaseId {
116    #[inline(always)]
117    fn as_ref(&self) -> &[u8] {
118        &self.0
119    }
120}
121
122impl DatabaseId {
123    #[inline(always)]
124    pub const fn new(bytes: [u8; 32]) -> Self {
125        Self(bytes)
126    }
127}
128
129#[derive(Default)]
130struct BlockRootHasher(u64);
131
132impl Hasher for BlockRootHasher {
133    #[inline(always)]
134    fn finish(&self) -> u64 {
135        self.0
136    }
137
138    #[inline(always)]
139    fn write(&mut self, bytes: &[u8]) {
140        let Some(state) = bytes.as_chunks().0.first().copied().map(u64::from_le_bytes) else {
141            return;
142        };
143
144        self.0 = state;
145    }
146}
147
148#[derive(Debug)]
149pub struct GenesisBlockBuilderResult<Block> {
150    /// Genesis block
151    pub block: Block,
152    /// System contracts state in the genesis block
153    pub system_contract_states: Arc<[ContractSlotState]>,
154}
155
156/// Options for [`ClientDatabase`].
157///
158/// Lifecycle of a block, by block offset from the best block with block number `n`, with
159/// `block_confirmation_depth = 5` and `soft_confirmation_depth = 2` as an example:
160///
161/// ```text
162/// block   block   fork       written   block details
163/// offset  number  blocks     to disk   in memory
164///         n + 1                                    next best block, being imported
165///      0  n       accepted   no        yes         best block
166///      1  n - 1   accepted   no        yes
167///      2  n - 2   accepted   yes       yes         soft-confirmed <- soft_confirmation_depth
168///      3  n - 3   accepted   yes       yes         soft-confirmed
169///      4  n - 4   rejected   yes       yes         confirmed once n + 1 is imported
170///      5  n - 5   rejected   yes       no          confirmed <- block_confirmation_depth
171///    ...
172/// ```
173///
174/// When block `n` is built, the block at `n - block_confirmation_depth` is confirmed (see
175/// `ConsensusConstants::block_confirmation_depth`), and the database marks the same block as
176/// confirmed. Fork blocks are only accepted above block offset `block_confirmation_depth - 1`. The
177/// block there is only confirmed once `n + 1` is imported, but it already can't change: a fork
178/// block next to it would start a reorg of `block_confirmation_depth` blocks, more than the largest
179/// allowed reorg of `block_confirmation_depth - 1` blocks. Soft-confirmed blocks are only written
180/// to disk early, a reorg can still revert them.
181///
182/// Importing `n + 1` on any fork makes it the best block and shifts all block offsets by one. Then
183/// other blocks at block offset `block_confirmation_depth - 1` are pruned with their descendants,
184/// the block at block offset `block_confirmation_depth` is confirmed, which drops its block details
185/// (MMR and system contract states) from memory, and blocks that reached block offset
186/// `soft_confirmation_depth` are written to disk. Block details of confirmed blocks are only left
187/// on disk, [`ChainInfo::header_with_details()`] doesn't return them.
188#[derive(Debug, Copy, Clone)]
189#[expect(unevaluated_default_field_value, reason = "Intentional")]
190pub struct ClientDatabaseOptions<GBB, StorageBackend> {
191    /// Write buffer size.
192    ///
193    /// Larger buffer allows buffering more async writes for improved responsiveness but requires
194    /// more RAM. Zero buffer size means all writes must be completed before returning from the
195    /// operation that triggered it. Non-zero buffer means writes can happen in the background.
196    ///
197    /// The recommended value is 5.
198    pub write_buffer_size: usize = 5,
199    /// Block confirmation depth, the value of `ConsensusConstants::block_confirmation_depth`.
200    ///
201    /// Fork blocks are only accepted above block offset `block_confirmation_depth - 1` from the
202    /// best block, other blocks at that block offset are pruned with their descendants, and blocks
203    /// are confirmed at block offset `block_confirmation_depth` (see the block lifecycle in
204    /// [`ClientDatabaseOptions`]).
205    ///
206    /// Must be at least 2.
207    pub block_confirmation_depth: BlockNumber,
208    /// Soft confirmation depth for blocks.
209    ///
210    /// Doesn't prevent forking on the consensus level but makes it extremely unlikely.
211    ///
212    /// This parameter determines how many blocks are retained in memory before being written to
213    /// disk. Writing discarded blocks to disk is a waste of resources, so they are retained in
214    /// memory before being soft-confirmed and written to disk for longer-term storage.
215    ///
216    /// Blocks are persisted as soon as they reach this depth. More recent blocks may be lost if the
217    /// node is not shut down gracefully (crash, killed by the OS, etc.).
218    ///
219    /// A smaller number reduces memory usage while increasing the probability of unnecessary disk
220    /// writes. A larger number increases memory usage and the number of blocks that may be lost
221    /// this way, while avoiding unnecessary disk writes.
222    ///
223    /// The recommended value is 3 blocks.
224    pub soft_confirmation_depth: BlockNumber = BlockNumber::from(3),
225    /// Defines how many fork tips should be maintained in total.
226    ///
227    /// As natural forks occur, there may be more than one tip in existence, with only one of them
228    /// being considered "canonical". This parameter defines how many of these tips to maintain in a
229    /// sort of LRU style cache. Tips beyond this limit that were not extended for a long time will
230    /// be pruned automatically.
231    ///
232    /// A larger number results in higher memory usage and higher complexity of pruning algorithms.
233    ///
234    /// The recommended value is 3 blocks.
235    pub max_fork_tips: NonZeroUsize = NonZeroUsize::new(3).expect("Not zero; qed"),
236    /// Max distance between fork tip and the best block.
237    ///
238    /// When forks are deeper than this, they will be pruned, even without reaching the
239    /// `max_fork_tips` limit. This essentially means the tip was not extended for some time, and
240    /// while it is theoretically possible for the chain to continue from this tip, the probability
241    /// is so small that it is not worth storing it.
242    ///
243    /// A larger value results in higher memory usage and higher complexity of pruning algorithms.
244    ///
245    /// Fork blocks at block offset `block_confirmation_depth - 1` and deeper are pruned regardless
246    /// (see the block lifecycle in [`ClientDatabaseOptions`]), so values of
247    /// `block_confirmation_depth - 2` and larger all have the same effect as no limit.
248    ///
249    /// The recommended value is 5 blocks.
250    pub max_fork_tip_distance: BlockNumber = BlockNumber::from(5),
251    /// Genesis block builder is responsible to create genesis block and corresponding state for
252    /// bootstrapping purposes.
253    pub genesis_block_builder: GBB,
254    /// Storage backend to use for storing and retrieving storage items
255    pub storage_backend: StorageBackend,
256}
257
258/// Options for [`ClientDatabase`]
259#[derive(Debug, Copy, Clone)]
260pub struct ClientDatabaseFormatOptions {
261    /// The number of [`AlignedPage`]s in a single page group.
262    ///
263    /// [`AlignedPage`]: crate::storage_backend::AlignedPage
264    ///
265    /// Each group always has a set of storage items with monotonically increasing sequence
266    /// numbers. The database only frees page groups for reuse when all storage items there are
267    /// no longer in use.
268    ///
269    /// A smaller number means storage can be reclaimed for reuse more quickly and higher
270    /// concurrency during restart, but must not be too small that no storage item fits within a
271    /// page group anymore. A larger number allows finding the range of sequence numbers that are
272    /// already used and where potential write interruption happened on restart more efficiently,
273    /// but will use more RAM in the process.
274    ///
275    /// The recommended size is 256 MiB unless a tiny database is used for testing purposes, where
276    /// a smaller value might work too.
277    ///
278    /// A storage item must fit into a single page group together with the page group header, so
279    /// this also bounds the size of the largest block that can be stored.
280    pub page_group_size: NonZeroU32,
281    /// By default, formatting will be aborted if the database appears to be already formatted.
282    ///
283    /// Setting this option to `true` skips the check and formats the database anyway.
284    pub force: bool,
285}
286
287#[derive(Debug, thiserror::Error)]
288pub enum ClientDatabaseError {
289    /// Invalid block confirmation depth, it must be at least 2.
290    ///
291    /// Fork blocks are only accepted above block offset `block_confirmation_depth - 1`, smaller
292    /// values leave no block offset for them.
293    #[error("Invalid block confirmation depth, it must be at least 2")]
294    InvalidBlockConfirmationDepth,
295    /// Invalid soft confirmation depth, it must be smaller than block confirmation depth
296    #[error("Invalid soft confirmation depth, it must be smaller than block confirmation depth")]
297    InvalidSoftConfirmationDepth,
298    /// Storage backend has canceled read request
299    #[error("Storage backend has canceled read request")]
300    ReadRequestCancelled,
301    /// Storage backend read error
302    #[error("Storage backend read error: {error}")]
303    ReadError {
304        /// Low-level error
305        error: io::Error,
306    },
307    /// Unsupported database version
308    #[error("Unsupported database version: {database_version}")]
309    UnsupportedDatabaseVersion {
310        /// Database version
311        database_version: u8,
312    },
313    /// Page group size is too small, must be at least two pages
314    #[error("Page group size is too small ({page_group_size}), must be at least two pages")]
315    PageGroupSizeTooSmall {
316        /// Page group size in pages
317        page_group_size: u32,
318    },
319    /// Page group size is larger than the storage backend
320    #[error(
321        "Page group size is too large ({page_group_size}), storage backend only has {num_pages} \
322        pages"
323    )]
324    PageGroupSizeTooLarge {
325        /// Page group size in pages
326        page_group_size: u32,
327        /// Number of pages in the storage backend
328        num_pages: u32,
329    },
330    /// Unexpected sequence number
331    #[error(
332        "Unexpected sequence number {actual} at page offset {page_offset} (expected \
333        {expected})"
334    )]
335    UnexpectedSequenceNumber {
336        /// Sequence number in the database
337        actual: u64,
338        /// Expected sequence number
339        expected: u64,
340        /// Page offset where storage item is found
341        page_offset: u32,
342    },
343    /// Unexpected storage item
344    #[error("Unexpected storage item at offset {page_offset}: {storage_item:?}")]
345    UnexpectedStorageItem {
346        /// First storage item
347        storage_item: Box<dyn fmt::Debug + Send + Sync>,
348        /// Page offset where storage item is found
349        page_offset: u32,
350    },
351    /// Invalid block
352    #[error("Invalid block at offset {page_offset}")]
353    InvalidBlock {
354        /// Page offset where storage item is found
355        page_offset: u32,
356    },
357    /// Invalid segment headers
358    #[error("Invalid segment headers at offset {page_offset}")]
359    InvalidSegmentHeaders {
360        /// Page offset where storage item is found
361        page_offset: u32,
362    },
363    /// Failed to adjust ancestor block forks
364    #[error("Failed to adjust ancestor block forks")]
365    FailedToAdjustAncestorBlockForks,
366    /// Database is not formatted yet
367    #[error("Database is not formatted yet")]
368    Unformatted,
369    /// Non-permanent first page group
370    #[error("Non-permanent first page group")]
371    NonPermanentFirstPageGroup,
372}
373
374/// Error for [`ClientDatabase::format()`]
375#[derive(Debug, thiserror::Error)]
376pub enum ClientDatabaseFormatError {
377    /// Storage backend has canceled read request
378    #[error("Storage backend has canceled read request")]
379    ReadRequestCancelled,
380    /// Storage backend read error
381    #[error("Storage backend read error: {error}")]
382    ReadError {
383        /// Low-level error
384        error: io::Error,
385    },
386    /// Failed to generate database id
387    #[error("Failed to generate database id")]
388    FailedToGenerateDatabaseId {
389        /// Low-level error
390        #[from]
391        error: SysError,
392    },
393    /// Page group size is too small, must be at least two pages
394    #[error("Page group size is too small ({page_group_size}), must be at least two pages")]
395    PageGroupSizeTooSmall {
396        /// Page group size in pages
397        page_group_size: u32,
398    },
399    /// Page group size is larger than the storage backend
400    #[error(
401        "Page group size is too large ({page_group_size}), storage backend only has {num_pages} \
402        pages"
403    )]
404    PageGroupSizeTooLarge {
405        /// Page group size in pages
406        page_group_size: u32,
407        /// Number of pages in the storage backend
408        num_pages: u32,
409    },
410    /// Database is already formatted yet
411    #[error("Database is already formatted yet")]
412    AlreadyFormatted,
413    /// Storage backend has canceled a writing request
414    #[error("Storage backend has canceled a writing request")]
415    WriteRequestCancelled,
416    /// Storage item write error
417    #[error("Storage item write error")]
418    StorageItemWriteError {
419        /// Low-level error
420        #[from]
421        error: io::Error,
422    },
423}
424
425#[derive(Debug, Copy, Clone)]
426struct ForkTip {
427    number: BlockNumber,
428    root: BlockRoot,
429}
430
431/// How far [`ClientDatabase::adjust_ancestor_block_forks()`] walks towards older blocks
432#[derive(Debug, Copy, Clone)]
433enum AncestorBlockForksWalk {
434    /// Stop at the first ancestor that is already at fork offset `0`, relies on the invariant
435    /// documented on [`StateData::blocks`]
436    UntilCanonical,
437    /// Walk block offsets up to and including `max_block_offset`, for when the invariant doesn't
438    /// hold (in [`ClientDatabase::open()`])
439    UpToBlockOffset { max_block_offset: usize },
440}
441
442enum FullBlock<'a, Block>
443where
444    Block: GenericOwnedBlock,
445{
446    InMemory(&'a Block),
447    Persisted {
448        header: &'a Block::Header,
449        write_location: WriteLocation,
450    },
451}
452
453#[derive(Debug)]
454struct BeaconChainBlockDetails {
455    // Shard segment roots, only present for beacon chain blocks
456    shard_segment_roots: Arc<[ShardSegmentRoot]>,
457}
458
459impl BeaconChainBlockDetails {
460    fn from_body(body: &BeaconChainBody<'_>) -> Self {
461        let shard_segment_roots = body
462            .intermediate_shard_blocks()
463            .iter()
464            .flat_map(|intermediate_shard_block_info| {
465                let own_segments = intermediate_shard_block_info
466                    .own_segments
467                    .into_iter()
468                    .flat_map({
469                        let shard_index = intermediate_shard_block_info.header.prefix.shard_index;
470
471                        move |own_segments| {
472                            own_segments
473                                .iter()
474                                .map(move |(segment_index, &segment_root)| ShardSegmentRoot {
475                                    shard_index,
476                                    segment_index,
477                                    segment_root,
478                                })
479                        }
480                    });
481                let child_shard_segment_roots = intermediate_shard_block_info
482                    .leaf_shards_segments()
483                    .flat_map(move |(_position, shard_index, own_segments)| {
484                        own_segments
485                            .iter()
486                            .map(move |(segment_index, &segment_root)| ShardSegmentRoot {
487                                shard_index,
488                                segment_index,
489                                segment_root,
490                            })
491                    });
492
493                own_segments.chain(child_shard_segment_roots)
494            })
495            .collect();
496
497        Self {
498            shard_segment_roots,
499        }
500    }
501}
502
503/// Client database block contains details about the block state in the database.
504///
505/// Originally all blocks are stored in memory. Once a block is soft-confirmed (see
506/// [`ClientDatabaseOptions::soft_confirmation_depth`]), it is persisted (likely on disk). Later, at
507/// block offset `block_confirmation_depth` (see the block lifecycle in [`ClientDatabaseOptions`]),
508/// it is marked as confirmed and its [`BlockDetails`] are dropped from memory, they are only left
509/// on disk.
510#[derive(Debug)]
511enum ClientDatabaseBlock<Block>
512where
513    Block: GenericOwnedBlock,
514{
515    /// Block is stored in memory and wasn't persisted yet
516    InMemory {
517        block: Block,
518        block_details: BlockDetails,
519        /// Only present for beacon chain blocks
520        beacon_chain_block_details: Option<BeaconChainBlockDetails>,
521    },
522    /// Block was persisted (likely on disk)
523    Persisted {
524        header: Block::Header,
525        block_details: BlockDetails,
526        /// Only present for beacon chain blocks
527        beacon_chain_block_details: Option<BeaconChainBlockDetails>,
528        write_location: WriteLocation,
529    },
530    /// Block was persisted (likely on disk) and confirmed, its [`BlockDetails`] are only on disk
531    PersistedConfirmed {
532        header: Block::Header,
533        /// Only present for beacon chain blocks
534        beacon_chain_block_details: Option<BeaconChainBlockDetails>,
535        write_location: WriteLocation,
536    },
537}
538
539impl<Block> ClientDatabaseBlock<Block>
540where
541    Block: GenericOwnedBlock,
542{
543    #[inline(always)]
544    fn header(&self) -> &Block::Header {
545        #[expect(
546            clippy::rest_pattern_accessible_field,
547            reason = "Do not need other fields"
548        )]
549        match self {
550            Self::InMemory { block, .. } => block.header(),
551            Self::Persisted { header, .. } | Self::PersistedConfirmed { header, .. } => header,
552        }
553    }
554
555    #[inline(always)]
556    fn full_block(&self) -> FullBlock<'_, Block> {
557        #[expect(
558            clippy::rest_pattern_accessible_field,
559            reason = "Do not need other fields"
560        )]
561        match self {
562            Self::InMemory { block, .. } => FullBlock::InMemory(block),
563            Self::Persisted {
564                header,
565                write_location,
566                ..
567            }
568            | Self::PersistedConfirmed {
569                header,
570                write_location,
571                ..
572            } => FullBlock::Persisted {
573                header,
574                write_location: *write_location,
575            },
576        }
577    }
578
579    #[inline(always)]
580    fn block_details(&self) -> Option<&BlockDetails> {
581        #[expect(
582            clippy::rest_pattern_accessible_field,
583            reason = "Do not need other fields"
584        )]
585        match self {
586            Self::InMemory { block_details, .. } | Self::Persisted { block_details, .. } => {
587                Some(block_details)
588            }
589            Self::PersistedConfirmed { .. } => None,
590        }
591    }
592
593    #[inline(always)]
594    fn beacon_chain_block_details(&self) -> Option<&BeaconChainBlockDetails> {
595        #[expect(
596            clippy::rest_pattern_accessible_field,
597            reason = "Do not need other fields"
598        )]
599        match self {
600            Self::InMemory {
601                beacon_chain_block_details,
602                ..
603            }
604            | Self::Persisted {
605                beacon_chain_block_details,
606                ..
607            }
608            | Self::PersistedConfirmed {
609                beacon_chain_block_details,
610                ..
611            } => beacon_chain_block_details.as_ref(),
612        }
613    }
614}
615
616#[derive(Debug)]
617struct StateData<Block>
618where
619    Block: GenericOwnedBlock,
620{
621    /// Tips of forks that have no descendants.
622    ///
623    /// The current best block is at the front, the rest are in the order from most recently
624    /// updated towards the front to least recently at the back.
625    fork_tips: VecDeque<ForkTip>,
626    /// Map from block root to block number.
627    ///
628    /// Is meant to be used in conjunction with `headers` and `blocks` fields, which are indexed by
629    /// block numbers.
630    block_roots: HashMap<BlockRoot, BlockNumber, BuildHasherDefault<BlockRootHasher>>,
631    /// List of blocks with the newest at the front.
632    ///
633    /// The first element of the first entry corresponds to the best block.
634    ///
635    /// It is expected that in most block numbers there will be exactly one block, some two,
636    /// anything more than that will be very rare. The list of forks for a block number is
637    /// organized in such a way that the first entry at every block number corresponds to the
638    /// canonical version of the blockchain at any point in time.
639    ///
640    /// In other words, except while reading blocks in [`ClientDatabase::open()`], the block at
641    /// block offset `n + 1` and fork offset `0` is always the parent of the block at block offset
642    /// `n` and fork offset `0`. New best blocks establish this with
643    /// [`ClientDatabase::adjust_ancestor_block_forks()`], other blocks are only appended at fork
644    /// offset `1` or higher, and pruning never removes the block at fork offset `0`. Block offset
645    /// [`ClientDatabaseInnerOptions::fork_pruning_block_offset()`] and deeper ones have no forks.
646    ///
647    /// A position withing this data structure is called "block offset". This is an ephemeral value
648    /// and changes as new best blocks are added. Blocks at the same height are collectively called
649    /// "block forks" and the position of the block within the same block height is called
650    /// "fork offset". While fork offset `0` always corresponds to the canonical version of the
651    /// blockchain, other offsets are not guaranteed to follow any particular ordering rules.
652    blocks: VecDeque<SmallVec<[ClientDatabaseBlock<Block>; 2]>>,
653}
654
655#[derive(Debug)]
656struct SegmentHeadersCache {
657    segment_headers_cache: Vec<SegmentHeader>,
658}
659
660impl SegmentHeadersCache {
661    #[inline(always)]
662    fn last_segment_header(&self) -> Option<SegmentHeader> {
663        self.segment_headers_cache.last().copied()
664    }
665
666    #[inline(always)]
667    fn max_local_segment_index(&self) -> Option<LocalSegmentIndex> {
668        self.segment_headers_cache
669            .last()
670            .map(|segment_header| segment_header.index.as_inner())
671    }
672
673    #[inline(always)]
674    fn get_segment_header(&self, local_segment_index: LocalSegmentIndex) -> Option<SegmentHeader> {
675        self.segment_headers_cache
676            .get(u64::from(local_segment_index) as usize)
677            .copied()
678    }
679
680    /// Returns actually added segments (some might have been skipped)
681    fn add_segment_headers(
682        &mut self,
683        mut segment_headers: Vec<SegmentHeader>,
684    ) -> Result<Vec<SegmentHeader>, PersistSegmentHeadersError> {
685        self.segment_headers_cache.reserve(segment_headers.len());
686
687        let mut maybe_last_local_segment_index = self.max_local_segment_index();
688
689        if let Some(last_segment_index) = maybe_last_local_segment_index {
690            // Skip already stored segment headers
691            segment_headers
692                .retain(|segment_header| segment_header.index.as_inner() > last_segment_index);
693        }
694
695        // Check all input segment headers to see which ones are not stored yet and verifying that
696        // segment indices are monotonically increasing
697        for segment_header in segment_headers.iter().copied() {
698            let local_segment_index = segment_header.index.as_inner();
699            if let Some(last_local_segment_index) = maybe_last_local_segment_index {
700                if local_segment_index != last_local_segment_index + LocalSegmentIndex::ONE {
701                    return Err(PersistSegmentHeadersError::MustFollowLastSegmentIndex {
702                        local_segment_index,
703                        last_local_segment_index,
704                    });
705                }
706
707                self.segment_headers_cache.push(segment_header);
708                maybe_last_local_segment_index.replace(local_segment_index);
709            } else {
710                if local_segment_index != LocalSegmentIndex::ZERO {
711                    return Err(PersistSegmentHeadersError::FirstSegmentIndexZero {
712                        local_segment_index,
713                    });
714                }
715
716                self.segment_headers_cache.push(segment_header);
717                maybe_last_local_segment_index.replace(local_segment_index);
718            }
719        }
720
721        Ok(segment_headers)
722    }
723}
724
725#[derive(Debug)]
726struct SuperSegmentHeadersCache {
727    super_segment_headers_cache: Vec<SuperSegmentHeader>,
728}
729
730impl SuperSegmentHeadersCache {
731    #[inline(always)]
732    fn last_super_segment_header(&self) -> Option<SuperSegmentHeader> {
733        self.super_segment_headers_cache.last().copied()
734    }
735
736    #[inline]
737    fn previous_super_segment_header(
738        &self,
739        target_block_number: BlockNumber,
740    ) -> Option<SuperSegmentHeader> {
741        let block_number = target_block_number.checked_sub(BlockNumber::ONE)?;
742        let index = match self.super_segment_headers_cache.binary_search_by_key(
743            &block_number,
744            |super_segment_header| {
745                super_segment_header
746                    .target_beacon_chain_block_number
747                    .as_inner()
748            },
749        ) {
750            Ok(found_index) => found_index,
751            Err(insert_index) => insert_index.checked_sub(1)?,
752        };
753
754        self.super_segment_headers_cache.get(index).copied()
755    }
756
757    #[inline(always)]
758    fn get_super_segment_header(
759        &self,
760        local_segment_index: SuperSegmentIndex,
761    ) -> Option<SuperSegmentHeader> {
762        self.super_segment_headers_cache
763            .get(u64::from(local_segment_index) as usize)
764            .copied()
765    }
766
767    #[inline(always)]
768    fn get_super_segment_header_for_segment_index(
769        &self,
770        segment_index: SegmentIndex,
771    ) -> Option<SuperSegmentHeader> {
772        let index = self
773            .super_segment_headers_cache
774            .binary_search_by_key(&segment_index, |super_segment_header| {
775                super_segment_header.max_segment_index.as_inner()
776            })
777            .unwrap_or_else(|insert_index| insert_index);
778
779        let super_segment_header = self.super_segment_headers_cache.get(index).copied()?;
780
781        let max_segment_index = super_segment_header.max_segment_index.as_inner();
782        let first_segment_index = max_segment_index
783            - SegmentIndex::from(u64::from(super_segment_header.num_segments))
784            + SegmentIndex::ONE;
785
786        (first_segment_index..=max_segment_index)
787            .contains(&segment_index)
788            .then_some(super_segment_header)
789    }
790
791    /// Returns actually added super segments (some might have been skipped)
792    fn add_super_segment_headers(
793        &mut self,
794        mut super_segment_headers: Vec<SuperSegmentHeader>,
795    ) -> Result<Vec<SuperSegmentHeader>, PersistSuperSegmentHeadersError> {
796        self.super_segment_headers_cache
797            .reserve(super_segment_headers.len());
798
799        let mut maybe_last_super_segment_index = self
800            .super_segment_headers_cache
801            .last()
802            .map(|header| header.index.as_inner());
803
804        if let Some(last_super_segment_index) = maybe_last_super_segment_index {
805            // Skip already stored super segment headers
806            super_segment_headers.retain(|super_segment_header| {
807                super_segment_header.index.as_inner() > last_super_segment_index
808            });
809        }
810
811        // Check all input super segment headers to see which ones are not stored yet and verifying
812        // that super segment indices are monotonically increasing
813        for super_segment_header in super_segment_headers.iter().copied() {
814            let super_segment_index = super_segment_header.index.as_inner();
815            if let Some(last_super_segment_index) = maybe_last_super_segment_index {
816                if super_segment_index != last_super_segment_index + SuperSegmentIndex::ONE {
817                    return Err(
818                        PersistSuperSegmentHeadersError::MustFollowLastSegmentIndex {
819                            super_segment_index,
820                            last_super_segment_index,
821                        },
822                    );
823                }
824
825                self.super_segment_headers_cache.push(super_segment_header);
826                maybe_last_super_segment_index.replace(super_segment_index);
827            } else {
828                if super_segment_index != SuperSegmentIndex::ZERO {
829                    return Err(PersistSuperSegmentHeadersError::FirstSegmentIndexZero {
830                        super_segment_index,
831                    });
832                }
833
834                self.super_segment_headers_cache.push(super_segment_header);
835                maybe_last_super_segment_index.replace(super_segment_index);
836            }
837        }
838
839        Ok(super_segment_headers)
840    }
841}
842
843// TODO: Hide implementation details
844#[derive(Debug)]
845struct State<Block, StorageBackend>
846where
847    Block: GenericOwnedBlock,
848{
849    data: StateData<Block>,
850    segment_headers_cache: SegmentHeadersCache,
851    super_segment_headers_cache: SuperSegmentHeadersCache,
852    storage_backend_adapter: AsyncRwLock<StorageBackendAdapter<StorageBackend>>,
853}
854
855impl<Block, StorageBackend> State<Block, StorageBackend>
856where
857    Block: GenericOwnedBlock,
858{
859    #[inline(always)]
860    fn best_tip(&self) -> &ForkTip {
861        self.data
862            .fork_tips
863            .front()
864            .expect("The best block is always present; qed")
865    }
866
867    #[inline(always)]
868    fn best_block(&self) -> &ClientDatabaseBlock<Block> {
869        self.data
870            .blocks
871            .front()
872            .expect("The best block is always present; qed")
873            .first()
874            .expect("The best block is always present; qed")
875    }
876}
877
878#[derive(Debug)]
879struct BlockToPersist<'a, Block>
880where
881    Block: GenericOwnedBlock,
882{
883    block_offset: usize,
884    fork_offset: usize,
885    block: &'a Block,
886    block_details: &'a BlockDetails,
887}
888
889#[derive(Debug)]
890struct PersistedBlock {
891    block_offset: usize,
892    fork_offset: usize,
893    write_location: WriteLocation,
894}
895
896#[derive(Debug)]
897struct ClientDatabaseInnerOptions {
898    block_confirmation_depth: BlockNumber,
899    soft_confirmation_depth: BlockNumber,
900    max_fork_tips: NonZeroUsize,
901    max_fork_tip_distance: BlockNumber,
902}
903
904impl ClientDatabaseInnerOptions {
905    /// Create options, checking that `block_confirmation_depth` is at least 2 and
906    /// `soft_confirmation_depth` is smaller than `block_confirmation_depth`
907    fn new(
908        block_confirmation_depth: BlockNumber,
909        soft_confirmation_depth: BlockNumber,
910        max_fork_tips: NonZeroUsize,
911        max_fork_tip_distance: BlockNumber,
912    ) -> Result<Self, ClientDatabaseError> {
913        if block_confirmation_depth < BlockNumber::from(2) {
914            return Err(ClientDatabaseError::InvalidBlockConfirmationDepth);
915        }
916
917        if soft_confirmation_depth >= block_confirmation_depth {
918            return Err(ClientDatabaseError::InvalidSoftConfirmationDepth);
919        }
920
921        Ok(Self {
922            block_confirmation_depth,
923            soft_confirmation_depth,
924            max_fork_tips,
925            max_fork_tip_distance,
926        })
927    }
928
929    /// Block offset of the block that the next best block (block number `best_number + 1`)
930    /// confirms, fork blocks are only accepted above it and other blocks there are pruned.
931    ///
932    /// With `block_confirmation_depth = 5` (see the block lifecycle in [`ClientDatabaseOptions`]):
933    ///
934    /// ```text
935    /// block offset  block number  fork blocks
936    ///            0  n             accepted
937    ///            1  n - 1         accepted
938    ///            2  n - 2         accepted
939    ///            3  n - 3         accepted
940    ///            4  n - 4         rejected, other blocks pruned,
941    ///                             confirmed once n + 1 is imported
942    /// ```
943    #[inline(always)]
944    fn fork_pruning_block_offset(&self) -> usize {
945        let fork_pruning_block_offset = self
946            .block_confirmation_depth
947            .checked_sub(BlockNumber::ONE)
948            .expect("Block confirmation depth is checked to be at least 2 in `Self::new()`; qed");
949
950        u64::from(fork_pruning_block_offset) as usize
951    }
952}
953
954#[derive(Debug)]
955struct Inner<Block, StorageBackend>
956where
957    Block: GenericOwnedBlock,
958{
959    state: AsyncRwLock<State<Block, StorageBackend>>,
960    options: ClientDatabaseInnerOptions,
961}
962
963/// Client database
964#[derive(Debug)]
965pub struct ClientDatabase<Block, StorageBackend>
966where
967    Block: GenericOwnedBlock,
968{
969    inner: Arc<Inner<Block, StorageBackend>>,
970}
971
972impl<Block, StorageBackend> Clone for ClientDatabase<Block, StorageBackend>
973where
974    Block: GenericOwnedBlock,
975{
976    fn clone(&self) -> Self {
977        Self {
978            inner: self.inner.clone(),
979        }
980    }
981}
982
983#[expect(clippy::empty_drop, reason = "Not implemented yet")]
984impl<Block, StorageBackend> Drop for ClientDatabase<Block, StorageBackend>
985where
986    Block: GenericOwnedBlock,
987{
988    fn drop(&mut self) {
989        // TODO: Persist things that were not persisted yet to reduce the data loss on shutdown.
990        //  Blocks must be written in the same order as in `Self::insert_new_best_block()`,
991        //  `Self::open()` relies on it.
992    }
993}
994
995impl<Block, StorageBackend> ChainInfo<Block> for ClientDatabase<Block, StorageBackend>
996where
997    Block: GenericOwnedBlock,
998    StorageBackend: ClientDatabaseStorageBackend,
999{
1000    #[inline]
1001    fn best_root(&self) -> BlockRoot {
1002        // Blocking read lock is fine because where a write lock is only taken for a short time and
1003        // most locks are read locks
1004        self.inner.state.read_blocking().best_tip().root
1005    }
1006
1007    #[inline]
1008    fn best_header(&self) -> Block::Header {
1009        // Blocking read lock is fine because where a write lock is only taken for a short time and
1010        // most locks are read locks
1011        self.inner
1012            .state
1013            .read_blocking()
1014            .best_block()
1015            .header()
1016            .clone()
1017    }
1018
1019    #[inline]
1020    fn best_header_with_details(&self) -> (Block::Header, BlockDetails) {
1021        // Blocking read lock is fine because where a write lock is only taken for a short time and
1022        // most locks are read locks
1023        let state = self.inner.state.read_blocking();
1024        let best_block = state.best_block();
1025        (
1026            best_block.header().clone(),
1027            best_block
1028                .block_details()
1029                .expect("Always present for the best block; qed")
1030                .clone(),
1031        )
1032    }
1033
1034    // TODO: Add fast path when `descendant_block_root` is the best block
1035    #[inline]
1036    fn ancestor_header(
1037        &self,
1038        ancestor_block_number: BlockNumber,
1039        descendant_block_root: &BlockRoot,
1040    ) -> Option<Block::Header> {
1041        // Blocking read lock is fine because where a write lock is only taken for a short time and
1042        // most locks are read locks
1043        let state = self.inner.state.read_blocking();
1044        let best_number = state.best_tip().number;
1045
1046        let ancestor_block_offset =
1047            u64::from(best_number.checked_sub(ancestor_block_number)?) as usize;
1048        let ancestor_block_candidates = state.data.blocks.get(ancestor_block_offset)?;
1049
1050        let descendant_block_number = *state.data.block_roots.get(descendant_block_root)?;
1051        if ancestor_block_number > descendant_block_number {
1052            return None;
1053        }
1054        let descendant_block_offset =
1055            u64::from(best_number.checked_sub(descendant_block_number)?) as usize;
1056
1057        // Range of blocks where the first item is expected to contain a descendant
1058        let mut blocks_range_iter = state
1059            .data
1060            .blocks
1061            .iter()
1062            .enumerate()
1063            .skip(descendant_block_offset);
1064
1065        let (_offset, descendant_block_candidates) = blocks_range_iter.next()?;
1066        let descendant_header = descendant_block_candidates
1067            .iter()
1068            .find(|block| &*block.header().header().root() == descendant_block_root)?
1069            .header();
1070
1071        if ancestor_block_number == descendant_block_number {
1072            return Some(descendant_header.clone());
1073        }
1074
1075        // If there are no forks at this level, then this is the canonical chain and ancestor
1076        // block number we're looking for is the first block at the corresponding block number.
1077        // Similarly, if there is just a single ancestor candidate and descendant exists, it must be
1078        // the one we care about.
1079        if descendant_block_candidates.len() == 1 || ancestor_block_candidates.len() == 1 {
1080            return ancestor_block_candidates
1081                .iter()
1082                .next()
1083                .map(|block| block.header().clone());
1084        }
1085
1086        let mut parent_block_root = &descendant_header.header().prefix.parent_root;
1087
1088        // Iterate over the blocks following descendant until ancestor is reached
1089        for (block_offset, parent_candidates) in blocks_range_iter {
1090            let parent_header = parent_candidates
1091                .iter()
1092                .find(|header| &*header.header().header().root() == parent_block_root)?
1093                .header();
1094
1095            // When header offset matches, we found the header
1096            if block_offset == ancestor_block_offset {
1097                return Some(parent_header.clone());
1098            }
1099
1100            parent_block_root = &parent_header.header().prefix.parent_root;
1101        }
1102
1103        None
1104    }
1105
1106    #[inline]
1107    fn header(&self, block_root: &BlockRoot) -> Option<Block::Header> {
1108        // Blocking read lock is fine because where a write lock is only taken for a short time and
1109        // most locks are read locks
1110        let state = self.inner.state.read_blocking();
1111        let best_number = state.best_tip().number;
1112
1113        let block_number = *state.data.block_roots.get(block_root)?;
1114        let block_offset = u64::from(best_number.checked_sub(block_number)?) as usize;
1115        let block_candidates = state.data.blocks.get(block_offset)?;
1116
1117        block_candidates.iter().find_map(|block| {
1118            let header = block.header();
1119
1120            if &*header.header().root() == block_root {
1121                Some(header.clone())
1122            } else {
1123                None
1124            }
1125        })
1126    }
1127
1128    #[inline]
1129    fn header_with_details(&self, block_root: &BlockRoot) -> Option<(Block::Header, BlockDetails)> {
1130        // Blocking read lock is fine because where a write lock is only taken for a short time and
1131        // most locks are read locks
1132        let state = self.inner.state.read_blocking();
1133        let best_number = state.best_tip().number;
1134
1135        let block_number = *state.data.block_roots.get(block_root)?;
1136        let block_offset = u64::from(best_number.checked_sub(block_number)?) as usize;
1137        let block_candidates = state.data.blocks.get(block_offset)?;
1138
1139        block_candidates.iter().find_map(|block| {
1140            let header = block.header();
1141            let block_details = block.block_details().cloned()?;
1142
1143            if &*header.header().root() == block_root {
1144                Some((header.clone(), block_details))
1145            } else {
1146                None
1147            }
1148        })
1149    }
1150
1151    #[inline]
1152    async fn block(&self, block_root: &BlockRoot) -> Result<Block, ReadBlockError> {
1153        let state = self.inner.state.read().await;
1154        let best_number = state.best_tip().number;
1155
1156        let block_number = *state
1157            .data
1158            .block_roots
1159            .get(block_root)
1160            .ok_or(ReadBlockError::UnknownBlockRoot)?;
1161        let block_offset = u64::from(
1162            best_number
1163                .checked_sub(block_number)
1164                .expect("Known block roots always have valid block offset; qed"),
1165        ) as usize;
1166        let block_candidates = state
1167            .data
1168            .blocks
1169            .get(block_offset)
1170            .expect("Valid block offsets always have block entries; qed");
1171
1172        for block_candidate in block_candidates {
1173            let header = block_candidate.header();
1174
1175            if &*header.header().root() == block_root {
1176                return match block_candidate.full_block() {
1177                    FullBlock::InMemory(block) => Ok(block.clone()),
1178                    FullBlock::Persisted {
1179                        header,
1180                        write_location,
1181                    } => {
1182                        let storage_backend_adapter = state.storage_backend_adapter.read().await;
1183
1184                        let storage_item = storage_backend_adapter
1185                            .read_storage_item::<StorageItemTemporary>(write_location)
1186                            .await?;
1187
1188                        let storage_item_block = match storage_item {
1189                            StorageItemTemporary::Block(storage_item_block) => storage_item_block,
1190                            StorageItemTemporary::SegmentHeaders(_) => {
1191                                return Err(ReadBlockError::StorageItemReadError {
1192                                    error: io::Error::other(
1193                                        "Unexpected storage item: `SegmentHeaders`",
1194                                    ),
1195                                });
1196                            }
1197                            StorageItemTemporary::SuperSegmentHeaders(_) => {
1198                                return Err(ReadBlockError::StorageItemReadError {
1199                                    error: io::Error::other(
1200                                        "Unexpected storage item: `SuperSegmentHeaders`",
1201                                    ),
1202                                });
1203                            }
1204                        };
1205
1206                        let StorageItemTemporaryBlock {
1207                            header: _,
1208                            body,
1209                            mmr_with_block: _,
1210                            system_contract_states: _,
1211                        } = storage_item_block;
1212
1213                        Block::from_buffers(header.buffer().clone(), body)
1214                            .ok_or(ReadBlockError::FailedToDecode)
1215                    }
1216                };
1217            }
1218        }
1219
1220        unreachable!("Known block root always has block candidate associated with it; qed")
1221    }
1222
1223    #[inline]
1224    fn last_segment_header(&self) -> Option<SegmentHeader> {
1225        // Blocking read lock is fine because where a write lock is only taken for a short time and
1226        // most locks are read locks
1227        let state = self.inner.state.read_blocking();
1228        state.segment_headers_cache.last_segment_header()
1229    }
1230
1231    #[inline]
1232    fn get_segment_header(&self, segment_index: LocalSegmentIndex) -> Option<SegmentHeader> {
1233        // Blocking read lock is fine because where a write lock is only taken for a short time and
1234        // most locks are read locks
1235        let state = self.inner.state.read_blocking();
1236
1237        state
1238            .segment_headers_cache
1239            .get_segment_header(segment_index)
1240    }
1241
1242    fn segment_headers_for_block(&self, block_number: BlockNumber) -> Vec<SegmentHeader> {
1243        // Blocking read lock is fine because where a write lock is only taken for a short time and
1244        // most locks are read locks
1245        let state = self.inner.state.read_blocking();
1246
1247        let Some(last_local_segment_index) = state.segment_headers_cache.max_local_segment_index()
1248        else {
1249            // Not initialized
1250            return Vec::new();
1251        };
1252
1253        // Special case for the initial segment (for beacon chain genesis block)
1254        if Block::Block::SHARD_KIND == RealShardKind::BeaconChain
1255            && block_number == BlockNumber::ONE
1256        {
1257            // If there is a segment index present, and we store monotonically increasing segment
1258            // headers, then the first header exists
1259            return vec![
1260                state
1261                    .segment_headers_cache
1262                    .get_segment_header(LocalSegmentIndex::ZERO)
1263                    .expect("Segment headers are stored in monotonically increasing order; qed"),
1264            ];
1265        }
1266
1267        if last_local_segment_index == LocalSegmentIndex::ZERO {
1268            // Genesis segment already included in block #1
1269            return Vec::new();
1270        }
1271
1272        let mut current_local_segment_index = last_local_segment_index;
1273        loop {
1274            // If the current segment index present, and we store monotonically increasing segment
1275            // headers, then the current segment header exists as well.
1276            let current_segment_header = state
1277                .segment_headers_cache
1278                .get_segment_header(current_local_segment_index)
1279                .expect("Segment headers are stored in monotonically increasing order; qed");
1280
1281            // The block immediately after the archived segment adding the confirmation depth
1282            let target_block_number = current_segment_header.last_archived_block.number()
1283                + BlockNumber::ONE
1284                + self.inner.options.block_confirmation_depth;
1285            if target_block_number == block_number {
1286                let mut headers_for_block = vec![current_segment_header];
1287
1288                // Check block spanning multiple segments
1289                let last_archived_block_number = current_segment_header.last_archived_block.number;
1290                let mut local_segment_index = current_local_segment_index - LocalSegmentIndex::ONE;
1291
1292                while let Some(segment_header) = state
1293                    .segment_headers_cache
1294                    .get_segment_header(local_segment_index)
1295                {
1296                    if segment_header.last_archived_block.number == last_archived_block_number {
1297                        headers_for_block.insert(0, segment_header);
1298                        local_segment_index -= LocalSegmentIndex::ONE;
1299                    } else {
1300                        break;
1301                    }
1302                }
1303
1304                return headers_for_block;
1305            }
1306
1307            // iterate segments further
1308            if target_block_number > block_number {
1309                // no need to check the initial segment
1310                if current_local_segment_index > LocalSegmentIndex::ONE {
1311                    current_local_segment_index -= LocalSegmentIndex::ONE;
1312                } else {
1313                    break;
1314                }
1315            } else {
1316                // No segment headers required
1317                return Vec::new();
1318            }
1319        }
1320
1321        // No segment headers required
1322        Vec::new()
1323    }
1324}
1325
1326impl<Block, StorageBackend> ChainInfoWrite<Block> for ClientDatabase<Block, StorageBackend>
1327where
1328    Block: GenericOwnedBlock,
1329    StorageBackend: ClientDatabaseStorageBackend,
1330{
1331    async fn persist_block(
1332        &self,
1333        block: Block,
1334        block_details: BlockDetails,
1335    ) -> Result<(), PersistBlockError> {
1336        let mut state = self.inner.state.write().await;
1337        let best_number = state.best_tip().number;
1338
1339        let header = block.header().header();
1340
1341        let block_number = header.prefix.number;
1342        let block_root = *header.root();
1343
1344        // A known block would be stored again as a separate fork block, pruning of which would
1345        // also remove the original block
1346        if state.data.block_roots.contains_key(&block_root) {
1347            return Err(PersistBlockError::AlreadyKnown);
1348        }
1349
1350        if best_number == BlockNumber::ZERO && block_number != BlockNumber::ONE {
1351            // Special case when syncing on top of the fresh database
1352            Self::insert_first_block(&mut state.data, block, block_details);
1353
1354            return Ok(());
1355        }
1356
1357        if block_number == best_number + BlockNumber::ONE {
1358            return Self::insert_new_best_block(state, &self.inner, block, block_details).await;
1359        }
1360
1361        let block_offset = u64::from(
1362            best_number
1363                .checked_sub(block_number)
1364                .ok_or(PersistBlockError::MissingParent)?,
1365        ) as usize;
1366
1367        // A fork block at the fork pruning block offset or deeper could only become a part of the
1368        // best chain with a reorg of at least `block_confirmation_depth` blocks (see the block
1369        // lifecycle in `ClientDatabaseOptions`)
1370        if block_offset >= self.inner.options.fork_pruning_block_offset() {
1371            return Err(PersistBlockError::OutsideAcceptableRange);
1372        }
1373
1374        // The parent may have been pruned after block import checked it and before this write
1375        // lock was taken
1376        if !state
1377            .data
1378            .block_roots
1379            .contains_key(&header.prefix.parent_root)
1380        {
1381            return Err(PersistBlockError::MissingParent);
1382        }
1383
1384        let state = &mut *state;
1385
1386        let block_forks = state.data.blocks.get_mut(block_offset).ok_or_else(|| {
1387            error!(
1388                %block_number,
1389                %block_offset,
1390                "Failed to store block fork, header offset is missing despite being within \
1391                acceptable range"
1392            );
1393
1394            PersistBlockError::OutsideAcceptableRange
1395        })?;
1396
1397        for (index, fork_tip) in state.data.fork_tips.iter_mut().enumerate() {
1398            // Block's parent is no longer a fork tip, remove it
1399            if fork_tip.root == header.prefix.parent_root {
1400                state.data.fork_tips.remove(index);
1401                break;
1402            }
1403        }
1404
1405        // Insert at position 1, which means the most recent tip, which doesn't correspond to
1406        // the best block
1407        state.data.fork_tips.insert(
1408            1,
1409            ForkTip {
1410                number: block_number,
1411                root: block_root,
1412            },
1413        );
1414        state.data.block_roots.insert(block_root, block_number);
1415        let beacon_chain_block_details = <dyn Any>::downcast_ref::<OwnedBeaconChainBlock>(&block)
1416            .map(|block| BeaconChainBlockDetails::from_body(block.body.body()));
1417        block_forks.push(ClientDatabaseBlock::InMemory {
1418            block,
1419            block_details,
1420            beacon_chain_block_details,
1421        });
1422
1423        // Forks at or below soft confirmation depth are not persisted here, such forks are only
1424        // imported as part of a chain that is about to become the best chain and are persisted by
1425        // `Self::insert_new_best_block()` once it does
1426
1427        Self::prune_outdated_fork_tips(best_number, &mut state.data, &self.inner.options);
1428
1429        Ok(())
1430    }
1431
1432    async fn persist_segment_headers(
1433        &self,
1434        segment_headers: Vec<SegmentHeader>,
1435    ) -> Result<(), PersistSegmentHeadersError> {
1436        let mut state = self.inner.state.write().await;
1437
1438        let added_segment_headers = state
1439            .segment_headers_cache
1440            .add_segment_headers(segment_headers)?;
1441
1442        if added_segment_headers.is_empty() {
1443            return Ok(());
1444        }
1445
1446        // Convert write lock into upgradable read lock to allow reads, while preventing segment
1447        // headers modifications
1448        // TODO: This assumes both guarantees in https://github.com/smol-rs/async-lock/issues/100
1449        //  are satisfied. If not, blocking read locks in other places will cause issues.
1450        let state = AsyncRwLockWriteGuard::downgrade_to_upgradable(state);
1451
1452        let mut storage_backend_adapter = state.storage_backend_adapter.write().await;
1453
1454        storage_backend_adapter
1455            .write_storage_item(StorageItemTemporary::SegmentHeaders(
1456                StorageItemTemporarySegmentHeaders {
1457                    segment_headers: added_segment_headers,
1458                },
1459            ))
1460            .await?;
1461
1462        Ok(())
1463    }
1464}
1465
1466impl<StorageBackend> BeaconChainInfo for ClientDatabase<OwnedBeaconChainBlock, StorageBackend>
1467where
1468    StorageBackend: ClientDatabaseStorageBackend,
1469{
1470    fn shard_segment_roots(
1471        &self,
1472        block_number: BlockNumber,
1473    ) -> Result<Arc<[ShardSegmentRoot]>, ShardSegmentRootsError> {
1474        // Blocking read lock is fine because where a write lock is only taken for a short time and
1475        // most locks are read locks
1476        let state = self.inner.state.read_blocking();
1477        let best_number = state.best_tip().number;
1478
1479        let block_offset = u64::from(
1480            best_number
1481                .checked_sub(block_number)
1482                .ok_or(ShardSegmentRootsError::BlockMissing { block_number })?,
1483        ) as usize;
1484
1485        let block = state
1486            .data
1487            .blocks
1488            .get(block_offset)
1489            .ok_or(ShardSegmentRootsError::BlockMissing { block_number })?
1490            .first()
1491            .expect("There is always at least one block candidate; qed");
1492
1493        Ok(Arc::clone(
1494            &block
1495                .beacon_chain_block_details()
1496                .as_ref()
1497                .expect("Always present in the beacon chain block; qed")
1498                .shard_segment_roots,
1499        ))
1500    }
1501
1502    #[inline]
1503    fn last_super_segment_header(&self) -> Option<SuperSegmentHeader> {
1504        // Blocking read lock is fine because where a write lock is only taken for a short time and
1505        // most locks are read locks
1506        let state = self.inner.state.read_blocking();
1507        state
1508            .super_segment_headers_cache
1509            .last_super_segment_header()
1510    }
1511
1512    #[inline]
1513    fn previous_super_segment_header(
1514        &self,
1515        block_number: BlockNumber,
1516    ) -> Option<SuperSegmentHeader> {
1517        // Blocking read lock is fine because where a write lock is only taken for a short time and
1518        // most locks are read locks
1519        let state = self.inner.state.read_blocking();
1520
1521        state
1522            .super_segment_headers_cache
1523            .previous_super_segment_header(block_number)
1524    }
1525
1526    #[inline]
1527    fn get_super_segment_header(
1528        &self,
1529        super_segment_index: SuperSegmentIndex,
1530    ) -> Option<SuperSegmentHeader> {
1531        // Blocking read lock is fine because where a write lock is only taken for a short time and
1532        // most locks are read locks
1533        let state = self.inner.state.read_blocking();
1534
1535        state
1536            .super_segment_headers_cache
1537            .get_super_segment_header(super_segment_index)
1538    }
1539
1540    fn get_super_segment_header_for_segment_index(
1541        &self,
1542        segment_index: SegmentIndex,
1543    ) -> Option<SuperSegmentHeader> {
1544        // Blocking read lock is fine because where a write lock is only taken for a short time and
1545        // most locks are read locks
1546        let state = self.inner.state.read_blocking();
1547
1548        state
1549            .super_segment_headers_cache
1550            .get_super_segment_header_for_segment_index(segment_index)
1551    }
1552}
1553
1554impl<StorageBackend> BeaconChainInfoWrite for ClientDatabase<OwnedBeaconChainBlock, StorageBackend>
1555where
1556    StorageBackend: ClientDatabaseStorageBackend,
1557{
1558    async fn persist_super_segment_header(
1559        &self,
1560        super_segment_header: SuperSegmentHeader,
1561    ) -> Result<bool, PersistSuperSegmentHeadersError> {
1562        let mut state = self.inner.state.write().await;
1563
1564        let added_super_segment_headers = state
1565            .super_segment_headers_cache
1566            .add_super_segment_headers(vec![super_segment_header])?;
1567
1568        if added_super_segment_headers.is_empty() {
1569            return Ok(false);
1570        }
1571
1572        // Convert write lock into upgradable read lock to allow reads, while preventing super
1573        // segment headers modifications
1574        // TODO: This assumes both guarantees in https://github.com/smol-rs/async-lock/issues/100
1575        //  are satisfied. If not, blocking read locks in other places will cause issues.
1576        let state = AsyncRwLockWriteGuard::downgrade_to_upgradable(state);
1577
1578        let mut storage_backend_adapter = state.storage_backend_adapter.write().await;
1579
1580        storage_backend_adapter
1581            .write_storage_item(StorageItemTemporary::SuperSegmentHeaders(
1582                StorageItemTemporarySuperSegmentHeaders {
1583                    super_segment_headers: added_super_segment_headers,
1584                },
1585            ))
1586            .await?;
1587
1588        Ok(true)
1589    }
1590
1591    async fn persist_super_segment_headers(
1592        &self,
1593        super_segment_headers: Vec<SuperSegmentHeader>,
1594    ) -> Result<(), PersistSuperSegmentHeadersError> {
1595        let mut state = self.inner.state.write().await;
1596
1597        let added_super_segment_headers = state
1598            .super_segment_headers_cache
1599            .add_super_segment_headers(super_segment_headers)?;
1600
1601        if added_super_segment_headers.is_empty() {
1602            return Ok(());
1603        }
1604
1605        // Convert write lock into upgradable read lock to allow reads, while preventing super
1606        // segment headers modifications
1607        // TODO: This assumes both guarantees in https://github.com/smol-rs/async-lock/issues/100
1608        //  are satisfied. If not, blocking read locks in other places will cause issues.
1609        let state = AsyncRwLockWriteGuard::downgrade_to_upgradable(state);
1610
1611        let mut storage_backend_adapter = state.storage_backend_adapter.write().await;
1612
1613        storage_backend_adapter
1614            .write_storage_item(StorageItemTemporary::SuperSegmentHeaders(
1615                StorageItemTemporarySuperSegmentHeaders {
1616                    super_segment_headers: added_super_segment_headers,
1617                },
1618            ))
1619            .await?;
1620
1621        Ok(())
1622    }
1623}
1624
1625impl<Block, StorageBackend> ClientDatabase<Block, StorageBackend>
1626where
1627    Block: GenericOwnedBlock,
1628    StorageBackend: ClientDatabaseStorageBackend,
1629{
1630    /// Open the existing database.
1631    ///
1632    /// NOTE: The database needs to be formatted with [`Self::format()`] before it can be used.
1633    ///
1634    /// Opening replays storage items in the order they were written. Every block read at a new
1635    /// highest block number is handled like a new best block: forks are adjusted towards it, other
1636    /// blocks at block offset `block_confirmation_depth - 1` are pruned, then the block at block
1637    /// offset `block_confirmation_depth` is confirmed (see the block lifecycle in
1638    /// [`ClientDatabaseOptions`]). The best block after opening is the last block written at the
1639    /// highest stored block number. It may be behind or on a different fork than the best block
1640    /// before shutdown, the node re-syncs from there.
1641    pub async fn open<GBB>(
1642        options: ClientDatabaseOptions<GBB, StorageBackend>,
1643    ) -> Result<Self, ClientDatabaseError>
1644    where
1645        GBB: FnOnce() -> GenesisBlockBuilderResult<Block>,
1646    {
1647        let ClientDatabaseOptions {
1648            write_buffer_size,
1649            block_confirmation_depth,
1650            soft_confirmation_depth,
1651            max_fork_tips,
1652            max_fork_tip_distance,
1653            genesis_block_builder,
1654            storage_backend,
1655        } = options;
1656        let options = ClientDatabaseInnerOptions::new(
1657            block_confirmation_depth,
1658            soft_confirmation_depth,
1659            max_fork_tips,
1660            max_fork_tip_distance,
1661        )?;
1662
1663        let mut state_data = StateData {
1664            fork_tips: VecDeque::new(),
1665            block_roots: HashMap::default(),
1666            blocks: VecDeque::new(),
1667        };
1668        let mut segment_headers_cache = SegmentHeadersCache {
1669            segment_headers_cache: Vec::new(),
1670        };
1671        let mut super_segment_headers_cache = SuperSegmentHeadersCache {
1672            super_segment_headers_cache: Vec::new(),
1673        };
1674
1675        // Deeper block offsets only have a single block, so ancestor walks stop there
1676        let fork_pruning_block_offset = options.fork_pruning_block_offset();
1677
1678        let storage_item_handlers = StorageItemHandlers {
1679            permanent: |_arg| {
1680                // TODO
1681                Ok(())
1682            },
1683            temporary: |arg| {
1684                let StorageItemHandlerArg {
1685                    storage_item,
1686                    page_offset,
1687                    num_pages,
1688                } = arg;
1689                let storage_item_block = match storage_item {
1690                    StorageItemTemporary::Block(storage_item_block) => storage_item_block,
1691                    StorageItemTemporary::SegmentHeaders(segment_headers) => {
1692                        let num_segment_headers = segment_headers.segment_headers.len();
1693                        return match segment_headers_cache
1694                            .add_segment_headers(segment_headers.segment_headers)
1695                        {
1696                            Ok(_) => Ok(()),
1697                            Err(error) => {
1698                                error!(
1699                                    %page_offset,
1700                                    %num_segment_headers,
1701                                    %error,
1702                                    "Failed to add segment headers from storage item"
1703                                );
1704
1705                                Err(ClientDatabaseError::InvalidSegmentHeaders { page_offset })
1706                            }
1707                        };
1708                    }
1709                    StorageItemTemporary::SuperSegmentHeaders(super_segment_headers) => {
1710                        let num_super_segment_headers =
1711                            super_segment_headers.super_segment_headers.len();
1712                        return match super_segment_headers_cache
1713                            .add_super_segment_headers(super_segment_headers.super_segment_headers)
1714                        {
1715                            Ok(_) => Ok(()),
1716                            Err(error) => {
1717                                error!(
1718                                    %page_offset,
1719                                    %num_super_segment_headers,
1720                                    %error,
1721                                    "Failed to add segment headers from storage item"
1722                                );
1723
1724                                Err(ClientDatabaseError::InvalidSegmentHeaders { page_offset })
1725                            }
1726                        };
1727                    }
1728                };
1729
1730                // TODO: It would be nice to not allocate body here since we'll not use it here
1731                //  anyway
1732                let StorageItemTemporaryBlock {
1733                    header,
1734                    body,
1735                    mmr_with_block,
1736                    system_contract_states,
1737                } = storage_item_block;
1738
1739                let header = Block::Header::from_buffer(header).map_err(|_buffer| {
1740                    error!(%page_offset, "Failed to decode block header from bytes");
1741
1742                    ClientDatabaseError::InvalidBlock { page_offset }
1743                })?;
1744                let body = Block::Body::from_buffer(body).map_err(|_buffer| {
1745                    error!(%page_offset, "Failed to decode block body from bytes");
1746
1747                    ClientDatabaseError::InvalidBlock { page_offset }
1748                })?;
1749
1750                let block_root = *header.header().root();
1751                let block_number = header.header().prefix.number;
1752
1753                state_data.block_roots.insert(block_root, block_number);
1754
1755                let maybe_best_number = state_data
1756                    .blocks
1757                    .front()
1758                    .and_then(|block_forks| block_forks.first())
1759                    .map(|best_block| {
1760                        // Type inference is not working here for some reason
1761                        let header: &Block::Header = best_block.header();
1762
1763                        header.header().prefix.number
1764                    });
1765
1766                let block_offset = if let Some(best_number) = maybe_best_number {
1767                    if block_number <= best_number {
1768                        u64::from(best_number - block_number) as usize
1769                    } else {
1770                        // The new best block must follow the previous best block
1771                        if block_number - best_number != BlockNumber::ONE {
1772                            error!(
1773                                %page_offset,
1774                                %best_number,
1775                                %block_number,
1776                                "Invalid new best block number, it must be only one block \
1777                                higher than the best block"
1778                            );
1779
1780                            return Err(ClientDatabaseError::InvalidBlock { page_offset });
1781                        }
1782
1783                        state_data.blocks.push_front(SmallVec::new());
1784                        // Will insert a new block at the front
1785                        0
1786                    }
1787                } else {
1788                    state_data.blocks.push_front(SmallVec::new());
1789                    // Will insert a new block at the front
1790                    0
1791                };
1792
1793                let Some(block_forks) = state_data.blocks.get_mut(block_offset) else {
1794                    // Ignore the older block, other blocks at its height were already pruned
1795                    // anyway
1796                    return Ok(());
1797                };
1798
1799                // Push a new block to the end of the list, we'll fix it up later
1800                let beacon_chain_block_details =
1801                    <dyn Any>::downcast_ref::<OwnedBeaconChainBody>(&body)
1802                        .map(|body| BeaconChainBlockDetails::from_body(body.body()));
1803                block_forks.push(ClientDatabaseBlock::Persisted {
1804                    header,
1805                    block_details: BlockDetails {
1806                        mmr_with_block,
1807                        system_contract_states,
1808                    },
1809                    beacon_chain_block_details,
1810                    write_location: WriteLocation {
1811                        page_offset,
1812                        num_pages,
1813                    },
1814                });
1815
1816                // If a new block was inserted, prune forks and confirm a new canonical block to
1817                // prune extra in-memory information, like `Self::insert_new_best_block()` does
1818                // (see the block lifecycle in `ClientDatabaseOptions`)
1819                if block_offset == 0 && block_forks.len() == 1 {
1820                    // Block forks are in the order they were written in rather than in canonical
1821                    // order here. Any block at a new highest block number descends from the only
1822                    // block that was left at the fork pruning block offset when it was written, so
1823                    // the forks are adjusted towards the block just read first.
1824                    if state_data
1825                        .blocks
1826                        .get(fork_pruning_block_offset)
1827                        .is_some_and(|block_forks| block_forks.len() > 1)
1828                    {
1829                        if !Self::adjust_ancestor_block_forks(
1830                            &mut state_data.blocks,
1831                            block_root,
1832                            AncestorBlockForksWalk::UpToBlockOffset {
1833                                max_block_offset: fork_pruning_block_offset,
1834                            },
1835                        ) {
1836                            error!(
1837                                %page_offset,
1838                                %block_number,
1839                                "Failed to find ancestors of the block read from the database"
1840                            );
1841
1842                            return Err(ClientDatabaseError::FailedToAdjustAncestorBlockForks);
1843                        }
1844
1845                        Self::prune_non_canonical_blocks(
1846                            fork_pruning_block_offset,
1847                            &mut state_data,
1848                        );
1849                    }
1850
1851                    Self::confirm_canonical_block(block_number, &mut state_data, &options);
1852                }
1853
1854                Ok(())
1855            },
1856        };
1857
1858        let storage_backend_adapter =
1859            StorageBackendAdapter::open(write_buffer_size, storage_item_handlers, storage_backend)
1860                .await?;
1861
1862        // NOTE: Confirmation is not stored, a deep reorg right after opening may revert blocks that
1863        // were confirmed before shutdown, consumers like the archiver then fail loudly, as intended
1864        if let Some(best_block) = state_data.blocks.front().and_then(|block_forks| {
1865            // The best block is the last block written at the highest block number, block forks
1866            // are in the order they were written in
1867            block_forks.last()
1868        }) {
1869            // Type inference is not working here for some reason
1870            let header: &Block::Header = best_block.header();
1871            let header = header.header();
1872            let block_number = header.prefix.number;
1873            let block_root = *header.root();
1874
1875            // Block forks are in the order they were written in, so all block offsets that may
1876            // have forks are adjusted
1877            if !Self::adjust_ancestor_block_forks(
1878                &mut state_data.blocks,
1879                block_root,
1880                AncestorBlockForksWalk::UpToBlockOffset {
1881                    max_block_offset: fork_pruning_block_offset,
1882                },
1883            ) {
1884                return Err(ClientDatabaseError::FailedToAdjustAncestorBlockForks);
1885            }
1886
1887            // Store the best block as the first and only fork tip
1888            state_data.fork_tips.push_front(ForkTip {
1889                number: block_number,
1890                root: block_root,
1891            });
1892        } else {
1893            let GenesisBlockBuilderResult {
1894                block,
1895                system_contract_states,
1896            } = genesis_block_builder();
1897
1898            // If the database is empty, initialize everything with the genesis block
1899            let header = block.header().header();
1900            let block_number = header.prefix.number;
1901            let block_root = *header.root();
1902
1903            state_data.fork_tips.push_front(ForkTip {
1904                number: block_number,
1905                root: block_root,
1906            });
1907            state_data.block_roots.insert(block_root, block_number);
1908            let beacon_chain_block_details =
1909                <dyn Any>::downcast_ref::<OwnedBeaconChainBlock>(&block)
1910                    .map(|block| BeaconChainBlockDetails::from_body(block.body.body()));
1911            state_data
1912                .blocks
1913                .push_front(smallvec![ClientDatabaseBlock::InMemory {
1914                    block,
1915                    block_details: BlockDetails {
1916                        system_contract_states,
1917                        mmr_with_block: Arc::new({
1918                            let mut mmr = BlockMerkleMountainRange::new();
1919                            mmr.add_leaf(&block_root);
1920                            mmr
1921                        })
1922                    },
1923                    beacon_chain_block_details,
1924                }]);
1925        }
1926
1927        let state = State {
1928            data: state_data,
1929            segment_headers_cache,
1930            super_segment_headers_cache,
1931            storage_backend_adapter: AsyncRwLock::new(storage_backend_adapter),
1932        };
1933
1934        let inner = Inner {
1935            state: AsyncRwLock::new(state),
1936            options,
1937        };
1938
1939        Ok(Self {
1940            inner: Arc::new(inner),
1941        })
1942    }
1943
1944    /// Format a new database
1945    pub async fn format(
1946        storage_backend: &StorageBackend,
1947        options: ClientDatabaseFormatOptions,
1948    ) -> Result<(), ClientDatabaseFormatError> {
1949        StorageBackendAdapter::format(storage_backend, options).await
1950    }
1951
1952    fn insert_first_block(state: &mut StateData<Block>, block: Block, block_details: BlockDetails) {
1953        // If the database is empty, initialize everything with the genesis block
1954        let header = block.header().header();
1955        let block_number = header.prefix.number;
1956        let block_root = *header.root();
1957
1958        state.fork_tips.clear();
1959        state.fork_tips.push_front(ForkTip {
1960            number: block_number,
1961            root: block_root,
1962        });
1963        state.block_roots.clear();
1964        state.block_roots.insert(block_root, block_number);
1965        state.blocks.clear();
1966        let beacon_chain_block_details = <dyn Any>::downcast_ref::<OwnedBeaconChainBlock>(&block)
1967            .map(|block| BeaconChainBlockDetails::from_body(block.body.body()));
1968        state
1969            .blocks
1970            .push_front(smallvec![ClientDatabaseBlock::InMemory {
1971                block,
1972                block_details,
1973                beacon_chain_block_details,
1974            }]);
1975    }
1976
1977    async fn insert_new_best_block(
1978        mut state: AsyncRwLockWriteGuard<'_, State<Block, StorageBackend>>,
1979        inner: &Inner<Block, StorageBackend>,
1980        block: Block,
1981        block_details: BlockDetails,
1982    ) -> Result<(), PersistBlockError> {
1983        let header = block.header().header();
1984        let block_number = header.prefix.number;
1985        let block_root = *header.root();
1986        let parent_root = header.prefix.parent_root;
1987
1988        // Adjust the relative order of forks to ensure the first index always corresponds to
1989        // ancestors of the new best block
1990        if !Self::adjust_ancestor_block_forks(
1991            &mut state.data.blocks,
1992            parent_root,
1993            AncestorBlockForksWalk::UntilCanonical,
1994        ) {
1995            return Err(PersistBlockError::MissingParent);
1996        }
1997
1998        // Store new block in the state
1999        {
2000            for (index, fork_tip) in state.data.fork_tips.iter_mut().enumerate() {
2001                // Block's parent is no longer a fork tip, remove it
2002                if fork_tip.root == parent_root {
2003                    state.data.fork_tips.remove(index);
2004                    break;
2005                }
2006            }
2007
2008            state.data.fork_tips.push_front(ForkTip {
2009                number: block_number,
2010                root: block_root,
2011            });
2012            state.data.block_roots.insert(block_root, block_number);
2013            let beacon_chain_block_details =
2014                <dyn Any>::downcast_ref::<OwnedBeaconChainBlock>(&block)
2015                    .map(|block| BeaconChainBlockDetails::from_body(block.body.body()));
2016            state
2017                .data
2018                .blocks
2019                .push_front(smallvec![ClientDatabaseBlock::InMemory {
2020                    block,
2021                    block_details: block_details.clone(),
2022                    beacon_chain_block_details,
2023                }]);
2024        }
2025
2026        let options = &inner.options;
2027
2028        // The new best block shifted all block offsets by one, other blocks that reached the fork
2029        // pruning block offset could only become a part of the best chain with a reorg of
2030        // `block_confirmation_depth` blocks now (see the block lifecycle in
2031        // `ClientDatabaseOptions`)
2032        Self::prune_non_canonical_blocks(options.fork_pruning_block_offset(), &mut state.data);
2033        Self::confirm_canonical_block(block_number, &mut state.data, options);
2034        Self::prune_outdated_fork_tips(block_number, &mut state.data, options);
2035
2036        // Convert write lock into upgradable read lock to allow reads, while preventing concurrent
2037        // block modifications
2038        // TODO: This assumes both guarantees in https://github.com/smol-rs/async-lock/issues/100
2039        //  are satisfied. If not, blocking read locks in other places will cause issues.
2040        let state = AsyncRwLockWriteGuard::downgrade_to_upgradable(state);
2041
2042        let mut blocks_to_persist = Vec::new();
2043        for block_offset in u64::from(options.soft_confirmation_depth) as usize.. {
2044            let Some(fork_blocks) = state.data.blocks.get(block_offset) else {
2045                break;
2046            };
2047
2048            let len_before = blocks_to_persist.len();
2049            fork_blocks
2050                .iter()
2051                .enumerate()
2052                .filter_map(|(fork_offset, client_database_block)| {
2053                    #[expect(
2054                        clippy::rest_pattern_accessible_field,
2055                        reason = "Do not need other fields"
2056                    )]
2057                    match client_database_block {
2058                        ClientDatabaseBlock::InMemory {
2059                            block,
2060                            block_details,
2061                            beacon_chain_block_details: _,
2062                        } => Some(BlockToPersist {
2063                            block_offset,
2064                            fork_offset,
2065                            block,
2066                            block_details,
2067                        }),
2068                        ClientDatabaseBlock::Persisted { .. }
2069                        | ClientDatabaseBlock::PersistedConfirmed { .. } => {
2070                            // Already persisted
2071                            None
2072                        }
2073                    }
2074                })
2075                .collect_into(&mut blocks_to_persist);
2076
2077            if blocks_to_persist.len() == len_before {
2078                break;
2079            }
2080        }
2081
2082        // Persist blocks from older to newer: the deepest block offset first and within each block
2083        // offset the canonical block at fork offset `0` last. The scan above stops at the first
2084        // block offset without blocks to persist, so a block is never written before its parent.
2085        // `Self::open()` relies on this order.
2086        let mut persisted_blocks = Vec::with_capacity(blocks_to_persist.len());
2087        {
2088            let mut storage_backend_adapter = state.storage_backend_adapter.write().await;
2089
2090            for block_to_persist in blocks_to_persist.into_iter().rev() {
2091                let BlockToPersist {
2092                    block_offset,
2093                    fork_offset,
2094                    block,
2095                    block_details,
2096                } = block_to_persist;
2097
2098                let write_location = storage_backend_adapter
2099                    .write_storage_item(StorageItemTemporary::Block(StorageItemTemporaryBlock {
2100                        header: block.header().buffer().clone(),
2101                        body: block.body().buffer().clone(),
2102                        mmr_with_block: Arc::clone(&block_details.mmr_with_block),
2103                        system_contract_states: Arc::clone(&block_details.system_contract_states),
2104                    }))
2105                    .await?;
2106
2107                persisted_blocks.push(PersistedBlock {
2108                    block_offset,
2109                    fork_offset,
2110                    write_location,
2111                });
2112            }
2113        }
2114
2115        // Convert blocks to persisted
2116        let mut state = RwLockUpgradableReadGuard::upgrade(state).await;
2117        for persisted_block in persisted_blocks {
2118            let PersistedBlock {
2119                block_offset,
2120                fork_offset,
2121                write_location,
2122            } = persisted_block;
2123
2124            let block = state
2125                .data
2126                .blocks
2127                .get_mut(block_offset)
2128                .expect("Still holding the same lock since last check; qed")
2129                .get_mut(fork_offset)
2130                .expect("Still holding the same lock since last check; qed");
2131
2132            replace_with_or_abort(block, |block| {
2133                if let ClientDatabaseBlock::InMemory {
2134                    block,
2135                    block_details,
2136                    beacon_chain_block_details,
2137                } = block
2138                {
2139                    let (header, _body) = block.split();
2140
2141                    ClientDatabaseBlock::Persisted {
2142                        header,
2143                        block_details,
2144                        beacon_chain_block_details,
2145                        write_location,
2146                    }
2147                } else {
2148                    unreachable!("Still holding the same lock since last check; qed");
2149                }
2150            });
2151        }
2152
2153        // TODO: Prune blocks that are no longer necessary
2154        // TODO: Prune unused page groups here or elsewhere? Page groups that contain blocks whose
2155        //  block number is within `block_confirmation_depth` of the highest stored block number
2156        //  must not be freed, `Self::open()` needs them to prune forks and find the canonical
2157        //  blocks to confirm. Once older page groups are freed, block numbers below that window
2158        //  may only have forks that are not ancestors of the best block left, `Self::open()`
2159        //  currently confirms such a fork and needs to drop it instead. Add a test for this
2160        //  together with freeing, it also pins the bound of
2161        //  `AncestorBlockForksWalk::UpToBlockOffset`: an unbounded walk fails to find the
2162        //  canonical ancestor at such a block number.
2163
2164        Ok(())
2165    }
2166
2167    /// Adjust the relative order of forks to ensure the first index always corresponds to
2168    /// `parent_block_root` and its ancestors.
2169    ///
2170    /// Returns `true` on success and `false` if one of the parents was not found.
2171    #[must_use = "The return value indicates whether all parent blocks were found"]
2172    fn adjust_ancestor_block_forks(
2173        blocks: &mut VecDeque<SmallVec<[ClientDatabaseBlock<Block>; 2]>>,
2174        mut parent_block_root: BlockRoot,
2175        walk: AncestorBlockForksWalk,
2176    ) -> bool {
2177        for (block_offset, parent_blocks) in blocks.iter_mut().enumerate() {
2178            if let AncestorBlockForksWalk::UpToBlockOffset { max_block_offset } = walk
2179                && block_offset > max_block_offset
2180            {
2181                break;
2182            }
2183
2184            let Some(fork_offset_parent_block_root) =
2185                parent_blocks
2186                    .iter()
2187                    .enumerate()
2188                    .find_map(|(fork_offset, fork_block)| {
2189                        let fork_header = fork_block.header().header();
2190                        if *fork_header.root() == parent_block_root {
2191                            Some((fork_offset, fork_header.prefix.parent_root))
2192                        } else {
2193                            None
2194                        }
2195                    })
2196            else {
2197                return false;
2198            };
2199
2200            let fork_offset;
2201            (fork_offset, parent_block_root) = fork_offset_parent_block_root;
2202
2203            if fork_offset == 0 && matches!(walk, AncestorBlockForksWalk::UntilCanonical) {
2204                // All older ancestors are already at fork offset `0` too
2205                break;
2206            }
2207
2208            parent_blocks.swap(0, fork_offset);
2209        }
2210
2211        true
2212    }
2213
2214    /// Prune outdated fork tips that are too deep and have not been updated for a long time.
2215    ///
2216    /// Note that actual headers, blocks and MMRs could remain if they are currently used by
2217    /// something or were already persisted on disk. With persisted blocks specifically, RAM usage
2218    /// implications are minimal, and we wouldn't want to re-download already stored blocks in case
2219    /// they end up being necessary later.
2220    fn prune_outdated_fork_tips(
2221        best_number: BlockNumber,
2222        state: &mut StateData<Block>,
2223        options: &ClientDatabaseInnerOptions,
2224    ) {
2225        let state = &mut *state;
2226
2227        // These forks are just candidates because they will not be pruned if the reference count is
2228        // not 1, indicating they are still in use by something
2229        let mut candidate_forks_to_remove = Vec::with_capacity(options.max_fork_tips.get());
2230
2231        // Prune forks that are too far away from the best block
2232        state.fork_tips.retain(|fork_tip| {
2233            if best_number - fork_tip.number > options.max_fork_tip_distance {
2234                candidate_forks_to_remove.push(*fork_tip);
2235                false
2236            } else {
2237                true
2238            }
2239        });
2240        // Prune forks that exceed the maximum number of forks
2241        if state.fork_tips.len() > options.max_fork_tips.get() {
2242            state
2243                .fork_tips
2244                .drain(options.max_fork_tips.get()..)
2245                .collect_into(&mut candidate_forks_to_remove);
2246        }
2247
2248        // Prune all possible candidates
2249        candidate_forks_to_remove
2250            .retain(|fork_tip| !Self::prune_outdated_fork(best_number, fork_tip, state));
2251        // Return those that were not pruned back to the list of tips
2252        state.fork_tips.extend(candidate_forks_to_remove);
2253    }
2254
2255    /// Returns `true` if the tip was pruned successfully and `false` if it should be returned to
2256    /// the list of fork tips
2257    #[must_use = "The return value indicates whether the fork tip was pruned"]
2258    fn prune_outdated_fork(
2259        best_number: BlockNumber,
2260        fork_tip: &ForkTip,
2261        state: &mut StateData<Block>,
2262    ) -> bool {
2263        let block_offset = u64::from(best_number - fork_tip.number) as usize;
2264
2265        // Prune fork top and all its ancestors that are not used
2266        let mut block_root_to_prune = fork_tip.root;
2267        let mut pruned_tip = false;
2268        for block_offset in block_offset.. {
2269            let Some(fork_blocks) = state.blocks.get_mut(block_offset) else {
2270                if !pruned_tip {
2271                    error!(
2272                        %best_number,
2273                        ?fork_tip,
2274                        block_offset,
2275                        "Block offset was not present in the database, this is an implementation \
2276                        bug #1"
2277                    );
2278                }
2279                // No forks left to prune
2280                break;
2281            };
2282
2283            if fork_blocks.len() == 1 {
2284                if !pruned_tip {
2285                    error!(
2286                        %best_number,
2287                        ?fork_tip,
2288                        block_offset,
2289                        "Block offset was not present in the database, this is an implementation \
2290                        bug #2"
2291                    );
2292                }
2293
2294                // No forks left to prune
2295                break;
2296            }
2297
2298            let Some((fork_offset, block)) = fork_blocks
2299                .iter()
2300                .enumerate()
2301                // Skip ancestor of the best block, it is certainly not a fork to be pruned
2302                .skip(1)
2303                .find(|(_fork_offset, block)| {
2304                    *block.header().header().root() == block_root_to_prune
2305                })
2306            else {
2307                if !pruned_tip {
2308                    error!(
2309                        %best_number,
2310                        ?fork_tip,
2311                        block_offset,
2312                        "Block offset was not present in the database, this is an implementation \
2313                        bug #3"
2314                    );
2315                }
2316
2317                // Nothing left to prune
2318                break;
2319            };
2320
2321            let header = block.header();
2322
2323            // More than one instance means something somewhere is using this block, children are
2324            // not counted here and are checked separately below
2325            if header.ref_count() > 1 {
2326                break;
2327            }
2328
2329            // Blocks that are already persisted
2330            #[expect(
2331                clippy::rest_pattern_accessible_field,
2332                reason = "Do not care about fields"
2333            )]
2334            match block {
2335                ClientDatabaseBlock::InMemory { .. } => {
2336                    // Prune
2337                }
2338                ClientDatabaseBlock::Persisted { .. }
2339                | ClientDatabaseBlock::PersistedConfirmed { .. } => {
2340                    // Already on disk, keep it in memory for later, but prune the tip
2341                    pruned_tip = true;
2342                    break;
2343                }
2344            }
2345
2346            let parent_root = header.header().prefix.parent_root;
2347            state.block_roots.remove(&block_root_to_prune);
2348            fork_blocks.swap_remove(fork_offset);
2349
2350            pruned_tip = true;
2351
2352            // The parent is still needed by its other children
2353            if fork_blocks
2354                .iter()
2355                .any(|fork_block| fork_block.header().header().prefix.parent_root == parent_root)
2356            {
2357                break;
2358            }
2359
2360            block_root_to_prune = parent_root;
2361        }
2362
2363        pruned_tip
2364    }
2365
2366    /// Confirm the block at block offset `block_confirmation_depth`.
2367    ///
2368    /// The block at that block offset must be an ancestor of the block at `best_number` that was
2369    /// just added, other blocks at that block offset were pruned by
2370    /// [`Self::prune_non_canonical_blocks()`] at
2371    /// [`ClientDatabaseInnerOptions::fork_pruning_block_offset()`] already.
2372    fn confirm_canonical_block(
2373        best_number: BlockNumber,
2374        state_data: &mut StateData<Block>,
2375        options: &ClientDatabaseInnerOptions,
2376    ) {
2377        // Blocks keep their `BlockDetails` in memory until they are confirmed here, and
2378        // `ChainInfo::header_with_details()` only returns them from memory. Block import needs them
2379        // for the parent of every block it imports. Fork blocks are only accepted above the fork
2380        // pruning block offset, so their parents are at most at it, which is exactly what is left
2381        // in memory:
2382        //
2383        // block offset                        block details
2384        // 0 ..= block_confirmation_depth - 2  in memory, fork blocks and their parents
2385        // block_confirmation_depth - 1        in memory, parents of the deepest fork blocks
2386        // block_confirmation_depth            dropped from memory here
2387        let block_offset = u64::from(options.block_confirmation_depth) as usize;
2388
2389        let Some(fork_blocks) = state_data.blocks.get_mut(block_offset) else {
2390            // Nothing to confirm yet
2391            return;
2392        };
2393
2394        // Mark the canonical block as confirmed
2395        let Some(canonical_block) = fork_blocks.first_mut() else {
2396            error!(
2397                %best_number,
2398                block_offset,
2399                "Have not found a canonical block to confirm, this is an implementation bug"
2400            );
2401            return;
2402        };
2403
2404        replace_with_or_abort(canonical_block, |block| {
2405            #[expect(
2406                clippy::rest_pattern_accessible_field,
2407                reason = "Do not need other fields"
2408            )]
2409            match block {
2410                ClientDatabaseBlock::InMemory { .. } => {
2411                    error!(
2412                        %best_number,
2413                        block_offset,
2414                        header = ?block.header(),
2415                        "Block to be confirmed must not be in memory, this is an implementation bug"
2416                    );
2417                    block
2418                }
2419                ClientDatabaseBlock::Persisted {
2420                    header,
2421                    block_details: _,
2422                    beacon_chain_block_details,
2423                    write_location,
2424                } => ClientDatabaseBlock::PersistedConfirmed {
2425                    header,
2426                    beacon_chain_block_details,
2427                    write_location,
2428                },
2429                ClientDatabaseBlock::PersistedConfirmed { .. } => {
2430                    error!(
2431                        %best_number,
2432                        block_offset,
2433                        header = ?block.header(),
2434                        "Block to be confirmed must not be confirmed yet, this is an \
2435                        implementation bug"
2436                    );
2437                    block
2438                }
2439            }
2440        });
2441    }
2442
2443    /// Prune blocks other than the one at fork offset `0` at `block_offset` with their descendants
2444    fn prune_non_canonical_blocks(block_offset: usize, state_data: &mut StateData<Block>) {
2445        let Some(fork_blocks) = state_data.blocks.get_mut(block_offset) else {
2446            return;
2447        };
2448
2449        let mut block_roots_to_prune = fork_blocks
2450            .drain(1..)
2451            .map(|block| *block.header().header().root())
2452            .collect::<Vec<_>>();
2453        let mut current_block_offset = block_offset;
2454        while !block_roots_to_prune.is_empty() {
2455            // Prune fork tips (if any)
2456            state_data
2457                .fork_tips
2458                .retain(|fork_tip| !block_roots_to_prune.contains(&fork_tip.root));
2459
2460            // Prune removed block roots
2461            for block_root in &block_roots_to_prune {
2462                state_data.block_roots.remove(block_root);
2463            }
2464
2465            // Block offset for direct descendants
2466            if let Some(next_block_offset) = current_block_offset.checked_sub(1) {
2467                current_block_offset = next_block_offset;
2468            } else {
2469                // Reached the tip
2470                break;
2471            }
2472
2473            let fork_blocks = state_data
2474                .blocks
2475                .get_mut(current_block_offset)
2476                .expect("Lower block offset always exists; qed");
2477
2478            // Collect descendants of pruned blocks to prune them next
2479            block_roots_to_prune = fork_blocks
2480                .drain_filter(|block| {
2481                    let header = block.header().header();
2482
2483                    block_roots_to_prune.contains(&header.prefix.parent_root)
2484                })
2485                .map(|block| *block.header().header().root())
2486                .collect();
2487        }
2488    }
2489}