radix_transactions/model/v2/
preview_v2.rs

1use crate::internal_prelude::*;
2
3/// A [`PreviewTransactionV2`] is the payload for V2 preview requests.
4///
5/// This model is similar to [`SignedTransactionIntentV2`], except it doesn't
6/// require signatures, and instead allows just using public keys.
7///
8/// It can be currently constructed from a [`TransactionV2Builder`].
9/// In future we may also support a `PreviewSubintentV2Builder` and
10/// `PreviewTransactionV2Builder` which don't require the subintents
11/// to be signed properly, and instead just allow the public keys to
12/// be specified. For now, if you wish to support that paradigm, just
13/// add the public keys manually.
14///
15/// Unlike with V1 preview, the V2 preview API (at least at launch) will take
16/// a raw payload of this type, rather than a JSON model. This will be more
17/// consistent with the transaction submit API, and avoid UX issues with
18/// ensuring the subintent hashes in the `USE_CHILD` manifest instructions
19/// survive encoding to JSON.
20#[derive(Debug, Clone, Eq, PartialEq, ManifestSbor, ScryptoDescribe, ScryptoSborAssertion)]
21#[sbor_assert(
22    fixed("FILE:preview_transaction_v2_schema.bin"),
23    settings(allow_name_changes)
24)]
25pub struct PreviewTransactionV2 {
26    pub transaction_intent: TransactionIntentV2,
27    pub root_signer_public_keys: IndexSet<PublicKey>,
28    pub non_root_subintent_signer_public_keys: Vec<Vec<PublicKey>>,
29}
30
31impl PreviewTransactionV2 {
32    pub fn prepare_and_validate(
33        &self,
34        validator: &TransactionValidator,
35    ) -> Result<ValidatedPreviewTransactionV2, TransactionValidationError> {
36        self.prepare(validator.preparation_settings())?
37            .validate(validator)
38    }
39}
40
41define_transaction_payload!(
42    PreviewTransactionV2,
43    RawPreviewTransaction,
44    PreparedPreviewTransactionV2 {
45        transaction_intent: PreparedTransactionIntentV2,
46        root_subintent_signatures: SummarizedRawValueBody<Vec<PublicKey>>,
47        non_root_subintent_signatures: SummarizedRawValueBody<Vec<Vec<PublicKey>>>,
48    },
49    TransactionDiscriminator::V2PreviewTransaction,
50);
51
52impl PreparedPreviewTransactionV2 {
53    pub fn validate(
54        self,
55        validator: &TransactionValidator,
56    ) -> Result<ValidatedPreviewTransactionV2, TransactionValidationError> {
57        validator.validate_preview_transaction_v2(self)
58    }
59}
60
61#[derive(Debug, Eq, PartialEq, Clone)]
62pub struct ValidatedPreviewTransactionV2 {
63    pub prepared: PreparedPreviewTransactionV2,
64    pub overall_validity_range: OverallValidityRangeV2,
65    /// This would be the expected number of signature validations, if the
66    /// given public keys (and the notary) signed the transaction
67    pub total_expected_signature_validations: usize,
68    pub transaction_intent_info: ValidatedIntentInformationV2,
69    pub non_root_subintents_info: Vec<ValidatedIntentInformationV2>,
70}
71
72impl ValidatedPreviewTransactionV2 {
73    pub fn create_executable(self, flags: PreviewFlags) -> ExecutableTransaction {
74        let transaction_intent = self.prepared.transaction_intent;
75        let transaction_intent_hash = transaction_intent.transaction_intent_hash();
76        let transaction_header = transaction_intent.transaction_header.inner;
77        let subintents = transaction_intent.non_root_subintents.subintents;
78
79        // NOTE: Ideally we'd use a slightly more accurate estimation for the notarized transaction
80        // payload size here, by estimating the additional size of the signatures and notarization models.
81        // This can be improved in future.
82        let payload_size = transaction_intent.summary.effective_length;
83
84        let mut simulate_every_proof_under_resources = BTreeSet::new();
85        if flags.assume_all_signature_proofs {
86            simulate_every_proof_under_resources.insert(SECP256K1_SIGNATURE_RESOURCE);
87            simulate_every_proof_under_resources.insert(ED25519_SIGNATURE_RESOURCE);
88        }
89
90        let costing_parameters = TransactionCostingParameters {
91            tip: TipSpecifier::BasisPoints(transaction_header.tip_basis_points),
92            free_credit_in_xrd: if flags.use_free_credit {
93                Decimal::try_from(PREVIEW_CREDIT_IN_XRD).unwrap()
94            } else {
95                Decimal::ZERO
96            },
97        };
98
99        let mut intent_hash_nullifications =
100            Vec::with_capacity(self.non_root_subintents_info.len() + 1);
101        {
102            let expiry_epoch = transaction_intent
103                .root_intent_core
104                .header
105                .inner
106                .end_epoch_exclusive;
107            intent_hash_nullifications
108                .push(IntentHash::from(transaction_intent_hash).to_nullification(expiry_epoch));
109        }
110        for subintent in subintents.iter() {
111            let expiry_epoch = subintent.intent_core.header.inner.end_epoch_exclusive;
112            intent_hash_nullifications
113                .push(IntentHash::from(subintent.subintent_hash()).to_nullification(expiry_epoch))
114        }
115
116        let executable_transaction_intent = create_executable_intent(
117            transaction_intent.root_intent_core,
118            self.transaction_intent_info,
119            &flags,
120        );
121
122        let executable_subintents = subintents
123            .into_iter()
124            .zip(self.non_root_subintents_info.into_iter())
125            .map(|(subintent, info)| create_executable_intent(subintent.intent_core, info, &flags))
126            .collect();
127
128        ExecutableTransaction::new_v2(
129            executable_transaction_intent,
130            executable_subintents,
131            ExecutionContext {
132                unique_hash: transaction_intent_hash.0,
133                intent_hash_nullifications,
134                payload_size,
135                num_of_signature_validations: self.total_expected_signature_validations,
136                costing_parameters,
137                pre_allocated_addresses: vec![],
138                disable_limits_and_costing_modules: false,
139                epoch_range: Some(self.overall_validity_range.epoch_range.clone()),
140                proposer_timestamp_range: Some(
141                    self.overall_validity_range.proposer_timestamp_range.clone(),
142                ),
143            },
144        )
145    }
146}
147
148fn create_executable_intent(
149    core: PreparedIntentCoreV2,
150    validated_info: ValidatedIntentInformationV2,
151    flags: &PreviewFlags,
152) -> ExecutableIntent {
153    let signer_keys = validated_info.signer_keys;
154
155    let mut simulate_every_proof_under_resources = BTreeSet::new();
156    if flags.assume_all_signature_proofs {
157        simulate_every_proof_under_resources.insert(SECP256K1_SIGNATURE_RESOURCE);
158        simulate_every_proof_under_resources.insert(ED25519_SIGNATURE_RESOURCE);
159    }
160
161    let auth_zone_init = AuthZoneInit::new(
162        AuthAddresses::signer_set(&signer_keys),
163        simulate_every_proof_under_resources,
164    );
165
166    ExecutableIntent {
167        encoded_instructions: validated_info.encoded_instructions,
168        auth_zone_init,
169        references: core.instructions.references,
170        blobs: core.blobs.blobs_by_hash,
171        children_subintent_indices: validated_info.children_subintent_indices,
172    }
173}