1use std::collections::{BTreeMap, BTreeSet};
6
7use iota_sdk_types::{
8 Digest, EpochId, ExecutionStatus, GasCostSummary, IntentScope, ObjectId, Owner,
9 UnchangedSharedKind, UnchangedSharedObject, Version, crypto::Intent,
10};
11pub use iota_sdk_types::{
12 effects::{
13 ChangedObject as EffectsObjectChange, IdOperation as IDOperation, ObjectIn, ObjectOut,
14 TransactionEffects, TransactionEffectsV1,
15 },
16 events::TransactionEvents,
17};
18pub use test_effects_builder::TestEffectsBuilder;
19use tracing::instrument;
20
21use crate::{
22 base_types::{ExecutionDigests, ObjectRef, SequenceNumber},
23 committee::Committee,
24 crypto::{
25 AuthoritySignInfo, AuthoritySignInfoTrait, AuthorityStrongQuorumSignInfo, EmptySignInfo,
26 default_hash,
27 },
28 digests::{TransactionDigest, TransactionEffectsDigest, TransactionEventsDigest},
29 error::IotaResult,
30 execution::SharedInput,
31 message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
32 storage::WriteKind,
33};
34
35mod test_effects_builder;
36mod v1;
37
38pub const APPROX_SIZE_OF_OBJECT_REF: usize = 80;
42pub const APPROX_SIZE_OF_EXECUTION_STATUS: usize = 144;
44pub const APPROX_SIZE_OF_EPOCH_ID: usize = 10;
46pub const APPROX_SIZE_OF_GAS_COST_SUMMARY: usize = 50;
48pub const APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST: usize = 40;
50pub const APPROX_SIZE_OF_TX_DIGEST: usize = 40;
52pub const APPROX_SIZE_OF_OWNER: usize = 48;
54
55impl Message for TransactionEffects {
56 type DigestType = TransactionEffectsDigest;
57 const SCOPE: IntentScope = IntentScope::TransactionEffects;
58
59 fn digest(&self) -> Self::DigestType {
60 TransactionEffectsDigest::new(default_hash(self))
61 }
62}
63
64pub enum ObjectRemoveKind {
65 Delete,
66 Wrap,
67}
68
69#[derive(Eq, PartialEq, Copy, Clone, Debug)]
75pub enum InputSharedObject {
76 Mutate(ObjectRef),
77 ReadOnly(ObjectRef),
78 ReadDeleted(ObjectId, Version),
79 MutateDeleted(ObjectId, Version),
80 Cancelled(ObjectId, Version),
81}
82
83impl InputSharedObject {
84 pub fn id_and_version(&self) -> (ObjectId, Version) {
85 let (object_id, version, ..) = self.object_ref().into_parts();
86 (object_id, version)
87 }
88
89 pub fn object_ref(&self) -> ObjectRef {
90 match self {
91 InputSharedObject::Mutate(oref) | InputSharedObject::ReadOnly(oref) => *oref,
92 InputSharedObject::ReadDeleted(id, version)
93 | InputSharedObject::MutateDeleted(id, version) => {
94 ObjectRef::new(*id, *version, Digest::OBJECT_DELETED)
95 }
96 InputSharedObject::Cancelled(id, version) => {
97 ObjectRef::new(*id, *version, Digest::OBJECT_CANCELLED)
98 }
99 }
100 }
101}
102
103#[derive(Eq, PartialEq, Copy, Clone, Debug)]
111pub struct ObjectChange {
112 pub id: ObjectId,
113 pub input_version: Option<Version>,
114 pub input_digest: Option<Digest>,
115 pub output_version: Option<Version>,
116 pub output_digest: Option<Digest>,
117 pub id_operation: IDOperation,
118}
119
120mod transaction_effects_api {
121 pub trait Sealed {}
122 impl Sealed for super::TransactionEffects {}
123 impl Sealed for super::TransactionEffectsV1 {}
124}
125
126pub trait TransactionEffectsAPI: transaction_effects_api::Sealed {
131 fn status(&self) -> &ExecutionStatus;
133
134 fn into_status(self) -> ExecutionStatus;
136
137 fn epoch(&self) -> EpochId;
139
140 fn modified_at_versions(&self) -> Vec<(ObjectId, Version)>;
144
145 fn lamport_version(&self) -> Version;
147
148 fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)>;
153
154 fn input_shared_objects(&self) -> Vec<InputSharedObject>;
162
163 fn created(&self) -> Vec<(ObjectRef, Owner)>;
167
168 fn mutated(&self) -> Vec<(ObjectRef, Owner)>;
172
173 fn unwrapped(&self) -> Vec<(ObjectRef, Owner)>;
176
177 fn deleted(&self) -> Vec<ObjectRef>;
181
182 fn unwrapped_then_deleted(&self) -> Vec<ObjectRef>;
187
188 fn wrapped(&self) -> Vec<ObjectRef>;
194
195 fn object_changes(&self) -> Vec<ObjectChange>;
200
201 fn gas_object(&self) -> (ObjectRef, Owner);
206
207 fn events_digest(&self) -> Option<&TransactionEventsDigest>;
210
211 fn dependencies(&self) -> &[TransactionDigest];
214
215 fn transaction_digest(&self) -> &TransactionDigest;
217
218 fn gas_cost_summary(&self) -> &GasCostSummary;
220
221 fn deleted_mutably_accessed_shared_objects(&self) -> Vec<ObjectId> {
224 self.input_shared_objects()
225 .into_iter()
226 .filter_map(|kind| match kind {
227 InputSharedObject::MutateDeleted(id, _) => Some(id),
228 InputSharedObject::Mutate(..)
229 | InputSharedObject::ReadOnly(..)
230 | InputSharedObject::ReadDeleted(..)
231 | InputSharedObject::Cancelled(..) => None,
232 })
233 .collect()
234 }
235
236 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)>;
239}
240
241pub trait TransactionEffectsAPIForTesting: TransactionEffectsAPI {
244 fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus;
249
250 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
252
253 fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest;
255
256 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
258
259 fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject);
262
263 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectRef);
267
268 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectRef);
271}
272
273mod transaction_effects_ext {
274 pub trait Sealed {}
275 impl Sealed for super::TransactionEffects {}
276}
277
278pub trait TransactionEffectsExt: transaction_effects_ext::Sealed {
281 fn new_from_execution_v1(
284 status: ExecutionStatus,
285 epoch: EpochId,
286 gas_cost_summary: GasCostSummary,
287 shared_objects: Vec<SharedInput>,
288 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
289 transaction_digest: TransactionDigest,
290 lamport_version: SequenceNumber,
291 changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
292 gas_object: Option<ObjectId>,
293 events_digest: Option<TransactionEventsDigest>,
294 dependencies: Vec<TransactionDigest>,
295 ) -> Self;
296
297 fn execution_digests(&self) -> ExecutionDigests;
300
301 fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)>;
306
307 fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)>;
312
313 fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)>;
316
317 fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)>;
319
320 fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)>;
322
323 fn all_affected_objects(&self) -> Vec<ObjectRef>;
327
328 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary;
331
332 fn estimate_size_upperbound_v1(
336 num_writes: usize,
337 num_modifies: usize,
338 num_deps: usize,
339 ) -> usize {
340 let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
341 + APPROX_SIZE_OF_EPOCH_ID
342 + APPROX_SIZE_OF_GAS_COST_SUMMARY
343 + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
344
345 let approx_change_entry_size = 1_000
347 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
348 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
349
350 let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
351
352 fixed_sizes + approx_change_entry_size + deps_size
353 }
354}
355
356pub trait TransactionEffectsExtForTesting: transaction_effects_ext::Sealed {
358 fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self;
365}
366
367macro_rules! delegate_effects_api {
369 ($self:ident, $method:ident $(, $arg:expr)*) => {
370 match $self {
371 TransactionEffects::V1(v1) => v1.$method($($arg),*),
372 _ => unimplemented!(
373 "a new TransactionEffects enum variant was added and needs to be handled"
374 ),
375 }
376 };
377}
378
379impl TransactionEffectsAPI for TransactionEffects {
380 fn status(&self) -> &ExecutionStatus {
381 delegate_effects_api!(self, status)
382 }
383
384 fn into_status(self) -> ExecutionStatus {
385 delegate_effects_api!(self, into_status)
386 }
387
388 fn epoch(&self) -> EpochId {
389 delegate_effects_api!(self, epoch)
390 }
391
392 fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
393 delegate_effects_api!(self, modified_at_versions)
394 }
395
396 fn lamport_version(&self) -> Version {
397 delegate_effects_api!(self, lamport_version)
398 }
399
400 fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
401 delegate_effects_api!(self, old_object_metadata)
402 }
403
404 fn input_shared_objects(&self) -> Vec<InputSharedObject> {
405 delegate_effects_api!(self, input_shared_objects)
406 }
407
408 fn created(&self) -> Vec<(ObjectRef, Owner)> {
409 delegate_effects_api!(self, created)
410 }
411
412 fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
413 delegate_effects_api!(self, mutated)
414 }
415
416 fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
417 delegate_effects_api!(self, unwrapped)
418 }
419
420 fn deleted(&self) -> Vec<ObjectRef> {
421 delegate_effects_api!(self, deleted)
422 }
423
424 fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
425 delegate_effects_api!(self, unwrapped_then_deleted)
426 }
427
428 fn wrapped(&self) -> Vec<ObjectRef> {
429 delegate_effects_api!(self, wrapped)
430 }
431
432 fn object_changes(&self) -> Vec<ObjectChange> {
433 delegate_effects_api!(self, object_changes)
434 }
435
436 fn gas_object(&self) -> (ObjectRef, Owner) {
437 delegate_effects_api!(self, gas_object)
438 }
439
440 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
441 delegate_effects_api!(self, events_digest)
442 }
443
444 fn dependencies(&self) -> &[TransactionDigest] {
445 delegate_effects_api!(self, dependencies)
446 }
447
448 fn transaction_digest(&self) -> &TransactionDigest {
449 delegate_effects_api!(self, transaction_digest)
450 }
451
452 fn gas_cost_summary(&self) -> &GasCostSummary {
453 delegate_effects_api!(self, gas_cost_summary)
454 }
455
456 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
457 delegate_effects_api!(self, unchanged_shared_objects)
458 }
459}
460
461impl TransactionEffectsAPIForTesting for TransactionEffects {
462 fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
463 delegate_effects_api!(self, status_mut_for_testing)
464 }
465
466 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
467 delegate_effects_api!(self, gas_cost_summary_mut_for_testing)
468 }
469
470 fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
471 delegate_effects_api!(self, transaction_digest_mut_for_testing)
472 }
473
474 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
475 delegate_effects_api!(self, dependencies_mut_for_testing)
476 }
477
478 fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
479 delegate_effects_api!(self, unsafe_add_input_shared_object_for_testing, kind)
480 }
481
482 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectRef) {
483 delegate_effects_api!(self, unsafe_add_deleted_live_object_for_testing, object_ref)
484 }
485
486 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectRef) {
487 delegate_effects_api!(self, unsafe_add_object_tombstone_for_testing, object_ref)
488 }
489}
490
491impl TransactionEffectsExt for TransactionEffects {
492 fn new_from_execution_v1(
493 status: ExecutionStatus,
494 epoch: EpochId,
495 gas_cost_summary: GasCostSummary,
496 shared_objects: Vec<SharedInput>,
497 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
498 transaction_digest: TransactionDigest,
499 lamport_version: SequenceNumber,
500 changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
501 gas_object: Option<ObjectId>,
502 events_digest: Option<TransactionEventsDigest>,
503 dependencies: Vec<TransactionDigest>,
504 ) -> Self {
505 TransactionEffects::V1(Box::new(v1::new_from_execution(
506 status,
507 epoch,
508 gas_cost_summary,
509 shared_objects,
510 loaded_per_epoch_config_objects,
511 transaction_digest,
512 lamport_version,
513 changed_objects,
514 gas_object,
515 events_digest,
516 dependencies,
517 )))
518 }
519
520 fn execution_digests(&self) -> ExecutionDigests {
521 ExecutionDigests {
522 transaction: *self.transaction_digest(),
523 effects: self.digest(),
524 }
525 }
526
527 fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)> {
528 self.mutated()
529 .into_iter()
530 .map(|(r, o)| (r, o, WriteKind::Mutate))
531 .chain(
532 self.created()
533 .into_iter()
534 .map(|(r, o)| (r, o, WriteKind::Create)),
535 )
536 .chain(
537 self.unwrapped()
538 .into_iter()
539 .map(|(r, o)| (r, o, WriteKind::Unwrap)),
540 )
541 .collect()
542 }
543
544 fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)> {
545 self.deleted()
546 .iter()
547 .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Delete))
548 .chain(
549 self.wrapped()
550 .iter()
551 .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Wrap)),
552 )
553 .collect()
554 }
555
556 fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)> {
557 self.deleted()
558 .into_iter()
559 .chain(self.unwrapped_then_deleted())
560 .chain(self.wrapped())
561 .map(|obj_ref| (obj_ref.object_id, obj_ref.version))
562 .collect()
563 }
564
565 fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)> {
566 self.object_changes()
570 .into_iter()
571 .filter_map(|change| {
572 if change.input_digest.is_none()
573 && change.output_digest.is_none()
574 && change.id_operation == IDOperation::Created
575 {
576 Some((change.id, change.output_version.unwrap_or_default()))
577 } else {
578 None
579 }
580 })
581 .collect::<Vec<_>>()
582 }
583
584 fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
585 self.mutated()
586 .into_iter()
587 .filter(|o| o != &self.gas_object())
588 .collect()
589 }
590
591 fn all_affected_objects(&self) -> Vec<ObjectRef> {
592 self.created()
593 .into_iter()
594 .map(|(r, _)| r)
595 .chain(self.mutated().into_iter().map(|(r, _)| r))
596 .chain(self.unwrapped().into_iter().map(|(r, _)| r))
597 .chain(
598 self.input_shared_objects()
599 .into_iter()
600 .map(|r| r.object_ref()),
601 )
602 .chain(self.deleted())
603 .chain(self.unwrapped_then_deleted())
604 .chain(self.wrapped())
605 .collect()
606 }
607
608 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
609 TransactionEffectsDebugSummary {
610 bcs_size: bcs::serialized_size(self).unwrap(),
611 status: self.status().clone(),
612 gas_cost_summary: self.gas_cost_summary().clone(),
613 transaction_digest: *self.transaction_digest(),
614 created_object_count: self.created().len(),
615 mutated_object_count: self.mutated().len(),
616 unwrapped_object_count: self.unwrapped().len(),
617 deleted_object_count: self.deleted().len(),
618 wrapped_object_count: self.wrapped().len(),
619 dependency_count: self.dependencies().len(),
620 }
621 }
622}
623
624impl TransactionEffectsExtForTesting for TransactionEffects {
625 fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self {
626 Self::new_from_execution_v1(
627 ExecutionStatus::Success,
628 0,
629 GasCostSummary::default(),
630 vec![],
631 BTreeSet::new(),
632 transaction_digest,
633 SequenceNumber::default(),
634 BTreeMap::new(),
635 None,
636 None,
637 vec![],
638 )
639 }
640}
641
642#[derive(Debug)]
643pub struct TransactionEffectsDebugSummary {
644 pub bcs_size: usize,
646 pub status: ExecutionStatus,
647 pub gas_cost_summary: GasCostSummary,
648 pub transaction_digest: TransactionDigest,
649 pub created_object_count: usize,
650 pub mutated_object_count: usize,
651 pub unwrapped_object_count: usize,
652 pub deleted_object_count: usize,
653 pub wrapped_object_count: usize,
654 pub dependency_count: usize,
655 }
657
658pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
659pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
660pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
661pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
662
663pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
664pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
665pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
666pub type VerifiedCertifiedTransactionEffects =
667 VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
668
669impl CertifiedTransactionEffects {
670 #[instrument(level = "trace", skip_all)]
671 pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
672 self.auth_sig().verify_secure(
673 self.data(),
674 Intent::iota_app(IntentScope::TransactionEffects),
675 committee,
676 )
677 }
678
679 #[instrument(level = "trace", skip_all)]
680 pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
681 self.verify_authority_signatures(committee)?;
682 Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
683 }
684}
685#[cfg(test)]
686mod tests {
687 use super::*;
688
689 #[test]
695 fn message_trait_and_effects_digest_match() {
696 let effects = TransactionEffects::new_empty_v1_for_testing(TransactionDigest::default());
697 let message_digest = <TransactionEffects as Message>::digest(&effects);
698 let effects_digest = effects.digest();
699 assert_eq!(message_digest, effects_digest);
700 }
701}