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).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 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(¬ary_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(¬ary_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}