1mod deployment;
17pub use deployment::*;
18
19mod execution;
20pub use execution::*;
21
22mod fee;
23pub use fee::*;
24
25mod bytes;
26mod merkle;
27mod serialize;
28mod string;
29
30use crate::Transition;
31use console::{
32 network::prelude::*,
33 program::{
34 Ciphertext,
35 DeploymentTree,
36 ExecutionTree,
37 ProgramOwner,
38 Record,
39 TRANSACTION_DEPTH,
40 TransactionLeaf,
41 TransactionPath,
42 TransactionTree,
43 },
44 types::{Field, Group, U64},
45};
46
47type DeploymentID<N> = Field<N>;
48type ExecutionID<N> = Field<N>;
49
50#[derive(Clone, PartialEq, Eq)]
51pub enum Transaction<N: Network> {
52 Deploy(N::TransactionID, DeploymentID<N>, ProgramOwner<N>, Box<Deployment<N>>, Fee<N>),
54 Execute(N::TransactionID, ExecutionID<N>, Box<Execution<N>>, Option<Fee<N>>),
56 Fee(N::TransactionID, Fee<N>),
58}
59
60impl<N: Network> Transaction<N> {
61 pub fn from_deployment(owner: ProgramOwner<N>, deployment: Deployment<N>, fee: Fee<N>) -> Result<Self> {
63 ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty deployment transaction");
65 let deployment_tree = Self::deployment_tree(&deployment)?;
67 let deployment_id = *deployment_tree.root();
69 let transaction_id = *Self::transaction_tree(deployment_tree, Some(&fee))?.root();
71 ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner");
73 if let Some(program_owner) = deployment.program_owner() {
75 ensure!(
76 owner.address() == program_owner,
77 "Attempted to create a deployment transaction with a provided owner '{}' and deployment owner '{}' that do not match",
78 owner.address(),
79 program_owner
80 )
81 }
82 Ok(Self::Deploy(transaction_id.into(), deployment_id, owner, Box::new(deployment), fee))
84 }
85
86 pub fn from_execution(execution: Execution<N>, fee: Option<Fee<N>>) -> Result<Self> {
88 ensure!(!execution.is_empty(), "Attempted to create an empty execution transaction");
90 let execution_tree = Self::execution_tree(&execution)?;
92 let execution_id = *execution_tree.root();
94 let transaction_id = *Self::transaction_tree(execution_tree, fee.as_ref())?.root();
96 Ok(Self::Execute(transaction_id.into(), execution_id, Box::new(execution), fee))
98 }
99
100 pub fn from_fee(fee: Fee<N>) -> Result<Self> {
102 ensure!(!fee.is_zero()?, "Attempted to create a zero fee transaction");
104 let id = *Self::fee_tree(&fee)?.root();
106 Ok(Self::Fee(id.into(), fee))
108 }
109}
110
111impl<N: Network> Transaction<N> {
112 #[inline]
114 pub const fn is_deploy(&self) -> bool {
115 matches!(self, Self::Deploy(..))
116 }
117
118 #[inline]
120 pub const fn is_execute(&self) -> bool {
121 matches!(self, Self::Execute(..))
122 }
123
124 #[inline]
126 pub const fn is_fee(&self) -> bool {
127 matches!(self, Self::Fee(..))
128 }
129}
130
131impl<N: Network> Transaction<N> {
132 #[inline]
134 pub fn contains_split(&self) -> bool {
135 match self {
136 Transaction::Execute(_, _, execution, _) => execution.transitions().any(|transition| transition.is_split()),
138 _ => false,
140 }
141 }
142
143 #[inline]
145 pub fn contains_upgrade(&self) -> bool {
146 match self {
147 Transaction::Execute(_, _, execution, _) => {
149 execution.transitions().any(|transition| transition.is_upgrade())
150 }
151 _ => false,
153 }
154 }
155}
156
157impl<N: Network> Transaction<N> {
158 #[inline]
160 pub fn owner(&self) -> Option<&ProgramOwner<N>> {
161 match self {
162 Self::Deploy(_, _, owner, _, _) => Some(owner),
163 _ => None,
164 }
165 }
166
167 #[inline]
169 pub fn deployment(&self) -> Option<&Deployment<N>> {
170 match self {
171 Self::Deploy(_, _, _, deployment, _) => Some(deployment.as_ref()),
172 _ => None,
173 }
174 }
175
176 #[inline]
178 pub fn execution(&self) -> Option<&Execution<N>> {
179 match self {
180 Self::Execute(_, _, execution, _) => Some(execution),
181 _ => None,
182 }
183 }
184}
185
186enum IterWrap<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> {
188 Deploy(I1),
189 Execute(I2),
190 Fee(I3),
191}
192
193impl<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> Iterator for IterWrap<T, I1, I2, I3> {
194 type Item = T;
195
196 fn next(&mut self) -> Option<Self::Item> {
197 match self {
198 Self::Deploy(iter) => iter.next(),
199 Self::Execute(iter) => iter.next(),
200 Self::Fee(iter) => iter.next(),
201 }
202 }
203}
204
205impl<T, I1: DoubleEndedIterator<Item = T>, I2: DoubleEndedIterator<Item = T>, I3: DoubleEndedIterator<Item = T>>
206 DoubleEndedIterator for IterWrap<T, I1, I2, I3>
207{
208 fn next_back(&mut self) -> Option<Self::Item> {
209 match self {
210 Self::Deploy(iter) => iter.next_back(),
211 Self::Execute(iter) => iter.next_back(),
212 Self::Fee(iter) => iter.next_back(),
213 }
214 }
215}
216
217impl<N: Network> Transaction<N> {
218 pub const fn id(&self) -> N::TransactionID {
220 match self {
221 Self::Deploy(id, ..) => *id,
222 Self::Execute(id, ..) => *id,
223 Self::Fee(id, ..) => *id,
224 }
225 }
226
227 pub fn fee_amount(&self) -> Result<U64<N>> {
229 match self {
230 Self::Deploy(_, _, _, _, fee) => fee.amount(),
231 Self::Execute(_, _, _, Some(fee)) => fee.amount(),
232 Self::Execute(_, _, _, None) => Ok(U64::zero()),
233 Self::Fee(_, fee) => fee.amount(),
234 }
235 }
236
237 pub fn base_fee_amount(&self) -> Result<U64<N>> {
239 match self {
240 Self::Deploy(_, _, _, _, fee) => fee.base_amount(),
241 Self::Execute(_, _, _, Some(fee)) => fee.base_amount(),
242 Self::Execute(_, _, _, None) => Ok(U64::zero()),
243 Self::Fee(_, fee) => fee.base_amount(),
244 }
245 }
246
247 pub fn priority_fee_amount(&self) -> Result<U64<N>> {
249 match self {
250 Self::Deploy(_, _, _, _, fee) => fee.priority_amount(),
251 Self::Execute(_, _, _, Some(fee)) => fee.priority_amount(),
252 Self::Execute(_, _, _, None) => Ok(U64::zero()),
253 Self::Fee(_, fee) => fee.priority_amount(),
254 }
255 }
256
257 pub fn fee_transition(&self) -> Option<Fee<N>> {
259 match self {
260 Self::Deploy(_, _, _, _, fee) => Some(fee.clone()),
261 Self::Execute(_, _, _, fee) => fee.clone(),
262 Self::Fee(_, fee) => Some(fee.clone()),
263 }
264 }
265}
266
267impl<N: Network> Transaction<N> {
268 pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool {
270 match self {
271 Self::Deploy(_, _, _, _, fee) => fee.id() == transition_id,
273 Self::Execute(_, _, execution, fee) => {
275 execution.contains_transition(transition_id)
276 || fee.as_ref().is_some_and(|fee| fee.id() == transition_id)
277 }
278 Self::Fee(_, fee) => fee.id() == transition_id,
280 }
281 }
282
283 pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
285 self.transitions().any(|transition| transition.contains_serial_number(serial_number))
286 }
287
288 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
290 self.transitions().any(|transition| transition.contains_commitment(commitment))
291 }
292}
293
294impl<N: Network> Transaction<N> {
295 pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> {
297 match self {
298 Self::Deploy(_, _, _, _, fee) => match fee.id() == transition_id {
300 true => Some(fee.transition()),
301 false => None,
302 },
303 Self::Execute(_, _, execution, fee) => execution.get_transition(transition_id).or_else(|| {
305 fee.as_ref().and_then(|fee| match fee.id() == transition_id {
306 true => Some(fee.transition()),
307 false => None,
308 })
309 }),
310 Self::Fee(_, fee) => match fee.id() == transition_id {
312 true => Some(fee.transition()),
313 false => None,
314 },
315 }
316 }
317
318 pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> {
320 self.transitions().find(|transition| transition.contains_serial_number(serial_number))
321 }
322
323 pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> {
325 self.transitions().find(|transition| transition.contains_commitment(commitment))
326 }
327
328 pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
330 self.transitions().find_map(|transition| transition.find_record(commitment))
331 }
332}
333
334impl<N: Network> Transaction<N> {
335 pub fn transition_ids(&self) -> impl '_ + DoubleEndedIterator<Item = &N::TransitionID> {
337 self.transitions().map(Transition::id)
338 }
339
340 pub fn transitions(&self) -> impl '_ + DoubleEndedIterator<Item = &Transition<N>> {
342 match self {
343 Self::Deploy(_, _, _, _, fee) => IterWrap::Deploy(Some(fee.transition()).into_iter()),
344 Self::Execute(_, _, execution, fee) => {
345 IterWrap::Execute(execution.transitions().chain(fee.as_ref().map(|fee| fee.transition())))
346 }
347 Self::Fee(_, fee) => IterWrap::Fee(Some(fee.transition()).into_iter()),
348 }
349 }
350
351 pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
355 self.transitions().flat_map(Transition::input_ids)
356 }
357
358 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
360 self.transitions().flat_map(Transition::serial_numbers)
361 }
362
363 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
365 self.transitions().flat_map(Transition::tags)
366 }
367
368 pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
372 self.transitions().flat_map(Transition::output_ids)
373 }
374
375 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
377 self.transitions().flat_map(Transition::commitments)
378 }
379
380 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
382 self.transitions().flat_map(Transition::records)
383 }
384
385 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
387 self.transitions().flat_map(Transition::nonces)
388 }
389
390 pub fn transition_public_keys(&self) -> impl '_ + DoubleEndedIterator<Item = &Group<N>> {
392 self.transitions().map(Transition::tpk)
393 }
394
395 pub fn transition_commitments(&self) -> impl '_ + DoubleEndedIterator<Item = &Field<N>> {
397 self.transitions().map(Transition::tcm)
398 }
399}
400
401impl<N: Network> Transaction<N> {
402 pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> {
404 self.into_transitions().map(Transition::into_id)
405 }
406
407 pub fn into_transitions(self) -> impl DoubleEndedIterator<Item = Transition<N>> {
409 match self {
410 Self::Deploy(_, _, _, _, fee) => IterWrap::Deploy(Some(fee.into_transition()).into_iter()),
411 Self::Execute(_, _, execution, fee) => {
412 IterWrap::Execute(execution.into_transitions().chain(fee.map(|fee| fee.into_transition())))
413 }
414 Self::Fee(_, fee) => IterWrap::Fee(Some(fee.into_transition()).into_iter()),
415 }
416 }
417
418 pub fn into_transition_public_keys(self) -> impl DoubleEndedIterator<Item = Group<N>> {
420 self.into_transitions().map(Transition::into_tpk)
421 }
422
423 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
425 self.into_transitions().flat_map(Transition::into_tags)
426 }
427
428 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
430 self.into_transitions().flat_map(Transition::into_serial_numbers)
431 }
432
433 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
435 self.into_transitions().flat_map(Transition::into_commitments)
436 }
437
438 pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
440 self.into_transitions().flat_map(Transition::into_records)
441 }
442
443 pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
445 self.into_transitions().flat_map(Transition::into_nonces)
446 }
447}
448
449#[cfg(test)]
450pub mod test_helpers {
451 use super::*;
452 use console::{account::PrivateKey, network::MainnetV0, program::ProgramOwner, types::Address};
453
454 type CurrentNetwork = MainnetV0;
455
456 pub fn sample_deployment_transaction(
458 version: u8,
459 edition: u16,
460 is_fee_private: bool,
461 rng: &mut TestRng,
462 ) -> Transaction<CurrentNetwork> {
463 let private_key = PrivateKey::new(rng).unwrap();
465 let deployment = match version {
467 1 => crate::transaction::deployment::test_helpers::sample_deployment_v1(edition, rng),
468 2 => {
469 let mut deployment = crate::transaction::deployment::test_helpers::sample_deployment_v2(edition, rng);
470 deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
472 deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap()));
474 deployment
476 }
477 _ => panic!("Invalid deployment version."),
478 };
479
480 let deployment_id = deployment.to_deployment_id().unwrap();
482 let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap();
484
485 let fee = match is_fee_private {
487 true => crate::transaction::fee::test_helpers::sample_fee_private(deployment_id, rng),
488 false => crate::transaction::fee::test_helpers::sample_fee_public(deployment_id, rng),
489 };
490
491 Transaction::from_deployment(owner, deployment, fee).unwrap()
493 }
494
495 pub fn sample_execution_transaction_with_fee(
497 is_fee_private: bool,
498 rng: &mut TestRng,
499 index: usize,
500 ) -> Transaction<CurrentNetwork> {
501 let execution = crate::transaction::execution::test_helpers::sample_execution(rng, index);
503 let execution_id = execution.to_execution_id().unwrap();
505
506 let fee = match is_fee_private {
508 true => crate::transaction::fee::test_helpers::sample_fee_private(execution_id, rng),
509 false => crate::transaction::fee::test_helpers::sample_fee_public(execution_id, rng),
510 };
511
512 Transaction::from_execution(execution, Some(fee)).unwrap()
514 }
515
516 pub fn sample_private_fee_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
518 let fee = crate::transaction::fee::test_helpers::sample_fee_private_hardcoded(rng);
520 Transaction::from_fee(fee).unwrap()
522 }
523
524 pub fn sample_fee_public_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
526 let fee = crate::transaction::fee::test_helpers::sample_fee_public_hardcoded(rng);
528 Transaction::from_fee(fee).unwrap()
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_transaction_id() -> Result<()> {
539 let rng = &mut TestRng::default();
540
541 for expected in [
543 crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng),
544 crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng),
545 crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng),
546 crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng),
547 crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng),
548 crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng),
549 crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng),
550 crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng),
551 crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0),
552 crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0),
553 ]
554 .into_iter()
555 {
556 match expected {
557 Transaction::Deploy(transaction_id, deployment_id, _, ref deployment, _) => {
559 let expected_transaction_id = *expected.clone().to_tree()?.root();
560 assert_eq!(expected_transaction_id, *transaction_id);
561 let expected_deployment_id = *Transaction::deployment_tree(deployment)?.root();
562 assert_eq!(expected_deployment_id, deployment_id);
563 }
564 Transaction::Execute(transaction_id, execution_id, ref execution, _) => {
566 let expected_transaction_id = *expected.clone().to_tree()?.root();
567 assert_eq!(expected_transaction_id, *transaction_id);
568 let expected_execution_id = *Transaction::execution_tree(execution)?.root();
569 assert_eq!(expected_execution_id, execution_id);
570 }
571 _ => panic!("Unexpected test case."),
572 };
573 }
574
575 Ok(())
576 }
577}