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