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(effects.iter().flat_map(|fx| {
174 fx.all_changed_objects()
175 .into_iter()
176 .map(|(oref, _, _)| oref.2)
177 }));
178
179 acc.insert_all(effects.iter().flat_map(|fx| {
182 fx.wrapped()
183 .into_iter()
184 .map(|oref| bcs::to_bytes(&WrappedObject::new(oref.0, oref.1)).unwrap())
185 }));
186
187 let all_unwrapped = effects
188 .iter()
189 .flat_map(|fx| {
190 fx.unwrapped()
191 .into_iter()
192 .map(|(oref, _owner)| (*fx.transaction_digest(), oref.0, oref.1))
193 })
194 .chain(effects.iter().flat_map(|fx| {
195 fx.unwrapped_then_deleted()
196 .into_iter()
197 .map(|oref| (*fx.transaction_digest(), oref.0, oref.1))
198 }))
199 .collect::<Vec<(TransactionDigest, ObjectID, SequenceNumber)>>();
200
201 let unwrapped_ids: HashMap<TransactionDigest, HashSet<ObjectID>> = all_unwrapped
202 .iter()
203 .map(|(digest, id, _)| (*digest, *id))
204 .into_group_map()
205 .iter()
206 .map(|(digest, ids)| (*digest, HashSet::from_iter(ids.iter().cloned())))
207 .collect();
208
209 let modified_at_version_keys: Vec<ObjectKey> = effects
214 .iter()
215 .flat_map(|fx| {
216 fx.modified_at_versions()
217 .into_iter()
218 .map(|(id, seq_num)| (*fx.transaction_digest(), id, seq_num))
219 })
220 .filter_map(|(tx_digest, id, seq_num)| {
221 if let Some(ids) = unwrapped_ids.get(&tx_digest) {
223 if ids.contains(&id) {
225 return None;
226 }
227 }
228 Some(ObjectKey(id, seq_num))
229 })
230 .collect();
231
232 let modified_at_digests = store
233 .multi_get_objects_by_key(&modified_at_version_keys.clone())
234 .into_iter()
235 .zip_debug_eq(modified_at_version_keys)
236 .map(|(obj, key)| {
237 obj.unwrap_or_else(|| panic!("Object for key {:?} from modified_at_versions effects does not exist in objects table", key))
238 .compute_object_reference()
239 .2
240 });
241 acc.remove_all(modified_at_digests);
242
243 let wrapped_objects_to_remove = all_unwrapped
251 .iter()
252 .filter_map(|(_tx_digest, id, seq_num)| {
253 let objref = store
254 .get_object_ref_prior_to_key_deprecated(id, *seq_num)
255 .expect("read cannot fail");
256
257 objref.map(|(id, version, digest)| {
258 assert!(
259 !protocol_config.loaded_child_objects_fixed() || digest.is_wrapped(),
260 "{:?}",
261 id
262 );
263 WrappedObject::new(id, version)
264 })
265 });
266
267 acc.remove_all(wrapped_objects_to_remove.map(|wrapped| bcs::to_bytes(&wrapped).unwrap()));
268
269 acc
270}
271
272fn accumulate_effects_v2<T, S>(store: S, effects: &[TransactionEffects]) -> GlobalStateHash
273where
274 S: std::ops::Deref<Target = T>,
275 T: GlobalStateHashStore + ?Sized,
276{
277 let mut acc = GlobalStateHash::default();
278
279 acc.insert_all(effects.iter().flat_map(|fx| {
281 fx.all_changed_objects()
282 .into_iter()
283 .map(|(oref, _, _)| oref.2)
284 }));
285
286 let modified_at_version_keys: Vec<_> = effects
288 .iter()
289 .flat_map(|fx| {
290 fx.modified_at_versions()
291 .into_iter()
292 .map(|(id, version)| ObjectKey(id, version))
293 })
294 .collect();
295
296 let modified_at_digests = store
297 .multi_get_objects_by_key(&modified_at_version_keys.clone())
298 .into_iter()
299 .zip_debug_eq(modified_at_version_keys)
300 .map(|(obj, key)| {
301 obj.unwrap_or_else(|| panic!("Object for key {:?} from modified_at_versions effects does not exist in objects table", key))
302 .compute_object_reference()
303 .2
304 });
305 acc.remove_all(modified_at_digests);
306
307 acc
308}
309
310fn accumulate_effects_v3(effects: &[TransactionEffects]) -> GlobalStateHash {
311 let mut acc = GlobalStateHash::default();
312 acc.insert_all(effects.iter().flat_map(|fx| {
313 fx.all_changed_objects()
314 .into_iter()
315 .map(|(object_ref, _, _)| object_ref.2)
316 }));
317 acc.remove_all(effects.iter().flat_map(|fx| {
318 fx.old_object_metadata()
319 .into_iter()
320 .map(|(object_ref, _owner)| object_ref.2)
321 }));
322 acc
323}
324
325impl GlobalStateHasher {
326 pub fn new(store: Arc<dyn GlobalStateHashStore>, metrics: Arc<GlobalStateHashMetrics>) -> Self {
327 Self { store, metrics }
328 }
329
330 pub fn new_for_tests(store: Arc<dyn GlobalStateHashStore>) -> Self {
331 Self::new(store, GlobalStateHashMetrics::new(&Registry::new()))
332 }
333
334 pub fn metrics(&self) -> Arc<GlobalStateHashMetrics> {
335 self.metrics.clone()
336 }
337
338 pub fn set_inconsistent_state(&self, is_inconsistent_state: bool) {
339 self.metrics
340 .inconsistent_state
341 .set(is_inconsistent_state as i64);
342 }
343
344 pub fn accumulate_checkpoint(
346 &self,
347 effects: &[TransactionEffects],
348 checkpoint_seq_num: CheckpointSequenceNumber,
349 epoch_store: &AuthorityPerEpochStore,
350 ) -> GlobalStateHash {
351 let _scope = monitored_scope("AccumulateCheckpoint");
352 if let Some(acc) = epoch_store.get_state_hash_for_checkpoint(&checkpoint_seq_num) {
353 return acc;
354 }
355
356 let acc = self.accumulate_effects(effects, epoch_store.protocol_config());
357
358 epoch_store.insert_state_hash_for_checkpoint(&checkpoint_seq_num, &acc);
359 debug!("Accumulated checkpoint {}", checkpoint_seq_num);
360
361 epoch_store
362 .checkpoint_state_notify_read
363 .notify(&checkpoint_seq_num, &acc);
364
365 acc
366 }
367
368 pub fn accumulate_cached_live_object_set_for_testing(
369 &self,
370 include_wrapped_tombstone: bool,
371 ) -> GlobalStateHash {
372 Self::accumulate_live_object_set_impl(
373 self.store
374 .iter_cached_live_object_set_for_testing(include_wrapped_tombstone),
375 )
376 }
377
378 pub fn accumulate_live_object_set(&self, include_wrapped_tombstone: bool) -> GlobalStateHash {
380 Self::accumulate_live_object_set_impl(
381 self.store.iter_live_object_set(include_wrapped_tombstone),
382 )
383 }
384
385 fn accumulate_live_object_set_impl(iter: impl Iterator<Item = LiveObject>) -> GlobalStateHash {
386 let mut acc = GlobalStateHash::default();
387 iter.for_each(|live_object| {
388 Self::accumulate_live_object(&mut acc, &live_object);
389 });
390 acc
391 }
392
393 pub fn accumulate_live_object(acc: &mut GlobalStateHash, live_object: &LiveObject) {
394 match live_object {
395 LiveObject::Normal(object) => {
396 acc.insert(object.compute_object_reference().2);
397 }
398 LiveObject::Wrapped(key) => {
399 acc.insert(
400 bcs::to_bytes(&WrappedObject::new(key.0, key.1))
401 .expect("Failed to serialize WrappedObject"),
402 );
403 }
404 }
405 }
406
407 pub fn digest_live_object_set(
408 &self,
409 include_wrapped_tombstone: bool,
410 ) -> ECMHLiveObjectSetDigest {
411 let acc = self.accumulate_live_object_set(include_wrapped_tombstone);
412 acc.digest().into()
413 }
414
415 pub async fn digest_epoch(
416 &self,
417 epoch_store: Arc<AuthorityPerEpochStore>,
418 last_checkpoint_of_epoch: CheckpointSequenceNumber,
419 ) -> SuiResult<ECMHLiveObjectSetDigest> {
420 Ok(self
421 .accumulate_epoch(epoch_store, last_checkpoint_of_epoch)?
422 .digest()
423 .into())
424 }
425
426 pub async fn wait_for_previous_running_root(
427 &self,
428 epoch_store: &AuthorityPerEpochStore,
429 checkpoint_seq_num: CheckpointSequenceNumber,
430 ) -> SuiResult {
431 assert!(checkpoint_seq_num > 0);
432
433 if let Some(prev_epoch) = epoch_store.epoch().checked_sub(1)
438 && self
439 .store
440 .get_root_state_hash_for_epoch(prev_epoch)?
441 .map(|(last_checkpoint_prev_epoch, _)| last_checkpoint_prev_epoch)
442 == Some(checkpoint_seq_num - 1)
443 {
444 return Ok(());
445 }
446
447 epoch_store
451 .notify_read_running_root(checkpoint_seq_num - 1)
452 .await?;
453 Ok(())
454 }
455
456 fn get_prior_root(
457 &self,
458 epoch_store: &AuthorityPerEpochStore,
459 checkpoint_seq_num: CheckpointSequenceNumber,
460 ) -> SuiResult<GlobalStateHash> {
461 if checkpoint_seq_num == 0 {
462 return Ok(GlobalStateHash::default());
463 }
464
465 if let Some(prior_running_root) =
466 epoch_store.get_running_root_state_hash(checkpoint_seq_num - 1)
467 {
468 return Ok(prior_running_root);
469 }
470
471 if let Some((last_checkpoint_prev_epoch, prev_acc)) = self
472 .store
473 .get_root_state_hash_for_epoch(epoch_store.epoch() - 1)?
474 && last_checkpoint_prev_epoch == checkpoint_seq_num - 1
475 {
476 return Ok(prev_acc);
477 }
478
479 fatal!(
480 "Running root state hasher must exist for checkpoint {}",
481 checkpoint_seq_num - 1
482 );
483 }
484
485 pub fn accumulate_running_root(
488 &self,
489 epoch_store: &AuthorityPerEpochStore,
490 checkpoint_seq_num: CheckpointSequenceNumber,
491 checkpoint_acc: Option<GlobalStateHash>,
492 ) -> SuiResult {
493 let _scope = monitored_scope("AccumulateRunningRoot");
494 tracing::debug!(
495 "accumulating running root for checkpoint {}",
496 checkpoint_seq_num
497 );
498
499 if epoch_store
501 .get_running_root_state_hash(checkpoint_seq_num)
502 .is_some()
503 {
504 debug!(
505 "accumulate_running_root {:?} {:?} already exists",
506 epoch_store.epoch(),
507 checkpoint_seq_num
508 );
509 return Ok(());
510 }
511
512 let mut running_root = self.get_prior_root(epoch_store, checkpoint_seq_num)?;
513
514 let checkpoint_acc = checkpoint_acc.unwrap_or_else(|| {
515 epoch_store
516 .get_state_hash_for_checkpoint(&checkpoint_seq_num)
517 .expect("Expected checkpoint accumulator to exist")
518 });
519 running_root.union(&checkpoint_acc);
520 epoch_store.insert_running_root_state_hash(&checkpoint_seq_num, &running_root)?;
521 debug!(
522 "Accumulated checkpoint {} to running root accumulator",
523 checkpoint_seq_num,
524 );
525 Ok(())
526 }
527
528 pub fn accumulate_epoch(
529 &self,
530 epoch_store: Arc<AuthorityPerEpochStore>,
531 last_checkpoint_of_epoch: CheckpointSequenceNumber,
532 ) -> SuiResult<GlobalStateHash> {
533 let _scope = monitored_scope("AccumulateEpochV2");
534 let running_root = epoch_store
535 .get_running_root_state_hash(last_checkpoint_of_epoch)
536 .expect("Expected running root accumulator to exist up to last checkpoint of epoch");
537
538 self.store.insert_state_hash_for_epoch(
539 epoch_store.epoch(),
540 &last_checkpoint_of_epoch,
541 &running_root,
542 )?;
543 debug!(
544 "Finalized root state hash for epoch {} (up to checkpoint {})",
545 epoch_store.epoch(),
546 last_checkpoint_of_epoch
547 );
548 Ok(running_root.clone())
549 }
550
551 pub fn accumulate_effects(
552 &self,
553 effects: &[TransactionEffects],
554 protocol_config: &ProtocolConfig,
555 ) -> GlobalStateHash {
556 accumulate_effects(&*self.store, effects, protocol_config)
557 }
558}