Skip to main content

iota_core/
storage.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::sync::Arc;
6
7use iota_node_storage::{GrpcIndexes, GrpcStateReader};
8use iota_sdk_types::{
9    CheckpointContentsDigest, CheckpointDigest, StructTag, TransactionDigest, TransactionEffects,
10    TransactionEvents,
11    checkpoint::{CheckpointContents, EndOfEpochData},
12};
13use iota_types::{
14    committee::{Committee, EpochId},
15    error::IotaError,
16    messages_checkpoint::{
17        CheckpointContentsExt, CheckpointSequenceNumber, FullCheckpointContents,
18        VerifiedCheckpoint, VerifiedCheckpointContents,
19    },
20    object::Object,
21    storage::{
22        ObjectKey, ObjectStore, ReadStore, WriteStore,
23        error::{Error as StorageError, Result},
24    },
25    transaction::VerifiedTransaction,
26};
27use parking_lot::Mutex;
28use tracing::instrument;
29
30use crate::{
31    authority::AuthorityState, checkpoints::CheckpointStore,
32    epoch::committee_store::CommitteeStore, execution_cache::ExecutionCacheTraitPointers,
33    grpc_indexes::GrpcIndexesStore,
34};
35
36#[derive(Clone)]
37pub struct RocksDbStore {
38    cache_traits: ExecutionCacheTraitPointers,
39
40    committee_store: Arc<CommitteeStore>,
41    checkpoint_store: Arc<CheckpointStore>,
42    // Lower bounds on the watermark rows, not mirrors of them: a row already
43    // ahead leaves its cache behind, which costs a repeated call and nothing
44    // else. Held so that the read and the write of a row happen under one
45    // lock.
46    highest_verified_checkpoint: Arc<Mutex<Option<u64>>>,
47    highest_synced_checkpoint: Arc<Mutex<Option<u64>>>,
48}
49
50impl RocksDbStore {
51    pub fn new(
52        cache_traits: ExecutionCacheTraitPointers,
53        committee_store: Arc<CommitteeStore>,
54        checkpoint_store: Arc<CheckpointStore>,
55    ) -> Self {
56        Self {
57            cache_traits,
58            committee_store,
59            checkpoint_store,
60            highest_verified_checkpoint: Arc::new(Mutex::new(None)),
61            highest_synced_checkpoint: Arc::new(Mutex::new(None)),
62        }
63    }
64
65    pub fn get_objects(&self, object_keys: &[ObjectKey]) -> Result<Vec<Option<Object>>, IotaError> {
66        self.cache_traits
67            .object_cache_reader
68            .try_multi_get_objects_by_key(object_keys)
69    }
70
71    pub fn get_last_executed_checkpoint(&self) -> Result<Option<VerifiedCheckpoint>, IotaError> {
72        Ok(self.checkpoint_store.get_highest_executed_checkpoint()?)
73    }
74
75    /// Marks a consecutive run of checkpoints as synced, writing the watermark
76    /// once for the last one but notifying waiters of every checkpoint.
77    fn update_highest_synced_checkpoints(
78        &self,
79        checkpoints: &[VerifiedCheckpoint],
80    ) -> Result<(), iota_types::storage::error::Error> {
81        let Some(last) = checkpoints.last() else {
82            return Ok(());
83        };
84        let mut locked = self.highest_synced_checkpoint.lock();
85        if locked.is_some_and(|seq| seq >= last.sequence_number) {
86            return Ok(());
87        }
88        self.checkpoint_store
89            .multi_update_highest_synced_checkpoint(checkpoints)
90            .map_err(iota_types::storage::error::Error::custom)?;
91        *locked = locked.max(Some(last.sequence_number));
92        Ok(())
93    }
94}
95
96impl ReadStore for RocksDbStore {
97    fn try_get_checkpoint_by_digest(
98        &self,
99        digest: &CheckpointDigest,
100    ) -> Result<Option<VerifiedCheckpoint>, StorageError> {
101        self.checkpoint_store
102            .get_checkpoint_by_digest(digest)
103            .map_err(Into::into)
104    }
105
106    fn try_get_checkpoint_by_sequence_number(
107        &self,
108        sequence_number: CheckpointSequenceNumber,
109    ) -> Result<Option<VerifiedCheckpoint>, StorageError> {
110        self.checkpoint_store
111            .get_checkpoint_by_sequence_number(sequence_number)
112            .map_err(Into::into)
113    }
114
115    fn try_get_highest_verified_checkpoint(&self) -> Result<VerifiedCheckpoint, StorageError> {
116        self.checkpoint_store
117            .get_highest_verified_checkpoint()
118            .map(|maybe_checkpoint| {
119                maybe_checkpoint
120                    .expect("storage should have been initialized with genesis checkpoint")
121            })
122            .map_err(Into::into)
123    }
124
125    fn try_get_highest_verified_checkpoint_seq_number(
126        &self,
127    ) -> Result<CheckpointSequenceNumber, StorageError> {
128        Ok(self
129            .checkpoint_store
130            .get_highest_verified_checkpoint_seq_number()?
131            .expect("storage should have been initialized with genesis checkpoint"))
132    }
133
134    fn try_get_highest_synced_checkpoint(&self) -> Result<VerifiedCheckpoint, StorageError> {
135        self.checkpoint_store
136            .get_highest_synced_checkpoint()
137            .map(|maybe_checkpoint| {
138                maybe_checkpoint
139                    .expect("storage should have been initialized with genesis checkpoint")
140            })
141            .map_err(Into::into)
142    }
143
144    fn try_get_highest_synced_checkpoint_seq_number(
145        &self,
146    ) -> Result<CheckpointSequenceNumber, StorageError> {
147        Ok(self
148            .checkpoint_store
149            .get_highest_synced_checkpoint_seq_number()?
150            .expect("storage should have been initialized with genesis checkpoint"))
151    }
152
153    fn try_get_lowest_available_checkpoint(
154        &self,
155    ) -> Result<CheckpointSequenceNumber, StorageError> {
156        if let Some(highest_pruned_cp) = self
157            .checkpoint_store
158            .get_highest_pruned_checkpoint_seq_number()
159            .map_err(Into::<StorageError>::into)?
160        {
161            Ok(highest_pruned_cp + 1)
162        } else {
163            Ok(0)
164        }
165    }
166
167    fn try_get_full_checkpoint_contents_by_sequence_number(
168        &self,
169        sequence_number: CheckpointSequenceNumber,
170    ) -> Result<Option<FullCheckpointContents>, StorageError> {
171        Ok(self
172            .checkpoint_store
173            .get_full_checkpoint_contents_by_sequence_number(sequence_number)
174            .map(|contents| contents.as_ref().clone()))
175    }
176
177    fn try_get_full_checkpoint_contents(
178        &self,
179        digest: &CheckpointContentsDigest,
180    ) -> Result<Option<FullCheckpointContents>, StorageError> {
181        // First look to see if the in-memory cache still holds the complete
182        // contents.
183        if let Some(contents) = self
184            .checkpoint_store
185            .get_full_checkpoint_contents_by_digest(digest)
186        {
187            return Ok(Some(contents.as_ref().clone()));
188        }
189
190        // Otherwise gather it from the individual components.
191        self.checkpoint_store
192            .get_checkpoint_contents(digest)
193            .map_err(iota_types::storage::error::Error::custom)?
194            .map(|contents| {
195                let mut transactions = Vec::with_capacity(contents.len());
196                for tx in contents.iter() {
197                    if let (Some(t), Some(e)) = (
198                        self.try_get_transaction(&tx.transaction)?,
199                        self.cache_traits
200                            .transaction_cache_reader
201                            .try_get_effects(&tx.effects)
202                            .map_err(iota_types::storage::error::Error::custom)?,
203                    ) {
204                        transactions.push(iota_types::base_types::ExecutionData::new(
205                            (*t).clone().into_inner(),
206                            e,
207                        ))
208                    } else {
209                        return Result::<
210                            Option<FullCheckpointContents>,
211                            iota_types::storage::error::Error,
212                        >::Ok(None);
213                    }
214                }
215                Ok(Some(
216                    FullCheckpointContents::from_contents_and_execution_data(
217                        contents,
218                        transactions.into_iter(),
219                    ),
220                ))
221            })
222            .transpose()
223            .map(|contents| contents.flatten())
224            .map_err(iota_types::storage::error::Error::custom)
225    }
226
227    fn try_get_committee(
228        &self,
229        epoch: EpochId,
230    ) -> Result<Option<Arc<Committee>>, iota_types::storage::error::Error> {
231        Ok(self.committee_store.get_committee(&epoch).unwrap())
232    }
233
234    fn try_get_transaction(
235        &self,
236        digest: &TransactionDigest,
237    ) -> Result<Option<Arc<VerifiedTransaction>>, StorageError> {
238        self.cache_traits
239            .transaction_cache_reader
240            .try_get_transaction_block(digest)
241            .map_err(StorageError::custom)
242    }
243
244    fn try_get_transaction_effects(
245        &self,
246        digest: &TransactionDigest,
247    ) -> Result<Option<TransactionEffects>, StorageError> {
248        self.cache_traits
249            .transaction_cache_reader
250            .try_get_executed_effects(digest)
251            .map_err(StorageError::custom)
252    }
253
254    fn try_get_events(
255        &self,
256        digest: &TransactionDigest,
257    ) -> Result<Option<TransactionEvents>, StorageError> {
258        self.cache_traits
259            .transaction_cache_reader
260            .try_get_events(digest)
261            .map_err(StorageError::custom)
262    }
263
264    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
265        self.checkpoint_store
266            .get_highest_executed_checkpoint()
267            .map_err(iota_types::storage::error::Error::custom)?
268            .ok_or_else(|| {
269                iota_types::storage::error::Error::missing("unable to get latest checkpoint")
270            })
271    }
272
273    fn try_get_checkpoint_contents_by_digest(
274        &self,
275        digest: &CheckpointContentsDigest,
276    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
277        self.checkpoint_store
278            .get_checkpoint_contents(digest)
279            .map_err(iota_types::storage::error::Error::custom)
280    }
281
282    fn try_get_checkpoint_contents_by_sequence_number(
283        &self,
284        sequence_number: CheckpointSequenceNumber,
285    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
286        match self.try_get_checkpoint_by_sequence_number(sequence_number) {
287            Ok(Some(checkpoint)) => {
288                self.try_get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
289            }
290            Ok(None) => Ok(None),
291            Err(e) => Err(e),
292        }
293    }
294}
295
296impl ObjectStore for RocksDbStore {
297    fn try_get_object(
298        &self,
299        object_id: &iota_sdk_types::ObjectId,
300    ) -> iota_types::storage::error::Result<Option<Object>> {
301        self.cache_traits.object_store.try_get_object(object_id)
302    }
303
304    fn try_get_object_by_key(
305        &self,
306        object_id: &iota_sdk_types::ObjectId,
307        version: iota_types::base_types::VersionNumber,
308    ) -> iota_types::storage::error::Result<Option<Object>> {
309        self.cache_traits
310            .object_store
311            .try_get_object_by_key(object_id, version)
312    }
313}
314
315impl WriteStore for RocksDbStore {
316    #[instrument(level = "trace", skip_all)]
317    fn try_insert_checkpoint(
318        &self,
319        checkpoint: &VerifiedCheckpoint,
320    ) -> Result<(), iota_types::storage::error::Error> {
321        if let Some(EndOfEpochData {
322            next_epoch_committee,
323            ..
324        }) = checkpoint.end_of_epoch_data.as_ref()
325        {
326            let committee = Committee::from_committee_members(
327                checkpoint.epoch().checked_add(1).unwrap(),
328                next_epoch_committee,
329            );
330            self.try_insert_committee(committee)?;
331        }
332
333        self.checkpoint_store
334            .insert_certified_checkpoint(checkpoint)
335            .map_err(iota_types::storage::error::Error::custom)?;
336        // Not `insert_verified_checkpoint`, which would write the watermark
337        // straight to the store: taking the same lock the archive path takes
338        // is what stops two writers leaving the row behind where one of them
339        // had already seen it.
340        self.try_update_highest_verified_checkpoint(checkpoint)
341    }
342
343    fn try_update_highest_verified_checkpoint(
344        &self,
345        checkpoint: &VerifiedCheckpoint,
346    ) -> Result<(), iota_types::storage::error::Error> {
347        let mut locked = self.highest_verified_checkpoint.lock();
348        if locked.is_some() && locked.unwrap() >= checkpoint.sequence_number {
349            return Ok(());
350        }
351        self.checkpoint_store
352            .update_highest_verified_checkpoint(checkpoint)
353            .map_err(iota_types::storage::error::Error::custom)?;
354        *locked = locked.max(Some(checkpoint.sequence_number));
355        Ok(())
356    }
357
358    fn try_update_highest_synced_checkpoint(
359        &self,
360        checkpoint: &VerifiedCheckpoint,
361    ) -> Result<(), iota_types::storage::error::Error> {
362        self.update_highest_synced_checkpoints(std::slice::from_ref(checkpoint))
363    }
364
365    fn try_insert_checkpoint_contents(
366        &self,
367        checkpoint: &VerifiedCheckpoint,
368        contents: VerifiedCheckpointContents,
369    ) -> Result<(), iota_types::storage::error::Error> {
370        self.cache_traits
371            .state_sync_store
372            .try_multi_insert_transaction_and_effects(contents.transactions())
373            .map_err(iota_types::storage::error::Error::custom)?;
374        self.checkpoint_store
375            .insert_verified_checkpoint_contents(checkpoint, contents)
376            .map_err(Into::into)
377    }
378
379    fn try_insert_committee(
380        &self,
381        new_committee: Committee,
382    ) -> Result<(), iota_types::storage::error::Error> {
383        self.committee_store
384            .insert_new_committee(&new_committee)
385            .unwrap();
386        Ok(())
387    }
388
389    fn try_get_highest_executed_checkpoint_seq_number(
390        &self,
391    ) -> Result<Option<CheckpointSequenceNumber>, iota_types::storage::error::Error> {
392        self.checkpoint_store
393            .get_highest_executed_checkpoint_seq_number()
394            .map_err(Into::into)
395    }
396
397    async fn wait_for_executed_checkpoint(&self, sequence_number: CheckpointSequenceNumber) {
398        self.checkpoint_store
399            .notify_read_executed_checkpoint(sequence_number)
400            .await;
401    }
402
403    fn try_insert_synced_checkpoints(
404        &self,
405        checkpoints: Vec<(VerifiedCheckpoint, VerifiedCheckpointContents)>,
406    ) -> Result<(), iota_types::storage::error::Error> {
407        let summaries: Vec<VerifiedCheckpoint> = checkpoints
408            .iter()
409            .map(|(checkpoint, _)| checkpoint.clone())
410            .collect();
411        let Some(last) = summaries.last() else {
412            return Ok(());
413        };
414
415        for checkpoint in &summaries {
416            if let Some(EndOfEpochData {
417                next_epoch_committee,
418                ..
419            }) = checkpoint.end_of_epoch_data.as_ref()
420            {
421                let committee = Committee::from_committee_members(
422                    checkpoint.epoch().checked_add(1).unwrap(),
423                    next_epoch_committee,
424                );
425                self.try_insert_committee(committee)?;
426            }
427        }
428
429        self.checkpoint_store
430            .multi_insert_certified_checkpoints(&summaries)?;
431        self.try_update_highest_verified_checkpoint(last)?;
432
433        // Transactions and effects must be durable before their contents
434        // rows (see `CheckpointStore::cache_full_checkpoint_contents`).
435        for (_, contents) in &checkpoints {
436            self.cache_traits
437                .state_sync_store
438                .try_multi_insert_transaction_and_effects(contents.transactions())
439                .map_err(iota_types::storage::error::Error::custom)?;
440        }
441        self.checkpoint_store
442            .multi_insert_verified_checkpoint_contents(checkpoints)?;
443
444        self.update_highest_synced_checkpoints(&summaries)
445    }
446}
447
448pub struct GrpcReadStore {
449    state: Arc<AuthorityState>,
450    rocks: RocksDbStore,
451}
452
453impl GrpcReadStore {
454    pub fn new(state: Arc<AuthorityState>, rocks: RocksDbStore) -> Self {
455        Self { state, rocks }
456    }
457
458    fn grpc_indexes_store(&self) -> iota_types::storage::error::Result<&GrpcIndexesStore> {
459        self.state.grpc_indexes_store.as_deref().ok_or_else(|| {
460            iota_types::storage::error::Error::custom("gRPC index store is disabled")
461        })
462    }
463}
464
465impl ObjectStore for GrpcReadStore {
466    fn try_get_object(
467        &self,
468        object_id: &iota_sdk_types::ObjectId,
469    ) -> iota_types::storage::error::Result<Option<Object>> {
470        self.rocks.try_get_object(object_id)
471    }
472
473    fn try_get_object_by_key(
474        &self,
475        object_id: &iota_sdk_types::ObjectId,
476        version: iota_types::base_types::VersionNumber,
477    ) -> iota_types::storage::error::Result<Option<Object>> {
478        self.rocks.try_get_object_by_key(object_id, version)
479    }
480}
481
482impl ReadStore for GrpcReadStore {
483    fn try_get_committee(
484        &self,
485        epoch: EpochId,
486    ) -> iota_types::storage::error::Result<Option<Arc<Committee>>> {
487        self.rocks.try_get_committee(epoch)
488    }
489
490    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
491        self.rocks.try_get_latest_checkpoint()
492    }
493
494    fn try_get_highest_verified_checkpoint(
495        &self,
496    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
497        self.rocks.try_get_highest_verified_checkpoint()
498    }
499
500    fn try_get_highest_verified_checkpoint_seq_number(
501        &self,
502    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
503        self.rocks.try_get_highest_verified_checkpoint_seq_number()
504    }
505
506    fn try_get_highest_synced_checkpoint(
507        &self,
508    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
509        self.rocks.try_get_highest_synced_checkpoint()
510    }
511
512    fn try_get_highest_synced_checkpoint_seq_number(
513        &self,
514    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
515        self.rocks.try_get_highest_synced_checkpoint_seq_number()
516    }
517
518    fn try_get_lowest_available_checkpoint(
519        &self,
520    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
521        self.rocks.try_get_lowest_available_checkpoint()
522    }
523
524    fn try_get_checkpoint_by_digest(
525        &self,
526        digest: &CheckpointDigest,
527    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
528        self.rocks.try_get_checkpoint_by_digest(digest)
529    }
530
531    fn try_get_checkpoint_by_sequence_number(
532        &self,
533        sequence_number: CheckpointSequenceNumber,
534    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
535        self.rocks
536            .try_get_checkpoint_by_sequence_number(sequence_number)
537    }
538
539    fn try_get_checkpoint_contents_by_digest(
540        &self,
541        digest: &CheckpointContentsDigest,
542    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
543        self.rocks.try_get_checkpoint_contents_by_digest(digest)
544    }
545
546    fn try_get_checkpoint_contents_by_sequence_number(
547        &self,
548        sequence_number: CheckpointSequenceNumber,
549    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
550        self.rocks
551            .try_get_checkpoint_contents_by_sequence_number(sequence_number)
552    }
553
554    fn try_get_transaction(
555        &self,
556        digest: &TransactionDigest,
557    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
558        self.rocks.try_get_transaction(digest)
559    }
560
561    fn try_get_transaction_effects(
562        &self,
563        digest: &TransactionDigest,
564    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
565        self.rocks.try_get_transaction_effects(digest)
566    }
567
568    fn try_get_events(
569        &self,
570        digest: &TransactionDigest,
571    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
572        self.rocks.try_get_events(digest)
573    }
574
575    fn try_get_full_checkpoint_contents_by_sequence_number(
576        &self,
577        sequence_number: CheckpointSequenceNumber,
578    ) -> iota_types::storage::error::Result<Option<FullCheckpointContents>> {
579        self.rocks
580            .try_get_full_checkpoint_contents_by_sequence_number(sequence_number)
581    }
582
583    fn try_get_full_checkpoint_contents(
584        &self,
585        digest: &CheckpointContentsDigest,
586    ) -> iota_types::storage::error::Result<Option<FullCheckpointContents>> {
587        self.rocks.try_get_full_checkpoint_contents(digest)
588    }
589}
590
591impl GrpcStateReader for GrpcReadStore {
592    fn get_lowest_available_checkpoint_objects(
593        &self,
594    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
595        Ok(self
596            .state
597            .get_object_cache_reader()
598            .try_get_highest_pruned_checkpoint()
599            .map_err(StorageError::custom)?
600            .map(|cp| cp + 1)
601            .unwrap_or(0))
602    }
603
604    fn get_chain_identifier(&self) -> Result<iota_types::digests::ChainIdentifier> {
605        Ok(self.state.get_chain_identifier())
606    }
607
608    fn get_epoch_last_checkpoint(
609        &self,
610        epoch_id: EpochId,
611    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
612        self.rocks
613            .checkpoint_store
614            .get_epoch_last_checkpoint(epoch_id)
615            .map_err(iota_types::storage::error::Error::custom)
616    }
617
618    fn get_epoch_info(
619        &self,
620        epoch: EpochId,
621    ) -> iota_types::storage::error::Result<Option<iota_types::storage::EpochInfoV2>> {
622        self.rocks
623            .checkpoint_store
624            .get_epoch_info(epoch)
625            .map_err(iota_types::storage::error::Error::custom)
626    }
627
628    fn grpc_indexes(&self) -> Option<&dyn GrpcIndexes> {
629        self.grpc_indexes_store().ok().map(|index| index as _)
630    }
631
632    fn get_struct_layout(
633        &self,
634        struct_tag: &StructTag,
635    ) -> Result<Option<move_core_types::annotated_value::MoveTypeLayout>> {
636        self.state
637            .load_epoch_store_one_call_per_task()
638            .executor()
639            // TODO(cache) - must read through cache
640            .type_layout_resolver(Box::new(self.state.get_backing_package_store().as_ref()))
641            .get_annotated_layout(struct_tag)
642            .map(|layout| layout.into_layout())
643            .map(Some)
644            .map_err(StorageError::custom)
645    }
646}