1use std::collections::{BTreeMap, BTreeSet};
6
7use iota_sdk_types::{
8 Digest, EpochId, ExecutionStatus, GasCostSummary, IntentScope, ObjectId, Owner,
9 UnchangedSharedObject, Version, crypto::Intent,
10};
11pub use iota_sdk_types::{
12 effects::{
13 ChangedObject as EffectsObjectChange, IdOperation as IDOperation, ObjectIn, ObjectOut,
14 TransactionEffects, TransactionEffectsV1, UnchangedSharedKind,
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 new_empty_v1(transaction_digest: TransactionDigest) -> Self;
301
302 fn execution_digests(&self) -> ExecutionDigests;
305
306 fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)>;
311
312 fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)>;
317
318 fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)>;
321
322 fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)>;
324
325 fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)>;
327
328 fn all_affected_objects(&self) -> Vec<ObjectRef>;
332
333 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary;
336
337 fn estimate_size_upperbound_v1(
341 num_writes: usize,
342 num_modifies: usize,
343 num_deps: usize,
344 ) -> usize {
345 let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
346 + APPROX_SIZE_OF_EPOCH_ID
347 + APPROX_SIZE_OF_GAS_COST_SUMMARY
348 + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
349
350 let approx_change_entry_size = 1_000
352 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
353 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
354
355 let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
356
357 fixed_sizes + approx_change_entry_size + deps_size
358 }
359}
360
361macro_rules! delegate_effects_api {
363 ($self:ident, $method:ident $(, $arg:expr)*) => {
364 match $self {
365 TransactionEffects::V1(v1) => v1.$method($($arg),*),
366 _ => unimplemented!(
367 "a new TransactionEffects enum variant was added and needs to be handled"
368 ),
369 }
370 };
371}
372
373impl TransactionEffectsAPI for TransactionEffects {
374 fn status(&self) -> &ExecutionStatus {
375 delegate_effects_api!(self, status)
376 }
377
378 fn into_status(self) -> ExecutionStatus {
379 delegate_effects_api!(self, into_status)
380 }
381
382 fn epoch(&self) -> EpochId {
383 delegate_effects_api!(self, epoch)
384 }
385
386 fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
387 delegate_effects_api!(self, modified_at_versions)
388 }
389
390 fn lamport_version(&self) -> Version {
391 delegate_effects_api!(self, lamport_version)
392 }
393
394 fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
395 delegate_effects_api!(self, old_object_metadata)
396 }
397
398 fn input_shared_objects(&self) -> Vec<InputSharedObject> {
399 delegate_effects_api!(self, input_shared_objects)
400 }
401
402 fn created(&self) -> Vec<(ObjectRef, Owner)> {
403 delegate_effects_api!(self, created)
404 }
405
406 fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
407 delegate_effects_api!(self, mutated)
408 }
409
410 fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
411 delegate_effects_api!(self, unwrapped)
412 }
413
414 fn deleted(&self) -> Vec<ObjectRef> {
415 delegate_effects_api!(self, deleted)
416 }
417
418 fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
419 delegate_effects_api!(self, unwrapped_then_deleted)
420 }
421
422 fn wrapped(&self) -> Vec<ObjectRef> {
423 delegate_effects_api!(self, wrapped)
424 }
425
426 fn object_changes(&self) -> Vec<ObjectChange> {
427 delegate_effects_api!(self, object_changes)
428 }
429
430 fn gas_object(&self) -> (ObjectRef, Owner) {
431 delegate_effects_api!(self, gas_object)
432 }
433
434 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
435 delegate_effects_api!(self, events_digest)
436 }
437
438 fn dependencies(&self) -> &[TransactionDigest] {
439 delegate_effects_api!(self, dependencies)
440 }
441
442 fn transaction_digest(&self) -> &TransactionDigest {
443 delegate_effects_api!(self, transaction_digest)
444 }
445
446 fn gas_cost_summary(&self) -> &GasCostSummary {
447 delegate_effects_api!(self, gas_cost_summary)
448 }
449
450 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
451 delegate_effects_api!(self, unchanged_shared_objects)
452 }
453}
454
455impl TransactionEffectsAPIForTesting for TransactionEffects {
456 fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
457 delegate_effects_api!(self, status_mut_for_testing)
458 }
459
460 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
461 delegate_effects_api!(self, gas_cost_summary_mut_for_testing)
462 }
463
464 fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
465 delegate_effects_api!(self, transaction_digest_mut_for_testing)
466 }
467
468 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
469 delegate_effects_api!(self, dependencies_mut_for_testing)
470 }
471
472 fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
473 delegate_effects_api!(self, unsafe_add_input_shared_object_for_testing, kind)
474 }
475
476 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectRef) {
477 delegate_effects_api!(self, unsafe_add_deleted_live_object_for_testing, object_ref)
478 }
479
480 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectRef) {
481 delegate_effects_api!(self, unsafe_add_object_tombstone_for_testing, object_ref)
482 }
483}
484
485impl TransactionEffectsExt for TransactionEffects {
486 fn new_from_execution_v1(
487 status: ExecutionStatus,
488 epoch: EpochId,
489 gas_cost_summary: GasCostSummary,
490 shared_objects: Vec<SharedInput>,
491 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
492 transaction_digest: TransactionDigest,
493 lamport_version: SequenceNumber,
494 changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
495 gas_object: Option<ObjectId>,
496 events_digest: Option<TransactionEventsDigest>,
497 dependencies: Vec<TransactionDigest>,
498 ) -> Self {
499 TransactionEffects::V1(Box::new(v1::new_from_execution(
500 status,
501 epoch,
502 gas_cost_summary,
503 shared_objects,
504 loaded_per_epoch_config_objects,
505 transaction_digest,
506 lamport_version,
507 changed_objects,
508 gas_object,
509 events_digest,
510 dependencies,
511 )))
512 }
513
514 fn new_empty_v1(transaction_digest: TransactionDigest) -> Self {
515 Self::new_from_execution_v1(
516 ExecutionStatus::Success,
517 0,
518 GasCostSummary::default(),
519 vec![],
520 BTreeSet::new(),
521 transaction_digest,
522 SequenceNumber::default(),
523 BTreeMap::new(),
524 None,
525 None,
526 vec![],
527 )
528 }
529
530 fn execution_digests(&self) -> ExecutionDigests {
531 ExecutionDigests {
532 transaction: *self.transaction_digest(),
533 effects: self.digest(),
534 }
535 }
536
537 fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)> {
538 self.mutated()
539 .into_iter()
540 .map(|(r, o)| (r, o, WriteKind::Mutate))
541 .chain(
542 self.created()
543 .into_iter()
544 .map(|(r, o)| (r, o, WriteKind::Create)),
545 )
546 .chain(
547 self.unwrapped()
548 .into_iter()
549 .map(|(r, o)| (r, o, WriteKind::Unwrap)),
550 )
551 .collect()
552 }
553
554 fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)> {
555 self.deleted()
556 .iter()
557 .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Delete))
558 .chain(
559 self.wrapped()
560 .iter()
561 .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Wrap)),
562 )
563 .collect()
564 }
565
566 fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)> {
567 self.deleted()
568 .into_iter()
569 .chain(self.unwrapped_then_deleted())
570 .chain(self.wrapped())
571 .map(|obj_ref| (obj_ref.object_id, obj_ref.version))
572 .collect()
573 }
574
575 fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)> {
576 self.object_changes()
580 .into_iter()
581 .filter_map(|change| {
582 if change.input_digest.is_none()
583 && change.output_digest.is_none()
584 && change.id_operation == IDOperation::Created
585 {
586 Some((change.id, change.output_version.unwrap_or_default()))
587 } else {
588 None
589 }
590 })
591 .collect::<Vec<_>>()
592 }
593
594 fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
595 self.mutated()
596 .into_iter()
597 .filter(|o| o != &self.gas_object())
598 .collect()
599 }
600
601 fn all_affected_objects(&self) -> Vec<ObjectRef> {
602 self.created()
603 .into_iter()
604 .map(|(r, _)| r)
605 .chain(self.mutated().into_iter().map(|(r, _)| r))
606 .chain(self.unwrapped().into_iter().map(|(r, _)| r))
607 .chain(
608 self.input_shared_objects()
609 .into_iter()
610 .map(|r| r.object_ref()),
611 )
612 .chain(self.deleted())
613 .chain(self.unwrapped_then_deleted())
614 .chain(self.wrapped())
615 .collect()
616 }
617
618 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
619 TransactionEffectsDebugSummary {
620 bcs_size: bcs::serialized_size(self).unwrap(),
621 status: self.status().clone(),
622 gas_cost_summary: self.gas_cost_summary().clone(),
623 transaction_digest: *self.transaction_digest(),
624 created_object_count: self.created().len(),
625 mutated_object_count: self.mutated().len(),
626 unwrapped_object_count: self.unwrapped().len(),
627 deleted_object_count: self.deleted().len(),
628 wrapped_object_count: self.wrapped().len(),
629 dependency_count: self.dependencies().len(),
630 }
631 }
632}
633
634#[derive(Debug)]
635pub struct TransactionEffectsDebugSummary {
636 pub bcs_size: usize,
638 pub status: ExecutionStatus,
639 pub gas_cost_summary: GasCostSummary,
640 pub transaction_digest: TransactionDigest,
641 pub created_object_count: usize,
642 pub mutated_object_count: usize,
643 pub unwrapped_object_count: usize,
644 pub deleted_object_count: usize,
645 pub wrapped_object_count: usize,
646 pub dependency_count: usize,
647 }
649
650pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
651pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
652pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
653pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
654
655pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
656pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
657pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
658pub type VerifiedCertifiedTransactionEffects =
659 VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
660
661impl CertifiedTransactionEffects {
662 #[instrument(level = "trace", skip_all)]
663 pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
664 self.auth_sig().verify_secure(
665 self.data(),
666 Intent::iota_app(IntentScope::TransactionEffects),
667 committee,
668 )
669 }
670
671 #[instrument(level = "trace", skip_all)]
672 pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
673 self.verify_authority_signatures(committee)?;
674 Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
675 }
676}
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
687 fn message_trait_and_effects_digest_match() {
688 let effects = TransactionEffects::new_empty_v1(TransactionDigest::default());
689 let message_digest = <TransactionEffects as Message>::digest(&effects);
690 let effects_digest = effects.digest();
691 assert_eq!(message_digest, effects_digest);
692 }
693}