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).into(),
126            UserTransaction::V2(t) => t.extract_manifests_with_names(names).into(),
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#[derive(Debug, Clone, Eq, PartialEq)]
160pub enum PreparedUserTransaction {
161    V1(PreparedNotarizedTransactionV1),
162    V2(PreparedNotarizedTransactionV2),
163}
164
165impl PreparedUserTransaction {
166    pub fn end_epoch_exclusive(&self) -> Epoch {
167        match self {
168            PreparedUserTransaction::V1(t) => t.end_epoch_exclusive(),
169            PreparedUserTransaction::V2(t) => t.end_epoch_exclusive(),
170        }
171    }
172
173    pub fn hashes(&self) -> UserTransactionHashes {
174        match self {
175            PreparedUserTransaction::V1(t) => t.hashes(),
176            PreparedUserTransaction::V2(t) => t.hashes(),
177        }
178    }
179
180    pub fn validate(
181        self,
182        validator: &TransactionValidator,
183    ) -> Result<ValidatedUserTransaction, TransactionValidationError> {
184        Ok(match self {
185            PreparedUserTransaction::V1(t) => ValidatedUserTransaction::V1(t.validate(validator)?),
186            PreparedUserTransaction::V2(t) => ValidatedUserTransaction::V2(t.validate(validator)?),
187        })
188    }
189}
190
191impl HasTransactionIntentHash for PreparedUserTransaction {
192    fn transaction_intent_hash(&self) -> TransactionIntentHash {
193        match self {
194            Self::V1(t) => t.transaction_intent_hash(),
195            Self::V2(t) => t.transaction_intent_hash(),
196        }
197    }
198}
199
200impl HasSignedTransactionIntentHash for PreparedUserTransaction {
201    fn signed_transaction_intent_hash(&self) -> SignedTransactionIntentHash {
202        match self {
203            Self::V1(t) => t.signed_transaction_intent_hash(),
204            Self::V2(t) => t.signed_transaction_intent_hash(),
205        }
206    }
207}
208
209impl HasNotarizedTransactionHash for PreparedUserTransaction {
210    fn notarized_transaction_hash(&self) -> NotarizedTransactionHash {
211        match self {
212            Self::V1(t) => t.notarized_transaction_hash(),
213            Self::V2(t) => t.notarized_transaction_hash(),
214        }
215    }
216}
217
218impl HasNonRootSubintentHashes for PreparedUserTransaction {
219    fn non_root_subintent_hashes(&self) -> Vec<SubintentHash> {
220        match self {
221            Self::V1(_) => Default::default(),
222            Self::V2(t) => t.non_root_subintent_hashes(),
223        }
224    }
225}
226
227impl HasSummary for PreparedUserTransaction {
228    fn get_summary(&self) -> &Summary {
229        match self {
230            Self::V1(t) => t.get_summary(),
231            Self::V2(t) => t.get_summary(),
232        }
233    }
234
235    fn summary_mut(&mut self) -> &mut Summary {
236        match self {
237            Self::V1(t) => t.summary_mut(),
238            Self::V2(t) => t.summary_mut(),
239        }
240    }
241}
242
243impl PreparedTransaction for PreparedUserTransaction {
244    type Raw = RawNotarizedTransaction;
245
246    fn prepare_from_transaction_enum(
247        decoder: &mut TransactionDecoder,
248    ) -> Result<Self, PrepareError> {
249        let offset = decoder.get_offset();
250        let slice = decoder.get_input_slice();
251        let discriminator_byte = slice
252            .get(offset + 1)
253            .ok_or(PrepareError::UnexpectedTransactionDiscriminator { actual: None })?;
254
255        let prepared = match TransactionDiscriminator::from_repr(*discriminator_byte) {
256            Some(TransactionDiscriminator::V1Notarized) => PreparedUserTransaction::V1(
257                PreparedNotarizedTransactionV1::prepare_from_transaction_enum(decoder)?,
258            ),
259            Some(TransactionDiscriminator::V2Notarized) => PreparedUserTransaction::V2(
260                PreparedNotarizedTransactionV2::prepare_from_transaction_enum(decoder)?,
261            ),
262            _ => {
263                return Err(PrepareError::UnexpectedTransactionDiscriminator {
264                    actual: Some(*discriminator_byte),
265                })
266            }
267        };
268
269        Ok(prepared)
270    }
271}
272
273#[derive(Debug, Clone, Eq, PartialEq)]
274pub enum ValidatedUserTransaction {
275    V1(ValidatedNotarizedTransactionV1),
276    V2(ValidatedNotarizedTransactionV2),
277}
278
279impl HasTransactionIntentHash for ValidatedUserTransaction {
280    fn transaction_intent_hash(&self) -> TransactionIntentHash {
281        match self {
282            Self::V1(t) => t.transaction_intent_hash(),
283            Self::V2(t) => t.transaction_intent_hash(),
284        }
285    }
286}
287
288impl HasSignedTransactionIntentHash for ValidatedUserTransaction {
289    fn signed_transaction_intent_hash(&self) -> SignedTransactionIntentHash {
290        match self {
291            Self::V1(t) => t.signed_transaction_intent_hash(),
292            Self::V2(t) => t.signed_transaction_intent_hash(),
293        }
294    }
295}
296
297impl HasNotarizedTransactionHash for ValidatedUserTransaction {
298    fn notarized_transaction_hash(&self) -> NotarizedTransactionHash {
299        match self {
300            Self::V1(t) => t.notarized_transaction_hash(),
301            Self::V2(t) => t.notarized_transaction_hash(),
302        }
303    }
304}
305
306impl HasNonRootSubintentHashes for ValidatedUserTransaction {
307    fn non_root_subintent_hashes(&self) -> Vec<SubintentHash> {
308        match self {
309            Self::V1(_) => Default::default(),
310            Self::V2(t) => t.non_root_subintent_hashes(),
311        }
312    }
313}
314
315impl IntoExecutable for ValidatedUserTransaction {
316    type Error = core::convert::Infallible;
317
318    fn into_executable(
319        self,
320        _validator: &TransactionValidator,
321    ) -> Result<ExecutableTransaction, Self::Error> {
322        Ok(self.create_executable())
323    }
324}
325
326impl ValidatedUserTransaction {
327    pub fn end_epoch_exclusive(&self) -> Epoch {
328        match self {
329            ValidatedUserTransaction::V1(t) => t.prepared.end_epoch_exclusive(),
330            ValidatedUserTransaction::V2(t) => {
331                t.overall_validity_range.epoch_range.end_epoch_exclusive
332            }
333        }
334    }
335
336    pub fn create_executable(self) -> ExecutableTransaction {
337        match self {
338            Self::V1(t) => t.create_executable(),
339            Self::V2(t) => t.create_executable(),
340        }
341    }
342
343    pub fn hashes(&self) -> UserTransactionHashes {
344        match self {
345            Self::V1(t) => t.hashes(),
346            Self::V2(t) => t.hashes(),
347        }
348    }
349}
350
351pub type UserTransactionHashes = UserTransactionHashesV2;
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Sbor)]
354pub struct UserTransactionHashesV1 {
355    pub transaction_intent_hash: TransactionIntentHash,
356    pub signed_transaction_intent_hash: SignedTransactionIntentHash,
357    pub notarized_transaction_hash: NotarizedTransactionHash,
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, Sbor)]
361pub struct UserTransactionHashesV2 {
362    pub transaction_intent_hash: TransactionIntentHash,
363    /// ## Validity Note
364    /// Preparable but invalid transactions may contain non-root subintents with duplicate [`SubintentHash`]es.
365    /// Therefore we return a `Vec` instead of an `IndexSet` here.
366    pub non_root_subintent_hashes: Vec<SubintentHash>,
367    pub signed_transaction_intent_hash: SignedTransactionIntentHash,
368    pub notarized_transaction_hash: NotarizedTransactionHash,
369}
370
371impl From<UserTransactionHashesV1> for UserTransactionHashesV2 {
372    fn from(value: UserTransactionHashesV1) -> Self {
373        let UserTransactionHashesV1 {
374            transaction_intent_hash,
375            signed_transaction_intent_hash,
376            notarized_transaction_hash,
377        } = value;
378        UserTransactionHashesV2 {
379            transaction_intent_hash,
380            non_root_subintent_hashes: vec![],
381            signed_transaction_intent_hash,
382            notarized_transaction_hash,
383        }
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390
391    #[test]
392    fn notarized_transaction_v1_can_be_decoded_as_user_transaction() {
393        let network = NetworkDefinition::simulator();
394
395        let notary_private_key = Ed25519PrivateKey::from_u64(3).unwrap();
396
397        let header = TransactionHeaderV1 {
398            network_id: network.id,
399            start_epoch_inclusive: Epoch::of(1),
400            end_epoch_exclusive: Epoch::of(5),
401            nonce: 0,
402            notary_public_key: notary_private_key.public_key().into(),
403            notary_is_signatory: false,
404            tip_percentage: 0,
405        };
406
407        let notarized = TransactionBuilder::new()
408            .header(header)
409            .manifest(ManifestBuilder::new_v1().build())
410            .notarize(&notary_private_key)
411            .build();
412
413        let raw = notarized.to_raw().unwrap();
414
415        let user_transaction = raw.into_typed().unwrap();
416
417        let UserTransaction::V1(decoded_notarized) = user_transaction else {
418            panic!("Was not v1");
419        };
420
421        assert_eq!(notarized, decoded_notarized);
422    }
423
424    #[test]
425    fn notarized_transaction_v2_can_be_decoded_as_user_transaction() {
426        let network = NetworkDefinition::simulator();
427
428        let notary_private_key = Ed25519PrivateKey::from_u64(3).unwrap();
429
430        let header = TransactionHeaderV2 {
431            notary_public_key: notary_private_key.public_key().into(),
432            notary_is_signatory: false,
433            tip_basis_points: 51,
434        };
435
436        let intent_header = IntentHeaderV2 {
437            network_id: network.id,
438            start_epoch_inclusive: Epoch::of(1),
439            end_epoch_exclusive: Epoch::of(5),
440            min_proposer_timestamp_inclusive: None,
441            max_proposer_timestamp_exclusive: None,
442            intent_discriminator: 21,
443        };
444
445        let notarized = TransactionV2Builder::new()
446            .transaction_header(header)
447            .intent_header(intent_header)
448            .manifest_builder(|builder| builder)
449            .notarize(&notary_private_key)
450            .build_minimal();
451
452        let raw = notarized.to_raw().unwrap();
453
454        let user_transaction = raw.into_typed().unwrap();
455
456        let UserTransaction::V2(decoded_notarized) = user_transaction else {
457            panic!("Was not v2");
458        };
459
460        assert_eq!(notarized, decoded_notarized);
461    }
462}