mod.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the alphavm library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 mod deployment; 17 pub use deployment::*; 18 19 mod execution; 20 pub use execution::*; 21 22 mod fee; 23 pub use fee::*; 24 25 mod bytes; 26 mod merkle; 27 mod serialize; 28 mod string; 29 30 use crate::Transition; 31 use 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 47 type DeploymentID<N> = Field<N>; 48 type ExecutionID<N> = Field<N>; 49 50 #[derive(Clone, PartialEq, Eq)] 51 pub enum Transaction<N: Network> { 52 /// The deploy transaction publishes an Alpha program to the network. 53 Deploy(N::TransactionID, DeploymentID<N>, ProgramOwner<N>, Box<Deployment<N>>, Fee<N>), 54 /// The execute transaction represents a call to an Alpha program. 55 Execute(N::TransactionID, ExecutionID<N>, Box<Execution<N>>, Option<Fee<N>>), 56 /// The fee transaction represents a fee paid to the network, used for rejected transactions. 57 Fee(N::TransactionID, Fee<N>), 58 } 59 60 impl<N: Network> Transaction<N> { 61 /// Initializes a new deployment transaction. 62 pub fn from_deployment(owner: ProgramOwner<N>, deployment: Deployment<N>, fee: Fee<N>) -> Result<Self> { 63 // Ensure the transaction is not empty. 64 ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty deployment transaction"); 65 // Compute the deployment tree. 66 let deployment_tree = Self::deployment_tree(&deployment)?; 67 // Compute the deployment ID. 68 let deployment_id = *deployment_tree.root(); 69 // Compute the transaction ID 70 let transaction_id = *Self::transaction_tree(deployment_tree, Some(&fee))?.root(); 71 // Ensure the owner signed the correct transaction ID. 72 ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner"); 73 // Ensure the owner matches the program owner in the deployment, if it exists. 74 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 // Construct the deployment transaction. 83 Ok(Self::Deploy(transaction_id.into(), deployment_id, owner, Box::new(deployment), fee)) 84 } 85 86 /// Initializes a new execution transaction. 87 pub fn from_execution(execution: Execution<N>, fee: Option<Fee<N>>) -> Result<Self> { 88 // Ensure the transaction is not empty. 89 ensure!(!execution.is_empty(), "Attempted to create an empty execution transaction"); 90 // Compute the execution tree. 91 let execution_tree = Self::execution_tree(&execution)?; 92 // Compute the execution ID. 93 let execution_id = *execution_tree.root(); 94 // Compute the transaction ID. 95 let transaction_id = *Self::transaction_tree(execution_tree, fee.as_ref())?.root(); 96 // Construct the execution transaction. 97 Ok(Self::Execute(transaction_id.into(), execution_id, Box::new(execution), fee)) 98 } 99 100 /// Initializes a new fee transaction. 101 pub fn from_fee(fee: Fee<N>) -> Result<Self> { 102 // Ensure the fee is nonzero. 103 ensure!(!fee.is_zero()?, "Attempted to create a zero fee transaction"); 104 // Compute the transaction ID. 105 let id = *Self::fee_tree(&fee)?.root(); 106 // Construct the execution transaction. 107 Ok(Self::Fee(id.into(), fee)) 108 } 109 } 110 111 impl<N: Network> Transaction<N> { 112 /// Returns `true` if the transaction is a deploy transaction. 113 #[inline] 114 pub const fn is_deploy(&self) -> bool { 115 matches!(self, Self::Deploy(..)) 116 } 117 118 /// Returns `true` if the transaction is an execute transaction. 119 #[inline] 120 pub const fn is_execute(&self) -> bool { 121 matches!(self, Self::Execute(..)) 122 } 123 124 /// Returns `true` if the transaction is a fee transaction. 125 #[inline] 126 pub const fn is_fee(&self) -> bool { 127 matches!(self, Self::Fee(..)) 128 } 129 } 130 131 impl<N: Network> Transaction<N> { 132 /// Returns `true` if this transaction contains a call to `credits.alpha/split`. 133 #[inline] 134 pub fn contains_split(&self) -> bool { 135 match self { 136 // Case 1 - The transaction contains a transition that calls 'credits.alpha/split'. 137 Transaction::Execute(_, _, execution, _) => execution.transitions().any(|transition| transition.is_split()), 138 // Otherwise, return 'false'. 139 _ => false, 140 } 141 } 142 143 /// Returns `true` if this transaction contains a call to `credits.alpha/upgrade`. 144 #[inline] 145 pub fn contains_upgrade(&self) -> bool { 146 match self { 147 // Case 1 - The transaction contains a transition that calls 'credits.alpha/upgrade'. 148 Transaction::Execute(_, _, execution, _) => { 149 execution.transitions().any(|transition| transition.is_upgrade()) 150 } 151 // Otherwise, return 'false'. 152 _ => false, 153 } 154 } 155 } 156 157 impl<N: Network> Transaction<N> { 158 /// Returns `Some(owner)` if the transaction is a deployment. Otherwise, returns `None`. 159 #[inline] 160 pub fn owner(&self) -> Option<&ProgramOwner<N>> { 161 match self { 162 Self::Deploy(_, _, owner, _, _) => Some(owner), 163 _ => None, 164 } 165 } 166 167 /// Returns `Some(deployment)` if the transaction is a deployment. Otherwise, returns `None`. 168 #[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 /// Returns `Some(execution)` if the transaction is an execution. Otherwise, returns `None`. 177 #[inline] 178 pub fn execution(&self) -> Option<&Execution<N>> { 179 match self { 180 Self::Execute(_, _, execution, _) => Some(execution), 181 _ => None, 182 } 183 } 184 } 185 186 /// A helper enum for iterators and consuming iterators over a transaction. 187 enum 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 193 impl<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 205 impl<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 217 impl<N: Network> Transaction<N> { 218 /// Returns the transaction ID. 219 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 /// Returns the transaction total fee. 228 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 /// Returns the transaction base fee. 238 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 /// Returns the transaction priority fee. 248 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 /// Returns the fee transition. 258 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 267 impl<N: Network> Transaction<N> { 268 /// Returns `true` if the transaction contains the given transition ID. 269 pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool { 270 match self { 271 // Check the fee. 272 Self::Deploy(_, _, _, _, fee) => fee.id() == transition_id, 273 // Check the execution and fee. 274 Self::Execute(_, _, execution, fee) => { 275 execution.contains_transition(transition_id) 276 || fee.as_ref().is_some_and(|fee| fee.id() == transition_id) 277 } 278 // Check the fee. 279 Self::Fee(_, fee) => fee.id() == transition_id, 280 } 281 } 282 283 /// Returns `true` if the transaction contains the given serial number. 284 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 /// Returns `true` if the transaction contains the given commitment. 289 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool { 290 self.transitions().any(|transition| transition.contains_commitment(commitment)) 291 } 292 } 293 294 impl<N: Network> Transaction<N> { 295 /// Returns the transition with the corresponding transition ID, if it exists. 296 pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> { 297 match self { 298 // Check the fee. 299 Self::Deploy(_, _, _, _, fee) => match fee.id() == transition_id { 300 true => Some(fee.transition()), 301 false => None, 302 }, 303 // Check the execution and fee. 304 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 // Check the fee. 311 Self::Fee(_, fee) => match fee.id() == transition_id { 312 true => Some(fee.transition()), 313 false => None, 314 }, 315 } 316 } 317 318 /// Returns the transition for the given serial number, if it exists. 319 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 /// Returns the transition for the given commitment, if it exists. 324 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 /// Returns the record with the corresponding commitment, if it exists. 329 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 334 impl<N: Network> Transaction<N> { 335 /// Returns an iterator over the transition IDs, for all transitions. 336 pub fn transition_ids(&self) -> impl '_ + DoubleEndedIterator<Item = &N::TransitionID> { 337 self.transitions().map(Transition::id) 338 } 339 340 /// Returns an iterator over all transitions. 341 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 /* Input */ 352 353 /// Returns an iterator over the input IDs, for all transition inputs that are records. 354 pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> { 355 self.transitions().flat_map(Transition::input_ids) 356 } 357 358 /// Returns an iterator over the serial numbers, for all transition inputs that are records. 359 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> { 360 self.transitions().flat_map(Transition::serial_numbers) 361 } 362 363 /// Returns an iterator over the tags, for all transition inputs that are records. 364 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> { 365 self.transitions().flat_map(Transition::tags) 366 } 367 368 /* Output */ 369 370 /// Returns an iterator over the output IDs, for all transition inputs that are records. 371 pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> { 372 self.transitions().flat_map(Transition::output_ids) 373 } 374 375 /// Returns an iterator over the commitments, for all transition outputs that are records. 376 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> { 377 self.transitions().flat_map(Transition::commitments) 378 } 379 380 /// Returns an iterator over the records, for all transition outputs that are records. 381 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> { 382 self.transitions().flat_map(Transition::records) 383 } 384 385 /// Returns an iterator over the nonces, for all transition outputs that are records. 386 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> { 387 self.transitions().flat_map(Transition::nonces) 388 } 389 390 /// Returns an iterator over the transition public keys, for all transitions. 391 pub fn transition_public_keys(&self) -> impl '_ + DoubleEndedIterator<Item = &Group<N>> { 392 self.transitions().map(Transition::tpk) 393 } 394 395 /// Returns an iterator over the transition commitments, for all transitions. 396 pub fn transition_commitments(&self) -> impl '_ + DoubleEndedIterator<Item = &Field<N>> { 397 self.transitions().map(Transition::tcm) 398 } 399 } 400 401 impl<N: Network> Transaction<N> { 402 /// Returns a consuming iterator over the transition IDs, for all transitions. 403 pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> { 404 self.into_transitions().map(Transition::into_id) 405 } 406 407 /// Returns a consuming iterator over all transitions. 408 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 /// Returns a consuming iterator over the transition public keys, for all transitions. 419 pub fn into_transition_public_keys(self) -> impl DoubleEndedIterator<Item = Group<N>> { 420 self.into_transitions().map(Transition::into_tpk) 421 } 422 423 /// Returns a consuming iterator over the tags, for all transition inputs that are records. 424 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> { 425 self.into_transitions().flat_map(Transition::into_tags) 426 } 427 428 /// Returns a consuming iterator over the serial numbers, for all transition inputs that are records. 429 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> { 430 self.into_transitions().flat_map(Transition::into_serial_numbers) 431 } 432 433 /// Returns a consuming iterator over the commitments, for all transition outputs that are records. 434 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> { 435 self.into_transitions().flat_map(Transition::into_commitments) 436 } 437 438 /// Returns a consuming iterator over the records, for all transition outputs that are records. 439 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 /// Returns a consuming iterator over the nonces, for all transition outputs that are records. 444 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)] 450 pub mod test_helpers { 451 use super::*; 452 use console::{account::PrivateKey, network::MainnetV0, program::ProgramOwner, types::Address}; 453 454 type CurrentNetwork = MainnetV0; 455 456 /// Samples a random deployment transaction with a private or public fee. 457 pub fn sample_deployment_transaction( 458 version: u8, 459 edition: u16, 460 is_fee_private: bool, 461 rng: &mut TestRng, 462 ) -> Transaction<CurrentNetwork> { 463 // Sample a private key. 464 let private_key = PrivateKey::new(rng).unwrap(); 465 // Sample a deployment. 466 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 // Set the program checksum. 471 deployment.set_program_checksum_raw(Some(deployment.program().to_checksum())); 472 // Set the program owner to the address of the private key. 473 deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap())); 474 // Return the deployment. 475 deployment 476 } 477 _ => panic!("Invalid deployment version."), 478 }; 479 480 // Compute the deployment ID. 481 let deployment_id = deployment.to_deployment_id().unwrap(); 482 // Construct a program owner. 483 let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap(); 484 485 // Sample the fee. 486 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 // Construct a deployment transaction. 492 Transaction::from_deployment(owner, deployment, fee).unwrap() 493 } 494 495 /// Samples a random execution transaction with a private or public fee. 496 pub fn sample_execution_transaction_with_fee( 497 is_fee_private: bool, 498 rng: &mut TestRng, 499 index: usize, 500 ) -> Transaction<CurrentNetwork> { 501 // Sample an execution. 502 let execution = crate::transaction::execution::test_helpers::sample_execution(rng, index); 503 // Compute the execution ID. 504 let execution_id = execution.to_execution_id().unwrap(); 505 506 // Sample the fee. 507 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 // Construct an execution transaction. 513 Transaction::from_execution(execution, Some(fee)).unwrap() 514 } 515 516 /// Samples a random fee transaction. 517 pub fn sample_private_fee_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> { 518 // Sample a fee. 519 let fee = crate::transaction::fee::test_helpers::sample_fee_private_hardcoded(rng); 520 // Construct a fee transaction. 521 Transaction::from_fee(fee).unwrap() 522 } 523 524 /// Samples a random fee transaction. 525 pub fn sample_fee_public_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> { 526 // Sample a fee. 527 let fee = crate::transaction::fee::test_helpers::sample_fee_public_hardcoded(rng); 528 // Construct a fee transaction. 529 Transaction::from_fee(fee).unwrap() 530 } 531 } 532 533 #[cfg(test)] 534 mod tests { 535 use super::*; 536 537 #[test] 538 fn test_transaction_id() -> Result<()> { 539 let rng = &mut TestRng::default(); 540 541 // Transaction IDs are created using `transaction_tree`. 542 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 // Compare against transaction IDs created using `deployment_tree`. 558 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 // Compare against transaction IDs created using `execution_tree`. 565 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 }