use std::collections::{HashMap, VecDeque};
use crate::chaindef::Transaction;
#[cfg(nexa)]
use crate::encode::{compute_outpoint_hash_from_tx, outpoint_hash};
use crate::mempool::MempoolEntry;
use bitcoincash::hash_types::Txid;
#[cfg(all(test, not(nexa)))]
use std::collections::HashSet;
#[cfg(all(test, not(nexa)))]
pub fn ttor_sorted(txs: Vec<Transaction>) -> Vec<Transaction> {
let txs = {
let mut queue: VecDeque<Transaction> = txs.into_iter().collect();
let mut queue_txids: HashSet<Txid> = queue.iter().map(|tx| tx.txid()).collect();
let mut txs: Vec<Transaction> = 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) {
has_parent = true;
break;
};
}
if has_parent {
queue.push_back(tx);
} else {
queue_txids.remove(&tx.txid());
txs.push(tx);
}
}
txs
};
txs
}
pub fn ttor_sorted_kahn(txs: Vec<Transaction>) -> Vec<Transaction> {
if txs.is_empty() {
return Vec::new();
}
let mut graph: HashMap<Txid, Vec<Txid>> = HashMap::new();
let mut in_degree: HashMap<Txid, usize> = HashMap::new();
let mut tx_map: HashMap<Txid, Transaction> = HashMap::new();
for tx in txs {
let txid = tx.txid();
tx_map.insert(txid, tx);
in_degree.insert(txid, 0);
graph.insert(txid, Vec::new());
}
#[cfg(nexa)]
let mut outpoint_to_txid: HashMap<crate::chaindef::OutPointHash, Txid> = HashMap::new();
#[cfg(nexa)]
for (txid, tx) in &tx_map {
for (out_idx, _) in tx.output.iter().enumerate() {
let oph = compute_outpoint_hash_from_tx(tx, out_idx as u32);
outpoint_to_txid.insert(oph, *txid);
}
}
for (txid, tx) in &tx_map {
for input in &tx.input {
#[cfg(bch)]
let parent_txid = input.previous_output.txid;
#[cfg(nexa)]
let parent_txid = {
let input_oph = outpoint_hash(&input.previous_output);
match outpoint_to_txid.get(&input_oph) {
Some(&parent) => parent,
None => continue, }
};
if tx_map.contains_key(&parent_txid) {
graph.entry(parent_txid).or_default().push(*txid);
*in_degree.entry(*txid).or_default() += 1;
}
}
}
let mut queue: VecDeque<Txid> = VecDeque::new();
for (txid, °ree) in &in_degree {
if degree == 0 {
queue.push_back(*txid);
}
}
let mut result: Vec<Transaction> = Vec::with_capacity(tx_map.len());
while let Some(txid) = queue.pop_front() {
result.push(tx_map.remove(&txid).unwrap());
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);
}
}
}
}
}
for (_, tx) in tx_map {
result.push(tx);
}
result
}
pub fn ttor_sorted_mempool_entries(entries: Vec<MempoolEntry>) -> Vec<MempoolEntry> {
if entries.is_empty() {
return Vec::new();
}
let mut entry_map: HashMap<Txid, MempoolEntry> =
entries.into_iter().map(|e| (e.tx().txid(), e)).collect();
let txs: Vec<Transaction> = entry_map.values().map(|e| e.tx().clone()).collect();
let sorted_txs = ttor_sorted_kahn(txs);
sorted_txs
.into_iter()
.filter_map(|tx| entry_map.remove(&tx.txid()))
.collect()
}
#[cfg(all(test, not(nexa)))]
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<TxIn> = 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();
let mut tx = Transaction {
version: 1,
lock_time: PackedLockTime(0),
input: tx_inputs,
output: vec![TxOut {
value: 1000,
script_pubkey: Script::new(),
token: None,
}],
};
tx.output[0].value = txid[0] as u64 * 1000;
tx
}
fn create_mock_transaction_with_deps(
base_txid: [u8; 32],
parent_txs: &[Transaction],
) -> Transaction {
let tx_inputs: Vec<TxIn> = 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,
}],
};
tx.output[0].value = base_txid[0] as u64 * 1000;
tx
}
#[test]
fn test_simple_chain() {
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);
assert_eq!(result_original.len(), 3);
assert_eq!(result_kahn.len(), 3);
let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
assert_eq!(original_txids, kahn_txids);
verify_dependencies_respected(&result_original);
verify_dependencies_respected(&result_kahn);
}
#[test]
fn test_diamond_dependency() {
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);
assert_eq!(result_original.len(), 4);
assert_eq!(result_kahn.len(), 4);
let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
assert_eq!(original_txids, kahn_txids);
verify_dependencies_respected(&result_original);
verify_dependencies_respected(&result_kahn);
}
#[test]
fn test_independent_transactions() {
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);
assert_eq!(result_original.len(), 3);
assert_eq!(result_kahn.len(), 3);
let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
assert_eq!(original_txids, kahn_txids);
verify_dependencies_respected(&result_original);
verify_dependencies_respected(&result_kahn);
}
#[test]
fn test_empty_input() {
let txs: Vec<Transaction> = 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() {
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);
assert_eq!(result_original.len(), 8);
assert_eq!(result_kahn.len(), 8);
let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
assert_eq!(original_txids, kahn_txids);
verify_dependencies_respected(&result_original);
verify_dependencies_respected(&result_kahn);
}
fn verify_dependencies_respected(txs: &[Transaction]) {
let tx_positions: HashMap<Txid, usize> = 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) {
assert!(
parent_pos < pos,
"Dependency violation: transaction {} (pos {}) depends on {} (pos {})",
tx.txid(),
pos,
input.previous_output.txid,
parent_pos
);
}
}
}
}
}