// Copyright (C) 2024-2026 Whiterun LLC // // This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later. // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html use std::collections::{HashMap, VecDeque}; use bitcoincash::{Transaction, Txid}; #[cfg(test)] use std::collections::HashSet; // TTOR sort a list of transactions (old algo; used in tests for comparison) #[cfg(test)] pub fn ttor_sorted(txs: Vec) -> Vec { let txs = { let mut queue: VecDeque = txs.into_iter().collect(); let mut queue_txids: HashSet = queue.iter().map(|tx| tx.txid()).collect(); let mut txs: Vec = Vec::with_capacity(queue.len()); while let Some(tx) = queue.pop_front() { let mut has_parent = false; for i in &tx.input { if queue_txids.contains(&i.previous_output.txid) { // depends on parent has_parent = true; break; }; } if has_parent { queue.push_back(tx); } else { queue_txids.remove(&tx.txid()); txs.push(tx); } } txs }; txs } /// Alternative implementation using Kahn's algorithm for topological sorting /// This is more efficient with O(V + E) complexity and handles cycles gracefully /// /// Note: This implementation may produce different but equally valid topological orderings /// compared to the original `ttor_sorted` function. Both implementations respect /// transaction dependencies, but may order independent transactions differently. pub fn ttor_sorted_kahn(txs: Vec) -> Vec { if txs.is_empty() { return Vec::new(); } // Build adjacency list and in-degree count let mut graph: HashMap> = HashMap::new(); let mut in_degree: HashMap = HashMap::new(); let mut tx_map: HashMap = HashMap::new(); // Initialize data structures for tx in txs { let txid = tx.txid(); tx_map.insert(txid, tx); in_degree.insert(txid, 0); graph.insert(txid, Vec::new()); } // Build the dependency graph for (txid, tx) in &tx_map { for input in &tx.input { let parent_txid = input.previous_output.txid; // Only consider dependencies within our transaction set if tx_map.contains_key(&parent_txid) { // Add edge from parent to current transaction graph.entry(parent_txid).or_default().push(*txid); // Increment in-degree of current transaction *in_degree.entry(*txid).or_default() += 1; } } } // Kahn's algorithm: find nodes with no incoming edges let mut queue: VecDeque = VecDeque::new(); for (txid, °ree) in &in_degree { if degree == 0 { queue.push_back(*txid); } } let mut result: Vec = Vec::with_capacity(tx_map.len()); // Process nodes in topological order while let Some(txid) = queue.pop_front() { result.push(tx_map.remove(&txid).unwrap()); // Remove edges from this node and update in-degrees if let Some(children) = graph.get(&txid) { for &child_txid in children { if let Some(degree) = in_degree.get_mut(&child_txid) { *degree -= 1; if *degree == 0 { queue.push_back(child_txid); } } } } } // Add any remaining transactions (handles cycles and orphaned transactions) // This matches the original implementation's behavior for (_, tx) in tx_map { result.push(tx); } result } #[cfg(test)] mod tests { use super::*; use bitcoin_hashes::{sha256d, Hash}; use bitcoincash::{OutPoint, PackedLockTime, Script, Sequence, TxIn, TxOut, Witness}; fn create_mock_transaction(txid: [u8; 32], inputs: Vec<[u8; 32]>) -> Transaction { let tx_inputs: Vec = inputs .into_iter() .map(|input_txid| TxIn { previous_output: OutPoint { txid: Txid::from_hash(sha256d::Hash::from_inner(input_txid)), vout: 0, }, script_sig: Script::new(), sequence: Sequence(0), witness: Witness::new(), }) .collect(); // Create a transaction with a unique txid by using the provided txid parameter // We need to create a transaction that has the desired txid when txid() is called let mut tx = Transaction { version: 1, lock_time: PackedLockTime(0), input: tx_inputs, output: vec![TxOut { value: 1000, script_pubkey: Script::new(), token: None, }], }; // For testing purposes, we'll create transactions with different outputs to ensure different txids // The txid is calculated from the transaction content, so we need to make them different tx.output[0].value = txid[0] as u64 * 1000; // Use first byte of txid to make value unique tx } /// Create a mock transaction with proper dependency relationships /// This function creates transactions where input txids actually match the txids of referenced transactions fn create_mock_transaction_with_deps( base_txid: [u8; 32], parent_txs: &[Transaction], ) -> Transaction { let tx_inputs: Vec = parent_txs .iter() .map(|parent_tx| TxIn { previous_output: OutPoint { txid: parent_tx.txid(), vout: 0, }, script_sig: Script::new(), sequence: Sequence(0), witness: Witness::new(), }) .collect(); let mut tx = Transaction { version: 1, lock_time: PackedLockTime(0), input: tx_inputs, output: vec![TxOut { value: 1000, script_pubkey: Script::new(), token: None, }], }; // Make the transaction unique by using the base_txid tx.output[0].value = base_txid[0] as u64 * 1000; tx } #[test] fn test_simple_chain() { // Create a simple chain: A -> B -> C let tx_a = create_mock_transaction([1; 32], vec![]); let tx_b = create_mock_transaction_with_deps([2; 32], &[tx_a.clone()]); let tx_c = create_mock_transaction_with_deps([3; 32], &[tx_b.clone()]); let txs = vec![tx_c.clone(), tx_a.clone(), tx_b.clone()]; let result_original = ttor_sorted(txs.clone()); let result_kahn = ttor_sorted_kahn(txs); // Both should produce the same number of transactions assert_eq!(result_original.len(), 3); assert_eq!(result_kahn.len(), 3); // Both should contain all transactions let original_txids: HashSet = result_original.iter().map(|tx| tx.txid()).collect(); let kahn_txids: HashSet = result_kahn.iter().map(|tx| tx.txid()).collect(); assert_eq!(original_txids, kahn_txids); // Verify dependencies are respected in both results verify_dependencies_respected(&result_original); verify_dependencies_respected(&result_kahn); } #[test] fn test_diamond_dependency() { // Create a diamond dependency: A -> B, A -> C, B -> D, C -> D let tx_a = create_mock_transaction([1; 32], vec![]); let tx_b = create_mock_transaction_with_deps([2; 32], &[tx_a.clone()]); let tx_c = create_mock_transaction_with_deps([3; 32], &[tx_a.clone()]); let tx_d = create_mock_transaction_with_deps([4; 32], &[tx_b.clone(), tx_c.clone()]); let txs = vec![tx_d.clone(), tx_c.clone(), tx_b.clone(), tx_a.clone()]; let result_original = ttor_sorted(txs.clone()); let result_kahn = ttor_sorted_kahn(txs); // Both should produce the same number of transactions assert_eq!(result_original.len(), 4); assert_eq!(result_kahn.len(), 4); // Both should contain all transactions let original_txids: HashSet = result_original.iter().map(|tx| tx.txid()).collect(); let kahn_txids: HashSet = result_kahn.iter().map(|tx| tx.txid()).collect(); assert_eq!(original_txids, kahn_txids); // Verify dependencies are respected in both results verify_dependencies_respected(&result_original); verify_dependencies_respected(&result_kahn); } #[test] fn test_independent_transactions() { // Create independent transactions (no dependencies) let tx_a = create_mock_transaction([1; 32], vec![]); let tx_b = create_mock_transaction([2; 32], vec![]); let tx_c = create_mock_transaction([3; 32], vec![]); let txs = vec![tx_c.clone(), tx_a.clone(), tx_b.clone()]; let result_original = ttor_sorted(txs.clone()); let result_kahn = ttor_sorted_kahn(txs); // Both should produce the same number of transactions assert_eq!(result_original.len(), 3); assert_eq!(result_kahn.len(), 3); // Both should contain all transactions let original_txids: HashSet = result_original.iter().map(|tx| tx.txid()).collect(); let kahn_txids: HashSet = result_kahn.iter().map(|tx| tx.txid()).collect(); assert_eq!(original_txids, kahn_txids); // For independent transactions, both should produce valid orderings verify_dependencies_respected(&result_original); verify_dependencies_respected(&result_kahn); } #[test] fn test_empty_input() { let txs: Vec = vec![]; let result_original = ttor_sorted(txs.clone()); let result_kahn = ttor_sorted_kahn(txs); assert_eq!(result_original.len(), 0); assert_eq!(result_kahn.len(), 0); } #[test] fn test_single_transaction() { let tx = create_mock_transaction([1; 32], vec![]); let txs = vec![tx.clone()]; let result_original = ttor_sorted(txs.clone()); let result_kahn = ttor_sorted_kahn(txs); assert_eq!(result_original.len(), 1); assert_eq!(result_kahn.len(), 1); assert_eq!(result_original[0].txid(), tx.txid()); assert_eq!(result_kahn[0].txid(), tx.txid()); } #[test] fn test_complex_dependency() { // Create a more complex dependency graph // A -> B -> D // A -> C -> D // E -> F -> G // H (independent) let tx_a = create_mock_transaction([1; 32], vec![]); let tx_b = create_mock_transaction_with_deps([2; 32], &[tx_a.clone()]); let tx_c = create_mock_transaction_with_deps([3; 32], &[tx_a.clone()]); let tx_d = create_mock_transaction_with_deps([4; 32], &[tx_b.clone(), tx_c.clone()]); let tx_e = create_mock_transaction([5; 32], vec![]); let tx_f = create_mock_transaction_with_deps([6; 32], &[tx_e.clone()]); let tx_g = create_mock_transaction_with_deps([7; 32], &[tx_f.clone()]); let tx_h = create_mock_transaction([8; 32], vec![]); let txs = vec![ tx_g.clone(), tx_f.clone(), tx_e.clone(), tx_d.clone(), tx_c.clone(), tx_b.clone(), tx_a.clone(), tx_h.clone(), ]; let result_original = ttor_sorted(txs.clone()); let result_kahn = ttor_sorted_kahn(txs); // Both should produce the same number of transactions assert_eq!(result_original.len(), 8); assert_eq!(result_kahn.len(), 8); // Both should contain all transactions let original_txids: HashSet = result_original.iter().map(|tx| tx.txid()).collect(); let kahn_txids: HashSet = result_kahn.iter().map(|tx| tx.txid()).collect(); assert_eq!(original_txids, kahn_txids); // Verify dependencies are respected in both results verify_dependencies_respected(&result_original); verify_dependencies_respected(&result_kahn); } /// Helper function to verify that dependencies are respected in a transaction ordering fn verify_dependencies_respected(txs: &[Transaction]) { let tx_positions: HashMap = txs .iter() .enumerate() .map(|(pos, tx)| (tx.txid(), pos)) .collect(); for (pos, tx) in txs.iter().enumerate() { for input in &tx.input { if let Some(&parent_pos) = tx_positions.get(&input.previous_output.txid) { // Parent transaction should come before this transaction assert!( parent_pos < pos, "Dependency violation: transaction {} (pos {}) depends on {} (pos {})", tx.txid(), pos, input.previous_output.txid, parent_pos ); } } } } }