Merge pull request #959 from rajarshimaitra/test-graph-2

Cleanup IndexedTxGraph test.
This commit is contained in:
志宇 2023-05-01 14:53:30 +08:00 committed by GitHub
commit 702fe7ac5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -6,6 +6,7 @@ use std::collections::{BTreeMap, BTreeSet};
use bdk_chain::{ use bdk_chain::{
indexed_tx_graph::{IndexedAdditions, IndexedTxGraph}, indexed_tx_graph::{IndexedAdditions, IndexedTxGraph},
keychain::{Balance, DerivationAdditions, KeychainTxOutIndex}, keychain::{Balance, DerivationAdditions, KeychainTxOutIndex},
local_chain::LocalChain,
tx_graph::Additions, tx_graph::Additions,
BlockId, ObservedAs, BlockId, ObservedAs,
}; };
@ -76,36 +77,66 @@ fn insert_relevant_txs() {
} }
#[test] #[test]
fn test_list_owned_txouts() { /// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists
let mut local_chain = local_chain![ /// relevant txouts and utxos from the information fetched from a ChainOracle (here a LocalChain).
(0, h!("Block 0")), ///
(1, h!("Block 1")), /// Test Setup:
(2, h!("Block 2")), ///
(3, h!("Block 3")) /// Local Chain => <0> ----- <1> ----- <2> ----- <3> ---- ... ---- <150>
]; ///
/// Keychains:
///
/// keychain_1: Trusted
/// keychain_2: Untrusted
///
/// Transactions:
///
/// tx1: A Coinbase, sending 70000 sats to "trusted" address. [Block 0]
/// tx2: A external Receive, sending 30000 sats to "untrusted" address. [Block 1]
/// tx3: Internal Spend. Spends tx2 and returns change of 10000 to "trusted" address. [Block 2]
/// tx4: Mempool tx, sending 20000 sats to "trusted" address.
/// tx5: Mempool tx, sending 15000 sats to "untested" address.
/// tx6: Complete unrelated tx. [Block 3]
///
/// Different transactions are added via `insert_relevant_txs`.
/// `list_owned_txout`, `list_owned_utxos` and `balance` method is asserted
/// with expected values at Block height 0, 1, and 2.
///
/// Finally Add more blocks to local chain until tx1 coinbase maturity hits.
/// Assert maturity at coinbase maturity inflection height. Block height 98 and 99.
let desc_1 : &str = "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)"; fn test_list_owned_txouts() {
let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), desc_1).unwrap(); // Create Local chains
let desc_2 : &str = "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)";
let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), desc_2).unwrap(); let local_chain = (0..150)
.map(|i| (i as u32, h!("random")))
.collect::<BTreeMap<u32, BlockHash>>();
let local_chain = LocalChain::from(local_chain);
// Initiate IndexedTxGraph
let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap();
let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<String>>::default(); let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<String>>::default();
graph.index.add_keychain("keychain_1".into(), desc_1); graph.index.add_keychain("keychain_1".into(), desc_1);
graph.index.add_keychain("keychain_2".into(), desc_2); graph.index.add_keychain("keychain_2".into(), desc_2);
graph.index.set_lookahead_for_all(10); graph.index.set_lookahead_for_all(10);
// Get trusted and untrusted addresses
let mut trusted_spks = Vec::new(); let mut trusted_spks = Vec::new();
let mut untrusted_spks = Vec::new(); let mut untrusted_spks = Vec::new();
{ {
// we need to scope here to take immutanble reference of the graph
for _ in 0..10 { for _ in 0..10 {
let ((_, script), _) = graph.index.reveal_next_spk(&"keychain_1".to_string()); let ((_, script), _) = graph.index.reveal_next_spk(&"keychain_1".to_string());
// TODO Assert indexes
trusted_spks.push(script.clone()); trusted_spks.push(script.clone());
} }
} }
{ {
for _ in 0..10 { for _ in 0..10 {
let ((_, script), _) = graph.index.reveal_next_spk(&"keychain_2".to_string()); let ((_, script), _) = graph.index.reveal_next_spk(&"keychain_2".to_string());
@ -113,9 +144,9 @@ fn test_list_owned_txouts() {
} }
} }
let trust_predicate = |spk: &Script| trusted_spks.contains(spk); // Create test transactions
// tx1 is coinbase transaction received at trusted keychain at block 0. // tx1 is the genesis coinbase
let tx1 = Transaction { let tx1 = Transaction {
input: vec![TxIn { input: vec![TxIn {
previous_output: OutPoint::null(), previous_output: OutPoint::null(),
@ -171,6 +202,9 @@ fn test_list_owned_txouts() {
// tx6 is an unrelated transaction confirmed at 3. // tx6 is an unrelated transaction confirmed at 3.
let tx6 = common::new_tx(0); let tx6 = common::new_tx(0);
// Insert transactions into graph with respective anchors
// For unconfirmed txs we pass in `None`.
let _ = graph.insert_relevant_txs( let _ = graph.insert_relevant_txs(
[&tx1, &tx2, &tx3, &tx6] [&tx1, &tx2, &tx3, &tx6]
.iter() .iter()
@ -181,22 +215,25 @@ fn test_list_owned_txouts() {
let _ = graph.insert_relevant_txs([&tx4, &tx5].iter().map(|tx| (*tx, None)), Some(100)); let _ = graph.insert_relevant_txs([&tx4, &tx5].iter().map(|tx| (*tx, None)), Some(100));
// AT Block 0 // A helper lambda to extract and filter data from the graph.
{ let fetch = |ht: u32, graph: &IndexedTxGraph<BlockId, KeychainTxOutIndex<String>>| {
let txouts = graph let txouts = graph
.list_owned_txouts(&local_chain, local_chain.get_block(0).unwrap()) .list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let utxos = graph let utxos = graph
.list_owned_unspents(&local_chain, local_chain.get_block(0).unwrap()) .list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let balance = graph.balance( let balance = graph.balance(
&local_chain, &local_chain,
local_chain.get_block(0).unwrap(), local_chain.get_block(ht).unwrap(),
trust_predicate, |spk: &Script| trusted_spks.contains(spk),
); );
assert_eq!(txouts.len(), 5);
assert_eq!(utxos.len(), 4);
let confirmed_txouts_txid = txouts let confirmed_txouts_txid = txouts
.iter() .iter()
.filter_map(|full_txout| { .filter_map(|full_txout| {
@ -208,7 +245,7 @@ fn test_list_owned_txouts() {
}) })
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
let unconfirmed_txout_txid = txouts let unconfirmed_txouts_txid = txouts
.iter() .iter()
.filter_map(|full_txout| { .filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
@ -241,12 +278,30 @@ fn test_list_owned_txouts() {
}) })
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
assert_eq!(txouts.len(), 5); (
assert_eq!(utxos.len(), 4); confirmed_txouts_txid,
unconfirmed_txouts_txid,
confirmed_utxos_txid,
unconfirmed_utxos_txid,
balance,
)
};
// ----- TEST BLOCK -----
// AT Block 0
{
let (
confirmed_txouts_txid,
unconfirmed_txouts_txid,
confirmed_utxos_txid,
unconfirmed_utxos_txid,
balance,
) = fetch(0, &graph);
assert_eq!(confirmed_txouts_txid, [tx1.txid()].into()); assert_eq!(confirmed_txouts_txid, [tx1.txid()].into());
assert_eq!( assert_eq!(
unconfirmed_txout_txid, unconfirmed_txouts_txid,
[tx2.txid(), tx3.txid(), tx4.txid(), tx5.txid()].into() [tx2.txid(), tx3.txid(), tx4.txid(), tx5.txid()].into()
); );
@ -269,71 +324,18 @@ fn test_list_owned_txouts() {
// AT Block 1 // AT Block 1
{ {
let txouts = graph let (
.list_owned_txouts(&local_chain, local_chain.get_block(1).unwrap()) confirmed_txouts_txid,
.collect::<Vec<_>>(); unconfirmed_txouts_txid,
confirmed_utxos_txid,
let utxos = graph unconfirmed_utxos_txid,
.list_owned_unspents(&local_chain, local_chain.get_block(1).unwrap()) balance,
.collect::<Vec<_>>(); ) = fetch(1, &graph);
let balance = graph.balance(
&local_chain,
local_chain.get_block(1).unwrap(),
trust_predicate,
);
let confirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let unconfirmed_txout_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let confirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let unconfirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
assert_eq!(txouts.len(), 5);
assert_eq!(utxos.len(), 4);
// tx2 gets into confirmed txout set // tx2 gets into confirmed txout set
assert_eq!(confirmed_txouts_txid, [tx1.txid(), tx2.txid()].into()); assert_eq!(confirmed_txouts_txid, [tx1.txid(), tx2.txid()].into());
assert_eq!( assert_eq!(
unconfirmed_txout_txid, unconfirmed_txouts_txid,
[tx3.txid(), tx4.txid(), tx5.txid()].into() [tx3.txid(), tx4.txid(), tx5.txid()].into()
); );
@ -344,7 +346,6 @@ fn test_list_owned_txouts() {
[tx3.txid(), tx4.txid(), tx5.txid()].into() [tx3.txid(), tx4.txid(), tx5.txid()].into()
); );
// Balance breakup remains same
assert_eq!( assert_eq!(
balance, balance,
Balance { Balance {
@ -358,73 +359,20 @@ fn test_list_owned_txouts() {
// AT Block 2 // AT Block 2
{ {
let txouts = graph let (
.list_owned_txouts(&local_chain, local_chain.get_block(2).unwrap()) confirmed_txouts_txid,
.collect::<Vec<_>>(); unconfirmed_txouts_txid,
confirmed_utxos_txid,
let utxos = graph unconfirmed_utxos_txid,
.list_owned_unspents(&local_chain, local_chain.get_block(2).unwrap()) balance,
.collect::<Vec<_>>(); ) = fetch(2, &graph);
let balance = graph.balance(
&local_chain,
local_chain.get_block(2).unwrap(),
trust_predicate,
);
let confirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let unconfirmed_txout_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let confirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let unconfirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
assert_eq!(txouts.len(), 5);
assert_eq!(utxos.len(), 4);
// tx3 now gets into the confirmed txout set // tx3 now gets into the confirmed txout set
assert_eq!( assert_eq!(
confirmed_txouts_txid, confirmed_txouts_txid,
[tx1.txid(), tx2.txid(), tx3.txid()].into() [tx1.txid(), tx2.txid(), tx3.txid()].into()
); );
assert_eq!(unconfirmed_txout_txid, [tx4.txid(), tx5.txid()].into()); assert_eq!(unconfirmed_txouts_txid, [tx4.txid(), tx5.txid()].into());
// tx3 also gets into confirmed utxo set // tx3 also gets into confirmed utxo set
assert_eq!(confirmed_utxos_txid, [tx1.txid(), tx3.txid()].into()); assert_eq!(confirmed_utxos_txid, [tx1.txid(), tx3.txid()].into());
@ -441,99 +389,49 @@ fn test_list_owned_txouts() {
); );
} }
// AT Block 110 // AT Block 98
{ {
let mut local_chain_extension = (4..150) let (
.map(|i| (i as u32, h!("random"))) confirmed_txouts_txid,
.collect::<BTreeMap<u32, BlockHash>>(); unconfirmed_txouts_txid,
confirmed_utxos_txid,
local_chain_extension.insert(3, h!("Block 3")); unconfirmed_utxos_txid,
balance,
local_chain ) = fetch(98, &graph);
.apply_update(local_chain_extension.into())
.unwrap();
let txouts = graph
.list_owned_txouts(&local_chain, local_chain.get_block(110).unwrap())
.collect::<Vec<_>>();
let utxos = graph
.list_owned_unspents(&local_chain, local_chain.get_block(110).unwrap())
.collect::<Vec<_>>();
let balance = graph.balance(
&local_chain,
local_chain.get_block(110).unwrap(),
trust_predicate,
);
let confirmed_txouts_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let unconfirmed_txout_txid = txouts
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let confirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
let unconfirmed_utxos_txid = utxos
.iter()
.filter_map(|full_txout| {
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
Some(full_txout.outpoint.txid)
} else {
None
}
})
.collect::<BTreeSet<_>>();
println!("TxOuts : {:#?}", txouts);
println!("UTXOS {:#?}", utxos);
println!("{:#?}", balance);
assert_eq!(txouts.len(), 5);
assert_eq!(utxos.len(), 4);
assert_eq!( assert_eq!(
confirmed_txouts_txid, confirmed_txouts_txid,
[tx1.txid(), tx2.txid(), tx3.txid()].into() [tx1.txid(), tx2.txid(), tx3.txid()].into()
); );
assert_eq!(unconfirmed_txout_txid, [tx4.txid(), tx5.txid()].into()); assert_eq!(unconfirmed_txouts_txid, [tx4.txid(), tx5.txid()].into());
assert_eq!(confirmed_utxos_txid, [tx1.txid(), tx3.txid()].into()); assert_eq!(confirmed_utxos_txid, [tx1.txid(), tx3.txid()].into());
assert_eq!(unconfirmed_utxos_txid, [tx4.txid(), tx5.txid()].into()); assert_eq!(unconfirmed_utxos_txid, [tx4.txid(), tx5.txid()].into());
// Coinbase is still immature
assert_eq!( assert_eq!(
balance, balance,
Balance { Balance {
immature: 0, // immature coinbase immature: 70000, // immature coinbase
trusted_pending: 15000, // tx5 trusted_pending: 15000, // tx5
untrusted_pending: 20000, // tx4 untrusted_pending: 20000, // tx4
confirmed: 80000 // tx1 got matured confirmed: 10000 // tx1 got matured
}
);
}
// AT Block 99
{
let (_, _, _, _, balance) = fetch(100, &graph);
// Coinbase maturity hits
assert_eq!(
balance,
Balance {
immature: 0, // coinbase matured
trusted_pending: 15000, // tx5
untrusted_pending: 20000, // tx4
confirmed: 80000 // tx1 + tx3
} }
); );
} }