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