1use super::object_change::{AccumulatorWriteV1, ObjectIn, ObjectOut};
5use super::{EffectsObjectChange, IDOperation, ObjectChange};
6use crate::accumulator_event::AccumulatorEvent;
7use crate::accumulator_root::AccumulatorObjId;
8use crate::base_types::{
9 EpochId, ObjectDigest, ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest,
10 VersionDigest,
11};
12use crate::digests::{EffectsAuxDataDigest, TransactionEventsDigest};
13use crate::effects::{InputConsensusObject, TransactionEffectsAPI};
14use crate::execution::SharedInput;
15use crate::execution_status::{
16 ExecutionErrorKind, ExecutionFailure, ExecutionStatus, MoveLocation,
17};
18use crate::gas::GasCostSummary;
19#[cfg(debug_assertions)]
20use crate::is_system_package;
21use crate::object::{OBJECT_START_VERSION, Owner};
22use crate::transaction::SharedObjectMutability;
23use serde::{Deserialize, Serialize};
24#[cfg(debug_assertions)]
25use std::collections::HashSet;
26use std::collections::{BTreeMap, BTreeSet};
27
28#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
30pub struct TransactionEffectsV2 {
31 pub(crate) status: ExecutionStatus,
33 pub(crate) executed_epoch: EpochId,
35 pub(crate) gas_used: GasCostSummary,
36 pub(crate) transaction_digest: TransactionDigest,
38 pub(crate) gas_object_index: Option<u32>,
42 pub(crate) events_digest: Option<TransactionEventsDigest>,
45 pub(crate) dependencies: Vec<TransactionDigest>,
47
48 pub(crate) lamport_version: SequenceNumber,
50 pub(crate) changed_objects: Vec<(ObjectID, EffectsObjectChange)>,
58 pub(crate) unchanged_consensus_objects: Vec<(ObjectID, UnchangedConsensusKind)>,
63 pub(crate) aux_data_digest: Option<EffectsAuxDataDigest>,
67}
68
69impl TransactionEffectsAPI for TransactionEffectsV2 {
70 fn status(&self) -> &ExecutionStatus {
71 &self.status
72 }
73
74 fn into_status(self) -> ExecutionStatus {
75 self.status
76 }
77
78 fn executed_epoch(&self) -> EpochId {
79 self.executed_epoch
80 }
81
82 fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> {
83 self.changed_objects
84 .iter()
85 .filter_map(|(id, change)| {
86 if let ObjectIn::Exist(((version, _digest), _owner)) = &change.input_state {
87 Some((*id, *version))
88 } else {
89 None
90 }
91 })
92 .collect()
93 }
94
95 fn move_abort(&self) -> Option<(MoveLocation, u64)> {
96 let ExecutionStatus::Failure(ExecutionFailure {
97 error: ExecutionErrorKind::MoveAbort(move_location, code),
98 ..
99 }) = self.status()
100 else {
101 return None;
102 };
103 Some((move_location.clone(), *code))
104 }
105
106 fn lamport_version(&self) -> SequenceNumber {
107 self.lamport_version
108 }
109
110 fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
111 self.changed_objects
112 .iter()
113 .filter_map(|(id, change)| {
114 if let ObjectIn::Exist(((version, digest), owner)) = &change.input_state {
115 Some(((*id, *version, *digest), owner.clone()))
116 } else {
117 None
118 }
119 })
120 .collect()
121 }
122
123 fn input_consensus_objects(&self) -> Vec<InputConsensusObject> {
124 self.changed_objects
125 .iter()
126 .filter_map(|(id, change)| match &change.input_state {
127 ObjectIn::Exist(((version, digest), owner)) if owner.is_consensus() => {
128 Some(InputConsensusObject::Mutate((*id, *version, *digest)))
129 }
130 _ => None,
131 })
132 .chain(
133 self.unchanged_consensus_objects
134 .iter()
135 .filter_map(|(id, change_kind)| match change_kind {
136 UnchangedConsensusKind::ReadOnlyRoot((version, digest)) => {
137 Some(InputConsensusObject::ReadOnly((*id, *version, *digest)))
138 }
139 UnchangedConsensusKind::MutateConsensusStreamEnded(seqno) => Some(
140 InputConsensusObject::MutateConsensusStreamEnded(*id, *seqno),
141 ),
142 UnchangedConsensusKind::ReadConsensusStreamEnded(seqno) => {
143 Some(InputConsensusObject::ReadConsensusStreamEnded(*id, *seqno))
144 }
145 UnchangedConsensusKind::Cancelled(seqno) => {
146 Some(InputConsensusObject::Cancelled(*id, *seqno))
147 }
148 UnchangedConsensusKind::PerEpochConfig => None,
152 }),
153 )
154 .collect()
155 }
156
157 fn created(&self) -> Vec<(ObjectRef, Owner)> {
158 self.changed_objects
159 .iter()
160 .filter_map(|(id, change)| {
161 match (
162 &change.input_state,
163 &change.output_state,
164 &change.id_operation,
165 ) {
166 (
167 ObjectIn::NotExist,
168 ObjectOut::ObjectWrite((digest, owner)),
169 IDOperation::Created,
170 ) => Some(((*id, self.lamport_version, *digest), owner.clone())),
171 (
172 ObjectIn::NotExist,
173 ObjectOut::PackageWrite((version, digest)),
174 IDOperation::Created,
175 ) => Some(((*id, *version, *digest), Owner::Immutable)),
176 _ => None,
177 }
178 })
179 .collect()
180 }
181
182 fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
183 self.changed_objects
184 .iter()
185 .filter_map(
186 |(id, change)| match (&change.input_state, &change.output_state) {
187 (ObjectIn::Exist(_), ObjectOut::ObjectWrite((digest, owner))) => {
188 Some(((*id, self.lamport_version, *digest), owner.clone()))
189 }
190 (ObjectIn::Exist(_), ObjectOut::PackageWrite((version, digest))) => {
191 Some(((*id, *version, *digest), Owner::Immutable))
192 }
193 _ => None,
194 },
195 )
196 .collect()
197 }
198
199 fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
200 self.changed_objects
201 .iter()
202 .filter_map(|(id, change)| {
203 match (
204 &change.input_state,
205 &change.output_state,
206 &change.id_operation,
207 ) {
208 (
209 ObjectIn::NotExist,
210 ObjectOut::ObjectWrite((digest, owner)),
211 IDOperation::None,
212 ) => Some(((*id, self.lamport_version, *digest), owner.clone())),
213 _ => None,
214 }
215 })
216 .collect()
217 }
218
219 fn deleted(&self) -> Vec<ObjectRef> {
220 self.changed_objects
221 .iter()
222 .filter_map(|(id, change)| {
223 match (
224 &change.input_state,
225 &change.output_state,
226 &change.id_operation,
227 ) {
228 (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::Deleted) => Some((
229 *id,
230 self.lamport_version,
231 ObjectDigest::OBJECT_DIGEST_DELETED,
232 )),
233 _ => None,
234 }
235 })
236 .collect()
237 }
238
239 fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
240 self.changed_objects
241 .iter()
242 .filter_map(|(id, change)| {
243 match (
244 &change.input_state,
245 &change.output_state,
246 &change.id_operation,
247 ) {
248 (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => Some((
249 *id,
250 self.lamport_version,
251 ObjectDigest::OBJECT_DIGEST_DELETED,
252 )),
253 _ => None,
254 }
255 })
256 .collect()
257 }
258
259 fn wrapped(&self) -> Vec<ObjectRef> {
260 self.changed_objects
261 .iter()
262 .filter_map(|(id, change)| {
263 match (
264 &change.input_state,
265 &change.output_state,
266 &change.id_operation,
267 ) {
268 (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::None) => Some((
269 *id,
270 self.lamport_version,
271 ObjectDigest::OBJECT_DIGEST_WRAPPED,
272 )),
273 _ => None,
274 }
275 })
276 .collect()
277 }
278
279 fn written(&self) -> Vec<ObjectRef> {
280 self.changed_objects
281 .iter()
282 .filter_map(
283 |(id, change)| match (&change.output_state, &change.id_operation) {
284 (ObjectOut::NotExist, IDOperation::Deleted) => Some((
285 *id,
286 self.lamport_version,
287 ObjectDigest::OBJECT_DIGEST_DELETED,
288 )),
289 (ObjectOut::NotExist, IDOperation::None) => Some((
290 *id,
291 self.lamport_version,
292 ObjectDigest::OBJECT_DIGEST_WRAPPED,
293 )),
294 (ObjectOut::ObjectWrite((d, _)), _) => Some((*id, self.lamport_version, *d)),
295 (ObjectOut::PackageWrite(vd), _) => Some((*id, vd.0, vd.1)),
296 (ObjectOut::AccumulatorWriteV1(_), _) => None,
297 _ => None,
298 },
299 )
300 .collect()
301 }
302
303 fn transferred_from_consensus(&self) -> Vec<ObjectRef> {
304 self.changed_objects
305 .iter()
306 .filter_map(|(id, change)| {
307 match (
308 &change.input_state,
309 &change.output_state,
310 &change.id_operation,
311 ) {
312 (
313 ObjectIn::Exist((_, Owner::ConsensusAddressOwner { .. })),
314 ObjectOut::ObjectWrite((
315 object_digest,
316 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable,
317 )),
318 IDOperation::None,
319 ) => Some((*id, self.lamport_version, *object_digest)),
320 _ => None,
321 }
322 })
323 .collect()
324 }
325
326 fn transferred_to_consensus(&self) -> Vec<ObjectRef> {
327 self.changed_objects
328 .iter()
329 .filter_map(|(id, change)| {
330 match (
331 &change.input_state,
332 &change.output_state,
333 &change.id_operation,
334 ) {
335 (
336 ObjectIn::Exist((_, Owner::AddressOwner(_) | Owner::ObjectOwner(_))),
337 ObjectOut::ObjectWrite((
338 object_digest,
339 Owner::ConsensusAddressOwner { .. },
340 )),
341 IDOperation::None,
342 ) => Some((*id, self.lamport_version, *object_digest)),
343 _ => None,
344 }
345 })
346 .collect()
347 }
348
349 fn consensus_owner_changed(&self) -> Vec<ObjectRef> {
350 self.changed_objects
351 .iter()
352 .filter_map(|(id, change)| {
353 match (
354 &change.input_state,
355 &change.output_state,
356 &change.id_operation,
357 ) {
358 (
359 ObjectIn::Exist((
360 _,
361 Owner::ConsensusAddressOwner {
362 owner: old_owner, ..
363 },
364 )),
365 ObjectOut::ObjectWrite((
366 object_digest,
367 Owner::ConsensusAddressOwner {
368 owner: new_owner, ..
369 },
370 )),
371 IDOperation::None,
372 ) if old_owner != new_owner => {
373 Some((*id, self.lamport_version, *object_digest))
374 }
375 _ => None,
376 }
377 })
378 .collect()
379 }
380
381 fn object_changes(&self) -> Vec<ObjectChange> {
382 self.changed_objects
383 .iter()
384 .filter_map(|(id, change)| {
385 let input_version_digest = match &change.input_state {
386 ObjectIn::NotExist => None,
387 ObjectIn::Exist((vd, _)) => Some(*vd),
388 };
389
390 let output_version_digest = match &change.output_state {
391 ObjectOut::NotExist => None,
392 ObjectOut::ObjectWrite((d, _)) => Some((self.lamport_version, *d)),
393 ObjectOut::PackageWrite(vd) => Some(*vd),
394 ObjectOut::AccumulatorWriteV1(_) => {
395 return None;
396 }
397 };
398
399 Some(ObjectChange {
400 id: *id,
401
402 input_version: input_version_digest.map(|k| k.0),
403 input_digest: input_version_digest.map(|k| k.1),
404
405 output_version: output_version_digest.map(|k| k.0),
406 output_digest: output_version_digest.map(|k| k.1),
407
408 id_operation: change.id_operation,
409 })
410 })
411 .collect()
412 }
413
414 fn published_packages(&self) -> Vec<ObjectID> {
415 self.changed_objects
416 .iter()
417 .filter_map(|(id, change)| {
418 if matches!(&change.output_state, ObjectOut::PackageWrite(_)) {
419 Some(*id)
420 } else {
421 None
422 }
423 })
424 .collect()
425 }
426
427 fn accumulator_events(&self) -> Vec<AccumulatorEvent> {
428 self.changed_objects
429 .iter()
430 .filter_map(|(id, change)| match &change.output_state {
431 ObjectOut::AccumulatorWriteV1(write) => Some(AccumulatorEvent::new(
432 AccumulatorObjId::new_unchecked(*id),
433 write.clone(),
434 )),
435 _ => None,
436 })
437 .collect()
438 }
439
440 fn gas_object(&self) -> (ObjectRef, Owner) {
441 if let Some(gas_object_index) = self.gas_object_index {
442 let entry = &self.changed_objects[gas_object_index as usize];
443 match &entry.1.output_state {
444 ObjectOut::ObjectWrite((digest, owner)) => {
445 ((entry.0, self.lamport_version, *digest), owner.clone())
446 }
447 _ => panic!("Gas object must be an ObjectWrite in changed_objects"),
448 }
449 } else {
450 (
451 (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
452 Owner::AddressOwner(SuiAddress::default()),
453 )
454 }
455 }
456
457 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
458 self.events_digest.as_ref()
459 }
460
461 fn dependencies(&self) -> &[TransactionDigest] {
462 &self.dependencies
463 }
464
465 fn transaction_digest(&self) -> &TransactionDigest {
466 &self.transaction_digest
467 }
468
469 fn gas_cost_summary(&self) -> &GasCostSummary {
470 &self.gas_used
471 }
472
473 fn unchanged_consensus_objects(&self) -> Vec<(ObjectID, UnchangedConsensusKind)> {
474 self.unchanged_consensus_objects.clone()
475 }
476
477 fn accumulator_updates(&self) -> Vec<(ObjectID, AccumulatorWriteV1)> {
478 self.changed_objects
479 .iter()
480 .filter_map(|(id, change)| match &change.output_state {
481 ObjectOut::AccumulatorWriteV1(update) => Some((*id, update.clone())),
482 _ => None,
483 })
484 .collect()
485 }
486
487 fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
488 &mut self.status
489 }
490
491 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
492 &mut self.gas_used
493 }
494
495 fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
496 &mut self.transaction_digest
497 }
498
499 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
500 &mut self.dependencies
501 }
502
503 fn unsafe_add_input_consensus_object_for_testing(&mut self, kind: InputConsensusObject) {
504 match kind {
505 InputConsensusObject::Mutate(obj_ref) => self.changed_objects.push((
506 obj_ref.0,
507 EffectsObjectChange {
508 input_state: ObjectIn::Exist((
509 (obj_ref.1, obj_ref.2),
510 Owner::Shared {
511 initial_shared_version: OBJECT_START_VERSION,
512 },
513 )),
514 output_state: ObjectOut::ObjectWrite((
515 obj_ref.2,
516 Owner::Shared {
517 initial_shared_version: obj_ref.1,
518 },
519 )),
520 id_operation: IDOperation::None,
521 },
522 )),
523 InputConsensusObject::ReadOnly(obj_ref) => self.unchanged_consensus_objects.push((
524 obj_ref.0,
525 UnchangedConsensusKind::ReadOnlyRoot((obj_ref.1, obj_ref.2)),
526 )),
527 InputConsensusObject::ReadConsensusStreamEnded(obj_id, seqno) => {
528 self.unchanged_consensus_objects.push((
529 obj_id,
530 UnchangedConsensusKind::ReadConsensusStreamEnded(seqno),
531 ))
532 }
533 InputConsensusObject::MutateConsensusStreamEnded(obj_id, seqno) => {
534 self.unchanged_consensus_objects.push((
535 obj_id,
536 UnchangedConsensusKind::MutateConsensusStreamEnded(seqno),
537 ))
538 }
539 InputConsensusObject::Cancelled(obj_id, seqno) => self
540 .unchanged_consensus_objects
541 .push((obj_id, UnchangedConsensusKind::Cancelled(seqno))),
542 }
543 }
544
545 fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef) {
546 self.changed_objects.push((
547 obj_ref.0,
548 EffectsObjectChange {
549 input_state: ObjectIn::Exist((
550 (obj_ref.1, obj_ref.2),
551 Owner::AddressOwner(SuiAddress::default()),
552 )),
553 output_state: ObjectOut::ObjectWrite((
554 obj_ref.2,
555 Owner::AddressOwner(SuiAddress::default()),
556 )),
557 id_operation: IDOperation::None,
558 },
559 ))
560 }
561
562 fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef) {
563 self.changed_objects.push((
564 obj_ref.0,
565 EffectsObjectChange {
566 input_state: ObjectIn::Exist((
567 (obj_ref.1, obj_ref.2),
568 Owner::AddressOwner(SuiAddress::default()),
569 )),
570 output_state: ObjectOut::NotExist,
571 id_operation: IDOperation::Deleted,
572 },
573 ))
574 }
575}
576
577impl TransactionEffectsV2 {
578 pub fn new(
579 status: ExecutionStatus,
580 executed_epoch: EpochId,
581 gas_used: GasCostSummary,
582 shared_objects: Vec<SharedInput>,
583 loaded_per_epoch_config_objects: BTreeSet<ObjectID>,
584 transaction_digest: TransactionDigest,
585 lamport_version: SequenceNumber,
586 changed_objects: BTreeMap<ObjectID, EffectsObjectChange>,
587 gas_object: Option<ObjectID>,
588 events_digest: Option<TransactionEventsDigest>,
589 dependencies: Vec<TransactionDigest>,
590 ) -> Self {
591 let unchanged_consensus_objects = shared_objects
592 .into_iter()
593 .filter_map(|shared_input| match shared_input {
594 SharedInput::Existing((id, version, digest)) => {
595 if changed_objects.contains_key(&id) {
596 None
597 } else {
598 Some((id, UnchangedConsensusKind::ReadOnlyRoot((version, digest))))
599 }
600 }
601 SharedInput::ConsensusStreamEnded((id, version, mutability, _)) => {
602 debug_assert!(!changed_objects.contains_key(&id));
603 match mutability {
604 SharedObjectMutability::Mutable => Some((
605 id,
606 UnchangedConsensusKind::MutateConsensusStreamEnded(version),
607 )),
608 SharedObjectMutability::Immutable => Some((
609 id,
610 UnchangedConsensusKind::ReadConsensusStreamEnded(version),
611 )),
612 SharedObjectMutability::NonExclusiveWrite => Some((
615 id,
616 UnchangedConsensusKind::MutateConsensusStreamEnded(version),
617 )),
618 }
619 }
620 SharedInput::Cancelled((id, version)) => {
621 debug_assert!(!changed_objects.contains_key(&id));
622 Some((id, UnchangedConsensusKind::Cancelled(version)))
623 }
624 })
625 .chain(
626 loaded_per_epoch_config_objects
627 .into_iter()
628 .map(|id| (id, UnchangedConsensusKind::PerEpochConfig)),
629 )
630 .collect();
631 let changed_objects: Vec<_> = changed_objects.into_iter().collect();
632
633 let gas_object_index = gas_object.map(|gas_id| {
634 changed_objects
635 .iter()
636 .position(|(id, _)| id == &gas_id)
637 .unwrap() as u32
638 });
639
640 let result = Self {
641 status,
642 executed_epoch,
643 gas_used,
644 transaction_digest,
645 lamport_version,
646 changed_objects,
647 unchanged_consensus_objects,
648 gas_object_index,
649 events_digest,
650 dependencies,
651 aux_data_digest: None,
652 };
653 #[cfg(debug_assertions)]
654 result.check_invariant();
655
656 result
657 }
658
659 #[cfg(debug_assertions)]
662 fn check_invariant(&self) {
663 let mut unique_ids = HashSet::new();
664 for (id, change) in &self.changed_objects {
665 assert!(unique_ids.insert(*id));
666 match (
667 &change.input_state,
668 &change.output_state,
669 &change.id_operation,
670 ) {
671 (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Created) => {
672 }
674 (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => {
675 }
677 (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => {
678 assert!(!owner.is_shared());
681 }
682 (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => {
683 }
685 (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => {
686 }
688 (
689 ObjectIn::Exist(((old_version, _), old_owner)),
690 ObjectOut::NotExist,
691 IDOperation::None,
692 ) => {
693 assert!(old_version.value() < self.lamport_version.value());
695 assert!(
696 !old_owner.is_shared() && !old_owner.is_immutable(),
697 "Cannot wrap shared or immutable object"
698 );
699 }
700 (
701 ObjectIn::Exist(((old_version, _), old_owner)),
702 ObjectOut::NotExist,
703 IDOperation::Deleted,
704 ) => {
705 assert!(old_version.value() < self.lamport_version.value());
707 assert!(!old_owner.is_immutable(), "Cannot delete immutable object");
708 }
709 (
710 ObjectIn::Exist(((old_version, old_digest), old_owner)),
711 ObjectOut::ObjectWrite((new_digest, new_owner)),
712 IDOperation::None,
713 ) => {
714 assert!(old_version.value() < self.lamport_version.value());
716 assert_ne!(old_digest, new_digest);
717 assert!(!old_owner.is_immutable(), "Cannot mutate immutable object");
718 if old_owner.is_shared() {
719 assert!(new_owner.is_shared(), "Cannot un-share an object");
720 } else {
721 assert!(!new_owner.is_shared(), "Cannot share an existing object");
722 }
723 }
724 (
725 ObjectIn::Exist(((old_version, old_digest), old_owner)),
726 ObjectOut::PackageWrite((new_version, new_digest)),
727 IDOperation::None,
728 ) => {
729 assert!(
731 old_owner.is_immutable() && is_system_package(*id),
732 "Must be a system package"
733 );
734 assert_eq!(old_version.value() + 1, new_version.value());
735 assert_ne!(old_digest, new_digest);
736 }
737 (ObjectIn::NotExist, ObjectOut::AccumulatorWriteV1(_), IDOperation::None) => {
738 }
740 _ => {
741 panic!("Impossible object change: {:?}, {:?}", id, change);
742 }
743 }
744 }
745 let (_, owner) = self.gas_object();
747 assert!(matches!(owner, Owner::AddressOwner(_)));
748
749 for (id, _) in &self.unchanged_consensus_objects {
750 assert!(
751 unique_ids.insert(*id),
752 "Duplicate object id: {:?}\n{:#?}",
753 id,
754 self
755 );
756 }
757 }
758}
759
760impl Default for TransactionEffectsV2 {
761 fn default() -> Self {
762 Self {
763 status: ExecutionStatus::Success,
764 executed_epoch: 0,
765 gas_used: GasCostSummary::default(),
766 transaction_digest: TransactionDigest::default(),
767 lamport_version: SequenceNumber::default(),
768 changed_objects: vec![],
769 unchanged_consensus_objects: vec![],
770 gas_object_index: None,
771 events_digest: None,
772 dependencies: vec![],
773 aux_data_digest: None,
774 }
775 }
776}
777
778#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
779pub enum UnchangedConsensusKind {
780 ReadOnlyRoot(VersionDigest),
783 MutateConsensusStreamEnded(SequenceNumber),
785 ReadConsensusStreamEnded(SequenceNumber),
787 Cancelled(SequenceNumber),
789 PerEpochConfig,
791}