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