Include tests for new updates of TxGraph
This commit is contained in:
parent
10ab77c549
commit
001efdd1cb
@ -67,6 +67,13 @@ impl LocalChain {
|
||||
.map(|(&height, &hash)| BlockId { height, hash })
|
||||
}
|
||||
|
||||
/// Get a block at the given height.
|
||||
pub fn get_block(&self, height: u32) -> Option<BlockId> {
|
||||
self.blocks
|
||||
.get(&height)
|
||||
.map(|&hash| BlockId { height, hash })
|
||||
}
|
||||
|
||||
/// This is like the sparsechain's logic, expect we must guarantee that all invalidated heights
|
||||
/// are to be re-filled.
|
||||
pub fn determine_changeset(&self, update: &Self) -> Result<ChangeSet, UpdateNotConnectedError> {
|
||||
|
@ -2,13 +2,18 @@
|
||||
mod common;
|
||||
use bdk_chain::{
|
||||
collections::*,
|
||||
local_chain::LocalChain,
|
||||
tx_graph::{Additions, TxGraph},
|
||||
BlockId, ObservedAs,
|
||||
};
|
||||
use bitcoin::{
|
||||
hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
|
||||
};
|
||||
use bitcoin::{hashes::Hash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid};
|
||||
use core::iter;
|
||||
|
||||
#[test]
|
||||
fn insert_txouts() {
|
||||
// 2 (Outpoint, TxOut) tupples that denotes original data in the graph, as partial transactions.
|
||||
let original_ops = [
|
||||
(
|
||||
OutPoint::new(h!("tx1"), 1),
|
||||
@ -26,6 +31,7 @@ fn insert_txouts() {
|
||||
),
|
||||
];
|
||||
|
||||
// Another (OutPoint, TxOut) tupple to be used as update as partial transaction.
|
||||
let update_ops = [(
|
||||
OutPoint::new(h!("tx2"), 0),
|
||||
TxOut {
|
||||
@ -34,8 +40,32 @@ fn insert_txouts() {
|
||||
},
|
||||
)];
|
||||
|
||||
// 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
|
||||
let mut graph = {
|
||||
let mut graph = TxGraph::<()>::default();
|
||||
let mut graph = TxGraph::<ObservedAs<BlockId>>::default();
|
||||
for (outpoint, txout) in &original_ops {
|
||||
assert_eq!(
|
||||
graph.insert_txout(*outpoint, txout.clone()),
|
||||
@ -48,9 +78,11 @@ fn insert_txouts() {
|
||||
graph
|
||||
};
|
||||
|
||||
// Make the update graph
|
||||
let update = {
|
||||
let mut graph = TxGraph::default();
|
||||
for (outpoint, txout) in &update_ops {
|
||||
// Insert partials transactions
|
||||
assert_eq!(
|
||||
graph.insert_txout(*outpoint, txout.clone()),
|
||||
Additions {
|
||||
@ -58,25 +90,101 @@ fn insert_txouts() {
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
// 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()
|
||||
}
|
||||
);
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
);
|
||||
graph
|
||||
};
|
||||
|
||||
// Check the resulting addition.
|
||||
let additions = graph.determine_additions(&update);
|
||||
|
||||
assert_eq!(
|
||||
additions,
|
||||
Additions {
|
||||
tx: [].into(),
|
||||
tx: [update_txs.clone()].into(),
|
||||
txout: update_ops.into(),
|
||||
..Default::default()
|
||||
anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
|
||||
last_seen: [(h!("tx2"), 1000000)].into()
|
||||
}
|
||||
);
|
||||
|
||||
// Apply addition and check the new graph counts.
|
||||
graph.apply_additions(additions);
|
||||
assert_eq!(graph.all_txouts().count(), 3);
|
||||
assert_eq!(graph.full_transactions().count(), 0);
|
||||
assert_eq!(graph.all_txouts().count(), 4);
|
||||
assert_eq!(graph.full_transactions().count(), 1);
|
||||
assert_eq!(graph.partial_transactions().count(), 2);
|
||||
|
||||
// Check TxOuts are fetched correctly from the graph.
|
||||
assert_eq!(
|
||||
graph.txouts(h!("tx1")).expect("should exists"),
|
||||
[
|
||||
(
|
||||
1u32,
|
||||
&TxOut {
|
||||
value: 10_000,
|
||||
script_pubkey: Script::new(),
|
||||
}
|
||||
),
|
||||
(
|
||||
2u32,
|
||||
&TxOut {
|
||||
value: 20_000,
|
||||
script_pubkey: Script::new(),
|
||||
}
|
||||
)
|
||||
]
|
||||
.into()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
graph.txouts(update_txs.txid()).expect("should exists"),
|
||||
[(
|
||||
0u32,
|
||||
&TxOut {
|
||||
value: 30_000,
|
||||
script_pubkey: Script::new()
|
||||
}
|
||||
)]
|
||||
.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -511,3 +619,165 @@ fn test_descendants_no_repeat() {
|
||||
}
|
||||
assert!(expected_txids.is_empty());
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user