1use std::collections::{BTreeMap, BTreeSet};
6
7use iota_sdk_types::{
8 Address, EpochId, ExecutionStatus, GasCostSummary, InputSharedObject, IntentScope,
9 ObjectDigest, ObjectId, ObjectReference, ObjectRemoveKind, ObjectVersion, OwnedObjectReference,
10 Owner, TransactionDigest, TransactionEffectsDigest, TransactionEventsDigest,
11 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 gas_object(&self) -> OwnedObjectReference;
142
143 fn events_digest(&self) -> Option<&TransactionEventsDigest>;
146
147 fn dependencies(&self) -> &[TransactionDigest];
150
151 fn transaction_digest(&self) -> &TransactionDigest;
153
154 fn gas_cost_summary(&self) -> &GasCostSummary;
156
157 fn deleted_mutably_accessed_shared_objects(&self) -> Vec<ObjectId> {
160 self.input_shared_objects()
161 .into_iter()
162 .filter_map(|kind| match kind {
163 InputSharedObject::MutateDeleted(object) => Some(*object.object_id()),
164 InputSharedObject::Mutate(..)
165 | InputSharedObject::ReadOnly(..)
166 | InputSharedObject::ReadDeleted(..)
167 | InputSharedObject::Canceled(..) => None,
168 _ => unimplemented!(
169 "a new InputSharedObject enum variant was added and needs to be handled"
170 ),
171 })
172 .collect()
173 }
174
175 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)>;
178}
179
180pub trait TransactionEffectsAPIForTesting: TransactionEffectsAPI {
183 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
188
189 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
191
192 fn unsafe_add_read_only_shared_object_for_testing(&mut self, object: ObjectReference);
195
196 fn unsafe_add_mutated_shared_object_for_testing(&mut self, object: ObjectReference);
199
200 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectReference);
204
205 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectReference);
208}
209
210mod transaction_effects_ext {
211 pub trait Sealed {}
212 impl Sealed for super::TransactionEffects {}
213}
214
215pub trait TransactionEffectsExt: transaction_effects_ext::Sealed {
218 fn new_from_execution_v1(
221 status: ExecutionStatus,
222 epoch: EpochId,
223 gas_cost_summary: GasCostSummary,
224 shared_objects: Vec<SharedInput>,
225 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
226 transaction_digest: TransactionDigest,
227 lamport_version: Version,
228 changed_objects: BTreeMap<ObjectId, ChangedObject>,
229 gas_object: Option<ObjectId>,
230 events_digest: Option<TransactionEventsDigest>,
231 dependencies: Vec<TransactionDigest>,
232 ) -> Self;
233
234 fn execution_digests(&self) -> ExecutionDigests;
237
238 fn all_changed_objects(&self) -> Vec<(OwnedObjectReference, WriteKind)>;
243
244 fn all_removed_objects(&self) -> Vec<(ObjectReference, ObjectRemoveKind)>;
249
250 fn all_tombstones(&self) -> Vec<(ObjectId, Version)>;
253
254 fn created_then_wrapped_objects(&self) -> Vec<ObjectId>;
258
259 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectReference>;
261
262 fn all_affected_objects(&self) -> Vec<ObjectReference>;
266
267 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary;
270
271 fn estimate_size_upperbound_v1(
275 num_writes: usize,
276 num_modifies: usize,
277 num_deps: usize,
278 ) -> usize {
279 let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
280 + APPROX_SIZE_OF_EPOCH_ID
281 + APPROX_SIZE_OF_GAS_COST_SUMMARY
282 + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
283
284 let approx_change_entry_size = 1_000
286 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
287 + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
288
289 let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
290
291 fixed_sizes + approx_change_entry_size + deps_size
292 }
293}
294
295pub trait TransactionEffectsExtForTesting: transaction_effects_ext::Sealed {
297 fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self;
304}
305
306macro_rules! effects_version {
309 ($self:ident) => {
310 match $self {
311 TransactionEffects::V1(v1) => &**v1,
312 _ => unimplemented!(
313 "a new TransactionEffects enum variant was added and needs to be handled"
314 ),
315 }
316 };
317 (mut $self:ident) => {
318 match $self {
319 TransactionEffects::V1(v1) => &mut **v1,
320 _ => unimplemented!(
321 "a new TransactionEffects enum variant was added and needs to be handled"
322 ),
323 }
324 };
325 (into $self:ident) => {
326 match $self {
327 TransactionEffects::V1(v1) => *v1,
328 _ => unimplemented!(
329 "a new TransactionEffects enum variant was added and needs to be handled"
330 ),
331 }
332 };
333}
334
335impl TransactionEffectsAPI for TransactionEffects {
336 fn status(&self) -> &ExecutionStatus {
337 &effects_version!(self).status
338 }
339
340 fn into_status(self) -> ExecutionStatus {
341 effects_version!(into self).status
342 }
343
344 fn epoch(&self) -> EpochId {
345 effects_version!(self).epoch
346 }
347
348 fn modified_at_versions(&self) -> Vec<ObjectVersion> {
349 effects_version!(self).modified_at_versions()
350 }
351
352 fn lamport_version(&self) -> Version {
353 effects_version!(self).lamport_version
354 }
355
356 fn old_object_metadata(&self) -> Vec<OwnedObjectReference> {
357 effects_version!(self).old_object_metadata()
358 }
359
360 fn input_shared_objects(&self) -> Vec<InputSharedObject> {
361 effects_version!(self).input_shared_objects()
362 }
363
364 fn created(&self) -> Vec<OwnedObjectReference> {
365 effects_version!(self).created()
366 }
367
368 fn mutated(&self) -> Vec<OwnedObjectReference> {
369 effects_version!(self).mutated()
370 }
371
372 fn unwrapped(&self) -> Vec<OwnedObjectReference> {
373 effects_version!(self).unwrapped()
374 }
375
376 fn deleted(&self) -> Vec<ObjectReference> {
377 effects_version!(self).deleted()
378 }
379
380 fn unwrapped_then_deleted(&self) -> Vec<ObjectReference> {
381 effects_version!(self).unwrapped_then_deleted()
382 }
383
384 fn wrapped(&self) -> Vec<ObjectReference> {
385 effects_version!(self).wrapped()
386 }
387
388 fn gas_object(&self) -> OwnedObjectReference {
389 effects_version!(self).gas_object().unwrap_or_else(|| {
393 OwnedObjectReference::new(
394 ObjectReference::new(ObjectId::ZERO, Version::default(), ObjectDigest::MIN),
395 Owner::Address(Address::ZERO),
396 )
397 })
398 }
399
400 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
401 effects_version!(self).events_digest.as_ref()
402 }
403
404 fn dependencies(&self) -> &[TransactionDigest] {
405 &effects_version!(self).dependencies
406 }
407
408 fn transaction_digest(&self) -> &TransactionDigest {
409 &effects_version!(self).transaction_digest
410 }
411
412 fn gas_cost_summary(&self) -> &GasCostSummary {
413 &effects_version!(self).gas_cost_summary
414 }
415
416 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
417 effects_version!(self)
418 .unchanged_shared_objects
419 .iter()
420 .map(|unchanged| (unchanged.object_id, unchanged.kind.clone()))
421 .collect()
422 }
423}
424
425impl TransactionEffectsAPIForTesting for TransactionEffects {
426 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
427 &mut effects_version!(mut self).gas_cost_summary
428 }
429
430 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
431 &mut effects_version!(mut self).dependencies
432 }
433
434 fn unsafe_add_read_only_shared_object_for_testing(&mut self, object: ObjectReference) {
435 let ObjectReference {
436 object_id,
437 version,
438 digest,
439 } = object;
440 effects_version!(mut self)
441 .unchanged_shared_objects
442 .push(UnchangedSharedObject {
443 object_id,
444 kind: UnchangedSharedKind::ReadOnlyRoot { version, digest },
445 })
446 }
447
448 fn unsafe_add_mutated_shared_object_for_testing(&mut self, object: ObjectReference) {
449 let ObjectReference {
450 object_id,
451 version,
452 digest,
453 } = object;
454 effects_version!(mut self)
455 .changed_objects
456 .push(ChangedObject {
457 object_id,
458 input_state: ObjectIn::Data {
459 version,
460 digest,
461 owner: Owner::Shared(OBJECT_START_VERSION),
462 },
463 output_state: ObjectOut::ObjectWrite {
464 digest,
465 owner: Owner::Shared(version),
466 },
467 id_operation: IdOperation::None,
468 })
469 }
470
471 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectReference) {
472 let v1 = effects_version!(mut self);
473 let (object_id, version, digest) = object_ref.into_parts();
474 v1.changed_objects.push(ChangedObject {
475 object_id,
476 input_state: ObjectIn::Data {
477 version,
478 digest,
479 owner: Owner::Address(Address::ZERO),
480 },
481 output_state: ObjectOut::ObjectWrite {
482 digest,
483 owner: Owner::Address(Address::ZERO),
484 },
485 id_operation: IdOperation::None,
486 })
487 }
488
489 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectReference) {
490 let v1 = effects_version!(mut self);
491 let (object_id, version, digest) = object_ref.into_parts();
492 v1.changed_objects.push(ChangedObject {
493 object_id,
494 input_state: ObjectIn::Data {
495 version,
496 digest,
497 owner: Owner::Address(Address::ZERO),
498 },
499 output_state: ObjectOut::Missing,
500 id_operation: IdOperation::Deleted,
501 })
502 }
503}
504
505impl TransactionEffectsExt for TransactionEffects {
506 fn new_from_execution_v1(
507 status: ExecutionStatus,
508 epoch: EpochId,
509 gas_cost_summary: GasCostSummary,
510 shared_objects: Vec<SharedInput>,
511 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
512 transaction_digest: TransactionDigest,
513 lamport_version: Version,
514 changed_objects: BTreeMap<ObjectId, ChangedObject>,
515 gas_object: Option<ObjectId>,
516 events_digest: Option<TransactionEventsDigest>,
517 dependencies: Vec<TransactionDigest>,
518 ) -> Self {
519 TransactionEffects::V1(Box::new(v1::new_from_execution(
520 status,
521 epoch,
522 gas_cost_summary,
523 shared_objects,
524 loaded_per_epoch_config_objects,
525 transaction_digest,
526 lamport_version,
527 changed_objects,
528 gas_object,
529 events_digest,
530 dependencies,
531 )))
532 }
533
534 fn execution_digests(&self) -> ExecutionDigests {
535 ExecutionDigests {
536 transaction: *self.transaction_digest(),
537 effects: self.digest(),
538 }
539 }
540
541 fn all_changed_objects(&self) -> Vec<(OwnedObjectReference, WriteKind)> {
542 effects_version!(self).all_changed_objects()
543 }
544
545 fn all_removed_objects(&self) -> Vec<(ObjectReference, ObjectRemoveKind)> {
546 effects_version!(self).all_removed_objects()
547 }
548
549 fn all_tombstones(&self) -> Vec<(ObjectId, Version)> {
550 self.deleted()
551 .into_iter()
552 .chain(self.unwrapped_then_deleted())
553 .chain(self.wrapped())
554 .map(|obj_ref| (obj_ref.object_id, obj_ref.version))
555 .collect()
556 }
557
558 fn created_then_wrapped_objects(&self) -> Vec<ObjectId> {
559 effects_version!(self)
560 .changed_objects
561 .iter()
562 .filter(|changed| {
563 matches!(
564 (
565 &changed.input_state,
566 &changed.output_state,
567 &changed.id_operation
568 ),
569 (ObjectIn::Missing, ObjectOut::Missing, IdOperation::Created)
570 )
571 })
572 .map(|changed| changed.object_id)
573 .collect()
574 }
575
576 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectReference> {
577 let gas = self.gas_object();
578 self.mutated()
579 .into_iter()
580 .filter(|mutated| *mutated != gas)
581 .collect()
582 }
583
584 fn all_affected_objects(&self) -> Vec<ObjectReference> {
585 let reference = |owned: OwnedObjectReference| *owned.reference();
586 self.created()
587 .into_iter()
588 .map(reference)
589 .chain(self.mutated().into_iter().map(reference))
590 .chain(self.unwrapped().into_iter().map(reference))
591 .chain(
592 self.input_shared_objects()
593 .into_iter()
594 .map(|shared| shared.object_reference()),
595 )
596 .chain(self.deleted())
597 .chain(self.unwrapped_then_deleted())
598 .chain(self.wrapped())
599 .collect()
600 }
601
602 fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
603 TransactionEffectsDebugSummary {
604 bcs_size: bcs::serialized_size(self).unwrap(),
605 status: self.status().clone(),
606 gas_cost_summary: self.gas_cost_summary().clone(),
607 transaction_digest: *self.transaction_digest(),
608 created_object_count: self.created().len(),
609 mutated_object_count: self.mutated().len(),
610 unwrapped_object_count: self.unwrapped().len(),
611 deleted_object_count: self.deleted().len(),
612 wrapped_object_count: self.wrapped().len(),
613 dependency_count: self.dependencies().len(),
614 }
615 }
616}
617
618impl TransactionEffectsExtForTesting for TransactionEffects {
619 fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self {
620 Self::new_from_execution_v1(
621 ExecutionStatus::Success,
622 0,
623 GasCostSummary::default(),
624 vec![],
625 BTreeSet::new(),
626 transaction_digest,
627 Version::default(),
628 BTreeMap::new(),
629 None,
630 None,
631 vec![],
632 )
633 }
634}
635
636#[derive(Debug)]
637pub struct TransactionEffectsDebugSummary {
638 pub bcs_size: usize,
640 pub status: ExecutionStatus,
641 pub gas_cost_summary: GasCostSummary,
642 pub transaction_digest: TransactionDigest,
643 pub created_object_count: usize,
644 pub mutated_object_count: usize,
645 pub unwrapped_object_count: usize,
646 pub deleted_object_count: usize,
647 pub wrapped_object_count: usize,
648 pub dependency_count: usize,
649 }
651
652pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
653pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
654pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
655pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
656
657pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
658pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
659pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
660pub type VerifiedCertifiedTransactionEffects =
661 VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
662
663impl CertifiedTransactionEffects {
664 #[instrument(level = "trace", skip_all)]
665 pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
666 self.auth_sig().verify_secure(
667 self.data(),
668 Intent::iota_app(IntentScope::TransactionEffects),
669 committee,
670 )
671 }
672
673 #[instrument(level = "trace", skip_all)]
674 pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
675 self.verify_authority_signatures(committee)?;
676 Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
677 }
678}
679#[cfg(test)]
680mod tests {
681 use super::*;
682
683 #[test]
689 fn message_trait_and_effects_digest_match() {
690 let effects = TransactionEffects::new_empty_v1_for_testing(TransactionDigest::default());
691 let message_digest = <TransactionEffects as Message>::digest(&effects);
692 let effects_digest = effects.digest();
693 assert_eq!(message_digest, effects_digest);
694 }
695}