Skip to main content

linera_storage/
db_storage.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::{BTreeMap, HashMap},
6    fmt::Debug,
7    sync::{Arc, OnceLock},
8};
9
10use async_trait::async_trait;
11#[cfg(with_metrics)]
12use linera_base::prometheus_util::MeasureLatency as _;
13use linera_base::{
14    crypto::CryptoHash,
15    data_types::{Blob, BlockHeight, NetworkDescription, TimeDelta, Timestamp},
16    identifiers::{ApplicationId, BlobId, ChainId, EventId, IndexAndEvent, StreamId},
17    time::Duration,
18};
19use linera_cache::{Arc as CacheArc, ValueCache};
20use linera_chain::{
21    types::{CertificateValue, ConfirmedBlock, ConfirmedBlockCertificate, LiteCertificate},
22    ChainStateView,
23};
24use linera_execution::{
25    BlobState, ExecutionRuntimeConfig, SharedCommittees, UserContractCode, UserServiceCode,
26    WasmRuntime,
27};
28use linera_views::{
29    backends::dual::{DualStoreRootKeyAssignment, StoreInUse},
30    batch::Batch,
31    context::ViewContext,
32    store::{
33        KeyValueDatabase, KeyValueStore, ReadableKeyValueStore as _, WritableKeyValueStore as _,
34    },
35    views::View,
36    ViewError,
37};
38use serde::{Deserialize, Serialize};
39use tracing::{debug, instrument};
40#[cfg(with_testing)]
41use {
42    futures::channel::oneshot::{self, Receiver},
43    linera_views::{random::generate_test_namespace, store::TestKeyValueDatabase},
44    std::cmp::Reverse,
45};
46
47use crate::{ChainRuntimeContext, Clock, Storage};
48
49/// Prometheus metrics for storage operations.
50#[cfg(with_metrics)]
51pub mod metrics {
52    use std::sync::LazyLock;
53
54    use linera_base::prometheus_util::{
55        exponential_bucket_interval, exponential_bucket_latencies, linear_bucket_interval,
56        register_histogram, register_histogram_vec, register_int_counter, register_int_counter_vec,
57    };
58    use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec};
59
60    /// Label name for distinguishing cache hits vs DB reads.
61    pub(super) const SOURCE_LABEL: &str = "source";
62    /// Label value for items served from the in-memory cache.
63    pub(super) const CACHE: &str = "cache";
64    /// Label value for items served from the database.
65    pub(super) const DB: &str = "db";
66
67    /// The metric counting how often a blob is tested for existence from storage
68    pub(super) static CONTAINS_BLOB_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
69        register_int_counter_vec(
70            "contains_blob",
71            "The metric counting how often a blob is tested for existence from storage",
72            &[SOURCE_LABEL],
73        )
74    });
75
76    /// The metric counting how often multiple blobs are tested for existence from storage
77    pub(super) static CONTAINS_BLOBS_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
78        register_int_counter_vec(
79            "contains_blobs",
80            "The metric counting how often multiple blobs are tested for existence from storage",
81            &[SOURCE_LABEL],
82        )
83    });
84
85    /// The metric counting how often a blob state is tested for existence from storage
86    pub(super) static CONTAINS_BLOB_STATE_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
87        register_int_counter_vec(
88            "contains_blob_state",
89            "The metric counting how often a blob state is tested for existence from storage",
90            &[SOURCE_LABEL],
91        )
92    });
93
94    /// The metric counting how often a certificate is tested for existence from storage.
95    pub(super) static CONTAINS_CERTIFICATE_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
96        register_int_counter_vec(
97            "contains_certificate",
98            "The metric counting how often a certificate is tested for existence from storage",
99            &[SOURCE_LABEL],
100        )
101    });
102
103    /// The metric counting how often a hashed certificate value is read from storage.
104    #[doc(hidden)]
105    pub static READ_CONFIRMED_BLOCK_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
106        register_int_counter_vec(
107            "read_confirmed_block",
108            "The metric counting how often a hashed confirmed block is read from storage",
109            &[SOURCE_LABEL],
110        )
111    });
112
113    /// The metric counting how often confirmed blocks are read from storage.
114    #[doc(hidden)]
115    pub(super) static READ_CONFIRMED_BLOCKS_COUNTER: LazyLock<IntCounterVec> =
116        LazyLock::new(|| {
117            register_int_counter_vec(
118                "read_confirmed_blocks",
119                "The metric counting how often confirmed blocks are read from storage",
120                &[SOURCE_LABEL],
121            )
122        });
123
124    /// The metric counting how often a blob is read from storage.
125    #[doc(hidden)]
126    pub(super) static READ_BLOB_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
127        register_int_counter_vec(
128            "read_blob",
129            "The metric counting how often a blob is read from storage",
130            &[SOURCE_LABEL],
131        )
132    });
133
134    /// The metric counting how often a blob state is read from storage.
135    #[doc(hidden)]
136    pub(super) static READ_BLOB_STATE_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
137        register_int_counter_vec(
138            "read_blob_state",
139            "The metric counting how often a blob state is read from storage",
140            &[SOURCE_LABEL],
141        )
142    });
143
144    /// The metric counting how often a blob is written to storage.
145    #[doc(hidden)]
146    pub(super) static WRITE_BLOB_COUNTER: LazyLock<IntCounter> = LazyLock::new(|| {
147        register_int_counter(
148            "write_blob",
149            "The metric counting how often a blob is written to storage",
150        )
151    });
152
153    /// The metric counting how often a certificate is read from storage.
154    #[doc(hidden)]
155    pub static READ_CERTIFICATE_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
156        register_int_counter_vec(
157            "read_certificate",
158            "The metric counting how often a certificate is read from storage",
159            &[SOURCE_LABEL],
160        )
161    });
162
163    /// The metric counting how often certificates are read from storage.
164    #[doc(hidden)]
165    pub(super) static READ_CERTIFICATES_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
166        register_int_counter_vec(
167            "read_certificates",
168            "The metric counting how often certificate are read from storage",
169            &[SOURCE_LABEL],
170        )
171    });
172
173    /// The metric counting how often a certificate is written to storage.
174    #[doc(hidden)]
175    pub static WRITE_CERTIFICATE_COUNTER: LazyLock<IntCounter> = LazyLock::new(|| {
176        register_int_counter(
177            "write_certificate",
178            "The metric counting how often a certificate is written to storage",
179        )
180    });
181
182    /// Serialized size of the lite-certificate component (round + value hash + validator
183    /// signatures), observed when a confirmed certificate is written to storage. Bytes are
184    /// taken from the already-produced BCS output, so this adds no extra serialization work.
185    /// Sized to track the signature component, which is what grows under post-quantum
186    /// signature migration (10x for Falcon-512, 38x for ML-DSA-44).
187    pub(super) static CERTIFICATE_LITE_BYTES: LazyLock<Histogram> = LazyLock::new(|| {
188        register_histogram(
189            "certificate_lite_bytes",
190            "Serialized size of the lite-certificate (signatures + metadata) in bytes",
191            exponential_bucket_interval(128.0, 2_097_152.0),
192        )
193    });
194
195    /// Serialized size of the certificate value (block payload), observed when a confirmed
196    /// certificate is written to storage. Bytes are taken from the already-produced BCS
197    /// output. Range matches the gRPC max message size cap.
198    pub(super) static CERTIFICATE_VALUE_BYTES: LazyLock<Histogram> = LazyLock::new(|| {
199        register_histogram(
200            "certificate_value_bytes",
201            "Serialized size of the certificate value (block payload) in bytes",
202            exponential_bucket_interval(256.0, 16_777_216.0),
203        )
204    });
205
206    /// Number of validator signatures attached to each confirmed certificate. Linear buckets
207    /// because committee size is small (typically under 20) and resolution at single-signer
208    /// granularity matters more than range.
209    pub(super) static CERTIFICATE_SIGNER_COUNT: LazyLock<Histogram> = LazyLock::new(|| {
210        register_histogram(
211            "certificate_signer_count",
212            "Number of validator signatures attached to each confirmed certificate",
213            linear_bucket_interval(1.0, 1.0, 20.0),
214        )
215    });
216
217    /// The latency to load a chain state.
218    #[doc(hidden)]
219    pub(crate) static LOAD_CHAIN_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
220        register_histogram_vec(
221            "load_chain_latency",
222            "The latency to load a chain state",
223            &[],
224            exponential_bucket_latencies(1000.0),
225        )
226    });
227
228    /// The metric counting how often an event is read from storage.
229    #[doc(hidden)]
230    pub(super) static READ_EVENT_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
231        register_int_counter_vec(
232            "read_event",
233            "The metric counting how often an event is read from storage",
234            &[SOURCE_LABEL],
235        )
236    });
237
238    /// The metric counting how often an event is tested for existence from storage
239    pub(super) static CONTAINS_EVENT_COUNTER: LazyLock<IntCounterVec> = LazyLock::new(|| {
240        register_int_counter_vec(
241            "contains_event",
242            "The metric counting how often an event is tested for existence from storage",
243            &[SOURCE_LABEL],
244        )
245    });
246
247    /// The metric counting how often an event is written to storage.
248    #[doc(hidden)]
249    pub(super) static WRITE_EVENT_COUNTER: LazyLock<IntCounter> = LazyLock::new(|| {
250        register_int_counter(
251            "write_event",
252            "The metric counting how often an event is written to storage",
253        )
254    });
255
256    /// The metric counting how often a block hash is read by height from storage.
257    #[doc(hidden)]
258    pub(super) static READ_BLOCK_HASH_BY_HEIGHT_COUNTER: LazyLock<IntCounterVec> =
259        LazyLock::new(|| {
260            register_int_counter_vec(
261                "read_block_hash_by_height",
262                "The metric counting how often a block hash is read by height from storage",
263                &[SOURCE_LABEL],
264            )
265        });
266
267    /// The metric counting how often an event block height is read from storage.
268    #[doc(hidden)]
269    pub(super) static READ_EVENT_BLOCK_HEIGHT_COUNTER: LazyLock<IntCounterVec> =
270        LazyLock::new(|| {
271            register_int_counter_vec(
272                "read_event_block_height",
273                "The metric counting how often an event block height is read from storage",
274                &[SOURCE_LABEL],
275            )
276        });
277
278    /// The metric counting how often the network description is read from storage.
279    #[doc(hidden)]
280    pub(super) static READ_NETWORK_DESCRIPTION: LazyLock<IntCounterVec> = LazyLock::new(|| {
281        register_int_counter_vec(
282            "network_description",
283            "The metric counting how often the network description is read from storage",
284            &[SOURCE_LABEL],
285        )
286    });
287
288    /// The metric counting how often the network description is written to storage.
289    #[doc(hidden)]
290    pub(super) static WRITE_NETWORK_DESCRIPTION: LazyLock<IntCounter> = LazyLock::new(|| {
291        register_int_counter(
292            "write_network_description",
293            "The metric counting how often the network description is written to storage",
294        )
295    });
296}
297
298/// The key used for blobs. The Blob ID itself is contained in the root key.
299const BLOB_KEY: &[u8] = &[0];
300
301/// The key used for blob states. The Blob ID itself is contained in the root key.
302const BLOB_STATE_KEY: &[u8] = &[1];
303
304/// The key used for lite certificates. The cryptohash itself is contained in the root key.
305const LITE_CERTIFICATE_KEY: &[u8] = &[2];
306
307/// The key used for confirmed blocks. The cryptohash itself is contained in the root key.
308const BLOCK_KEY: &[u8] = &[3];
309
310/// The key used for the network description.
311const NETWORK_DESCRIPTION_KEY: &[u8] = &[4];
312
313fn get_block_keys() -> Vec<Vec<u8>> {
314    vec![LITE_CERTIFICATE_KEY.to_vec(), BLOCK_KEY.to_vec()]
315}
316
317#[derive(Default)]
318#[expect(clippy::type_complexity)]
319struct MultiPartitionBatch {
320    keys_value_bytes: BTreeMap<Vec<u8>, Vec<(Vec<u8>, Vec<u8>)>>,
321}
322
323impl MultiPartitionBatch {
324    fn new() -> Self {
325        Self::default()
326    }
327
328    fn put_key_values(&mut self, root_key: Vec<u8>, key_values: Vec<(Vec<u8>, Vec<u8>)>) {
329        let entry = self.keys_value_bytes.entry(root_key).or_default();
330        entry.extend(key_values);
331    }
332
333    fn put_key_value(&mut self, root_key: Vec<u8>, key: Vec<u8>, value: Vec<u8>) {
334        self.put_key_values(root_key, vec![(key, value)]);
335    }
336
337    fn add_blob(&mut self, blob: &Blob) {
338        #[cfg(with_metrics)]
339        metrics::WRITE_BLOB_COUNTER.inc();
340        let root_key = RootKey::BlobId(blob.id()).bytes();
341        let key = BLOB_KEY.to_vec();
342        self.put_key_value(root_key, key, blob.bytes().to_vec());
343    }
344
345    fn add_blob_state(&mut self, blob_id: BlobId, blob_state: &BlobState) -> Result<(), ViewError> {
346        let root_key = RootKey::BlobId(blob_id).bytes();
347        let key = BLOB_STATE_KEY.to_vec();
348        let value = bcs::to_bytes(blob_state)?;
349        self.put_key_value(root_key, key, value);
350        Ok(())
351    }
352
353    /// Adds a certificate to the batch.
354    ///
355    /// Writes both the certificate data (indexed by hash) and a height index
356    /// (mapping chain_id + height to hash).
357    ///
358    /// Note: If called multiple times with the same `(chain_id, height)`, the height
359    /// index will be overwritten. The caller is responsible for ensuring that
360    /// certificates at the same height have the same hash.
361    fn add_certificate(
362        &mut self,
363        certificate: &ConfirmedBlockCertificate,
364    ) -> Result<(), ViewError> {
365        #[cfg(with_metrics)]
366        {
367            metrics::WRITE_CERTIFICATE_COUNTER.inc();
368            metrics::CERTIFICATE_SIGNER_COUNT.observe(certificate.signatures().len() as f64);
369        }
370        let hash = certificate.hash();
371
372        // Write certificate data by hash
373        let root_key = RootKey::BlockHash(hash).bytes();
374        let mut key_values = Vec::new();
375        let key = LITE_CERTIFICATE_KEY.to_vec();
376        let value = bcs::to_bytes(&certificate.lite_certificate())?;
377        #[cfg(with_metrics)]
378        metrics::CERTIFICATE_LITE_BYTES.observe(value.len() as f64);
379        key_values.push((key, value));
380        let key = BLOCK_KEY.to_vec();
381        let value = bcs::to_bytes(&certificate.value())?;
382        #[cfg(with_metrics)]
383        metrics::CERTIFICATE_VALUE_BYTES.observe(value.len() as f64);
384        key_values.push((key, value));
385        self.put_key_values(root_key, key_values);
386
387        // Write height index: chain_id -> height -> hash
388        let chain_id = certificate.value().block().header.chain_id;
389        let height = certificate.value().block().header.height;
390        let index_root_key = RootKey::BlockByHeight(chain_id).bytes();
391        let height_key = to_height_key(height);
392        let index_value = bcs::to_bytes(&hash)?;
393        self.put_key_value(index_root_key, height_key, index_value);
394
395        // Write event block height index: chain_id -> (stream_id, index) -> height
396        let event_index_root_key = RootKey::EventBlockHeight(chain_id).bytes();
397        let height_value = bcs::to_bytes(&height)?;
398        for event in certificate.value().block().body.events.iter().flatten() {
399            let event_key = to_event_key(&EventId {
400                chain_id,
401                stream_id: event.stream_id.clone(),
402                index: event.index,
403            });
404            self.put_key_value(
405                event_index_root_key.clone(),
406                event_key,
407                height_value.clone(),
408            );
409        }
410
411        Ok(())
412    }
413
414    fn add_event(&mut self, event_id: &EventId, value: Vec<u8>) {
415        #[cfg(with_metrics)]
416        metrics::WRITE_EVENT_COUNTER.inc();
417        let key = to_event_key(event_id);
418        let root_key = RootKey::Event(event_id.chain_id).bytes();
419        self.put_key_value(root_key, key, value);
420    }
421
422    fn add_network_description(
423        &mut self,
424        information: &NetworkDescription,
425    ) -> Result<(), ViewError> {
426        #[cfg(with_metrics)]
427        metrics::WRITE_NETWORK_DESCRIPTION.inc();
428        let root_key = RootKey::NetworkDescription.bytes();
429        let key = NETWORK_DESCRIPTION_KEY.to_vec();
430        let value = bcs::to_bytes(information)?;
431        self.put_key_value(root_key, key, value);
432        Ok(())
433    }
434}
435
436/// Individual cache sizes for each `ValueCache` in `DbStorage`.
437#[derive(Clone, Copy, Debug)]
438pub struct StorageCacheConfig {
439    /// The maximum number of blobs to cache.
440    pub blob_cache_size: usize,
441    /// The maximum number of confirmed blocks to cache.
442    pub confirmed_block_cache_size: usize,
443    /// The maximum number of assembled certificates to cache.
444    pub certificate_cache_size: usize,
445    /// The maximum number of raw (serialized) certificates to cache.
446    pub certificate_raw_cache_size: usize,
447    /// The maximum number of events to cache.
448    pub event_cache_size: usize,
449    /// The maximum number of block hashes to cache, keyed by `(chain, height)`.
450    pub block_hash_by_height_cache_size: usize,
451    /// The maximum number of event-to-block-height index entries to cache.
452    pub event_block_height_cache_size: usize,
453    /// The interval, in seconds, between cache cleanup passes.
454    pub cache_cleanup_interval_secs: u64,
455}
456
457/// Default cache configuration for testing.
458#[cfg(with_testing)]
459pub const DEFAULT_STORAGE_CACHE_CONFIG: StorageCacheConfig = StorageCacheConfig {
460    blob_cache_size: 1000,
461    confirmed_block_cache_size: 1000,
462    certificate_cache_size: 1000,
463    certificate_raw_cache_size: 1000,
464    event_cache_size: 1000,
465    block_hash_by_height_cache_size: 1000,
466    event_block_height_cache_size: 1000,
467    cache_cleanup_interval_secs: linera_cache::DEFAULT_CLEANUP_INTERVAL_SECS,
468};
469
470/// Raw certificate bytes: (lite_certificate_bytes, confirmed_block_bytes).
471type RawCertificate = (Vec<u8>, Vec<u8>);
472
473/// Groups all `ValueCache` instances used by `DbStorage`.
474///
475/// All caches use `ValueCache` which stores values as `Arc<V>` internally,
476/// ensuring memory-efficient sharing across consumers. Adding a new cache
477/// here automatically inherits Arc-based sharing.
478#[derive(Clone)]
479pub struct StorageCaches {
480    pub(crate) blob: Arc<ValueCache<BlobId, Blob>>,
481    pub(crate) confirmed_block: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
482    pub(crate) certificate: Arc<ValueCache<CryptoHash, ConfirmedBlockCertificate>>,
483    pub(crate) certificate_raw: Arc<ValueCache<CryptoHash, RawCertificate>>,
484    pub(crate) event: Arc<ValueCache<EventId, Vec<u8>>>,
485    pub(crate) block_hash_by_height: Arc<ValueCache<(ChainId, BlockHeight), CryptoHash>>,
486    pub(crate) event_block_height: Arc<ValueCache<EventId, BlockHeight>>,
487    pub(crate) network_description: Arc<OnceLock<NetworkDescription>>,
488}
489
490impl StorageCaches {
491    /// Creates all caches with the given sizes.
492    pub fn new(sizes: StorageCacheConfig) -> Self {
493        let interval = sizes.cache_cleanup_interval_secs;
494        Self {
495            blob: Arc::new(ValueCache::new(
496                "storage_blob",
497                sizes.blob_cache_size,
498                interval,
499            )),
500            confirmed_block: Arc::new(ValueCache::new(
501                "storage_confirmed_block",
502                sizes.confirmed_block_cache_size,
503                interval,
504            )),
505            certificate: Arc::new(ValueCache::new(
506                "storage_certificate",
507                sizes.certificate_cache_size,
508                interval,
509            )),
510            certificate_raw: Arc::new(ValueCache::new(
511                "storage_certificate_raw",
512                sizes.certificate_raw_cache_size,
513                interval,
514            )),
515            event: Arc::new(ValueCache::new(
516                "storage_event",
517                sizes.event_cache_size,
518                interval,
519            )),
520            block_hash_by_height: Arc::new(ValueCache::new(
521                "storage_block_hash_by_height",
522                sizes.block_hash_by_height_cache_size,
523                interval,
524            )),
525            event_block_height: Arc::new(ValueCache::new(
526                "storage_event_block_height",
527                sizes.event_block_height_cache_size,
528                interval,
529            )),
530            network_description: Arc::new(OnceLock::new()),
531        }
532    }
533}
534
535/// Main implementation of the [`Storage`] trait.
536#[derive(Clone)]
537pub struct DbStorage<Database, Clock = WallClock> {
538    database: Arc<Database>,
539    clock: Clock,
540    thread_pool: Arc<linera_execution::ThreadPool>,
541    wasm_runtime: Option<WasmRuntime>,
542    user_contracts: Arc<papaya::HashMap<ApplicationId, UserContractCode>>,
543    user_services: Arc<papaya::HashMap<ApplicationId, UserServiceCode>>,
544    shared_committees: SharedCommittees,
545    caches: StorageCaches,
546    execution_runtime_config: ExecutionRuntimeConfig,
547}
548
549/// The partition key under which a group of related entries is stored.
550#[derive(Debug, Serialize, Deserialize)]
551pub enum RootKey {
552    /// The network description.
553    NetworkDescription,
554    /// The state of a block exporter, keyed by its ID.
555    BlockExporterState(u32),
556    /// The state of a chain.
557    ChainState(ChainId),
558    /// A certificate and confirmed block, keyed by block hash.
559    BlockHash(CryptoHash),
560    /// A blob and its state, keyed by blob ID.
561    BlobId(BlobId),
562    /// The events of a chain.
563    Event(ChainId),
564    /// The block-height-to-hash index of a chain.
565    BlockByHeight(ChainId),
566    /// The event-to-block-height index of a chain.
567    EventBlockHeight(ChainId),
568}
569
570const CHAIN_ID_TAG: u8 = 2;
571const BLOB_ID_TAG: u8 = 4;
572const EVENT_ID_TAG: u8 = 5;
573
574impl RootKey {
575    /// Returns the serialized bytes of this root key.
576    pub fn bytes(&self) -> Vec<u8> {
577        bcs::to_bytes(self).unwrap()
578    }
579}
580
581#[derive(Debug, Serialize, Deserialize)]
582struct RestrictedEventId {
583    pub stream_id: StreamId,
584    pub index: u32,
585}
586
587fn to_event_key(event_id: &EventId) -> Vec<u8> {
588    let restricted_event_id = RestrictedEventId {
589        stream_id: event_id.stream_id.clone(),
590        index: event_id.index,
591    };
592    bcs::to_bytes(&restricted_event_id).unwrap()
593}
594
595pub(crate) fn to_height_key(height: BlockHeight) -> Vec<u8> {
596    bcs::to_bytes(&height).unwrap()
597}
598
599fn is_chain_state(root_key: &[u8]) -> bool {
600    if root_key.is_empty() {
601        return false;
602    }
603    root_key[0] == CHAIN_ID_TAG
604}
605
606/// An implementation of [`DualStoreRootKeyAssignment`] that stores the
607/// chain states into the first store.
608#[derive(Clone, Copy)]
609pub struct ChainStatesFirstAssignment;
610
611impl DualStoreRootKeyAssignment for ChainStatesFirstAssignment {
612    fn assigned_store(root_key: &[u8]) -> Result<StoreInUse, bcs::Error> {
613        if root_key.is_empty() {
614            return Ok(StoreInUse::Second);
615        }
616        let store = match is_chain_state(root_key) {
617            true => StoreInUse::First,
618            false => StoreInUse::Second,
619        };
620        Ok(store)
621    }
622}
623
624/// A `Clock` implementation using the system clock.
625#[derive(Clone)]
626pub struct WallClock;
627
628#[cfg_attr(not(web), async_trait)]
629#[cfg_attr(web, async_trait(?Send))]
630impl Clock for WallClock {
631    fn current_time(&self) -> Timestamp {
632        Timestamp::now()
633    }
634
635    async fn sleep_until(&self, timestamp: Timestamp) {
636        let delta = timestamp.delta_since(Timestamp::now());
637        if delta > TimeDelta::ZERO {
638            linera_base::time::timer::sleep(delta.as_duration()).await
639        }
640    }
641
642    async fn sleep_for(&self, duration: Duration) {
643        linera_base::time::timer::sleep(duration).await
644    }
645}
646
647#[cfg(with_testing)]
648#[derive(Default)]
649struct TestClockInner {
650    time: Timestamp,
651    sleeps: BTreeMap<Reverse<Timestamp>, Vec<oneshot::Sender<()>>>,
652    /// Optional callback that decides whether to auto-advance for a given target timestamp.
653    /// Returns `true` if the clock should auto-advance to that time.
654    sleep_callback: Option<Box<dyn Fn(Timestamp) -> bool + Send + Sync>>,
655}
656
657#[cfg(with_testing)]
658impl TestClockInner {
659    fn set(&mut self, time: Timestamp) {
660        self.time = time;
661        let senders = self.sleeps.split_off(&Reverse(time));
662        for sender in senders.into_values().flatten() {
663            // Receiver may have been dropped if the sleep was cancelled.
664            sender.send(()).ok();
665        }
666    }
667
668    fn add_sleep_until(&mut self, time: Timestamp) -> Receiver<()> {
669        let (sender, receiver) = oneshot::channel();
670        let should_auto_advance = self
671            .sleep_callback
672            .as_ref()
673            .is_some_and(|callback| callback(time));
674        if should_auto_advance && time > self.time {
675            // Auto-advance mode: immediately advance the clock and complete the sleep.
676            self.set(time);
677            // Receiver may have been dropped if the sleep was cancelled.
678            sender.send(()).ok();
679        } else if self.time >= time {
680            // Receiver may have been dropped if the sleep was cancelled.
681            sender.send(()).ok();
682        } else {
683            self.sleeps.entry(Reverse(time)).or_default().push(sender);
684        }
685        receiver
686    }
687}
688
689/// A clock implementation that uses a stored number of microseconds and that can be updated
690/// explicitly. All clones share the same time, and setting it in one clone updates all the others.
691#[cfg(with_testing)]
692#[derive(Clone, Default)]
693pub struct TestClock(Arc<std::sync::Mutex<TestClockInner>>);
694
695#[cfg(with_testing)]
696#[cfg_attr(not(web), async_trait)]
697#[cfg_attr(web, async_trait(?Send))]
698impl Clock for TestClock {
699    fn current_time(&self) -> Timestamp {
700        self.lock().time
701    }
702
703    async fn sleep_until(&self, timestamp: Timestamp) {
704        let receiver = self.lock().add_sleep_until(timestamp);
705        // Sender may have been dropped if the clock was dropped; just stop waiting.
706        receiver.await.ok();
707    }
708}
709
710#[cfg(with_testing)]
711impl TestClock {
712    /// Creates a new clock with its time set to 0, i.e. the Unix epoch.
713    pub fn new() -> Self {
714        TestClock(Arc::default())
715    }
716
717    /// Sets the current time.
718    pub fn set(&self, time: Timestamp) {
719        self.lock().set(time);
720    }
721
722    /// Advances the current time by the specified delta.
723    pub fn add(&self, delta: TimeDelta) {
724        let mut guard = self.lock();
725        let time = guard.time.saturating_add(delta);
726        guard.set(time);
727    }
728
729    /// Returns the current time according to the test clock.
730    pub fn current_time(&self) -> Timestamp {
731        self.lock().time
732    }
733
734    /// Sets a callback that decides whether to auto-advance for each sleep call.
735    ///
736    /// The callback receives the target timestamp and should return `true` if the clock
737    /// should auto-advance to that time, or `false` if the sleep should block normally.
738    #[cfg(with_testing)]
739    pub fn set_sleep_callback<F>(&self, callback: F)
740    where
741        F: Fn(Timestamp) -> bool + Send + Sync + 'static,
742    {
743        self.lock().sleep_callback = Some(Box::new(callback));
744    }
745
746    fn lock(&self) -> std::sync::MutexGuard<'_, TestClockInner> {
747        self.0.lock().expect("poisoned TestClock mutex")
748    }
749}
750
751#[cfg_attr(not(web), async_trait)]
752#[cfg_attr(web, async_trait(?Send))]
753impl<Database, C> Storage for DbStorage<Database, C>
754where
755    Database: KeyValueDatabase<
756            Store: KeyValueStore + Clone + linera_base::util::traits::AutoTraits + 'static,
757            Error: Send + Sync,
758        > + Clone
759        + linera_base::util::traits::AutoTraits
760        + 'static,
761    C: Clock + Clone + Send + Sync + 'static,
762{
763    type Context = ViewContext<ChainRuntimeContext<Self>, Database::Store>;
764    type Clock = C;
765    type BlockExporterContext = ViewContext<u32, Database::Store>;
766
767    fn clock(&self) -> &C {
768        &self.clock
769    }
770
771    fn thread_pool(&self) -> &Arc<linera_execution::ThreadPool> {
772        &self.thread_pool
773    }
774
775    fn shared_committees(&self) -> &SharedCommittees {
776        &self.shared_committees
777    }
778
779    #[instrument(level = "trace", skip_all, fields(chain_id = %chain_id))]
780    async fn load_chain(
781        &self,
782        chain_id: ChainId,
783    ) -> Result<ChainStateView<Self::Context>, ViewError> {
784        #[cfg(with_metrics)]
785        let _metric = metrics::LOAD_CHAIN_LATENCY.measure_latency();
786        let runtime_context = ChainRuntimeContext {
787            storage: self.clone(),
788            thread_pool: self.thread_pool.clone(),
789            chain_id,
790            execution_runtime_config: self.execution_runtime_config,
791            user_contracts: self.user_contracts.clone(),
792            user_services: self.user_services.clone(),
793        };
794        let root_key = RootKey::ChainState(chain_id).bytes();
795        let store = self.database.open_exclusive(&root_key)?;
796        let context = ViewContext::create_root_context(store, runtime_context).await?;
797        ChainStateView::load(context).await
798    }
799
800    #[instrument(level = "trace", skip_all, fields(%blob_id))]
801    async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError> {
802        if self.caches.blob.contains(&blob_id) {
803            #[cfg(with_metrics)]
804            metrics::CONTAINS_BLOB_COUNTER
805                .with_label_values(&[metrics::CACHE])
806                .inc();
807            return Ok(true);
808        }
809        let root_key = RootKey::BlobId(blob_id).bytes();
810        let store = self.database.open_shared(&root_key)?;
811        let test = store.contains_key(BLOB_KEY).await?;
812        #[cfg(with_metrics)]
813        metrics::CONTAINS_BLOB_COUNTER
814            .with_label_values(&[metrics::DB])
815            .inc();
816        Ok(test)
817    }
818
819    #[instrument(skip_all, fields(blob_count = blob_ids.len()))]
820    async fn missing_blobs(&self, blob_ids: &[BlobId]) -> Result<Vec<BlobId>, ViewError> {
821        let mut missing_blobs = Vec::new();
822        #[cfg(with_metrics)]
823        let mut cache_hits: u64 = 0;
824        #[cfg(with_metrics)]
825        let mut db_checks: u64 = 0;
826        for blob_id in blob_ids {
827            if self.caches.blob.contains(blob_id) {
828                #[cfg(with_metrics)]
829                {
830                    cache_hits += 1;
831                }
832                continue;
833            }
834            #[cfg(with_metrics)]
835            {
836                db_checks += 1;
837            }
838            let root_key = RootKey::BlobId(*blob_id).bytes();
839            let store = self.database.open_shared(&root_key)?;
840            if !store.contains_key(BLOB_KEY).await? {
841                missing_blobs.push(*blob_id);
842            }
843        }
844        #[cfg(with_metrics)]
845        {
846            if cache_hits > 0 {
847                metrics::CONTAINS_BLOBS_COUNTER
848                    .with_label_values(&[metrics::CACHE])
849                    .inc_by(cache_hits);
850            }
851            if db_checks > 0 {
852                metrics::CONTAINS_BLOBS_COUNTER
853                    .with_label_values(&[metrics::DB])
854                    .inc_by(db_checks);
855            }
856        }
857        Ok(missing_blobs)
858    }
859
860    #[instrument(skip_all, fields(%blob_id))]
861    async fn contains_blob_state(&self, blob_id: BlobId) -> Result<bool, ViewError> {
862        let root_key = RootKey::BlobId(blob_id).bytes();
863        let store = self.database.open_shared(&root_key)?;
864        let test = store.contains_key(BLOB_STATE_KEY).await?;
865        #[cfg(with_metrics)]
866        metrics::CONTAINS_BLOB_STATE_COUNTER
867            .with_label_values(&[metrics::DB])
868            .inc();
869        Ok(test)
870    }
871
872    #[instrument(skip_all, fields(%hash))]
873    async fn read_confirmed_block(
874        &self,
875        hash: CryptoHash,
876    ) -> Result<Option<CacheArc<ConfirmedBlock>>, ViewError> {
877        if let Some(block) = self.caches.confirmed_block.get(&hash) {
878            #[cfg(with_metrics)]
879            metrics::READ_CONFIRMED_BLOCK_COUNTER
880                .with_label_values(&[metrics::CACHE])
881                .inc();
882            return Ok(Some(block));
883        }
884        let root_key = RootKey::BlockHash(hash).bytes();
885        let store = self.database.open_shared(&root_key)?;
886        let value = store.read_value::<ConfirmedBlock>(BLOCK_KEY).await?;
887        #[cfg(with_metrics)]
888        metrics::READ_CONFIRMED_BLOCK_COUNTER
889            .with_label_values(&[metrics::DB])
890            .inc();
891        match value {
892            Some(block) => Ok(Some(self.caches.confirmed_block.insert(&hash, block))),
893            None => Ok(None),
894        }
895    }
896
897    #[instrument(skip_all)]
898    async fn read_confirmed_blocks<I: IntoIterator<Item = CryptoHash> + Send>(
899        &self,
900        hashes: I,
901    ) -> Result<Vec<Option<CacheArc<ConfirmedBlock>>>, ViewError> {
902        let hashes = hashes.into_iter().collect::<Vec<_>>();
903        if hashes.is_empty() {
904            return Ok(Vec::new());
905        }
906        let mut results = vec![None; hashes.len()];
907        let mut misses = Vec::new();
908        for (i, hash) in hashes.iter().enumerate() {
909            if let Some(block) = self.caches.confirmed_block.get(hash) {
910                results[i] = Some(block);
911            } else {
912                misses.push(i);
913            }
914        }
915        if !misses.is_empty() {
916            let miss_hashes: Vec<_> = misses.iter().map(|&i| hashes[i]).collect();
917            let root_keys = Self::get_root_keys_for_certificates(&miss_hashes);
918            for (miss_idx, root_key) in misses.iter().zip(root_keys) {
919                let store = self.database.open_shared(&root_key)?;
920                if let Some(block) = store.read_value::<ConfirmedBlock>(BLOCK_KEY).await? {
921                    results[*miss_idx] = Some(
922                        self.caches
923                            .confirmed_block
924                            .insert(&hashes[*miss_idx], block),
925                    );
926                }
927            }
928        }
929        #[cfg(with_metrics)]
930        {
931            let cache_hits = (hashes.len() - misses.len()) as u64;
932            if cache_hits > 0 {
933                metrics::READ_CONFIRMED_BLOCKS_COUNTER
934                    .with_label_values(&[metrics::CACHE])
935                    .inc_by(cache_hits);
936            }
937            let db_reads = misses.len() as u64;
938            if db_reads > 0 {
939                metrics::READ_CONFIRMED_BLOCKS_COUNTER
940                    .with_label_values(&[metrics::DB])
941                    .inc_by(db_reads);
942            }
943        }
944        Ok(results)
945    }
946
947    #[instrument(skip_all, fields(%blob_id))]
948    async fn read_blob(&self, blob_id: BlobId) -> Result<Option<CacheArc<Blob>>, ViewError> {
949        if let Some(blob) = self.caches.blob.get(&blob_id) {
950            #[cfg(with_metrics)]
951            metrics::READ_BLOB_COUNTER
952                .with_label_values(&[metrics::CACHE])
953                .inc();
954            return Ok(Some(blob));
955        }
956        let root_key = RootKey::BlobId(blob_id).bytes();
957        let store = self.database.open_shared(&root_key)?;
958        let maybe_blob_bytes = store.read_value_bytes(BLOB_KEY).await?;
959        #[cfg(with_metrics)]
960        metrics::READ_BLOB_COUNTER
961            .with_label_values(&[metrics::DB])
962            .inc();
963        match maybe_blob_bytes {
964            Some(blob_bytes) => {
965                let blob = Blob::new_with_id_unchecked(blob_id, blob_bytes);
966                Ok(Some(self.caches.blob.insert(&blob_id, blob)))
967            }
968            None => Ok(None),
969        }
970    }
971
972    #[instrument(skip_all, fields(blob_ids_len = %blob_ids.len()))]
973    async fn read_blobs(
974        &self,
975        blob_ids: &[BlobId],
976    ) -> Result<Vec<Option<CacheArc<Blob>>>, ViewError> {
977        if blob_ids.is_empty() {
978            return Ok(Vec::new());
979        }
980        // Each blob lives under its own root_key (partition), so cross-partition
981        // reads can't be coalesced into a single IN query. The ScyllaDB best
982        // practice is parallel queries via the shard-aware driver, which routes
983        // each query to the right shard on the right node. RocksDB benefits too:
984        // concurrent point lookups let the scheduler overlap cache/SST reads.
985        futures::future::try_join_all(blob_ids.iter().map(|blob_id| self.read_blob(*blob_id))).await
986    }
987
988    #[instrument(skip_all, fields(%blob_id))]
989    async fn read_blob_state(&self, blob_id: BlobId) -> Result<Option<BlobState>, ViewError> {
990        let root_key = RootKey::BlobId(blob_id).bytes();
991        let store = self.database.open_shared(&root_key)?;
992        let blob_state = store.read_value::<BlobState>(BLOB_STATE_KEY).await?;
993        #[cfg(with_metrics)]
994        metrics::READ_BLOB_STATE_COUNTER
995            .with_label_values(&[metrics::DB])
996            .inc();
997        Ok(blob_state)
998    }
999
1000    #[instrument(skip_all, fields(blob_ids_len = %blob_ids.len()))]
1001    async fn read_blob_states(
1002        &self,
1003        blob_ids: &[BlobId],
1004    ) -> Result<Vec<Option<BlobState>>, ViewError> {
1005        if blob_ids.is_empty() {
1006            return Ok(Vec::new());
1007        }
1008        futures::future::try_join_all(
1009            blob_ids
1010                .iter()
1011                .map(|blob_id| self.read_blob_state(*blob_id)),
1012        )
1013        .await
1014    }
1015
1016    #[instrument(skip_all, fields(blob_id = %blob.id()))]
1017    async fn write_blob(&self, blob: &Blob) -> Result<(), ViewError> {
1018        let mut batch = MultiPartitionBatch::new();
1019        batch.add_blob(blob);
1020        self.write_batch(batch).await?;
1021        Ok(())
1022    }
1023
1024    #[instrument(skip_all, fields(blob_ids_len = %blob_ids.len()))]
1025    async fn maybe_write_blob_states(
1026        &self,
1027        blob_ids: &[BlobId],
1028        blob_state: BlobState,
1029    ) -> Result<(), ViewError> {
1030        if blob_ids.is_empty() {
1031            return Ok(());
1032        }
1033        let mut maybe_blob_states = Vec::new();
1034        for blob_id in blob_ids {
1035            let root_key = RootKey::BlobId(*blob_id).bytes();
1036            let store = self.database.open_shared(&root_key)?;
1037            let maybe_blob_state = store.read_value::<BlobState>(BLOB_STATE_KEY).await?;
1038            maybe_blob_states.push(maybe_blob_state);
1039        }
1040        let mut batch = MultiPartitionBatch::new();
1041        for (maybe_blob_state, blob_id) in maybe_blob_states.iter().zip(blob_ids) {
1042            match maybe_blob_state {
1043                None => {
1044                    batch.add_blob_state(*blob_id, &blob_state)?;
1045                }
1046                Some(state) => {
1047                    if state.epoch < blob_state.epoch {
1048                        batch.add_blob_state(*blob_id, &blob_state)?;
1049                    }
1050                }
1051            }
1052        }
1053        // We tolerate race conditions because two active chains are likely to
1054        // be both from the latest epoch, and otherwise failing to pick the
1055        // more recent blob state has limited impact.
1056        self.write_batch(batch).await?;
1057        Ok(())
1058    }
1059
1060    #[instrument(skip_all, fields(blobs_len = %blobs.len()))]
1061    async fn maybe_write_blobs(&self, blobs: &[Blob]) -> Result<Vec<bool>, ViewError> {
1062        if blobs.is_empty() {
1063            return Ok(Vec::new());
1064        }
1065        let mut batch = MultiPartitionBatch::new();
1066        let mut blob_states = Vec::new();
1067        for blob in blobs {
1068            let root_key = RootKey::BlobId(blob.id()).bytes();
1069            let store = self.database.open_shared(&root_key)?;
1070            let has_state = store.contains_key(BLOB_STATE_KEY).await?;
1071            blob_states.push(has_state);
1072            if has_state {
1073                batch.add_blob(blob);
1074            }
1075        }
1076        self.write_batch(batch).await?;
1077        Ok(blob_states)
1078    }
1079
1080    #[instrument(skip_all, fields(blobs_len = %blobs.len()))]
1081    async fn write_blobs(&self, blobs: &[Blob]) -> Result<(), ViewError> {
1082        if blobs.is_empty() {
1083            return Ok(());
1084        }
1085        let mut batch = MultiPartitionBatch::new();
1086        for blob in blobs {
1087            batch.add_blob(blob);
1088        }
1089        self.write_batch(batch).await
1090    }
1091
1092    #[instrument(skip_all, fields(blobs_len = %blobs.len()))]
1093    async fn write_blobs_and_certificate(
1094        &self,
1095        blobs: &[Blob],
1096        certificate: &ConfirmedBlockCertificate,
1097    ) -> Result<(), ViewError> {
1098        let mut batch = MultiPartitionBatch::new();
1099        for blob in blobs {
1100            batch.add_blob(blob);
1101        }
1102        batch.add_certificate(certificate)?;
1103        self.write_batch(batch).await?;
1104        // Populate immutable-data caches so subsequent reads are served from memory.
1105        let block = certificate.value().block();
1106        let chain_id = block.header.chain_id;
1107        let height = block.header.height;
1108        let hash = certificate.hash();
1109        self.caches
1110            .block_hash_by_height
1111            .insert(&(chain_id, height), hash);
1112        for event in block.body.events.iter().flatten() {
1113            let event_id = EventId {
1114                chain_id,
1115                stream_id: event.stream_id.clone(),
1116                index: event.index,
1117            };
1118            self.caches.event_block_height.insert(&event_id, height);
1119        }
1120        Ok(())
1121    }
1122
1123    fn cache_certificate(
1124        &self,
1125        certificate: ConfirmedBlockCertificate,
1126    ) -> CacheArc<ConfirmedBlockCertificate> {
1127        self.caches
1128            .certificate
1129            .insert(&certificate.hash(), certificate)
1130    }
1131
1132    fn cache_blob(&self, blob: Blob) -> CacheArc<Blob> {
1133        self.caches.blob.insert(&blob.id(), blob)
1134    }
1135
1136    fn cache_confirmed_block(&self, block: ConfirmedBlock) -> CacheArc<ConfirmedBlock> {
1137        self.caches.confirmed_block.insert(&block.hash(), block)
1138    }
1139
1140    #[instrument(skip_all, fields(%hash))]
1141    async fn contains_certificate(&self, hash: CryptoHash) -> Result<bool, ViewError> {
1142        if self.caches.certificate.contains(&hash) || self.caches.certificate_raw.contains(&hash) {
1143            #[cfg(with_metrics)]
1144            metrics::CONTAINS_CERTIFICATE_COUNTER
1145                .with_label_values(&[metrics::CACHE])
1146                .inc();
1147            return Ok(true);
1148        }
1149        let root_key = RootKey::BlockHash(hash).bytes();
1150        let store = self.database.open_shared(&root_key)?;
1151        let results = store.contains_keys(&get_block_keys()).await?;
1152        #[cfg(with_metrics)]
1153        metrics::CONTAINS_CERTIFICATE_COUNTER
1154            .with_label_values(&[metrics::DB])
1155            .inc();
1156        Ok(results[0] && results[1])
1157    }
1158
1159    #[instrument(skip_all, fields(%hash))]
1160    async fn read_certificate(
1161        &self,
1162        hash: CryptoHash,
1163    ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, ViewError> {
1164        // Assembled certificate cache (single Arc, no re-assembly)
1165        if let Some(cert) = self.caches.certificate.get(&hash) {
1166            #[cfg(with_metrics)]
1167            metrics::READ_CERTIFICATE_COUNTER
1168                .with_label_values(&[metrics::CACHE])
1169                .inc();
1170            return Ok(Some(cert));
1171        }
1172        // Raw bytes cache — deserialize + populate caches
1173        if let Some(raw) = self.caches.certificate_raw.get(&hash) {
1174            #[cfg(with_metrics)]
1175            metrics::READ_CERTIFICATE_COUNTER
1176                .with_label_values(&[metrics::CACHE])
1177                .inc();
1178            return self.deserialize_and_cache_certificate(&raw.0, &raw.1);
1179        }
1180        // DB
1181        let root_key = RootKey::BlockHash(hash).bytes();
1182        let store = self.database.open_shared(&root_key)?;
1183        let values = store.read_multi_values_bytes(&get_block_keys()).await?;
1184        #[cfg(with_metrics)]
1185        metrics::READ_CERTIFICATE_COUNTER
1186            .with_label_values(&[metrics::DB])
1187            .inc();
1188        let Some(lite_cert_bytes) = values[0].as_ref() else {
1189            return Ok(None);
1190        };
1191        let Some(confirmed_block_bytes) = values[1].as_ref() else {
1192            return Ok(None);
1193        };
1194        self.caches.certificate_raw.insert(
1195            &hash,
1196            (lite_cert_bytes.clone(), confirmed_block_bytes.clone()),
1197        );
1198        self.deserialize_and_cache_certificate(lite_cert_bytes, confirmed_block_bytes)
1199    }
1200
1201    #[instrument(skip_all)]
1202    async fn read_certificates(
1203        &self,
1204        hashes: &[CryptoHash],
1205    ) -> Result<Vec<Option<CacheArc<ConfirmedBlockCertificate>>>, ViewError> {
1206        let raw_certs = self.read_certificates_raw(hashes).await?;
1207
1208        raw_certs
1209            .into_iter()
1210            .map(|maybe_raw| {
1211                let Some(raw) = maybe_raw else {
1212                    return Ok(None);
1213                };
1214                self.deserialize_and_cache_certificate(&raw.0, &raw.1)
1215            })
1216            .collect()
1217    }
1218
1219    #[instrument(skip_all)]
1220    async fn read_certificates_raw(
1221        &self,
1222        hashes: &[CryptoHash],
1223    ) -> Result<Vec<Option<CacheArc<(Vec<u8>, Vec<u8>)>>>, ViewError> {
1224        if hashes.is_empty() {
1225            return Ok(Vec::new());
1226        }
1227        let mut results = vec![None; hashes.len()];
1228        let mut misses = Vec::new();
1229        for (i, hash) in hashes.iter().enumerate() {
1230            if let Some(raw) = self.caches.certificate_raw.get(hash) {
1231                results[i] = Some(raw);
1232            } else {
1233                misses.push(i);
1234            }
1235        }
1236        if !misses.is_empty() {
1237            let miss_hashes: Vec<_> = misses.iter().map(|&i| hashes[i]).collect();
1238            let root_keys = Self::get_root_keys_for_certificates(&miss_hashes);
1239            for (miss_idx, root_key) in misses.iter().zip(root_keys) {
1240                let store = self.database.open_shared(&root_key)?;
1241                let values = store.read_multi_values_bytes(&get_block_keys()).await?;
1242                if let (Some(lite), Some(block)) = (values[0].as_ref(), values[1].as_ref()) {
1243                    results[*miss_idx] = Some(
1244                        self.caches
1245                            .certificate_raw
1246                            .insert(&hashes[*miss_idx], (lite.clone(), block.clone())),
1247                    );
1248                }
1249            }
1250        }
1251        #[cfg(with_metrics)]
1252        {
1253            let cache_hits = (hashes.len() - misses.len()) as u64;
1254            if cache_hits > 0 {
1255                metrics::READ_CERTIFICATES_COUNTER
1256                    .with_label_values(&[metrics::CACHE])
1257                    .inc_by(cache_hits);
1258            }
1259            let db_reads = misses.len() as u64;
1260            if db_reads > 0 {
1261                metrics::READ_CERTIFICATES_COUNTER
1262                    .with_label_values(&[metrics::DB])
1263                    .inc_by(db_reads);
1264            }
1265        }
1266        Ok(results)
1267    }
1268
1269    async fn read_certificate_hashes_by_heights(
1270        &self,
1271        chain_id: ChainId,
1272        heights: &[BlockHeight],
1273    ) -> Result<Vec<Option<CryptoHash>>, ViewError> {
1274        if heights.is_empty() {
1275            return Ok(Vec::new());
1276        }
1277
1278        let mut results = vec![None; heights.len()];
1279        let mut misses = Vec::new();
1280        for (i, &height) in heights.iter().enumerate() {
1281            if let Some(hash) = self.caches.block_hash_by_height.get(&(chain_id, height)) {
1282                results[i] = Some(*hash);
1283            } else {
1284                misses.push(i);
1285            }
1286        }
1287        #[cfg(with_metrics)]
1288        {
1289            let cache_hits = (heights.len() - misses.len()) as u64;
1290            if cache_hits > 0 {
1291                metrics::READ_BLOCK_HASH_BY_HEIGHT_COUNTER
1292                    .with_label_values(&[metrics::CACHE])
1293                    .inc_by(cache_hits);
1294            }
1295        }
1296        if !misses.is_empty() {
1297            let miss_keys: Vec<Vec<u8>> =
1298                misses.iter().map(|&i| to_height_key(heights[i])).collect();
1299            let index_root_key = RootKey::BlockByHeight(chain_id).bytes();
1300            let store = self.database.open_shared(&index_root_key)?;
1301            let hash_bytes = store.read_multi_values_bytes(&miss_keys).await?;
1302            #[cfg(with_metrics)]
1303            {
1304                let db_reads = misses.len() as u64;
1305                metrics::READ_BLOCK_HASH_BY_HEIGHT_COUNTER
1306                    .with_label_values(&[metrics::DB])
1307                    .inc_by(db_reads);
1308            }
1309            for (miss_idx, opt_bytes) in misses.iter().zip(hash_bytes) {
1310                if let Some(bytes) = opt_bytes {
1311                    let hash = bcs::from_bytes::<CryptoHash>(&bytes)?;
1312                    self.caches
1313                        .block_hash_by_height
1314                        .insert(&(chain_id, heights[*miss_idx]), hash);
1315                    results[*miss_idx] = Some(hash);
1316                }
1317            }
1318        }
1319
1320        Ok(results)
1321    }
1322
1323    async fn read_event_block_heights(
1324        &self,
1325        event_ids: &[EventId],
1326    ) -> Result<Vec<Option<BlockHeight>>, ViewError> {
1327        if event_ids.is_empty() {
1328            return Ok(Vec::new());
1329        }
1330
1331        let mut results = vec![None; event_ids.len()];
1332        // Check cache first; collect misses.
1333        let mut misses: Vec<usize> = Vec::new();
1334        for (i, event_id) in event_ids.iter().enumerate() {
1335            if let Some(height) = self.caches.event_block_height.get(event_id) {
1336                results[i] = Some(*height);
1337            } else {
1338                misses.push(i);
1339            }
1340        }
1341        #[cfg(with_metrics)]
1342        {
1343            let cache_hits = (event_ids.len() - misses.len()) as u64;
1344            if cache_hits > 0 {
1345                metrics::READ_EVENT_BLOCK_HEIGHT_COUNTER
1346                    .with_label_values(&[metrics::CACHE])
1347                    .inc_by(cache_hits);
1348            }
1349        }
1350        if misses.is_empty() {
1351            return Ok(results);
1352        }
1353        // Group cache-miss event IDs by chain ID for batch lookups per partition.
1354        let mut chain_groups = BTreeMap::<_, Vec<_>>::new();
1355        for &i in &misses {
1356            let event_id = &event_ids[i];
1357            chain_groups
1358                .entry(event_id.chain_id)
1359                .or_default()
1360                .push((i, to_event_key(event_id)));
1361        }
1362        for (chain_id, entries) in chain_groups {
1363            let root_key = RootKey::EventBlockHeight(chain_id).bytes();
1364            let store = self.database.open_shared(&root_key)?;
1365            let keys = entries
1366                .iter()
1367                .map(|(_, key)| key.clone())
1368                .collect::<Vec<_>>();
1369            let values = store.read_multi_values_bytes(&keys).await?;
1370            #[cfg(with_metrics)]
1371            {
1372                let db_reads = entries.len() as u64;
1373                metrics::READ_EVENT_BLOCK_HEIGHT_COUNTER
1374                    .with_label_values(&[metrics::DB])
1375                    .inc_by(db_reads);
1376            }
1377            for ((original_index, _), value) in entries.into_iter().zip(values) {
1378                if let Some(bytes) = value {
1379                    let height = bcs::from_bytes::<BlockHeight>(&bytes)?;
1380                    self.caches
1381                        .event_block_height
1382                        .insert(&event_ids[original_index], height);
1383                    results[original_index] = Some(height);
1384                }
1385            }
1386        }
1387        Ok(results)
1388    }
1389
1390    #[instrument(skip_all)]
1391    async fn read_certificates_by_heights_raw(
1392        &self,
1393        chain_id: ChainId,
1394        heights: &[BlockHeight],
1395    ) -> Result<Vec<Option<CacheArc<(Vec<u8>, Vec<u8>)>>>, ViewError> {
1396        let hashes: Vec<Option<CryptoHash>> = self
1397            .read_certificate_hashes_by_heights(chain_id, heights)
1398            .await?;
1399
1400        // Map from hash to all indices in the heights array (handles duplicates)
1401        let mut indices: HashMap<CryptoHash, Vec<usize>> = HashMap::new();
1402        for (index, maybe_hash) in hashes.iter().enumerate() {
1403            if let Some(hash) = maybe_hash {
1404                indices.entry(*hash).or_default().push(index);
1405            }
1406        }
1407
1408        // Deduplicate hashes for the storage query
1409        let unique_hashes = indices.keys().copied().collect::<Vec<_>>();
1410
1411        let mut result = vec![None; heights.len()];
1412
1413        for (raw_cert, hash) in self
1414            .read_certificates_raw(&unique_hashes)
1415            .await?
1416            .into_iter()
1417            .zip(unique_hashes)
1418        {
1419            if let Some(idx_list) = indices.get(&hash) {
1420                for &index in idx_list {
1421                    result[index] = raw_cert.clone();
1422                }
1423            } else {
1424                // This should not happen, but log a warning if it does.
1425                tracing::error!(?hash, "certificate hash not found in indices map",);
1426            }
1427        }
1428
1429        Ok(result)
1430    }
1431
1432    #[instrument(skip_all, fields(%chain_id, heights_len = heights.len()))]
1433    async fn read_certificates_by_heights(
1434        &self,
1435        chain_id: ChainId,
1436        heights: &[BlockHeight],
1437    ) -> Result<Vec<Option<CacheArc<ConfirmedBlockCertificate>>>, ViewError> {
1438        self.read_certificates_by_heights_raw(chain_id, heights)
1439            .await?
1440            .into_iter()
1441            .map(|maybe_raw| match maybe_raw {
1442                None => Ok(None),
1443                Some(raw) => self.deserialize_and_cache_certificate(&raw.0, &raw.1),
1444            })
1445            .collect()
1446    }
1447
1448    #[instrument(skip_all, fields(event_id = ?event_id))]
1449    async fn read_event(&self, event_id: EventId) -> Result<Option<CacheArc<Vec<u8>>>, ViewError> {
1450        if let Some(event) = self.caches.event.get(&event_id) {
1451            #[cfg(with_metrics)]
1452            metrics::READ_EVENT_COUNTER
1453                .with_label_values(&[metrics::CACHE])
1454                .inc();
1455            return Ok(Some(event));
1456        }
1457        let event_key = to_event_key(&event_id);
1458        let root_key = RootKey::Event(event_id.chain_id).bytes();
1459        let store = self.database.open_shared(&root_key)?;
1460        let event = store.read_value_bytes(&event_key).await?;
1461        #[cfg(with_metrics)]
1462        metrics::READ_EVENT_COUNTER
1463            .with_label_values(&[metrics::DB])
1464            .inc();
1465        match event {
1466            Some(event_bytes) => Ok(Some(self.caches.event.insert(&event_id, event_bytes))),
1467            None => Ok(None),
1468        }
1469    }
1470
1471    #[instrument(skip_all, fields(event_id = ?event_id))]
1472    async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError> {
1473        if self.caches.event.contains(&event_id) {
1474            #[cfg(with_metrics)]
1475            metrics::CONTAINS_EVENT_COUNTER
1476                .with_label_values(&[metrics::CACHE])
1477                .inc();
1478            return Ok(true);
1479        }
1480        let event_key = to_event_key(&event_id);
1481        let root_key = RootKey::Event(event_id.chain_id).bytes();
1482        let store = self.database.open_shared(&root_key)?;
1483        let exists = store.contains_key(&event_key).await?;
1484        #[cfg(with_metrics)]
1485        metrics::CONTAINS_EVENT_COUNTER
1486            .with_label_values(&[metrics::DB])
1487            .inc();
1488        Ok(exists)
1489    }
1490
1491    #[instrument(skip_all, fields(chain_id = %chain_id, stream_id = %stream_id, start_index = %start_index))]
1492    async fn read_events_from_index(
1493        &self,
1494        chain_id: &ChainId,
1495        stream_id: &StreamId,
1496        start_index: u32,
1497    ) -> Result<Vec<IndexAndEvent>, ViewError> {
1498        let root_key = RootKey::Event(*chain_id).bytes();
1499        let store = self.database.open_shared(&root_key)?;
1500        // Pair each index with its cached value, or `None` for a cache miss to be
1501        // read from the database, so results keep the key-scan order.
1502        let mut entries = Vec::new();
1503        let mut db_keys = Vec::new();
1504        let prefix = bcs::to_bytes(stream_id).unwrap();
1505        for short_key in store.find_keys_by_prefix(&prefix).await? {
1506            let index = bcs::from_bytes::<u32>(&short_key)?;
1507            if index >= start_index {
1508                let event_id = EventId {
1509                    chain_id: *chain_id,
1510                    stream_id: stream_id.clone(),
1511                    index,
1512                };
1513                let cached = self.caches.event.get(&event_id).map(|arc| (*arc).clone());
1514                if cached.is_none() {
1515                    let mut key = prefix.clone();
1516                    key.extend(short_key);
1517                    db_keys.push(key);
1518                }
1519                entries.push((index, cached));
1520            }
1521        }
1522        let mut db_values = if db_keys.is_empty() {
1523            Vec::new()
1524        } else {
1525            store.read_multi_values_bytes(&db_keys).await?
1526        }
1527        .into_iter();
1528        let mut returned_values = Vec::with_capacity(entries.len());
1529        for (index, cached) in entries {
1530            let event = match cached {
1531                Some(event) => event,
1532                None => {
1533                    let event_bytes = db_values
1534                        .next()
1535                        .expect("one database value per cache miss")
1536                        .unwrap();
1537                    let event_id = EventId {
1538                        chain_id: *chain_id,
1539                        stream_id: stream_id.clone(),
1540                        index,
1541                    };
1542                    self.caches.event.insert(&event_id, event_bytes.clone());
1543                    event_bytes
1544                }
1545            };
1546            returned_values.push(IndexAndEvent { index, event });
1547        }
1548        Ok(returned_values)
1549    }
1550
1551    #[instrument(skip_all)]
1552    async fn write_events(
1553        &self,
1554        events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
1555    ) -> Result<(), ViewError> {
1556        let mut batch = MultiPartitionBatch::new();
1557        for (event_id, value) in events {
1558            batch.add_event(&event_id, value);
1559        }
1560        self.write_batch(batch).await
1561    }
1562
1563    #[instrument(skip_all)]
1564    async fn read_network_description(&self) -> Result<Option<NetworkDescription>, ViewError> {
1565        if let Some(desc) = self.caches.network_description.get() {
1566            #[cfg(with_metrics)]
1567            metrics::READ_NETWORK_DESCRIPTION
1568                .with_label_values(&[metrics::CACHE])
1569                .inc();
1570            return Ok(Some(desc.clone()));
1571        }
1572        let root_key = RootKey::NetworkDescription.bytes();
1573        let store = self.database.open_shared(&root_key)?;
1574        let maybe_value: Option<NetworkDescription> =
1575            store.read_value(NETWORK_DESCRIPTION_KEY).await?;
1576        #[cfg(with_metrics)]
1577        metrics::READ_NETWORK_DESCRIPTION
1578            .with_label_values(&[metrics::DB])
1579            .inc();
1580        if let Some(ref desc) = maybe_value {
1581            if self.caches.network_description.set(desc.clone()).is_err() {
1582                debug!("network description cache was already populated concurrently");
1583            }
1584        }
1585        Ok(maybe_value)
1586    }
1587
1588    #[instrument(skip_all)]
1589    async fn write_network_description(
1590        &self,
1591        information: &NetworkDescription,
1592    ) -> Result<(), ViewError> {
1593        let mut batch = MultiPartitionBatch::new();
1594        batch.add_network_description(information)?;
1595        self.write_batch(batch).await?;
1596        Ok(())
1597    }
1598
1599    fn wasm_runtime(&self) -> Option<WasmRuntime> {
1600        self.wasm_runtime
1601    }
1602
1603    #[instrument(skip_all)]
1604    async fn block_exporter_context(
1605        &self,
1606        block_exporter_id: u32,
1607    ) -> Result<Self::BlockExporterContext, ViewError> {
1608        let root_key = RootKey::BlockExporterState(block_exporter_id).bytes();
1609        let store = self.database.open_exclusive(&root_key)?;
1610        Ok(ViewContext::create_root_context(store, block_exporter_id).await?)
1611    }
1612
1613    async fn list_blob_ids(&self) -> Result<Vec<BlobId>, ViewError> {
1614        let root_keys = self.database.list_root_keys().await?;
1615        let mut blob_ids = Vec::new();
1616        for root_key in root_keys {
1617            if !root_key.is_empty() && root_key[0] == BLOB_ID_TAG {
1618                let root_key_red = &root_key[1..];
1619                let blob_id = bcs::from_bytes(root_key_red)?;
1620                blob_ids.push(blob_id);
1621            }
1622        }
1623        Ok(blob_ids)
1624    }
1625
1626    async fn list_chain_ids(&self) -> Result<Vec<ChainId>, ViewError> {
1627        let root_keys = self.database.list_root_keys().await?;
1628        let mut chain_ids = Vec::new();
1629        for root_key in root_keys {
1630            if !root_key.is_empty() && root_key[0] == CHAIN_ID_TAG {
1631                let root_key_red = &root_key[1..];
1632                let chain_id = bcs::from_bytes(root_key_red)?;
1633                chain_ids.push(chain_id);
1634            }
1635        }
1636        Ok(chain_ids)
1637    }
1638
1639    async fn list_event_ids(&self) -> Result<Vec<EventId>, ViewError> {
1640        let root_keys = self.database.list_root_keys().await?;
1641        let mut event_ids = Vec::new();
1642        for root_key in root_keys {
1643            if !root_key.is_empty() && root_key[0] == EVENT_ID_TAG {
1644                let root_key_red = &root_key[1..];
1645                let chain_id = bcs::from_bytes(root_key_red)?;
1646                let store = self.database.open_shared(&root_key)?;
1647                let keys = store.find_keys_by_prefix(&[]).await?;
1648                for key in keys {
1649                    let restricted_event_id = bcs::from_bytes::<RestrictedEventId>(&key)?;
1650                    let event_id = EventId {
1651                        chain_id,
1652                        stream_id: restricted_event_id.stream_id,
1653                        index: restricted_event_id.index,
1654                    };
1655                    event_ids.push(event_id);
1656                }
1657            }
1658        }
1659        Ok(event_ids)
1660    }
1661}
1662
1663impl<Database, C> DbStorage<Database, C>
1664where
1665    Database: KeyValueDatabase + Clone,
1666    Database::Store: KeyValueStore + Clone,
1667    C: Clock,
1668    Database::Error: Send + Sync,
1669{
1670    #[instrument(skip_all)]
1671    fn get_root_keys_for_certificates(hashes: &[CryptoHash]) -> Vec<Vec<u8>> {
1672        hashes
1673            .iter()
1674            .map(|hash| RootKey::BlockHash(*hash).bytes())
1675            .collect()
1676    }
1677
1678    fn deserialize_and_cache_certificate(
1679        &self,
1680        lite_cert_bytes: &[u8],
1681        confirmed_block_bytes: &[u8],
1682    ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, ViewError> {
1683        let lite = bcs::from_bytes::<LiteCertificate>(lite_cert_bytes)?;
1684        let block = bcs::from_bytes::<ConfirmedBlock>(confirmed_block_bytes)?;
1685        let hash = block.hash();
1686        self.caches.confirmed_block.insert(&hash, block.clone());
1687        let certificate = lite
1688            .into_confirmed_certificate(block)
1689            .ok_or(ViewError::InconsistentEntries)?;
1690        let arc = self.caches.certificate.insert(&hash, certificate);
1691        Ok(Some(arc))
1692    }
1693
1694    #[instrument(skip_all)]
1695    async fn write_entry(
1696        store: &Database::Store,
1697        key_values: Vec<(Vec<u8>, Vec<u8>)>,
1698    ) -> Result<(), ViewError> {
1699        let mut batch = Batch::new();
1700        for (key, value) in key_values {
1701            batch.put_key_value_bytes(key, value);
1702        }
1703        store.write_batch(batch).await?;
1704        Ok(())
1705    }
1706
1707    #[instrument(skip_all, fields(batch_size = batch.keys_value_bytes.len()))]
1708    async fn write_batch(&self, batch: MultiPartitionBatch) -> Result<(), ViewError> {
1709        if batch.keys_value_bytes.is_empty() {
1710            return Ok(());
1711        }
1712        let mut futures = Vec::new();
1713        for (root_key, key_values) in batch.keys_value_bytes {
1714            let store = self.database.open_shared(&root_key)?;
1715            futures.push(async move { Self::write_entry(&store, key_values).await });
1716        }
1717        futures::future::try_join_all(futures).await?;
1718        Ok(())
1719    }
1720}
1721
1722impl<Database, C> DbStorage<Database, C> {
1723    fn new(
1724        database: Database,
1725        wasm_runtime: Option<WasmRuntime>,
1726        cache_sizes: StorageCacheConfig,
1727        clock: C,
1728    ) -> Self {
1729        Self {
1730            database: Arc::new(database),
1731            clock,
1732            // The `Arc` here is required on native but useless on the Web.
1733            #[cfg_attr(web, expect(clippy::arc_with_non_send_sync))]
1734            thread_pool: Arc::new(linera_execution::ThreadPool::new(20)),
1735            wasm_runtime,
1736            user_contracts: Arc::new(papaya::HashMap::new()),
1737            user_services: Arc::new(papaya::HashMap::new()),
1738            shared_committees: SharedCommittees::new(),
1739            caches: StorageCaches::new(cache_sizes),
1740            execution_runtime_config: ExecutionRuntimeConfig::default(),
1741        }
1742    }
1743
1744    /// Sets whether contract log messages should be output.
1745    pub fn with_allow_application_logs(mut self, allow: bool) -> Self {
1746        self.execution_runtime_config.allow_application_logs = allow;
1747        self
1748    }
1749}
1750
1751impl<Database> DbStorage<Database, WallClock>
1752where
1753    Database: KeyValueDatabase + Clone + 'static,
1754    Database::Error: Send + Sync,
1755    Database::Store: KeyValueStore + Clone + 'static,
1756{
1757    /// Connects to the storage in the given namespace, creating it if it does not exist.
1758    pub async fn maybe_create_and_connect(
1759        config: &Database::Config,
1760        namespace: &str,
1761        wasm_runtime: Option<WasmRuntime>,
1762        cache_sizes: StorageCacheConfig,
1763    ) -> Result<Self, Database::Error> {
1764        let database = Database::maybe_create_and_connect(config, namespace).await?;
1765        Ok(Self::new(database, wasm_runtime, cache_sizes, WallClock))
1766    }
1767
1768    /// Connects to the existing storage in the given namespace.
1769    pub async fn connect(
1770        config: &Database::Config,
1771        namespace: &str,
1772        wasm_runtime: Option<WasmRuntime>,
1773        cache_sizes: StorageCacheConfig,
1774    ) -> Result<Self, Database::Error> {
1775        let database = Database::connect(config, namespace).await?;
1776        Ok(Self::new(database, wasm_runtime, cache_sizes, WallClock))
1777    }
1778}
1779
1780#[cfg(with_testing)]
1781impl<Database, C> DbStorage<Database, C>
1782where
1783    Database: linera_views::backends::DatabaseBackup,
1784{
1785    /// Backs up the underlying database to the given directory.
1786    pub fn backup_to(&self, dir: &std::path::Path) -> anyhow::Result<()> {
1787        self.database.backup_to(dir)
1788    }
1789}
1790
1791#[cfg(with_testing)]
1792impl<Database> DbStorage<Database, TestClock>
1793where
1794    Database: TestKeyValueDatabase + Clone + Send + Sync + 'static,
1795    Database::Store: KeyValueStore + Clone + Send + Sync + 'static,
1796    Database::Error: Send + Sync,
1797{
1798    /// Creates a test storage in a fresh random namespace with a `TestClock`.
1799    pub async fn make_test_storage(wasm_runtime: Option<WasmRuntime>) -> Self {
1800        let config = Database::new_test_config().await.unwrap();
1801        let namespace = generate_test_namespace();
1802        DbStorage::<Database, TestClock>::new_for_testing(
1803            config,
1804            &namespace,
1805            wasm_runtime,
1806            TestClock::new(),
1807        )
1808        .await
1809        .unwrap()
1810    }
1811
1812    /// Recreates the storage in the given namespace and connects to it, for testing.
1813    pub async fn new_for_testing(
1814        config: Database::Config,
1815        namespace: &str,
1816        wasm_runtime: Option<WasmRuntime>,
1817        clock: TestClock,
1818    ) -> Result<Self, Database::Error> {
1819        let database = Database::recreate_and_connect(&config, namespace).await?;
1820        Ok(Self::new(
1821            database,
1822            wasm_runtime,
1823            DEFAULT_STORAGE_CACHE_CONFIG,
1824            clock,
1825        ))
1826    }
1827
1828    /// Connects to the existing storage in the given namespace, for testing.
1829    pub async fn connect_for_testing(
1830        config: Database::Config,
1831        namespace: &str,
1832        wasm_runtime: Option<WasmRuntime>,
1833        clock: TestClock,
1834    ) -> Result<Self, Database::Error> {
1835        let database = Database::connect(&config, namespace).await?;
1836        Ok(Self::new(
1837            database,
1838            wasm_runtime,
1839            DEFAULT_STORAGE_CACHE_CONFIG,
1840            clock,
1841        ))
1842    }
1843}
1844
1845#[cfg(test)]
1846mod tests {
1847    use linera_base::{
1848        crypto::{CryptoHash, TestString},
1849        data_types::{Amount, Blob, BlobContent, BlockHeight, Event, OracleResponse, Round},
1850        identifiers::{
1851            Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, EventId,
1852            GenericApplicationId, StreamId, StreamName,
1853        },
1854    };
1855    use linera_chain::{
1856        block::{Block, ConfirmedBlock},
1857        data_types::{OperationResult, Transaction},
1858        test::BlockBuilder,
1859        types::ConfirmedBlockCertificate,
1860    };
1861    use linera_execution::{
1862        system::{SystemMessage, SystemOperation},
1863        Message, MessageKind, Operation, OutgoingMessage,
1864    };
1865    use linera_views::{
1866        memory::MemoryDatabase,
1867        store::{KeyValueDatabase, ReadableKeyValueStore as _},
1868    };
1869
1870    use crate::{
1871        db_storage::{
1872            to_event_key, to_height_key, MultiPartitionBatch, RootKey, BLOB_ID_TAG, CHAIN_ID_TAG,
1873            EVENT_ID_TAG,
1874        },
1875        DbStorage, Storage, TestClock,
1876    };
1877
1878    /// Builds a block populated with one item of each body kind, with values derived from the
1879    /// height so blocks are distinct. The header is computed from the body via `Block::new`, so
1880    /// the block round-trips through storage (the block hash commits to that header).
1881    fn populated_block(chain_id: ChainId, height: u64) -> Block {
1882        let owner = AccountOwner::CHAIN;
1883        let stream_id = StreamId {
1884            application_id: GenericApplicationId::System,
1885            stream_name: StreamName(b"test_stream".to_vec()),
1886        };
1887        BlockBuilder::new(chain_id, BlockHeight(height))
1888            .with_state_hash(CryptoHash::new(&TestString::new(format!(
1889                "state_hash_{height}"
1890            ))))
1891            .with_transaction(Transaction::ExecuteOperation(Operation::System(Box::new(
1892                SystemOperation::Transfer {
1893                    owner,
1894                    recipient: Account::chain(chain_id),
1895                    amount: Amount::ONE,
1896                },
1897            ))))
1898            .with_messages(vec![OutgoingMessage {
1899                destination: chain_id,
1900                authenticated_owner: None,
1901                grant: Amount::ZERO,
1902                refund_grant_to: None,
1903                kind: MessageKind::Simple,
1904                message: Message::System(SystemMessage::Credit {
1905                    target: owner,
1906                    amount: Amount::ONE,
1907                    source: owner,
1908                }),
1909            }])
1910            .with_events(vec![Event {
1911                stream_id,
1912                index: 0,
1913                value: b"event".to_vec(),
1914            }])
1915            .with_oracle_responses(vec![OracleResponse::Round(Some(0))])
1916            .with_blobs(vec![Blob::new(BlobContent::new_data(b"blob".to_vec()))])
1917            .with_operation_result(OperationResult(b"result".to_vec()))
1918            .build()
1919    }
1920
1921    // Several functionalities of the storage rely on the way that the serialization
1922    // is done. Thus we need to check that the serialization works in the way that
1923    // we expect.
1924
1925    // The listing of the blobs in `list_blob_ids` depends on the serialization
1926    // of `RootKey::Blob`.
1927    #[test]
1928    fn test_root_key_blob_serialization() {
1929        let hash = CryptoHash::default();
1930        let blob_type = BlobType::default();
1931        let blob_id = BlobId::new(hash, blob_type);
1932        let root_key = RootKey::BlobId(blob_id).bytes();
1933        assert_eq!(root_key[0], BLOB_ID_TAG);
1934        assert_eq!(bcs::from_bytes::<BlobId>(&root_key[1..]).unwrap(), blob_id);
1935    }
1936
1937    // The listing of the chains in `list_chain_ids` depends on the serialization
1938    // of `RootKey::ChainState`.
1939    #[test]
1940    fn test_root_key_chainstate_serialization() {
1941        let hash = CryptoHash::default();
1942        let chain_id = ChainId(hash);
1943        let root_key = RootKey::ChainState(chain_id).bytes();
1944        assert_eq!(root_key[0], CHAIN_ID_TAG);
1945        assert_eq!(
1946            bcs::from_bytes::<ChainId>(&root_key[1..]).unwrap(),
1947            chain_id
1948        );
1949    }
1950
1951    // The listing of the events in `read_events_from_index` depends on the
1952    // serialization of `RootKey::Event`.
1953    #[test]
1954    fn test_root_key_event_serialization() {
1955        let hash = CryptoHash::test_hash("49");
1956        let chain_id = ChainId(hash);
1957        let application_description_hash = CryptoHash::test_hash("42");
1958        let application_id = ApplicationId::new(application_description_hash);
1959        let application_id = GenericApplicationId::User(application_id);
1960        let stream_name = StreamName(bcs::to_bytes("linera_stream").unwrap());
1961        let stream_id = StreamId {
1962            application_id,
1963            stream_name,
1964        };
1965        let prefix = bcs::to_bytes(&stream_id).unwrap();
1966
1967        let index = 1567;
1968        let event_id = EventId {
1969            chain_id,
1970            stream_id,
1971            index,
1972        };
1973        let root_key = RootKey::Event(chain_id).bytes();
1974        assert_eq!(root_key[0], EVENT_ID_TAG);
1975        let key = to_event_key(&event_id);
1976        assert!(key.starts_with(&prefix));
1977    }
1978
1979    // The height index lookup depends on the serialization of RootKey::BlockByHeight
1980    // and to_height_key, following the same pattern as Event.
1981    #[test]
1982    fn test_root_key_block_by_height_serialization() {
1983        use linera_base::data_types::BlockHeight;
1984
1985        let hash = CryptoHash::default();
1986        let chain_id = ChainId(hash);
1987        let height = BlockHeight(42);
1988
1989        // RootKey::BlockByHeight uses only ChainId for partitioning (like Event)
1990        let root_key = RootKey::BlockByHeight(chain_id).bytes();
1991        let deserialized_chain_id: ChainId = bcs::from_bytes(&root_key[1..]).unwrap();
1992        assert_eq!(deserialized_chain_id, chain_id);
1993
1994        // Height is encoded as a key (like index in Event)
1995        let height_key = to_height_key(height);
1996        let deserialized_height: BlockHeight = bcs::from_bytes(&height_key).unwrap();
1997        assert_eq!(deserialized_height, height);
1998    }
1999
2000    #[cfg(with_testing)]
2001    #[tokio::test]
2002    async fn test_add_certificate_creates_height_index() {
2003        // Create test storage
2004        let storage = DbStorage::<MemoryDatabase, TestClock>::make_test_storage(None).await;
2005
2006        // Create a test certificate at a specific height
2007        let chain_id = ChainId(CryptoHash::test_hash("test_chain"));
2008        let height = BlockHeight(5);
2009        let block = populated_block(chain_id, height.0);
2010        let confirmed_block = ConfirmedBlock::new(block);
2011        let certificate = ConfirmedBlockCertificate::new(confirmed_block, Round::Fast, vec![]);
2012
2013        // Write certificate
2014        let mut batch = MultiPartitionBatch::new();
2015        batch.add_certificate(&certificate).unwrap();
2016        storage.write_batch(batch).await.unwrap();
2017
2018        // Verify height index was created (following Event pattern)
2019        let hash = certificate.hash();
2020        let index_root_key = RootKey::BlockByHeight(chain_id).bytes();
2021        let store = storage.database.open_shared(&index_root_key).unwrap();
2022        let height_key = to_height_key(height);
2023        let value_bytes = store.read_value_bytes(&height_key).await.unwrap();
2024
2025        assert!(value_bytes.is_some(), "Height index was not created");
2026        let stored_hash: CryptoHash = bcs::from_bytes(&value_bytes.unwrap()).unwrap();
2027        assert_eq!(stored_hash, hash, "Height index contains wrong hash");
2028    }
2029
2030    #[cfg(with_testing)]
2031    #[tokio::test]
2032    async fn test_read_certificates_by_heights() {
2033        let storage = DbStorage::<MemoryDatabase, TestClock>::make_test_storage(None).await;
2034        let chain_id = ChainId(CryptoHash::test_hash("test_chain"));
2035
2036        // Write certificates at heights 1, 3, 5
2037        let mut batch = MultiPartitionBatch::new();
2038        let mut expected_certs = vec![];
2039
2040        for height in [1, 3, 5] {
2041            let block = populated_block(chain_id, height);
2042            let confirmed_block = ConfirmedBlock::new(block);
2043            let cert = ConfirmedBlockCertificate::new(confirmed_block, Round::Fast, vec![]);
2044            expected_certs.push((height, cert.clone()));
2045            batch.add_certificate(&cert).unwrap();
2046        }
2047        storage.write_batch(batch).await.unwrap();
2048
2049        // Test: Read in order [1, 3, 5]
2050        let heights = vec![BlockHeight(1), BlockHeight(3), BlockHeight(5)];
2051        let result = storage
2052            .read_certificates_by_heights(chain_id, &heights)
2053            .await
2054            .unwrap();
2055        assert_eq!(result.len(), 3);
2056        assert_eq!(
2057            result[0].as_ref().unwrap().hash(),
2058            expected_certs[0].1.hash()
2059        );
2060        assert_eq!(
2061            result[1].as_ref().unwrap().hash(),
2062            expected_certs[1].1.hash()
2063        );
2064        assert_eq!(
2065            result[2].as_ref().unwrap().hash(),
2066            expected_certs[2].1.hash()
2067        );
2068
2069        // Test: Read out of order [5, 1, 3]
2070        let heights = vec![BlockHeight(5), BlockHeight(1), BlockHeight(3)];
2071        let result = storage
2072            .read_certificates_by_heights(chain_id, &heights)
2073            .await
2074            .unwrap();
2075        assert_eq!(result.len(), 3);
2076        assert_eq!(
2077            result[0].as_ref().unwrap().hash(),
2078            expected_certs[2].1.hash()
2079        );
2080        assert_eq!(
2081            result[1].as_ref().unwrap().hash(),
2082            expected_certs[0].1.hash()
2083        );
2084        assert_eq!(
2085            result[2].as_ref().unwrap().hash(),
2086            expected_certs[1].1.hash()
2087        );
2088
2089        // Test: Read with missing heights [1, 2, 3]
2090        let heights = vec![
2091            BlockHeight(1),
2092            BlockHeight(2),
2093            BlockHeight(3),
2094            BlockHeight(3),
2095        ];
2096        let result = storage
2097            .read_certificates_by_heights(chain_id, &heights)
2098            .await
2099            .unwrap();
2100        assert_eq!(result.len(), 4); // BlockHeight(3) was duplicated.
2101        assert!(result[0].is_some());
2102        assert!(result[1].is_none()); // Height 2 doesn't exist
2103        assert!(result[2].is_some());
2104        assert!(result[3].is_some());
2105        assert_eq!(
2106            result[2].as_ref().unwrap().hash(),
2107            result[3].as_ref().unwrap().hash()
2108        ); // Both correspond to height 3
2109
2110        // Test: Empty heights
2111        let heights = vec![];
2112        let result = storage
2113            .read_certificates_by_heights(chain_id, &heights)
2114            .await
2115            .unwrap();
2116        assert_eq!(result.len(), 0);
2117    }
2118
2119    #[cfg(with_testing)]
2120    #[tokio::test]
2121    async fn test_read_certificates_by_heights_multiple_chains() {
2122        let storage = DbStorage::<MemoryDatabase, TestClock>::make_test_storage(None).await;
2123
2124        // Create certificates for two different chains at same heights
2125        let chain_a = ChainId(CryptoHash::test_hash("chain_a"));
2126        let chain_b = ChainId(CryptoHash::test_hash("chain_b"));
2127
2128        let mut batch = MultiPartitionBatch::new();
2129
2130        let block_a = populated_block(chain_a, 10);
2131        let confirmed_block_a = ConfirmedBlock::new(block_a);
2132        let cert_a = ConfirmedBlockCertificate::new(confirmed_block_a, Round::Fast, vec![]);
2133        batch.add_certificate(&cert_a).unwrap();
2134
2135        let block_b = populated_block(chain_b, 10);
2136        let confirmed_block_b = ConfirmedBlock::new(block_b);
2137        let cert_b = ConfirmedBlockCertificate::new(confirmed_block_b, Round::Fast, vec![]);
2138        batch.add_certificate(&cert_b).unwrap();
2139
2140        storage.write_batch(batch).await.unwrap();
2141
2142        // Read from chain A - should get cert A
2143        let result = storage
2144            .read_certificates_by_heights(chain_a, &[BlockHeight(10)])
2145            .await
2146            .unwrap();
2147        assert_eq!(result[0].as_ref().unwrap().hash(), cert_a.hash());
2148
2149        // Read from chain B - should get cert B
2150        let result = storage
2151            .read_certificates_by_heights(chain_b, &[BlockHeight(10)])
2152            .await
2153            .unwrap();
2154        assert_eq!(result[0].as_ref().unwrap().hash(), cert_b.hash());
2155
2156        // Read from chain A for height that only chain B has - should get None
2157        let result = storage
2158            .read_certificates_by_heights(chain_a, &[BlockHeight(20)])
2159            .await
2160            .unwrap();
2161        assert!(result[0].is_none());
2162    }
2163
2164    #[cfg(with_testing)]
2165    #[tokio::test]
2166    async fn test_read_certificates_by_heights_consistency() {
2167        let storage = DbStorage::<MemoryDatabase, TestClock>::make_test_storage(None).await;
2168        let chain_id = ChainId(CryptoHash::test_hash("test_chain"));
2169
2170        // Write certificate
2171        let mut batch = MultiPartitionBatch::new();
2172        let block = populated_block(chain_id, 7);
2173        let confirmed_block = ConfirmedBlock::new(block);
2174        let cert = ConfirmedBlockCertificate::new(confirmed_block, Round::Fast, vec![]);
2175        let hash = cert.hash();
2176        batch.add_certificate(&cert).unwrap();
2177        storage.write_batch(batch).await.unwrap();
2178
2179        // Read by hash
2180        let cert_by_hash = storage.read_certificate(hash).await.unwrap().unwrap();
2181
2182        // Read by height
2183        let certs_by_height = storage
2184            .read_certificates_by_heights(chain_id, &[BlockHeight(7)])
2185            .await
2186            .unwrap();
2187        let cert_by_height = certs_by_height[0].as_ref().unwrap();
2188
2189        // Should be identical
2190        assert_eq!(cert_by_hash.hash(), cert_by_height.hash());
2191        assert_eq!(
2192            cert_by_hash.value().block().header,
2193            cert_by_height.value().block().header
2194        );
2195    }
2196}