2023-03-01 11:09:08 +01:00
|
|
|
#[macro_use]
|
|
|
|
mod common;
|
|
|
|
use bdk_chain::{
|
|
|
|
collections::*,
|
2023-04-08 21:02:33 +05:30
|
|
|
local_chain::LocalChain,
|
2023-03-30 18:33:53 +08:00
|
|
|
tx_graph::{Additions, TxGraph},
|
2023-04-08 21:02:33 +05:30
|
|
|
BlockId, ObservedAs,
|
|
|
|
};
|
|
|
|
use bitcoin::{
|
|
|
|
hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
|
2023-03-01 11:09:08 +01:00
|
|
|
};
|
|
|
|
use core::iter;
|
2023-04-19 16:14:52 +08:00
|
|
|
use std::vec;
|
2023-03-01 11:09:08 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_txouts() {
|
2023-04-08 21:02:33 +05:30
|
|
|
// 2 (Outpoint, TxOut) tupples that denotes original data in the graph, as partial transactions.
|
2023-03-01 11:09:08 +01:00
|
|
|
let original_ops = [
|
|
|
|
(
|
|
|
|
OutPoint::new(h!("tx1"), 1),
|
|
|
|
TxOut {
|
|
|
|
value: 10_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
OutPoint::new(h!("tx1"), 2),
|
|
|
|
TxOut {
|
|
|
|
value: 20_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
2023-04-08 21:02:33 +05:30
|
|
|
// Another (OutPoint, TxOut) tupple to be used as update as partial transaction.
|
2023-03-01 11:09:08 +01:00
|
|
|
let update_ops = [(
|
|
|
|
OutPoint::new(h!("tx2"), 0),
|
|
|
|
TxOut {
|
|
|
|
value: 20_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
)];
|
|
|
|
|
2023-04-08 21:02:33 +05:30
|
|
|
// One full transaction to be included in the update
|
|
|
|
let update_txs = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::null(),
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut {
|
|
|
|
value: 30_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
}],
|
|
|
|
};
|
|
|
|
|
|
|
|
// Conf anchor used to mark the full transaction as confirmed.
|
|
|
|
let conf_anchor = ObservedAs::Confirmed(BlockId {
|
|
|
|
height: 100,
|
|
|
|
hash: h!("random blockhash"),
|
|
|
|
});
|
|
|
|
|
|
|
|
// Unconfirmed anchor to mark the partial transactions as unconfirmed
|
|
|
|
let unconf_anchor = ObservedAs::<BlockId>::Unconfirmed(1000000);
|
|
|
|
|
|
|
|
// Make the original graph
|
2023-03-01 11:09:08 +01:00
|
|
|
let mut graph = {
|
2023-04-08 21:02:33 +05:30
|
|
|
let mut graph = TxGraph::<ObservedAs<BlockId>>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
for (outpoint, txout) in &original_ops {
|
|
|
|
assert_eq!(
|
|
|
|
graph.insert_txout(*outpoint, txout.clone()),
|
|
|
|
Additions {
|
|
|
|
txout: [(*outpoint, txout.clone())].into(),
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
graph
|
|
|
|
};
|
|
|
|
|
2023-04-08 21:02:33 +05:30
|
|
|
// Make the update graph
|
2023-03-01 11:09:08 +01:00
|
|
|
let update = {
|
2023-03-08 11:39:25 +13:00
|
|
|
let mut graph = TxGraph::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
for (outpoint, txout) in &update_ops {
|
2023-04-08 21:02:33 +05:30
|
|
|
// Insert partials transactions
|
2023-03-01 11:09:08 +01:00
|
|
|
assert_eq!(
|
|
|
|
graph.insert_txout(*outpoint, txout.clone()),
|
|
|
|
Additions {
|
|
|
|
txout: [(*outpoint, txout.clone())].into(),
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
);
|
2023-04-08 21:02:33 +05:30
|
|
|
// Mark them unconfirmed.
|
|
|
|
assert_eq!(
|
|
|
|
graph.insert_anchor(outpoint.txid, unconf_anchor),
|
|
|
|
Additions {
|
|
|
|
tx: [].into(),
|
|
|
|
txout: [].into(),
|
|
|
|
anchors: [(unconf_anchor, outpoint.txid)].into(),
|
|
|
|
last_seen: [].into()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
// Mark them last seen at.
|
|
|
|
assert_eq!(
|
|
|
|
graph.insert_seen_at(outpoint.txid, 1000000),
|
|
|
|
Additions {
|
|
|
|
tx: [].into(),
|
|
|
|
txout: [].into(),
|
|
|
|
anchors: [].into(),
|
|
|
|
last_seen: [(outpoint.txid, 1000000)].into()
|
|
|
|
}
|
|
|
|
);
|
2023-03-01 11:09:08 +01:00
|
|
|
}
|
2023-04-08 21:02:33 +05:30
|
|
|
// Insert the full transaction
|
|
|
|
assert_eq!(
|
|
|
|
graph.insert_tx(update_txs.clone()),
|
|
|
|
Additions {
|
|
|
|
tx: [update_txs.clone()].into(),
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Mark it as confirmed.
|
|
|
|
assert_eq!(
|
|
|
|
graph.insert_anchor(update_txs.txid(), conf_anchor),
|
|
|
|
Additions {
|
|
|
|
tx: [].into(),
|
|
|
|
txout: [].into(),
|
|
|
|
anchors: [(conf_anchor, update_txs.txid())].into(),
|
|
|
|
last_seen: [].into()
|
|
|
|
}
|
|
|
|
);
|
2023-03-01 11:09:08 +01:00
|
|
|
graph
|
|
|
|
};
|
|
|
|
|
2023-04-08 21:02:33 +05:30
|
|
|
// Check the resulting addition.
|
2023-03-01 11:09:08 +01:00
|
|
|
let additions = graph.determine_additions(&update);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
additions,
|
|
|
|
Additions {
|
2023-04-08 21:02:33 +05:30
|
|
|
tx: [update_txs.clone()].into(),
|
2023-03-01 11:09:08 +01:00
|
|
|
txout: update_ops.into(),
|
2023-04-08 21:02:33 +05:30
|
|
|
anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
|
|
|
|
last_seen: [(h!("tx2"), 1000000)].into()
|
2023-03-01 11:09:08 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2023-04-08 21:02:33 +05:30
|
|
|
// Apply addition and check the new graph counts.
|
2023-03-01 11:09:08 +01:00
|
|
|
graph.apply_additions(additions);
|
2023-04-08 21:02:33 +05:30
|
|
|
assert_eq!(graph.all_txouts().count(), 4);
|
2023-04-17 23:25:57 +08:00
|
|
|
assert_eq!(graph.full_txs().count(), 1);
|
|
|
|
assert_eq!(graph.floating_txouts().count(), 3);
|
2023-04-08 21:02:33 +05:30
|
|
|
|
|
|
|
// Check TxOuts are fetched correctly from the graph.
|
|
|
|
assert_eq!(
|
2023-04-17 23:25:57 +08:00
|
|
|
graph.tx_outputs(h!("tx1")).expect("should exists"),
|
2023-04-08 21:02:33 +05:30
|
|
|
[
|
|
|
|
(
|
|
|
|
1u32,
|
|
|
|
&TxOut {
|
|
|
|
value: 10_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
}
|
|
|
|
),
|
|
|
|
(
|
|
|
|
2u32,
|
|
|
|
&TxOut {
|
|
|
|
value: 20_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
]
|
|
|
|
.into()
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
2023-04-17 23:25:57 +08:00
|
|
|
graph.tx_outputs(update_txs.txid()).expect("should exists"),
|
2023-04-08 21:02:33 +05:30
|
|
|
[(
|
|
|
|
0u32,
|
|
|
|
&TxOut {
|
|
|
|
value: 30_000,
|
|
|
|
script_pubkey: Script::new()
|
|
|
|
}
|
|
|
|
)]
|
|
|
|
.into()
|
|
|
|
);
|
2023-03-01 11:09:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
|
|
|
|
let tx = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::null(),
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
output: vec![],
|
|
|
|
};
|
|
|
|
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let _ = graph.insert_tx(tx);
|
2023-04-21 12:33:03 +08:00
|
|
|
assert!(graph.outspends(OutPoint::null()).is_empty());
|
2023-04-17 23:25:57 +08:00
|
|
|
assert!(graph.tx_spends(Txid::all_zeros()).next().is_none());
|
2023-03-01 11:09:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_tx_graph_keeps_track_of_spend() {
|
|
|
|
let tx1 = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
};
|
|
|
|
|
|
|
|
let op = OutPoint {
|
|
|
|
txid: tx1.txid(),
|
|
|
|
vout: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
let tx2 = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: op,
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
output: vec![],
|
|
|
|
};
|
|
|
|
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut graph1 = TxGraph::<()>::default();
|
|
|
|
let mut graph2 = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
|
|
|
|
// insert in different order
|
|
|
|
let _ = graph1.insert_tx(tx1.clone());
|
|
|
|
let _ = graph1.insert_tx(tx2.clone());
|
|
|
|
|
|
|
|
let _ = graph2.insert_tx(tx2.clone());
|
2023-03-02 19:08:33 +01:00
|
|
|
let _ = graph2.insert_tx(tx1);
|
2023-03-01 11:09:08 +01:00
|
|
|
|
|
|
|
assert_eq!(
|
2023-04-21 12:33:03 +08:00
|
|
|
graph1.outspends(op),
|
2023-03-01 11:09:08 +01:00
|
|
|
&iter::once(tx2.txid()).collect::<HashSet<_>>()
|
|
|
|
);
|
2023-04-21 12:33:03 +08:00
|
|
|
assert_eq!(graph2.outspends(op), graph1.outspends(op));
|
2023-03-01 11:09:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_tx_can_retrieve_full_tx_from_graph() {
|
|
|
|
let tx = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::null(),
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
};
|
|
|
|
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let _ = graph.insert_tx(tx.clone());
|
2023-03-30 18:33:53 +08:00
|
|
|
assert_eq!(graph.get_tx(tx.txid()), Some(&tx));
|
2023-03-01 11:09:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_tx_displaces_txouts() {
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut tx_graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let tx = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![],
|
|
|
|
output: vec![TxOut {
|
|
|
|
value: 42_000,
|
|
|
|
script_pubkey: Script::default(),
|
|
|
|
}],
|
|
|
|
};
|
|
|
|
|
|
|
|
let _ = tx_graph.insert_txout(
|
|
|
|
OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
TxOut {
|
2023-03-02 19:08:33 +01:00
|
|
|
value: 1_337_000,
|
2023-03-01 11:09:08 +01:00
|
|
|
script_pubkey: Script::default(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = tx_graph.insert_txout(
|
|
|
|
OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
TxOut {
|
|
|
|
value: 1_000_000_000,
|
|
|
|
script_pubkey: Script::default(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _additions = tx_graph.insert_tx(tx.clone());
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
tx_graph
|
|
|
|
.get_txout(OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 0
|
|
|
|
})
|
|
|
|
.unwrap()
|
|
|
|
.value,
|
|
|
|
42_000
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
tx_graph.get_txout(OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 1
|
|
|
|
}),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_txout_does_not_displace_tx() {
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut tx_graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let tx = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![],
|
|
|
|
output: vec![TxOut {
|
|
|
|
value: 42_000,
|
|
|
|
script_pubkey: Script::default(),
|
|
|
|
}],
|
|
|
|
};
|
|
|
|
|
|
|
|
let _additions = tx_graph.insert_tx(tx.clone());
|
|
|
|
|
|
|
|
let _ = tx_graph.insert_txout(
|
|
|
|
OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
TxOut {
|
2023-03-02 19:08:33 +01:00
|
|
|
value: 1_337_000,
|
2023-03-01 11:09:08 +01:00
|
|
|
script_pubkey: Script::default(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = tx_graph.insert_txout(
|
|
|
|
OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
TxOut {
|
|
|
|
value: 1_000_000_000,
|
|
|
|
script_pubkey: Script::default(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
tx_graph
|
|
|
|
.get_txout(OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 0
|
|
|
|
})
|
|
|
|
.unwrap()
|
|
|
|
.value,
|
|
|
|
42_000
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
tx_graph.get_txout(OutPoint {
|
|
|
|
txid: tx.txid(),
|
|
|
|
vout: 1
|
|
|
|
}),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_calculate_fee() {
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let intx1 = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![],
|
|
|
|
output: vec![TxOut {
|
|
|
|
value: 100,
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
};
|
|
|
|
let intx2 = Transaction {
|
|
|
|
version: 0x02,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![],
|
|
|
|
output: vec![TxOut {
|
|
|
|
value: 200,
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
};
|
|
|
|
|
|
|
|
let intxout1 = (
|
|
|
|
OutPoint {
|
|
|
|
txid: h!("dangling output"),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
TxOut {
|
|
|
|
value: 300,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = graph.insert_tx(intx1.clone());
|
|
|
|
let _ = graph.insert_tx(intx2.clone());
|
|
|
|
let _ = graph.insert_txout(intxout1.0, intxout1.1);
|
|
|
|
|
|
|
|
let mut tx = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![
|
|
|
|
TxIn {
|
|
|
|
previous_output: OutPoint {
|
|
|
|
txid: intx1.txid(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
TxIn {
|
|
|
|
previous_output: OutPoint {
|
|
|
|
txid: intx2.txid(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
TxIn {
|
|
|
|
previous_output: intxout1.0,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
],
|
|
|
|
output: vec![TxOut {
|
|
|
|
value: 500,
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(graph.calculate_fee(&tx), Some(100));
|
|
|
|
|
|
|
|
tx.input.remove(2);
|
|
|
|
|
|
|
|
// fee would be negative
|
|
|
|
assert_eq!(graph.calculate_fee(&tx), Some(-200));
|
|
|
|
|
|
|
|
// If we have an unknown outpoint, fee should return None.
|
|
|
|
tx.input.push(TxIn {
|
|
|
|
previous_output: OutPoint {
|
|
|
|
txid: h!("unknown_txid"),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
assert_eq!(graph.calculate_fee(&tx), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_calculate_fee_on_coinbase() {
|
|
|
|
let tx = Transaction {
|
|
|
|
version: 0x01,
|
|
|
|
lock_time: PackedLockTime(0),
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::null(),
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
};
|
|
|
|
|
2023-03-30 18:33:53 +08:00
|
|
|
let graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
|
|
|
|
assert_eq!(graph.calculate_fee(&tx), Some(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_conflicting_descendants() {
|
|
|
|
let previous_output = OutPoint::new(h!("op"), 2);
|
|
|
|
|
|
|
|
// tx_a spends previous_output
|
|
|
|
let tx_a = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output,
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
// tx_a2 spends previous_output and conflicts with tx_a
|
|
|
|
let tx_a2 = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output,
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default(), TxOut::default()],
|
|
|
|
..common::new_tx(1)
|
|
|
|
};
|
|
|
|
|
|
|
|
// tx_b spends tx_a
|
|
|
|
let tx_b = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_a.txid(), 0),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(2)
|
|
|
|
};
|
|
|
|
|
|
|
|
let txid_a = tx_a.txid();
|
|
|
|
let txid_b = tx_b.txid();
|
|
|
|
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let _ = graph.insert_tx(tx_a);
|
|
|
|
let _ = graph.insert_tx(tx_b);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
graph
|
|
|
|
.walk_conflicts(&tx_a2, |depth, txid| Some((depth, txid)))
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
vec![(0_usize, txid_a), (1_usize, txid_b),],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_descendants_no_repeat() {
|
|
|
|
let tx_a = Transaction {
|
|
|
|
output: vec![TxOut::default(), TxOut::default(), TxOut::default()],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
let txs_b = (0..3)
|
|
|
|
.map(|vout| Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_a.txid(), vout),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(1)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let txs_c = (0..2)
|
|
|
|
.map(|vout| Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(txs_b[vout as usize].txid(), vout),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(2)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let tx_d = Transaction {
|
|
|
|
input: vec![
|
|
|
|
TxIn {
|
|
|
|
previous_output: OutPoint::new(txs_c[0].txid(), 0),
|
|
|
|
..TxIn::default()
|
|
|
|
},
|
|
|
|
TxIn {
|
|
|
|
previous_output: OutPoint::new(txs_c[1].txid(), 0),
|
|
|
|
..TxIn::default()
|
|
|
|
},
|
|
|
|
],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(3)
|
|
|
|
};
|
|
|
|
|
|
|
|
let tx_e = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_d.txid(), 0),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(4)
|
|
|
|
};
|
|
|
|
|
|
|
|
let txs_not_connected = (10..20)
|
|
|
|
.map(|v| Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(h!("tx_does_not_exist"), v),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![TxOut::default()],
|
|
|
|
..common::new_tx(v)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2023-03-30 18:33:53 +08:00
|
|
|
let mut graph = TxGraph::<()>::default();
|
2023-03-01 11:09:08 +01:00
|
|
|
let mut expected_txids = BTreeSet::new();
|
|
|
|
|
|
|
|
// these are NOT descendants of `tx_a`
|
|
|
|
for tx in txs_not_connected {
|
|
|
|
let _ = graph.insert_tx(tx.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
// these are the expected descendants of `tx_a`
|
|
|
|
for tx in txs_b
|
|
|
|
.iter()
|
|
|
|
.chain(&txs_c)
|
|
|
|
.chain(core::iter::once(&tx_d))
|
|
|
|
.chain(core::iter::once(&tx_e))
|
|
|
|
{
|
|
|
|
let _ = graph.insert_tx(tx.clone());
|
|
|
|
assert!(expected_txids.insert(tx.txid()));
|
|
|
|
}
|
|
|
|
|
|
|
|
let descendants = graph
|
|
|
|
.walk_descendants(tx_a.txid(), |_, txid| Some(txid))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
assert_eq!(descendants.len(), expected_txids.len());
|
|
|
|
|
|
|
|
for txid in descendants {
|
|
|
|
assert!(expected_txids.remove(&txid));
|
|
|
|
}
|
|
|
|
assert!(expected_txids.is_empty());
|
|
|
|
}
|
2023-04-08 21:02:33 +05:30
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_chain_spends() {
|
|
|
|
let local_chain: LocalChain = (0..=100)
|
|
|
|
.map(|ht| (ht, BlockHash::hash(format!("Block Hash {}", ht).as_bytes())))
|
|
|
|
.collect::<BTreeMap<u32, BlockHash>>()
|
|
|
|
.into();
|
|
|
|
let tip = local_chain.tip().expect("must have tip");
|
|
|
|
|
|
|
|
// The parent tx contains 2 outputs. Which are spent by one confirmed and one unconfirmed tx.
|
|
|
|
// The parent tx is confirmed at block 95.
|
|
|
|
let tx_0 = Transaction {
|
|
|
|
input: vec![],
|
|
|
|
output: vec![
|
|
|
|
TxOut {
|
|
|
|
value: 10_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
TxOut {
|
|
|
|
value: 20_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
// The first confirmed transaction spends vout: 0. And is confirmed at block 98.
|
|
|
|
let tx_1 = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_0.txid(), 0),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![
|
|
|
|
TxOut {
|
|
|
|
value: 5_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
TxOut {
|
|
|
|
value: 5_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
// The second transactions spends vout:1, and is unconfirmed.
|
|
|
|
let tx_2 = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_0.txid(), 1),
|
|
|
|
..TxIn::default()
|
|
|
|
}],
|
|
|
|
output: vec![
|
|
|
|
TxOut {
|
|
|
|
value: 10_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
TxOut {
|
|
|
|
value: 10_000,
|
|
|
|
script_pubkey: Script::new(),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut graph = TxGraph::<BlockId>::default();
|
|
|
|
|
|
|
|
let _ = graph.insert_tx(tx_0.clone());
|
|
|
|
let _ = graph.insert_tx(tx_1.clone());
|
|
|
|
let _ = graph.insert_tx(tx_2.clone());
|
|
|
|
|
|
|
|
[95, 98]
|
|
|
|
.iter()
|
|
|
|
.zip([&tx_0, &tx_1].into_iter())
|
|
|
|
.for_each(|(ht, tx)| {
|
|
|
|
let block_id = local_chain.get_block(*ht).expect("block expected");
|
|
|
|
let _ = graph.insert_anchor(tx.txid(), block_id);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert that confirmed spends are returned correctly.
|
|
|
|
assert_eq!(
|
|
|
|
graph
|
|
|
|
.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 0))
|
|
|
|
.unwrap(),
|
|
|
|
(
|
|
|
|
ObservedAs::Confirmed(&local_chain.get_block(98).expect("block expected")),
|
|
|
|
tx_1.txid()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Check if chain position is returned correctly.
|
|
|
|
assert_eq!(
|
|
|
|
graph
|
|
|
|
.get_chain_position(&local_chain, tip, tx_0.txid())
|
|
|
|
.expect("position expected"),
|
|
|
|
ObservedAs::Confirmed(&local_chain.get_block(95).expect("block expected"))
|
|
|
|
);
|
|
|
|
|
|
|
|
// As long the unconfirmed tx isn't marked as seen, chain_spend will return None.
|
|
|
|
assert!(graph
|
|
|
|
.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
|
|
|
|
.is_none());
|
|
|
|
|
|
|
|
// Mark the unconfirmed as seen and check correct ObservedAs status is returned.
|
|
|
|
let _ = graph.insert_seen_at(tx_2.txid(), 1234567);
|
|
|
|
|
|
|
|
// Check chain spend returned correctly.
|
|
|
|
assert_eq!(
|
|
|
|
graph
|
|
|
|
.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
|
|
|
|
.unwrap(),
|
|
|
|
(ObservedAs::Unconfirmed(1234567), tx_2.txid())
|
|
|
|
);
|
|
|
|
|
|
|
|
// A conflicting transaction that conflicts with tx_1.
|
|
|
|
let tx_1_conflict = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_0.txid(), 0),
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
let _ = graph.insert_tx(tx_1_conflict.clone());
|
|
|
|
|
|
|
|
// Because this tx conflicts with an already confirmed transaction, chain position should return none.
|
|
|
|
assert!(graph
|
|
|
|
.get_chain_position(&local_chain, tip, tx_1_conflict.txid())
|
|
|
|
.is_none());
|
|
|
|
|
|
|
|
// Another conflicting tx that conflicts with tx_2.
|
|
|
|
let tx_2_conflict = Transaction {
|
|
|
|
input: vec![TxIn {
|
|
|
|
previous_output: OutPoint::new(tx_0.txid(), 1),
|
|
|
|
..Default::default()
|
|
|
|
}],
|
|
|
|
..common::new_tx(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Insert in graph and mark it as seen.
|
|
|
|
let _ = graph.insert_tx(tx_2_conflict.clone());
|
|
|
|
let _ = graph.insert_seen_at(tx_2_conflict.txid(), 1234568);
|
|
|
|
|
|
|
|
// This should return a valid observation with correct last seen.
|
|
|
|
assert_eq!(
|
|
|
|
graph
|
|
|
|
.get_chain_position(&local_chain, tip, tx_2_conflict.txid())
|
|
|
|
.expect("position expected"),
|
|
|
|
ObservedAs::Unconfirmed(1234568)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Chain_spend now catches the new transaction as the spend.
|
|
|
|
assert_eq!(
|
|
|
|
graph
|
|
|
|
.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
|
|
|
|
.expect("expect observation"),
|
|
|
|
(ObservedAs::Unconfirmed(1234568), tx_2_conflict.txid())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Chain position of the `tx_2` is now none, as it is older than `tx_2_conflict`
|
|
|
|
assert!(graph
|
|
|
|
.get_chain_position(&local_chain, tip, tx_2.txid())
|
|
|
|
.is_none());
|
|
|
|
}
|
2023-04-19 16:14:52 +08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_relevant_heights() {
|
|
|
|
let mut graph = TxGraph::<BlockId>::default();
|
|
|
|
|
|
|
|
let tx1 = common::new_tx(1);
|
|
|
|
let tx2 = common::new_tx(2);
|
|
|
|
|
|
|
|
let _ = graph.insert_tx(tx1.clone());
|
|
|
|
assert_eq!(
|
|
|
|
graph.relevant_heights().collect::<Vec<_>>(),
|
|
|
|
vec![],
|
|
|
|
"no anchors in graph"
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = graph.insert_anchor(
|
|
|
|
tx1.txid(),
|
|
|
|
BlockId {
|
|
|
|
height: 3,
|
|
|
|
hash: h!("3a"),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
graph.relevant_heights().collect::<Vec<_>>(),
|
|
|
|
vec![3],
|
|
|
|
"one anchor at height 3"
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = graph.insert_anchor(
|
|
|
|
tx1.txid(),
|
|
|
|
BlockId {
|
|
|
|
height: 3,
|
|
|
|
hash: h!("3b"),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
graph.relevant_heights().collect::<Vec<_>>(),
|
|
|
|
vec![3],
|
|
|
|
"introducing duplicate anchor at height 3, must not iterate over duplicate heights"
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = graph.insert_anchor(
|
|
|
|
tx1.txid(),
|
|
|
|
BlockId {
|
|
|
|
height: 4,
|
|
|
|
hash: h!("4a"),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
graph.relevant_heights().collect::<Vec<_>>(),
|
|
|
|
vec![3, 4],
|
|
|
|
"anchors in height 3 and now 4"
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = graph.insert_anchor(
|
|
|
|
tx2.txid(),
|
|
|
|
BlockId {
|
|
|
|
height: 5,
|
|
|
|
hash: h!("5a"),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
graph.relevant_heights().collect::<Vec<_>>(),
|
|
|
|
vec![3, 4, 5],
|
|
|
|
"anchor for non-existant tx is inserted at height 5, must still be in relevant heights",
|
|
|
|
);
|
|
|
|
}
|