radix_transactions/model/
user_transaction.rs

1use crate::internal_prelude::*;
2
3#[derive(Debug, Clone, Eq, PartialEq)]
4pub enum UserTransactionManifest {
5    V1(TransactionManifestV1),
6    V2(TransactionManifestV2),
7}
8
9impl From<TransactionManifestV1> for UserTransactionManifest {
10    fn from(value: TransactionManifestV1) -> Self {
11        Self::V1(value)
12    }
13}
14
15impl From<TransactionManifestV2> for UserTransactionManifest {
16    fn from(value: TransactionManifestV2) -> Self {
17        Self::V2(value)
18    }
19}
20
21impl UserTransactionManifest {
22    pub fn set_names(&mut self, names: KnownManifestObjectNames) {
23        match self {
24            Self::V1(m) => m.set_names(names),
25            Self::V2(m) => m.set_names(names),
26        }
27    }
28
29    pub fn get_blobs<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a Hash, &'a Vec<u8>)> + 'a> {
30        match self {
31            Self::V1(m) => Box::new(m.get_blobs()),
32            Self::V2(m) => Box::new(m.get_blobs()),
33        }
34    }
35}
36
37pub trait UserTransactionPayload:
38    Into<UserTransaction> + TransactionPayload<Raw = RawNotarizedTransaction>
39{
40}
41
42impl<T: Into<UserTransaction> + TransactionPayload<Raw = RawNotarizedTransaction>>
43    UserTransactionPayload for T
44{
45}
46
47#[derive(Debug, Clone, Eq, PartialEq)]
48pub enum UserSubintentManifest {
49    V2(SubintentManifestV2),
50}
51
52impl From<SubintentManifestV2> for UserSubintentManifest {
53    fn from(value: SubintentManifestV2) -> Self {
54        Self::V2(value)
55    }
56}
57
58impl UserSubintentManifest {
59    pub fn set_names(&mut self, names: KnownManifestObjectNames) {
60        match self {
61            Self::V2(m) => m.set_names(names),
62        }
63    }
64
65    pub fn get_blobs<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a Hash, &'a Vec<u8>)> + 'a> {
66        match self {
67            Self::V2(m) => Box::new(m.get_blobs()),
68        }
69    }
70}
71
72const V1_DISCRIMINATOR: u8 = TransactionDiscriminator::V1Notarized as u8;
73const V2_DISCRIMINATOR: u8 = TransactionDiscriminator::V2Notarized as u8;
74
75/// This can be used like [`AnyTransaction`], but just for notarized transactions.
76#[derive(Debug, Clone, Eq, PartialEq, ManifestSbor)]
77pub enum UserTransaction {
78    #[sbor(discriminator(V1_DISCRIMINATOR))]
79    V1(#[sbor(flatten)] NotarizedTransactionV1),
80    #[sbor(discriminator(V2_DISCRIMINATOR))]
81    V2(#[sbor(flatten)] NotarizedTransactionV2),
82}
83
84impl From<NotarizedTransactionV1> for UserTransaction {
85    fn from(value: NotarizedTransactionV1) -> Self {
86        Self::V1(value)
87    }
88}
89
90impl From<NotarizedTransactionV2> for UserTransaction {
91    fn from(value: NotarizedTransactionV2) -> Self {
92        Self::V2(value)
93    }
94}
95
96impl From<UserTransaction> for LedgerTransaction {
97    fn from(value: UserTransaction) -> Self {
98        match value {
99            UserTransaction::V1(tx) => LedgerTransaction::UserV1(Box::new(tx)),
100            UserTransaction::V2(tx) => LedgerTransaction::UserV2(Box::new(tx)),
101        }
102    }
103}
104
105impl UserTransaction {
106    pub fn from_raw(raw: &RawNotarizedTransaction) -> Result<Self, DecodeError> {
107        manifest_decode(raw.as_slice())
108    }
109
110    pub fn prepare(
111        self,
112        settings: &PreparationSettings,
113    ) -> Result<PreparedUserTransaction, PrepareError> {
114        Ok(match self {
115            Self::V1(t) => PreparedUserTransaction::V1(t.prepare(settings)?),
116            Self::V2(t) => PreparedUserTransaction::V2(t.prepare(settings)?),
117        })
118    }
119
120    pub fn extract_manifests_with_names(
121        &self,
122        names: TransactionObjectNames,
123    ) -> (UserTransactionManifest, Vec<UserSubintentManifest>) {
124        match self {
125            UserTransaction::V1(t) => t.extract_manifests_with_names(names),
126            UserTransaction::V2(t) => t.extract_manifests_with_names(names),
127        }
128    }
129}
130
131impl UserTransaction {
132    pub fn prepare_and_validate(
133        &self,
134        validator: &TransactionValidator,
135    ) -> Result<ValidatedUserTransaction, TransactionValidationError> {
136        Ok(match self {
137            UserTransaction::V1(t) => {
138                ValidatedUserTransaction::V1(t.prepare_and_validate(validator)?)
139            }
140            UserTransaction::V2(t) => {
141                ValidatedUserTransaction::V2(t.prepare_and_validate(validator)?)
142            }
143        })
144    }
145}
146
147impl IntoExecutable for UserTransaction {
148    type Error = TransactionValidationError;
149
150    fn into_executable(
151        self,
152        validator: &TransactionValidator,
153    ) -> Result<ExecutableTransaction, Self::Error> {
154        let executable = self.prepare_and_validate(validator)?.create_executable();
155        Ok(executable)
156    }
157}
158
159#[allow(clippy::large_enum_variant)]
160#[derive(Debug, Clone, Eq, PartialEq)]
161pub enum PreparedUserTransaction {
162    V1(PreparedNotarizedTransactionV1),
163    V2(PreparedNotarizedTransactionV2),
164}
165
166impl PreparedUserTransaction {
167    pub fn end_epoch_exclusive(&self) -> Epoch {
168        match self {
169            PreparedUserTransaction::V1(t) => t.end_epoch_exclusive(),
170            PreparedUserTransaction::V2(t) => t.end_epoch_exclusive(),
171        }
172    }
173
174    pub fn hashes(&self) -> UserTransactionHashes {
175        match self {
176            PreparedUserTransaction::V1(t) => t.hashes(),
177            PreparedUserTransaction::V2(t) => t.hashes(),
178        }
179    }
180
181    pub fn validate(
182        self,
183        validator: &TransactionValidator,
184    ) -> Result<ValidatedUserTransaction, TransactionValidationError> {
185        Ok(match self {
186            PreparedUserTransaction::V1(t) => ValidatedUserTransaction::V1(t.validate(validator)?),
187            PreparedUserTransaction::V2(t) => ValidatedUserTransaction::V2(t.validate(validator)?),
188        })
189    }
190}
191
192impl HasTransactionIntentHash for PreparedUserTransaction {
193    fn transaction_intent_hash(&self) -> TransactionIntentHash {
194        match self {
195            Self::V1(t) => t.transaction_intent_hash(),
196            Self::V2(t) => t.transaction_intent_hash(),
197        }
198    }
199}
200
201impl HasSignedTransactionIntentHash for PreparedUserTransaction {
202    fn signed_transaction_intent_hash(&self) -> SignedTransactionIntentHash {
203        match self {
204            Self::V1(t) => t.signed_transaction_intent_hash(),
205            Self::V2(t) => t.signed_transaction_intent_hash(),
206        }
207    }
208}
209
210impl HasNotarizedTransactionHash for PreparedUserTransaction {
211    fn notarized_transaction_hash(&self) -> NotarizedTransactionHash {
212        match self {
213            Self::V1(t) => t.notarized_transaction_hash(),
214            Self::V2(t) => t.notarized_transaction_hash(),
215        }
216    }
217}
218
219impl HasNonRootSubintentHashes for PreparedUserTransaction {
220    fn non_root_subintent_hashes(&self) -> Vec<SubintentHash> {
221        match self {
222            Self::V1(_) => Default::default(),
223            Self::V2(t) => t.non_root_subintent_hashes(),
224        }
225    }
226}
227
228impl HasSummary for PreparedUserTransaction {
229    fn get_summary(&self) -> &Summary {
230        match self {
231            Self::V1(t) => t.get_summary(),
232            Self::V2(t) => t.get_summary(),
233        }
234    }
235
236    fn summary_mut(&mut self) -> &mut Summary {
237        match self {
238            Self::V1(t) => t.summary_mut(),
239            Self::V2(t) => t.summary_mut(),
240        }
241    }
242}
243
244impl PreparedTransaction for PreparedUserTransaction {
245    type Raw = RawNotarizedTransaction;
246
247    fn prepare_from_transaction_enum(
248        decoder: &mut TransactionDecoder,
249    ) -> Result<Self, PrepareError> {
250        let offset = decoder.get_offset();
251        let slice = decoder.get_input_slice();
252        let discriminator_byte = slice
253            .get(offset + 1)
254            .ok_or(PrepareError::UnexpectedTransactionDiscriminator { actual: None })?;
255
256        let prepared = match TransactionDiscriminator::from_repr(*discriminator_byte) {
257            Some(TransactionDiscriminator::V1Notarized) => PreparedUserTransaction::V1(
258                PreparedNotarizedTransactionV1::prepare_from_transaction_enum(decoder)?,
259            ),
260            Some(TransactionDiscriminator::V2Notarized) => PreparedUserTransaction::V2(
261                PreparedNotarizedTransactionV2::prepare_from_transaction_enum(decoder)?,
262            ),
263            _ => {
264                return Err(PrepareError::UnexpectedTransactionDiscriminator {
265                    actual: Some(*discriminator_byte),
266                })
267            }
268        };
269
270        Ok(prepared)
271    }
272}
273
274#[allow(clippy::large_enum_variant)]
275#[derive(Debug, Clone, Eq, PartialEq)]
276pub enum ValidatedUserTransaction {
277    V1(ValidatedNotarizedTransactionV1),
278    V2(ValidatedNotarizedTransactionV2),
279}
280
281impl HasTransactionIntentHash for ValidatedUserTransaction {
282    fn transaction_intent_hash(&self) -> TransactionIntentHash {
283        match self {
284            Self::V1(t) => t.transaction_intent_hash(),
285            Self::V2(t) => t.transaction_intent_hash(),
286        }
287    }
288}
289
290impl HasSignedTransactionIntentHash for ValidatedUserTransaction {
291    fn signed_transaction_intent_hash(&self) -> SignedTransactionIntentHash {
292        match self {
293            Self::V1(t) => t.signed_transaction_intent_hash(),
294            Self::V2(t) => t.signed_transaction_intent_hash(),
295        }
296    }
297}
298
299impl HasNotarizedTransactionHash for ValidatedUserTransaction {
300    fn notarized_transaction_hash(&self) -> NotarizedTransactionHash {
301        match self {
302            Self::V1(t) => t.notarized_transaction_hash(),
303            Self::V2(t) => t.notarized_transaction_hash(),
304        }
305    }
306}
307
308impl HasNonRootSubintentHashes for ValidatedUserTransaction {
309    fn non_root_subintent_hashes(&self) -> Vec<SubintentHash> {
310        match self {
311            Self::V1(_) => Default::default(),
312            Self::V2(t) => t.non_root_subintent_hashes(),
313        }
314    }
315}
316
317impl IntoExecutable for ValidatedUserTransaction {
318    type Error = core::convert::Infallible;
319
320    fn into_executable(
321        self,
322        _validator: &TransactionValidator,
323    ) -> Result<ExecutableTransaction, Self::Error> {
324        Ok(self.create_executable())
325    }
326}
327
328impl ValidatedUserTransaction {
329    pub fn end_epoch_exclusive(&self) -> Epoch {
330        match self {
331            ValidatedUserTransaction::V1(t) => t.prepared.end_epoch_exclusive(),
332            ValidatedUserTransaction::V2(t) => {
333                t.overall_validity_range.epoch_range.end_epoch_exclusive
334            }
335        }
336    }
337
338    pub fn create_executable(self) -> ExecutableTransaction {
339        match self {
340            Self::V1(t) => t.create_executable(),
341            Self::V2(t) => t.create_executable(),
342        }
343    }
344
345    pub fn hashes(&self) -> UserTransactionHashes {
346        match self {
347            Self::V1(t) => t.hashes(),
348            Self::V2(t) => t.hashes(),
349        }
350    }
351}
352
353pub type UserTransactionHashes = UserTransactionHashesV2;
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Sbor)]
356pub struct UserTransactionHashesV1 {
357    pub transaction_intent_hash: TransactionIntentHash,
358    pub signed_transaction_intent_hash: SignedTransactionIntentHash,
359    pub notarized_transaction_hash: NotarizedTransactionHash,
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
363pub struct UserTransactionHashesV2 {
364    pub transaction_intent_hash: TransactionIntentHash,
365    /// ## Validity Note
366    /// Preparable but invalid transactions may contain non-root subintents with duplicate [`SubintentHash`]es.
367    /// Therefore we return a `Vec` instead of an `IndexSet` here.
368    pub non_root_subintent_hashes: Vec<SubintentHash>,
369    pub signed_transaction_intent_hash: SignedTransactionIntentHash,
370    pub notarized_transaction_hash: NotarizedTransactionHash,
371}
372
373impl From<UserTransactionHashesV1> for UserTransactionHashesV2 {
374    fn from(value: UserTransactionHashesV1) -> Self {
375        let UserTransactionHashesV1 {
376            transaction_intent_hash,
377            signed_transaction_intent_hash,
378            notarized_transaction_hash,
379        } = value;
380        UserTransactionHashesV2 {
381            transaction_intent_hash,
382            non_root_subintent_hashes: vec![],
383            signed_transaction_intent_hash,
384            notarized_transaction_hash,
385        }
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn notarized_transaction_v1_can_be_decoded_as_user_transaction() {
395        let network = NetworkDefinition::simulator();
396
397        let notary_private_key = Ed25519PrivateKey::from_u64(3).unwrap();
398
399        let header = TransactionHeaderV1 {
400            network_id: network.id,
401            start_epoch_inclusive: Epoch::of(1),
402            end_epoch_exclusive: Epoch::of(5),
403            nonce: 0,
404            notary_public_key: notary_private_key.public_key().into(),
405            notary_is_signatory: false,
406            tip_percentage: 0,
407        };
408
409        let notarized = TransactionBuilder::new()
410            .header(header)
411            .manifest(ManifestBuilder::new_v1().build())
412            .notarize(&notary_private_key)
413            .build();
414
415        let raw = notarized.to_raw().unwrap();
416
417        let user_transaction = raw.into_typed().unwrap();
418
419        let UserTransaction::V1(decoded_notarized) = user_transaction else {
420            panic!("Was not v1");
421        };
422
423        assert_eq!(notarized, decoded_notarized);
424    }
425
426    #[test]
427    fn notarized_transaction_v2_can_be_decoded_as_user_transaction() {
428        let network = NetworkDefinition::simulator();
429
430        let notary_private_key = Ed25519PrivateKey::from_u64(3).unwrap();
431
432        let header = TransactionHeaderV2 {
433            notary_public_key: notary_private_key.public_key().into(),
434            notary_is_signatory: false,
435            tip_basis_points: 51,
436        };
437
438        let intent_header = IntentHeaderV2 {
439            network_id: network.id,
440            start_epoch_inclusive: Epoch::of(1),
441            end_epoch_exclusive: Epoch::of(5),
442            min_proposer_timestamp_inclusive: None,
443            max_proposer_timestamp_exclusive: None,
444            intent_discriminator: 21,
445        };
446
447        let notarized = TransactionV2Builder::new()
448            .transaction_header(header)
449            .intent_header(intent_header)
450            .manifest_builder(|builder| builder)
451            .notarize(&notary_private_key)
452            .build_minimal();
453
454        let raw = notarized.to_raw().unwrap();
455
456        let user_transaction = raw.into_typed().unwrap();
457
458        let UserTransaction::V2(decoded_notarized) = user_transaction else {
459            panic!("Was not v2");
460        };
461
462        assert_eq!(notarized, decoded_notarized);
463    }
464}