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