radix_transactions/model/
user_transaction.rs1use 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#[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 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(¬ary_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(¬ary_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}