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 pub mod confirmed; 17 pub use confirmed::*; 18 19 pub mod rejected; 20 pub use rejected::*; 21 22 mod bytes; 23 mod merkle; 24 mod serialize; 25 mod string; 26 27 use crate::{Transaction, Transition}; 28 use alphavm_ledger_committee::Committee; 29 use alphavm_ledger_narwhal_batch_header::BatchHeader; 30 use alphavm_synthesizer_program::FinalizeOperation; 31 use console::{ 32 network::prelude::*, 33 program::{ 34 Ciphertext, 35 FINALIZE_ID_DEPTH, 36 FINALIZE_OPERATIONS_DEPTH, 37 ProgramOwner, 38 Record, 39 TRANSACTIONS_DEPTH, 40 TransactionsPath, 41 TransactionsTree, 42 }, 43 types::{Field, Group, U64}, 44 }; 45 46 use indexmap::IndexMap; 47 48 #[cfg(not(feature = "serial"))] 49 use rayon::prelude::*; 50 51 /// The set of transactions included in a block. 52 #[derive(Clone, PartialEq, Eq)] 53 pub struct Transactions<N: Network> { 54 /// The transactions included in a block. 55 transactions: IndexMap<N::TransactionID, ConfirmedTransaction<N>>, 56 } 57 58 impl<N: Network> Transactions<N> { 59 /// Initializes from a given transactions list. 60 pub fn from(transactions: &[ConfirmedTransaction<N>]) -> Self { 61 Self::from_iter(transactions.iter()) 62 } 63 } 64 65 impl<N: Network> FromIterator<ConfirmedTransaction<N>> for Transactions<N> { 66 /// Initializes from an iterator of transactions. 67 fn from_iter<T: IntoIterator<Item = ConfirmedTransaction<N>>>(iter: T) -> Self { 68 Self { transactions: iter.into_iter().map(|transaction| (transaction.id(), transaction)).collect() } 69 } 70 } 71 72 impl<'a, N: Network> FromIterator<&'a ConfirmedTransaction<N>> for Transactions<N> { 73 /// Initializes from an iterator of transactions. 74 fn from_iter<T: IntoIterator<Item = &'a ConfirmedTransaction<N>>>(iter: T) -> Self { 75 Self::from_iter(iter.into_iter().cloned()) 76 } 77 } 78 79 impl<N: Network> Transactions<N> { 80 /// Returns the transaction for the given transaction ID. 81 pub fn get(&self, transaction_id: &N::TransactionID) -> Option<&ConfirmedTransaction<N>> { 82 self.transactions.get(transaction_id) 83 } 84 85 /// Returns 'true' if there are no accepted or rejected transactions. 86 pub fn is_empty(&self) -> bool { 87 self.transactions.is_empty() 88 } 89 90 /// Returns the number of confirmed transactions. 91 pub fn len(&self) -> usize { 92 self.transactions.len() 93 } 94 95 /// Returns the number of accepted transactions. 96 pub fn num_accepted(&self) -> usize { 97 cfg_values!(self.transactions).filter(|tx| tx.is_accepted()).count() 98 } 99 100 /// Returns the number of rejected transactions. 101 pub fn num_rejected(&self) -> usize { 102 cfg_values!(self.transactions).filter(|tx| tx.is_rejected()).count() 103 } 104 105 /// Returns the number of finalize operations. 106 pub fn num_finalize(&self) -> usize { 107 cfg_values!(self.transactions).map(|tx| tx.num_finalize()).sum() 108 } 109 110 /// Returns the index of the transaction with the given ID, if it exists. 111 pub fn index_of(&self, transaction_id: &N::TransactionID) -> Option<usize> { 112 self.transactions.get_index_of(transaction_id) 113 } 114 } 115 116 impl<N: Network> Transactions<N> { 117 /// Returns `true` if the transactions contains the given transition ID. 118 pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool { 119 cfg_values!(self.transactions).any(|tx| tx.contains_transition(transition_id)) 120 } 121 122 /// Returns `true` if the transactions contains the given serial number. 123 pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool { 124 cfg_values!(self.transactions).any(|tx| tx.contains_serial_number(serial_number)) 125 } 126 127 /// Returns `true` if the transactions contains the given commitment. 128 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool { 129 cfg_values!(self.transactions).any(|tx| tx.contains_commitment(commitment)) 130 } 131 } 132 133 impl<N: Network> Transactions<N> { 134 /// Returns the confirmed transaction for the given unconfirmed transaction ID, if it exists. 135 pub fn find_confirmed_transaction_for_unconfirmed_transaction_id( 136 &self, 137 unconfirmed_transaction_id: &N::TransactionID, 138 ) -> Option<&ConfirmedTransaction<N>> { 139 cfg_find!(self.transactions, |txn| txn.contains_unconfirmed_transaction_id(unconfirmed_transaction_id)) 140 } 141 142 /// Returns the transaction with the given transition ID, if it exists. 143 /// 144 /// If the given transition ID is a fee transition for a rejected transaction, 145 /// this will return the fee transaction. 146 pub fn find_transaction_for_transition_id(&self, transition_id: &N::TransitionID) -> Option<&Transaction<N>> { 147 cfg_find!(self.transactions, |txn| txn.contains_transition(transition_id)).map(|tx| tx.transaction()) 148 } 149 150 /// Returns the unconfirmed transaction with the given transition ID, if it exists. 151 /// 152 /// If the given transition ID is a fee transition for a rejected transaction, 153 /// this will return the original/unconfirmed transaction, not the fee transaction. 154 pub fn find_unconfirmed_transaction_for_transition_id( 155 &self, 156 transition_id: &N::TransitionID, 157 ) -> Result<Option<Transaction<N>>> { 158 let result = cfg_find!(self.transactions, |tx| tx.contains_transition(transition_id)); 159 160 match result { 161 Some(txn) => Ok(Some(txn.to_unconfirmed_transaction()?)), 162 None => Ok(None), 163 } 164 } 165 166 /// Returns the transaction with the given serial number, if it exists. 167 pub fn find_transaction_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transaction<N>> { 168 cfg_find!(self.transactions, |txn| txn.contains_serial_number(serial_number)).map(|tx| tx.transaction()) 169 } 170 171 /// Returns the transaction with the given commitment, if it exists. 172 pub fn find_transaction_for_commitment(&self, commitment: &Field<N>) -> Option<&Transaction<N>> { 173 cfg_find!(self.transactions, |txn| txn.contains_commitment(commitment)).map(|tx| tx.transaction()) 174 } 175 176 /// Returns the transition with the corresponding transition ID, if it exists. 177 pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> { 178 cfg_find_map!(self.transactions, |txn| txn.find_transition(transition_id)) 179 } 180 181 /// Returns the transition for the given serial number, if it exists. 182 pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> { 183 cfg_find_map!(self.transactions, |txn| txn.find_transition_for_serial_number(serial_number)) 184 } 185 186 /// Returns the transition for the given commitment, if it exists. 187 pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> { 188 cfg_find_map!(self.transactions, |txn| txn.find_transition_for_commitment(commitment)) 189 } 190 191 /// Returns the record with the corresponding commitment, if it exists. 192 pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> { 193 cfg_find_map!(self.transactions, |txn| txn.find_record(commitment)) 194 } 195 } 196 197 impl<N: Network> Transactions<N> { 198 /// The maximum number of transactions allowed in a block. 199 pub const MAX_TRANSACTIONS: usize = usize::pow(2, TRANSACTIONS_DEPTH as u32).saturating_sub(1); 200 201 /// The maximum number of aborted transactions allowed in a block. 202 pub fn max_aborted_transactions() -> Result<usize> { 203 Ok(BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH 204 * BatchHeader::<N>::MAX_GC_ROUNDS 205 * Committee::<N>::max_committee_size()? as usize) 206 } 207 208 /// Returns an iterator over all transactions, for all transactions in `self`. 209 pub fn iter(&self) -> impl '_ + ExactSizeIterator<Item = &ConfirmedTransaction<N>> { 210 self.transactions.values() 211 } 212 213 /// Returns a parallel iterator over all transactions, for all transactions in `self`. 214 #[cfg(not(feature = "serial"))] 215 pub fn par_iter(&self) -> impl '_ + IndexedParallelIterator<Item = &ConfirmedTransaction<N>> { 216 self.transactions.par_values() 217 } 218 219 /// Returns an iterator over the transaction IDs, for all transactions in `self`. 220 pub fn transaction_ids(&self) -> impl '_ + ExactSizeIterator<Item = &N::TransactionID> { 221 self.transactions.keys() 222 } 223 224 /// Returns an iterator over all transactions in `self` that are accepted deploy transactions. 225 pub fn deployments(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> { 226 self.iter().filter(|tx| tx.is_accepted() && tx.is_deploy()) 227 } 228 229 /// Returns an iterator over all transactions in `self` that are accepted execute transactions. 230 pub fn executions(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> { 231 self.iter().filter(|tx| tx.is_accepted() && tx.is_execute()) 232 } 233 234 /// Returns an iterator over all transitions. 235 pub fn transitions(&self) -> impl '_ + Iterator<Item = &Transition<N>> { 236 self.iter().flat_map(|tx| tx.transitions()) 237 } 238 239 /// Returns an iterator over the transition IDs, for all transitions. 240 pub fn transition_ids(&self) -> impl '_ + Iterator<Item = &N::TransitionID> { 241 self.iter().flat_map(|tx| tx.transition_ids()) 242 } 243 244 /// Returns an iterator over the transition public keys, for all transactions. 245 pub fn transition_public_keys(&self) -> impl '_ + Iterator<Item = &Group<N>> { 246 self.iter().flat_map(|tx| tx.transition_public_keys()) 247 } 248 249 /// Returns an iterator over the transition commitments, for all transactions. 250 pub fn transition_commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> { 251 self.iter().flat_map(|tx| tx.transition_commitments()) 252 } 253 254 /// Returns an iterator over the tags, for all transition inputs that are records. 255 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> { 256 self.iter().flat_map(|tx| tx.tags()) 257 } 258 259 /// Returns an iterator over the input IDs, for all transition inputs that are records. 260 pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> { 261 self.iter().flat_map(|tx| tx.input_ids()) 262 } 263 264 /// Returns an iterator over the serial numbers, for all transition inputs that are records. 265 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> { 266 self.iter().flat_map(|tx| tx.serial_numbers()) 267 } 268 269 /// Returns an iterator over the output IDs, for all transition inputs that are records. 270 pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> { 271 self.iter().flat_map(|tx| tx.output_ids()) 272 } 273 274 /// Returns an iterator over the commitments, for all transition outputs that are records. 275 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> { 276 self.iter().flat_map(|tx| tx.commitments()) 277 } 278 279 /// Returns an iterator over the records, for all transition outputs that are records. 280 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> { 281 self.iter().flat_map(|tx| tx.records()) 282 } 283 284 /// Returns an iterator over the nonces, for all transition outputs that are records. 285 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> { 286 self.iter().flat_map(|tx| tx.nonces()) 287 } 288 289 /// Returns an iterator over the transaction fee amounts, for all transactions. 290 pub fn transaction_fee_amounts(&self) -> impl '_ + Iterator<Item = Result<U64<N>>> { 291 self.iter().map(|tx| tx.fee_amount()) 292 } 293 294 /// Returns an iterator over the finalize operations, for all transactions. 295 pub fn finalize_operations(&self) -> impl '_ + Iterator<Item = &FinalizeOperation<N>> { 296 self.iter().flat_map(|tx| tx.finalize_operations()) 297 } 298 } 299 300 impl<N: Network> IntoIterator for Transactions<N> { 301 type IntoIter = indexmap::map::IntoValues<N::TransactionID, Self::Item>; 302 type Item = ConfirmedTransaction<N>; 303 304 /// Returns a consuming iterator over all transactions, for all transactions in `self`. 305 fn into_iter(self) -> Self::IntoIter { 306 self.transactions.into_values() 307 } 308 } 309 310 impl<N: Network> Transactions<N> { 311 /// Returns a consuming iterator over the transaction IDs, for all transactions in `self`. 312 pub fn into_transaction_ids(self) -> impl ExactSizeIterator<Item = N::TransactionID> { 313 self.transactions.into_keys() 314 } 315 316 /// Returns a consuming iterator over all transactions in `self` that are accepted deploy transactions. 317 pub fn into_deployments(self) -> impl Iterator<Item = ConfirmedTransaction<N>> { 318 self.into_iter().filter(|tx| tx.is_accepted() && tx.is_deploy()) 319 } 320 321 /// Returns a consuming iterator over all transactions in `self` that are accepted execute transactions. 322 pub fn into_executions(self) -> impl Iterator<Item = ConfirmedTransaction<N>> { 323 self.into_iter().filter(|tx| tx.is_accepted() && tx.is_execute()) 324 } 325 326 /// Returns a consuming iterator over all transitions. 327 pub fn into_transitions(self) -> impl Iterator<Item = Transition<N>> { 328 self.into_iter().flat_map(|tx| tx.into_transaction().into_transitions()) 329 } 330 331 /// Returns a consuming iterator over the transition IDs, for all transitions. 332 pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> { 333 self.into_iter().flat_map(|tx| tx.into_transaction().into_transition_ids()) 334 } 335 336 /// Returns a consuming iterator over the transition public keys, for all transactions. 337 pub fn into_transition_public_keys(self) -> impl Iterator<Item = Group<N>> { 338 self.into_iter().flat_map(|tx| tx.into_transaction().into_transition_public_keys()) 339 } 340 341 /// Returns a consuming iterator over the tags, for all transition inputs that are records. 342 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> { 343 self.into_iter().flat_map(|tx| tx.into_transaction().into_tags()) 344 } 345 346 /// Returns a consuming iterator over the serial numbers, for all transition inputs that are records. 347 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> { 348 self.into_iter().flat_map(|tx| tx.into_transaction().into_serial_numbers()) 349 } 350 351 /// Returns a consuming iterator over the commitments, for all transition outputs that are records. 352 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> { 353 self.into_iter().flat_map(|tx| tx.into_transaction().into_commitments()) 354 } 355 356 /// Returns a consuming iterator over the records, for all transition outputs that are records. 357 pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> { 358 self.into_iter().flat_map(|tx| tx.into_transaction().into_records()) 359 } 360 361 /// Returns a consuming iterator over the nonces, for all transition outputs that are records. 362 pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> { 363 self.into_iter().flat_map(|tx| tx.into_transaction().into_nonces()) 364 } 365 } 366 367 #[cfg(test)] 368 pub mod test_helpers { 369 use super::*; 370 371 type CurrentNetwork = console::network::MainnetV0; 372 373 /// Samples a block transactions. 374 pub(crate) fn sample_block_transactions(rng: &mut TestRng) -> Transactions<CurrentNetwork> { 375 crate::test_helpers::sample_genesis_block(rng).transactions().clone() 376 } 377 } 378 379 #[cfg(test)] 380 mod tests { 381 use super::*; 382 use alphavm_ledger_narwhal_batch_header::BatchHeader; 383 384 type CurrentNetwork = console::network::MainnetV0; 385 386 #[test] 387 fn test_max_transmissions() { 388 // Determine the maximum number of transmissions in a block. 389 let max_transmissions_per_block = BatchHeader::<CurrentNetwork>::MAX_TRANSMISSIONS_PER_BATCH 390 * BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS 391 * CurrentNetwork::LATEST_MAX_CERTIFICATES().unwrap() as usize; 392 393 // Note: The maximum number of *transmissions* in a block cannot exceed the maximum number of *transactions* in a block. 394 // If you intended to change the number of 'MAX_TRANSACTIONS', note that this will break the inclusion proof, 395 // and you will need to migrate all users to a new circuit for the inclusion proof. 396 assert!( 397 max_transmissions_per_block <= Transactions::<CurrentNetwork>::MAX_TRANSACTIONS, 398 "The maximum number of transmissions in a block is too large" 399 ); 400 } 401 }