1use 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 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#[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 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 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 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 if let Some(ids) = unwrapped_ids.get(&tx_digest) {
238 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 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 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 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 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 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 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 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 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 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 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 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}