[chain_redesign] BlockId should not implement Anchor
If `BlockId` implements `Anchor`, the meaning is ambiguous. We cannot tell whether it means the tx is anchors at the block, or whether it also means the tx is confirmed at that block. Instead, `ConfirmationHeightAnchor` and `ConfirmationTimeAnchor` structs are introduced as non-ambiguous `Anchor` implementations. Additionally, `TxGraph::relevant_heights` is removed because it is also ambiguous. What heights are deemed relevant? A simpler and more flexible method `TxGraph::all_anchors` is introduced instead.
This commit is contained in:
@@ -8,7 +8,7 @@ use bdk_chain::{
|
||||
keychain::{Balance, DerivationAdditions, KeychainTxOutIndex},
|
||||
local_chain::LocalChain,
|
||||
tx_graph::Additions,
|
||||
BlockId, ObservedAs,
|
||||
ConfirmationHeightAnchor, ObservedAs,
|
||||
};
|
||||
use bitcoin::{secp256k1::Secp256k1, BlockHash, OutPoint, Script, Transaction, TxIn, TxOut};
|
||||
use miniscript::Descriptor;
|
||||
@@ -28,7 +28,7 @@ fn insert_relevant_txs() {
|
||||
let spk_0 = descriptor.at_derivation_index(0).script_pubkey();
|
||||
let spk_1 = descriptor.at_derivation_index(9).script_pubkey();
|
||||
|
||||
let mut graph = IndexedTxGraph::<BlockId, KeychainTxOutIndex<()>>::default();
|
||||
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default();
|
||||
graph.index.add_keychain((), descriptor);
|
||||
graph.index.set_lookahead(&(), 10);
|
||||
|
||||
@@ -118,7 +118,8 @@ fn test_list_owned_txouts() {
|
||||
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::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::default();
|
||||
|
||||
graph.index.add_keychain("keychain_1".into(), desc_1);
|
||||
graph.index.add_keychain("keychain_2".into(), desc_2);
|
||||
@@ -206,86 +207,94 @@ fn test_list_owned_txouts() {
|
||||
// For unconfirmed txs we pass in `None`.
|
||||
|
||||
let _ = graph.insert_relevant_txs(
|
||||
[&tx1, &tx2, &tx3, &tx6]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, tx)| (*tx, [local_chain.get_block(i as u32).unwrap()])),
|
||||
[&tx1, &tx2, &tx3, &tx6].iter().enumerate().map(|(i, tx)| {
|
||||
(
|
||||
*tx,
|
||||
local_chain
|
||||
.get_block(i as u32)
|
||||
.map(|anchor_block| ConfirmationHeightAnchor {
|
||||
anchor_block,
|
||||
confirmation_height: anchor_block.height,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
let _ = graph.insert_relevant_txs([&tx4, &tx5].iter().map(|tx| (*tx, None)), Some(100));
|
||||
|
||||
// A helper lambda to extract and filter data from the graph.
|
||||
let fetch = |ht: u32, graph: &IndexedTxGraph<BlockId, KeychainTxOutIndex<String>>| {
|
||||
let txouts = graph
|
||||
.list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let fetch =
|
||||
|ht: u32, graph: &IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>| {
|
||||
let txouts = graph
|
||||
.list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let utxos = graph
|
||||
.list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let utxos = graph
|
||||
.list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let balance = graph.balance(
|
||||
&local_chain,
|
||||
local_chain.get_block(ht).unwrap(),
|
||||
|spk: &Script| trusted_spks.contains(spk),
|
||||
);
|
||||
let balance = graph.balance(
|
||||
&local_chain,
|
||||
local_chain.get_block(ht).unwrap(),
|
||||
|spk: &Script| trusted_spks.contains(spk),
|
||||
);
|
||||
|
||||
assert_eq!(txouts.len(), 5);
|
||||
assert_eq!(utxos.len(), 4);
|
||||
assert_eq!(txouts.len(), 5);
|
||||
assert_eq!(utxos.len(), 4);
|
||||
|
||||
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 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_txouts_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 unconfirmed_txouts_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 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<_>>();
|
||||
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<_>>();
|
||||
|
||||
(
|
||||
confirmed_txouts_txid,
|
||||
unconfirmed_txouts_txid,
|
||||
confirmed_utxos_txid,
|
||||
unconfirmed_utxos_txid,
|
||||
balance,
|
||||
)
|
||||
};
|
||||
(
|
||||
confirmed_txouts_txid,
|
||||
unconfirmed_txouts_txid,
|
||||
confirmed_utxos_txid,
|
||||
unconfirmed_utxos_txid,
|
||||
balance,
|
||||
)
|
||||
};
|
||||
|
||||
// ----- TEST BLOCK -----
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use bdk_chain::{
|
||||
collections::*,
|
||||
local_chain::LocalChain,
|
||||
tx_graph::{Additions, TxGraph},
|
||||
Append, BlockId, ObservedAs,
|
||||
Append, BlockId, ConfirmationHeightAnchor, ObservedAs,
|
||||
};
|
||||
use bitcoin::{
|
||||
hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
|
||||
@@ -684,7 +684,7 @@ fn test_chain_spends() {
|
||||
..common::new_tx(0)
|
||||
};
|
||||
|
||||
let mut graph = TxGraph::<BlockId>::default();
|
||||
let mut graph = TxGraph::<ConfirmationHeightAnchor>::default();
|
||||
|
||||
let _ = graph.insert_tx(tx_0.clone());
|
||||
let _ = graph.insert_tx(tx_1.clone());
|
||||
@@ -694,27 +694,36 @@ fn test_chain_spends() {
|
||||
.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);
|
||||
// let block_id = local_chain.get_block(*ht).expect("block expected");
|
||||
let _ = graph.insert_anchor(
|
||||
tx.txid(),
|
||||
ConfirmationHeightAnchor {
|
||||
anchor_block: tip,
|
||||
confirmation_height: *ht,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// 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()
|
||||
)
|
||||
graph.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 0)),
|
||||
Some((
|
||||
ObservedAs::Confirmed(&ConfirmationHeightAnchor {
|
||||
anchor_block: tip,
|
||||
confirmation_height: 98
|
||||
}),
|
||||
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"))
|
||||
graph.get_chain_position(&local_chain, tip, tx_0.txid()),
|
||||
// Some(ObservedAs::Confirmed(&local_chain.get_block(95).expect("block expected"))),
|
||||
Some(ObservedAs::Confirmed(&ConfirmationHeightAnchor {
|
||||
anchor_block: tip,
|
||||
confirmation_height: 95
|
||||
}))
|
||||
);
|
||||
|
||||
// Even if unconfirmed tx has a last_seen of 0, it can still be part of a chain spend.
|
||||
@@ -784,73 +793,6 @@ fn test_chain_spends() {
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[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",
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that `last_seen` values only increase during [`Append::append`].
|
||||
#[test]
|
||||
fn test_additions_last_seen_append() {
|
||||
|
||||
Reference in New Issue
Block a user