Skip to main content

sui_core/
global_state_hasher.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use itertools::Itertools;
5use mysten_common::ZipDebugEqIteratorExt;
6use mysten_common::fatal;
7use mysten_metrics::monitored_scope;
8use prometheus::{IntGauge, Registry, register_int_gauge_with_registry};
9use serde::Serialize;
10use sui_protocol_config::ProtocolConfig;
11use sui_types::base_types::{ObjectID, ObjectRef, SequenceNumber, VersionNumber};
12use sui_types::committee::EpochId;
13use sui_types::digests::{ObjectDigest, TransactionDigest};
14use sui_types::in_memory_storage::InMemoryStorage;
15use sui_types::storage::{ObjectKey, ObjectStore};
16use tracing::debug;
17
18use std::collections::{HashMap, HashSet};
19use std::sync::Arc;
20
21use fastcrypto::hash::MultisetHash;
22use sui_types::effects::TransactionEffects;
23use sui_types::effects::TransactionEffectsAPI;
24use sui_types::error::SuiResult;
25use sui_types::global_state_hash::GlobalStateHash;
26use sui_types::messages_checkpoint::{CheckpointSequenceNumber, ECMHLiveObjectSetDigest};
27
28use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
29use crate::authority::authority_store_tables::LiveObject;
30
31pub struct GlobalStateHashMetrics {
32    inconsistent_state: IntGauge,
33}
34
35impl GlobalStateHashMetrics {
36    pub fn new(registry: &Registry) -> Arc<Self> {
37        let this = Self {
38            inconsistent_state: register_int_gauge_with_registry!(
39                "global_state_hasher_inconsistent_state",
40                "1 if accumulated live object set differs from GlobalStateHasher root state hash for the previous epoch",
41                registry
42            )
43            .unwrap(),
44        };
45        Arc::new(this)
46    }
47}
48
49pub struct GlobalStateHasher {
50    store: Arc<dyn GlobalStateHashStore>,
51    metrics: Arc<GlobalStateHashMetrics>,
52}
53
54pub trait GlobalStateHashStore: ObjectStore + Send + Sync {
55    /// This function is only called in older protocol versions, and should no longer be used.
56    /// It creates an explicit dependency to tombstones which is not desired.
57    fn get_object_ref_prior_to_key_deprecated(
58        &self,
59        object_id: &ObjectID,
60        version: VersionNumber,
61    ) -> SuiResult<Option<ObjectRef>>;
62
63    fn get_root_state_hash_for_epoch(
64        &self,
65        epoch: EpochId,
66    ) -> SuiResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>>;
67
68    fn insert_state_hash_for_epoch(
69        &self,
70        epoch: EpochId,
71        checkpoint_seq_num: &CheckpointSequenceNumber,
72        acc: &GlobalStateHash,
73    ) -> SuiResult;
74
75    fn iter_live_object_set(
76        &self,
77        include_wrapped_tombstone: bool,
78    ) -> Box<dyn Iterator<Item = LiveObject> + '_>;
79
80    fn iter_cached_live_object_set_for_testing(
81        &self,
82        include_wrapped_tombstone: bool,
83    ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
84        self.iter_live_object_set(include_wrapped_tombstone)
85    }
86}
87
88impl GlobalStateHashStore for InMemoryStorage {
89    fn get_object_ref_prior_to_key_deprecated(
90        &self,
91        _object_id: &ObjectID,
92        _version: VersionNumber,
93    ) -> SuiResult<Option<ObjectRef>> {
94        unreachable!(
95            "get_object_ref_prior_to_key is only called by accumulate_effects_v1, while InMemoryStorage is used by testing and genesis only, which always uses latest protocol "
96        )
97    }
98
99    fn get_root_state_hash_for_epoch(
100        &self,
101        _epoch: EpochId,
102    ) -> SuiResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>> {
103        unreachable!("not used for testing")
104    }
105
106    fn insert_state_hash_for_epoch(
107        &self,
108        _epoch: EpochId,
109        _checkpoint_seq_num: &CheckpointSequenceNumber,
110        _acc: &GlobalStateHash,
111    ) -> SuiResult {
112        unreachable!("not used for testing")
113    }
114
115    fn iter_live_object_set(
116        &self,
117        _include_wrapped_tombstone: bool,
118    ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
119        unreachable!("not used for testing")
120    }
121}
122
123/// Serializable representation of the ObjectRef of an
124/// object that has been wrapped
125/// TODO: This can be replaced with ObjectKey.
126#[derive(Serialize, Debug)]
127pub struct WrappedObject {
128    id: ObjectID,
129    wrapped_at: SequenceNumber,
130    digest: ObjectDigest,
131}
132
133impl WrappedObject {
134    pub fn new(id: ObjectID, wrapped_at: SequenceNumber) -> Self {
135        Self {
136            id,
137            wrapped_at,
138            digest: ObjectDigest::OBJECT_DIGEST_WRAPPED,
139        }
140    }
141}
142
143pub fn accumulate_effects<T, S>(
144    store: S,
145    effects: &[TransactionEffects],
146    protocol_config: &ProtocolConfig,
147) -> GlobalStateHash
148where
149    S: std::ops::Deref<Target = T>,
150    T: GlobalStateHashStore + ?Sized,
151{
152    if protocol_config.enable_effects_v2() {
153        accumulate_effects_v3(effects)
154    } else if protocol_config.simplified_unwrap_then_delete() {
155        accumulate_effects_v2(store, effects)
156    } else {
157        accumulate_effects_v1(store, effects, protocol_config)
158    }
159}
160
161fn accumulate_effects_v1<T, S>(
162    store: S,
163    effects: &[TransactionEffects],
164    protocol_config: &ProtocolConfig,
165) -> GlobalStateHash
166where
167    S: std::ops::Deref<Target = T>,
168    T: GlobalStateHashStore + ?Sized,
169{
170    let mut acc = GlobalStateHash::default();
171
172    // process insertions to the set
173    acc.insert_all(
174        effects
175            .iter()
176            .flat_map(|fx| {
177                fx.all_changed_objects()
178                    .into_iter()
179                    .map(|(oref, _, _)| oref.2)
180            })
181            .collect::<Vec<ObjectDigest>>(),
182    );
183
184    // insert wrapped tombstones. We use a custom struct in order to contain the tombstone
185    // against the object id and sequence number, as the tombstone by itself is not unique.
186    acc.insert_all(
187        effects
188            .iter()
189            .flat_map(|fx| {
190                fx.wrapped()
191                    .iter()
192                    .map(|oref| {
193                        bcs::to_bytes(&WrappedObject::new(oref.0, oref.1))
194                            .unwrap()
195                            .to_vec()
196                    })
197                    .collect::<Vec<Vec<u8>>>()
198            })
199            .collect::<Vec<Vec<u8>>>(),
200    );
201
202    let all_unwrapped = effects
203        .iter()
204        .flat_map(|fx| {
205            fx.unwrapped()
206                .into_iter()
207                .map(|(oref, _owner)| (*fx.transaction_digest(), oref.0, oref.1))
208        })
209        .chain(effects.iter().flat_map(|fx| {
210            fx.unwrapped_then_deleted()
211                .into_iter()
212                .map(|oref| (*fx.transaction_digest(), oref.0, oref.1))
213        }))
214        .collect::<Vec<(TransactionDigest, ObjectID, SequenceNumber)>>();
215
216    let unwrapped_ids: HashMap<TransactionDigest, HashSet<ObjectID>> = all_unwrapped
217        .iter()
218        .map(|(digest, id, _)| (*digest, *id))
219        .into_group_map()
220        .iter()
221        .map(|(digest, ids)| (*digest, HashSet::from_iter(ids.iter().cloned())))
222        .collect();
223
224    // Collect keys from modified_at_versions to remove from the hasher.
225    // Filter all unwrapped objects (from unwrapped or unwrapped_then_deleted effects)
226    // as these were inserted into the hasher as a WrappedObject. Will handle these
227    // separately.
228    let modified_at_version_keys: Vec<ObjectKey> = effects
229        .iter()
230        .flat_map(|fx| {
231            fx.modified_at_versions()
232                .into_iter()
233                .map(|(id, seq_num)| (*fx.transaction_digest(), id, seq_num))
234        })
235        .filter_map(|(tx_digest, id, seq_num)| {
236            // unwrapped tx
237            if let Some(ids) = unwrapped_ids.get(&tx_digest) {
238                // object unwrapped in this tx. We handle it later
239                if ids.contains(&id) {
240                    return None;
241                }
242            }
243            Some(ObjectKey(id, seq_num))
244        })
245        .collect();
246
247    let modified_at_digests: Vec<_> = store
248        .multi_get_objects_by_key(&modified_at_version_keys.clone())
249        .into_iter()
250        .zip_debug_eq(modified_at_version_keys)
251        .map(|(obj, key)| {
252            obj.unwrap_or_else(|| panic!("Object for key {:?} from modified_at_versions effects does not exist in objects table", key))
253                .compute_object_reference()
254                .2
255        })
256        .collect();
257    acc.remove_all(modified_at_digests);
258
259    // Process unwrapped and unwrapped_then_deleted effects, which need to be
260    // removed as WrappedObject using the last sequence number it was tombstoned
261    // against. Since this happened in a past transaction, and the child object may
262    // have been modified since (and hence its sequence number incremented), we
263    // seek the version prior to the unwrapped version from the objects table directly.
264    // If the tombstone is not found, then assume this is a newly created wrapped object hence
265    // we don't expect to find it in the table.
266    let wrapped_objects_to_remove: Vec<WrappedObject> = all_unwrapped
267        .iter()
268        .filter_map(|(_tx_digest, id, seq_num)| {
269            let objref = store
270                .get_object_ref_prior_to_key_deprecated(id, *seq_num)
271                .expect("read cannot fail");
272
273            objref.map(|(id, version, digest)| {
274                assert!(
275                    !protocol_config.loaded_child_objects_fixed() || digest.is_wrapped(),
276                    "{:?}",
277                    id
278                );
279                WrappedObject::new(id, version)
280            })
281        })
282        .collect();
283
284    acc.remove_all(
285        wrapped_objects_to_remove
286            .iter()
287            .map(|wrapped| bcs::to_bytes(wrapped).unwrap().to_vec())
288            .collect::<Vec<Vec<u8>>>(),
289    );
290
291    acc
292}
293
294fn accumulate_effects_v2<T, S>(store: S, effects: &[TransactionEffects]) -> GlobalStateHash
295where
296    S: std::ops::Deref<Target = T>,
297    T: GlobalStateHashStore + ?Sized,
298{
299    let mut acc = GlobalStateHash::default();
300
301    // process insertions to the set
302    acc.insert_all(
303        effects
304            .iter()
305            .flat_map(|fx| {
306                fx.all_changed_objects()
307                    .into_iter()
308                    .map(|(oref, _, _)| oref.2)
309            })
310            .collect::<Vec<ObjectDigest>>(),
311    );
312
313    // Collect keys from modified_at_versions to remove from the hasher.
314    let modified_at_version_keys: Vec<_> = effects
315        .iter()
316        .flat_map(|fx| {
317            fx.modified_at_versions()
318                .into_iter()
319                .map(|(id, version)| ObjectKey(id, version))
320        })
321        .collect();
322
323    let modified_at_digests: Vec<_> = store
324        .multi_get_objects_by_key(&modified_at_version_keys.clone())
325        .into_iter()
326        .zip_debug_eq(modified_at_version_keys)
327        .map(|(obj, key)| {
328            obj.unwrap_or_else(|| panic!("Object for key {:?} from modified_at_versions effects does not exist in objects table", key))
329                .compute_object_reference()
330                .2
331        })
332        .collect();
333    acc.remove_all(modified_at_digests);
334
335    acc
336}
337
338fn accumulate_effects_v3(effects: &[TransactionEffects]) -> GlobalStateHash {
339    let mut acc = GlobalStateHash::default();
340
341    // process insertions to the set
342    acc.insert_all(
343        effects
344            .iter()
345            .flat_map(|fx| {
346                fx.all_changed_objects()
347                    .into_iter()
348                    .map(|(object_ref, _, _)| object_ref.2)
349            })
350            .collect::<Vec<ObjectDigest>>(),
351    );
352
353    // process modified objects to the set
354    acc.remove_all(
355        effects
356            .iter()
357            .flat_map(|fx| {
358                fx.old_object_metadata()
359                    .into_iter()
360                    .map(|(object_ref, _owner)| object_ref.2)
361            })
362            .collect::<Vec<ObjectDigest>>(),
363    );
364
365    acc
366}
367
368impl GlobalStateHasher {
369    pub fn new(store: Arc<dyn GlobalStateHashStore>, metrics: Arc<GlobalStateHashMetrics>) -> Self {
370        Self { store, metrics }
371    }
372
373    pub fn new_for_tests(store: Arc<dyn GlobalStateHashStore>) -> Self {
374        Self::new(store, GlobalStateHashMetrics::new(&Registry::new()))
375    }
376
377    pub fn metrics(&self) -> Arc<GlobalStateHashMetrics> {
378        self.metrics.clone()
379    }
380
381    pub fn set_inconsistent_state(&self, is_inconsistent_state: bool) {
382        self.metrics
383            .inconsistent_state
384            .set(is_inconsistent_state as i64);
385    }
386
387    /// Accumulates the effects of a single checkpoint and persists the hasher.
388    pub fn accumulate_checkpoint(
389        &self,
390        effects: &[TransactionEffects],
391        checkpoint_seq_num: CheckpointSequenceNumber,
392        epoch_store: &AuthorityPerEpochStore,
393    ) -> GlobalStateHash {
394        let _scope = monitored_scope("AccumulateCheckpoint");
395        if let Some(acc) = epoch_store.get_state_hash_for_checkpoint(&checkpoint_seq_num) {
396            return acc;
397        }
398
399        let acc = self.accumulate_effects(effects, epoch_store.protocol_config());
400
401        epoch_store.insert_state_hash_for_checkpoint(&checkpoint_seq_num, &acc);
402        debug!("Accumulated checkpoint {}", checkpoint_seq_num);
403
404        epoch_store
405            .checkpoint_state_notify_read
406            .notify(&checkpoint_seq_num, &acc);
407
408        acc
409    }
410
411    pub fn accumulate_cached_live_object_set_for_testing(
412        &self,
413        include_wrapped_tombstone: bool,
414    ) -> GlobalStateHash {
415        Self::accumulate_live_object_set_impl(
416            self.store
417                .iter_cached_live_object_set_for_testing(include_wrapped_tombstone),
418        )
419    }
420
421    /// Returns the result of accumulating the live object set, without side effects
422    pub fn accumulate_live_object_set(&self, include_wrapped_tombstone: bool) -> GlobalStateHash {
423        Self::accumulate_live_object_set_impl(
424            self.store.iter_live_object_set(include_wrapped_tombstone),
425        )
426    }
427
428    fn accumulate_live_object_set_impl(iter: impl Iterator<Item = LiveObject>) -> GlobalStateHash {
429        let mut acc = GlobalStateHash::default();
430        iter.for_each(|live_object| {
431            Self::accumulate_live_object(&mut acc, &live_object);
432        });
433        acc
434    }
435
436    pub fn accumulate_live_object(acc: &mut GlobalStateHash, live_object: &LiveObject) {
437        match live_object {
438            LiveObject::Normal(object) => {
439                acc.insert(object.compute_object_reference().2);
440            }
441            LiveObject::Wrapped(key) => {
442                acc.insert(
443                    bcs::to_bytes(&WrappedObject::new(key.0, key.1))
444                        .expect("Failed to serialize WrappedObject"),
445                );
446            }
447        }
448    }
449
450    pub fn digest_live_object_set(
451        &self,
452        include_wrapped_tombstone: bool,
453    ) -> ECMHLiveObjectSetDigest {
454        let acc = self.accumulate_live_object_set(include_wrapped_tombstone);
455        acc.digest().into()
456    }
457
458    pub async fn digest_epoch(
459        &self,
460        epoch_store: Arc<AuthorityPerEpochStore>,
461        last_checkpoint_of_epoch: CheckpointSequenceNumber,
462    ) -> SuiResult<ECMHLiveObjectSetDigest> {
463        Ok(self
464            .accumulate_epoch(epoch_store, last_checkpoint_of_epoch)?
465            .digest()
466            .into())
467    }
468
469    pub async fn wait_for_previous_running_root(
470        &self,
471        epoch_store: &AuthorityPerEpochStore,
472        checkpoint_seq_num: CheckpointSequenceNumber,
473    ) -> SuiResult {
474        assert!(checkpoint_seq_num > 0);
475
476        // Check if this is the first checkpoint of the new epoch, in which case
477        // there is nothing to wait for. Look up the previous epoch explicitly: when
478        // this checkpoint is rebuilt after a restart, the root of the current epoch
479        // may already be stored.
480        if let Some(prev_epoch) = epoch_store.epoch().checked_sub(1)
481            && self
482                .store
483                .get_root_state_hash_for_epoch(prev_epoch)?
484                .map(|(last_checkpoint_prev_epoch, _)| last_checkpoint_prev_epoch)
485                == Some(checkpoint_seq_num - 1)
486        {
487            return Ok(());
488        }
489
490        // There is an edge case here where checkpoint_seq_num is 1. This means the previous
491        // checkpoint is the genesis checkpoint. CheckpointExecutor is guaranteed to execute
492        // and accumulate the genesis checkpoint, so this will resolve.
493        epoch_store
494            .notify_read_running_root(checkpoint_seq_num - 1)
495            .await?;
496        Ok(())
497    }
498
499    fn get_prior_root(
500        &self,
501        epoch_store: &AuthorityPerEpochStore,
502        checkpoint_seq_num: CheckpointSequenceNumber,
503    ) -> SuiResult<GlobalStateHash> {
504        if checkpoint_seq_num == 0 {
505            return Ok(GlobalStateHash::default());
506        }
507
508        if let Some(prior_running_root) =
509            epoch_store.get_running_root_state_hash(checkpoint_seq_num - 1)
510        {
511            return Ok(prior_running_root);
512        }
513
514        if let Some((last_checkpoint_prev_epoch, prev_acc)) = self
515            .store
516            .get_root_state_hash_for_epoch(epoch_store.epoch() - 1)?
517            && last_checkpoint_prev_epoch == checkpoint_seq_num - 1
518        {
519            return Ok(prev_acc);
520        }
521
522        fatal!(
523            "Running root state hasher must exist for checkpoint {}",
524            checkpoint_seq_num - 1
525        );
526    }
527
528    // Accumulate the running root.
529    // The previous checkpoint must be accumulated before calling this function, or it will panic.
530    pub fn accumulate_running_root(
531        &self,
532        epoch_store: &AuthorityPerEpochStore,
533        checkpoint_seq_num: CheckpointSequenceNumber,
534        checkpoint_acc: Option<GlobalStateHash>,
535    ) -> SuiResult {
536        let _scope = monitored_scope("AccumulateRunningRoot");
537        tracing::debug!(
538            "accumulating running root for checkpoint {}",
539            checkpoint_seq_num
540        );
541
542        // Idempotency.
543        if epoch_store
544            .get_running_root_state_hash(checkpoint_seq_num)
545            .is_some()
546        {
547            debug!(
548                "accumulate_running_root {:?} {:?} already exists",
549                epoch_store.epoch(),
550                checkpoint_seq_num
551            );
552            return Ok(());
553        }
554
555        let mut running_root = self.get_prior_root(epoch_store, checkpoint_seq_num)?;
556
557        let checkpoint_acc = checkpoint_acc.unwrap_or_else(|| {
558            epoch_store
559                .get_state_hash_for_checkpoint(&checkpoint_seq_num)
560                .expect("Expected checkpoint accumulator to exist")
561        });
562        running_root.union(&checkpoint_acc);
563        epoch_store.insert_running_root_state_hash(&checkpoint_seq_num, &running_root)?;
564        debug!(
565            "Accumulated checkpoint {} to running root accumulator",
566            checkpoint_seq_num,
567        );
568        Ok(())
569    }
570
571    pub fn accumulate_epoch(
572        &self,
573        epoch_store: Arc<AuthorityPerEpochStore>,
574        last_checkpoint_of_epoch: CheckpointSequenceNumber,
575    ) -> SuiResult<GlobalStateHash> {
576        let _scope = monitored_scope("AccumulateEpochV2");
577        let running_root = epoch_store
578            .get_running_root_state_hash(last_checkpoint_of_epoch)
579            .expect("Expected running root accumulator to exist up to last checkpoint of epoch");
580
581        self.store.insert_state_hash_for_epoch(
582            epoch_store.epoch(),
583            &last_checkpoint_of_epoch,
584            &running_root,
585        )?;
586        debug!(
587            "Finalized root state hash for epoch {} (up to checkpoint {})",
588            epoch_store.epoch(),
589            last_checkpoint_of_epoch
590        );
591        Ok(running_root.clone())
592    }
593
594    pub fn accumulate_effects(
595        &self,
596        effects: &[TransactionEffects],
597        protocol_config: &ProtocolConfig,
598    ) -> GlobalStateHash {
599        accumulate_effects(&*self.store, effects, protocol_config)
600    }
601}