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