1use std::collections::{BTreeMap, BTreeSet};
6
7use iota_sdk_types::{
8 Address, EpochId, ExecutionStatus, GasCostSummary, InputSharedObject, IntentScope,
9 ObjectChange, ObjectDigest, ObjectId, ObjectReference, ObjectRemoveKind, ObjectVersion,
10 OwnedObjectReference, Owner, TransactionDigest, TransactionEffectsDigest,
11 TransactionEventsDigest, UnchangedSharedKind, UnchangedSharedObject, Version, WriteKind,
12 crypto::Intent,
13 effects::{
14 ChangedObject, IdOperation, ObjectIn, ObjectOut, TransactionEffects, TransactionEffectsV1,
15 },
16};
17pub use test_effects_builder::TestEffectsBuilder;
18use tracing::instrument;
19
20use crate::{
21 base_types::ExecutionDigests,
22 committee::Committee,
23 crypto::{
24 AuthoritySignInfo, AuthoritySignInfoTrait, AuthorityStrongQuorumSignInfo, EmptySignInfo,
25 default_hash,
26 },
27 error::IotaResult,
28 execution::SharedInput,
29 message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
30 object::OBJECT_START_VERSION,
31};
32
33mod test_effects_builder;
34mod v1;
35
36pub const APPROX_SIZE_OF_OBJECT_REF: usize = 80;
40pub const APPROX_SIZE_OF_EXECUTION_STATUS: usize = 144;
42pub const APPROX_SIZE_OF_EPOCH_ID: usize = 10;
44pub const APPROX_SIZE_OF_GAS_COST_SUMMARY: usize = 50;
46pub const APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST: usize = 40;
48pub const APPROX_SIZE_OF_TX_DIGEST: usize = 40;
50pub const APPROX_SIZE_OF_OWNER: usize = 48;
52
53impl Message for TransactionEffects {
54 type DigestType = TransactionEffectsDigest;
55 const SCOPE: IntentScope = IntentScope::TransactionEffects;
56
57 fn digest(&self) -> Self::DigestType {
58 TransactionEffectsDigest::new(default_hash(self))
59 }
60}
61
62mod transaction_effects_api {
63 pub trait Sealed {}
64 impl Sealed for super::TransactionEffects {}
65 impl Sealed for super::TransactionEffectsV1 {}
66}
67
68pub trait TransactionEffectsAPI: transaction_effects_api::Sealed {
73 fn status(&self) -> &ExecutionStatus;
75
76 fn into_status(self) -> ExecutionStatus;
78
79 fn epoch(&self) -> EpochId;
81
82 fn modified_at_versions(&self) -> Vec<ObjectVersion>;
86
87 fn lamport_version(&self) -> Version;
89
90 fn old_object_metadata(&self) -> Vec<OwnedObjectReference>;
95
96 fn input_shared_objects(&self) -> Vec<InputSharedObject>;
104
105 fn created(&self) -> Vec<OwnedObjectReference>;
109
110 fn mutated(&self) -> Vec<OwnedObjectReference>;
114
115 fn unwrapped(&self) -> Vec<OwnedObjectReference>;
118
119 fn deleted(&self) -> Vec<ObjectReference>;
123
124 fn unwrapped_then_deleted(&self) -> Vec<ObjectReference>;
129
130 fn wrapped(&self) -> Vec<ObjectReference>;
136
137 fn object_changes(&self) -> Vec<ObjectChange>;
142
143 fn gas_object(&self) -> OwnedObjectReference;
148
149 fn events_digest(&self) -> Option<&TransactionEventsDigest>;
152
153 fn dependencies(&self) -> &[TransactionDigest];
156
157 fn transaction_digest(&self) -> &TransactionDigest;
159
160 fn gas_cost_summary(&self) -> &GasCostSummary;
162
163 fn deleted_mutably_accessed_shared_objects(&self) -> Vec<ObjectId> {
166 self.input_shared_objects()
167 .into_iter()
168 .filter_map(|kind| match kind {
169 InputSharedObject::MutateDeleted(object) => Some(object.object_id),
170 InputSharedObject::Mutate(..)
171 | InputSharedObject::ReadOnly(..)
172 | InputSharedObject::ReadDeleted(..)
173 | InputSharedObject::Canceled(..) => None,
174 })
175 .collect()
176 }
177
178 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)>;
181}
182
183pub trait TransactionEffectsAPIForTesting: TransactionEffectsAPI {
186 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
191
192 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
194
195 fn unsafe_add_read_only_shared_object_for_testing(&mut self, object: ObjectReference);
198
199 fn unsafe_add_mutated_shared_object_for_testing(&mut self, object: ObjectReference);
202
203 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectReference);
207
208 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectReference);
211}
212
213mod transaction_effects_ext {
214 pub trait Sealed {}
215 impl Sealed for super::TransactionEffects {}
216}
217
218pub trait TransactionEffectsExt: transaction_effects_ext::Sealed {
221 fn new_from_execution_v1(
224 status: ExecutionStatus,
225 epoch: EpochId,
226 gas_cost_summary: GasCostSummary,
227 shared_objects: Vec<SharedInput>,
228 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
229 transaction_digest: TransactionDigest,
230 lamport_version: Version,
231 changed_objects: BTreeMap<ObjectId, ChangedObject>,
232 gas_object: Option<ObjectId>,
233 events_digest: Option<TransactionEventsDigest>,
234 dependencies: Vec<TransactionDigest>,
235 ) -> Self;
236
237 fn execution_digests(&self) -> ExecutionDigests;
240
241 fn all_changed_objects(&self) -> Vec<(OwnedObjectReference, WriteKind)>;
246
247 fn all_removed_objects(&self) -> Vec<(ObjectReference, ObjectRemoveKind)>;
252
253 fn all_tombstones(&self) -> Vec<(ObjectId, Version)>;
256
257 fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, Version)>;
259
260 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectReference>;
262
263 fn all_affected_objects(&self) -> Vec<ObjectReference>;
267
268 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary;
271
272 fn estimate_size_upperbound_v1(
276 num_writes: usize,
277 num_modifies: usize,
278 num_deps: usize,
279 ) -> usize {
280 let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
281 + APPROX_SIZE_OF_EPOCH_ID
282 + APPROX_SIZE_OF_GAS_COST_SUMMARY
283 + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
284
285 let approx_change_entry_size = 1_000
287 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
288 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
289
290 let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
291
292 fixed_sizes + approx_change_entry_size + deps_size
293 }
294}
295
296pub trait TransactionEffectsExtForTesting: transaction_effects_ext::Sealed {
298 fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self;
305}
306
307macro_rules! effects_version {
310 ($self:ident) => {
311 match $self {
312 TransactionEffects::V1(v1) => &**v1,
313 _ => unimplemented!(
314 "a new TransactionEffects enum variant was added and needs to be handled"
315 ),
316 }
317 };
318 (mut $self:ident) => {
319 match $self {
320 TransactionEffects::V1(v1) => &mut **v1,
321 _ => unimplemented!(
322 "a new TransactionEffects enum variant was added and needs to be handled"
323 ),
324 }
325 };
326 (into $self:ident) => {
327 match $self {
328 TransactionEffects::V1(v1) => *v1,
329 _ => unimplemented!(
330 "a new TransactionEffects enum variant was added and needs to be handled"
331 ),
332 }
333 };
334}
335
336impl TransactionEffectsAPI for TransactionEffects {
337 fn status(&self) -> &ExecutionStatus {
338 &effects_version!(self).status
339 }
340
341 fn into_status(self) -> ExecutionStatus {
342 effects_version!(into self).status
343 }
344
345 fn epoch(&self) -> EpochId {
346 effects_version!(self).epoch
347 }
348
349 fn modified_at_versions(&self) -> Vec<ObjectVersion> {
350 effects_version!(self).modified_at_versions()
351 }
352
353 fn lamport_version(&self) -> Version {
354 effects_version!(self).lamport_version
355 }
356
357 fn old_object_metadata(&self) -> Vec<OwnedObjectReference> {
358 effects_version!(self).old_object_metadata()
359 }
360
361 fn input_shared_objects(&self) -> Vec<InputSharedObject> {
362 effects_version!(self).input_shared_objects()
363 }
364
365 fn created(&self) -> Vec<OwnedObjectReference> {
366 effects_version!(self).created()
367 }
368
369 fn mutated(&self) -> Vec<OwnedObjectReference> {
370 effects_version!(self).mutated()
371 }
372
373 fn unwrapped(&self) -> Vec<OwnedObjectReference> {
374 effects_version!(self).unwrapped()
375 }
376
377 fn deleted(&self) -> Vec<ObjectReference> {
378 effects_version!(self).deleted()
379 }
380
381 fn unwrapped_then_deleted(&self) -> Vec<ObjectReference> {
382 effects_version!(self).unwrapped_then_deleted()
383 }
384
385 fn wrapped(&self) -> Vec<ObjectReference> {
386 effects_version!(self).wrapped()
387 }
388
389 fn object_changes(&self) -> Vec<ObjectChange> {
390 effects_version!(self).object_changes()
391 }
392
393 fn gas_object(&self) -> OwnedObjectReference {
394 effects_version!(self).gas_object().unwrap_or_else(|| {
398 OwnedObjectReference::new(
399 ObjectReference::new(ObjectId::ZERO, Version::default(), ObjectDigest::MIN),
400 Owner::Address(Address::ZERO),
401 )
402 })
403 }
404
405 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
406 effects_version!(self).events_digest.as_ref()
407 }
408
409 fn dependencies(&self) -> &[TransactionDigest] {
410 &effects_version!(self).dependencies
411 }
412
413 fn transaction_digest(&self) -> &TransactionDigest {
414 &effects_version!(self).transaction_digest
415 }
416
417 fn gas_cost_summary(&self) -> &GasCostSummary {
418 &effects_version!(self).gas_cost_summary
419 }
420
421 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
422 effects_version!(self)
423 .unchanged_shared_objects
424 .iter()
425 .map(|unchanged| (unchanged.object_id, unchanged.kind.clone()))
426 .collect()
427 }
428}
429
430impl TransactionEffectsAPIForTesting for TransactionEffects {
431 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
432 &mut effects_version!(mut self).gas_cost_summary
433 }
434
435 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
436 &mut effects_version!(mut self).dependencies
437 }
438
439 fn unsafe_add_read_only_shared_object_for_testing(&mut self, object: ObjectReference) {
440 let ObjectReference {
441 object_id,
442 version,
443 digest,
444 } = object;
445 effects_version!(mut self)
446 .unchanged_shared_objects
447 .push(UnchangedSharedObject {
448 object_id,
449 kind: UnchangedSharedKind::ReadOnlyRoot { version, digest },
450 })
451 }
452
453 fn unsafe_add_mutated_shared_object_for_testing(&mut self, object: ObjectReference) {
454 let ObjectReference {
455 object_id,
456 version,
457 digest,
458 } = object;
459 effects_version!(mut self)
460 .changed_objects
461 .push(ChangedObject {
462 object_id,
463 input_state: ObjectIn::Data {
464 version,
465 digest,
466 owner: Owner::Shared(OBJECT_START_VERSION),
467 },
468 output_state: ObjectOut::ObjectWrite {
469 digest,
470 owner: Owner::Shared(version),
471 },
472 id_operation: IdOperation::None,
473 })
474 }
475
476 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectReference) {
477 let v1 = effects_version!(mut self);
478 let (object_id, version, digest) = object_ref.into_parts();
479 v1.changed_objects.push(ChangedObject {
480 object_id,
481 input_state: ObjectIn::Data {
482 version,
483 digest,
484 owner: Owner::Address(Address::ZERO),
485 },
486 output_state: ObjectOut::ObjectWrite {
487 digest,
488 owner: Owner::Address(Address::ZERO),
489 },
490 id_operation: IdOperation::None,
491 })
492 }
493
494 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectReference) {
495 let v1 = effects_version!(mut self);
496 let (object_id, version, digest) = object_ref.into_parts();
497 v1.changed_objects.push(ChangedObject {
498 object_id,
499 input_state: ObjectIn::Data {
500 version,
501 digest,
502 owner: Owner::Address(Address::ZERO),
503 },
504 output_state: ObjectOut::Missing,
505 id_operation: IdOperation::Deleted,
506 })
507 }
508}
509
510impl TransactionEffectsExt for TransactionEffects {
511 fn new_from_execution_v1(
512 status: ExecutionStatus,
513 epoch: EpochId,
514 gas_cost_summary: GasCostSummary,
515 shared_objects: Vec<SharedInput>,
516 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
517 transaction_digest: TransactionDigest,
518 lamport_version: Version,
519 changed_objects: BTreeMap<ObjectId, ChangedObject>,
520 gas_object: Option<ObjectId>,
521 events_digest: Option<TransactionEventsDigest>,
522 dependencies: Vec<TransactionDigest>,
523 ) -> Self {
524 TransactionEffects::V1(Box::new(v1::new_from_execution(
525 status,
526 epoch,
527 gas_cost_summary,
528 shared_objects,
529 loaded_per_epoch_config_objects,
530 transaction_digest,
531 lamport_version,
532 changed_objects,
533 gas_object,
534 events_digest,
535 dependencies,
536 )))
537 }
538
539 fn execution_digests(&self) -> ExecutionDigests {
540 ExecutionDigests {
541 transaction: *self.transaction_digest(),
542 effects: self.digest(),
543 }
544 }
545
546 fn all_changed_objects(&self) -> Vec<(OwnedObjectReference, WriteKind)> {
547 effects_version!(self).all_changed_objects()
548 }
549
550 fn all_removed_objects(&self) -> Vec<(ObjectReference, ObjectRemoveKind)> {
551 effects_version!(self).all_removed_objects()
552 }
553
554 fn all_tombstones(&self) -> Vec<(ObjectId, Version)> {
555 self.deleted()
556 .into_iter()
557 .chain(self.unwrapped_then_deleted())
558 .chain(self.wrapped())
559 .map(|obj_ref| (obj_ref.object_id, obj_ref.version))
560 .collect()
561 }
562
563 fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, Version)> {
564 self.object_changes()
568 .into_iter()
569 .filter_map(|change| {
570 if change.input_digest.is_none()
571 && change.output_digest.is_none()
572 && change.id_operation == IdOperation::Created
573 {
574 Some((change.object_id, change.output_version.unwrap_or_default()))
575 } else {
576 None
577 }
578 })
579 .collect::<Vec<_>>()
580 }
581
582 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectReference> {
583 let gas = self.gas_object();
584 self.mutated()
585 .into_iter()
586 .filter(|mutated| *mutated != gas)
587 .collect()
588 }
589
590 fn all_affected_objects(&self) -> Vec<ObjectReference> {
591 let reference = |owned: OwnedObjectReference| owned.reference;
592 self.created()
593 .into_iter()
594 .map(reference)
595 .chain(self.mutated().into_iter().map(reference))
596 .chain(self.unwrapped().into_iter().map(reference))
597 .chain(
598 self.input_shared_objects()
599 .into_iter()
600 .map(|shared| shared.object_reference()),
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 Version::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}