keychain::ChangeSet includes the descriptor
- The KeychainTxOutIndex's internal SpkIterator now uses DescriptorId instead of K. The DescriptorId -> K translation is made at the KeychainTxOutIndex level. - The keychain::Changeset is now a struct, which includes a map for last revealed indexes, and one for newly added keychains and their descriptor. API changes in bdk: - Wallet::keychains returns a `impl Iterator` instead of `BTreeMap` - Wallet::load doesn't take descriptors anymore, since they're stored in the db - Wallet::new_or_load checks if the loaded descriptor from db is the same as the provided one API changes in bdk_chain: - `ChangeSet` is now a struct, which includes a map for last revealed indexes, and one for keychains and descriptors. - `KeychainTxOutIndex::inner` returns a `SpkIterator<(DescriptorId, u32)>` - `KeychainTxOutIndex::outpoints` returns a `impl Iterator` instead of `&BTreeSet` - `KeychainTxOutIndex::keychains` returns a `impl Iterator` instead of `&BTreeMap` - `KeychainTxOutIndex::txouts` doesn't return a ExactSizeIterator anymore - `KeychainTxOutIndex::unbounded_spk_iter` returns an `Option` - `KeychainTxOutIndex::next_index` returns an `Option` - `KeychainTxOutIndex::last_revealed_indices` returns a `BTreeMap` instead of `&BTreeMap` - `KeychainTxOutIndex::reveal_to_target` returns an `Option` - `KeychainTxOutIndex::reveal_next_spk` returns an `Option` - `KeychainTxOutIndex::next_unused_spk` returns an `Option` - `KeychainTxOutIndex::add_keychain` has been renamed to `KeychainTxOutIndex::insert_descriptor`, and now it returns a ChangeSet
This commit is contained in:
15 files changed
+979
-432
No files matched your search
@@ -75,10 +75,13 @@ pub fn new_tx(lt: u32) -> bitcoin::Transaction {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub const DESCRIPTORS: [&str; 5] = [
|
||||
pub const DESCRIPTORS: [&str; 7] = [
|
||||
"tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)",
|
||||
"tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)",
|
||||
"wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0/*)",
|
||||
"tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)",
|
||||
"tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)",
|
||||
"wpkh(xprv9s21ZrQH143K4EXURwMHuLS469fFzZyXk7UUpdKfQwhoHcAiYTakpe8pMU2RiEdvrU9McyuE7YDoKcXkoAwEGoK53WBDnKKv2zZbb9BzttX/1/0/*)",
|
||||
// non-wildcard
|
||||
"wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)",
|
||||
];
|
||||
@@ -52,7 +52,8 @@ impl TxOutTemplate {
|
||||
pub fn init_graph<'a, A: Anchor + Clone + 'a>(
|
||||
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
|
||||
) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
|
||||
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), super::DESCRIPTORS[2]).unwrap();
|
||||
let (descriptor, _) =
|
||||
Descriptor::parse_descriptor(&Secp256k1::signing_only(), super::DESCRIPTORS[2]).unwrap();
|
||||
let mut graph = TxGraph::<A>::default();
|
||||
let mut spk_index = SpkTxOutIndex::default();
|
||||
(0..10).for_each(|index| {
|
||||
|
||||
@@ -3,11 +3,12 @@ mod common;
|
||||
|
||||
use std::{collections::BTreeSet, sync::Arc};
|
||||
|
||||
use crate::common::DESCRIPTORS;
|
||||
use bdk_chain::{
|
||||
indexed_tx_graph::{self, IndexedTxGraph},
|
||||
keychain::{self, Balance, KeychainTxOutIndex},
|
||||
local_chain::LocalChain,
|
||||
tx_graph, ChainPosition, ConfirmationHeightAnchor,
|
||||
tx_graph, ChainPosition, ConfirmationHeightAnchor, DescriptorExt,
|
||||
};
|
||||
use bitcoin::{
|
||||
secp256k1::Secp256k1, Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
|
||||
@@ -23,16 +24,15 @@ use miniscript::Descriptor;
|
||||
/// agnostic.
|
||||
#[test]
|
||||
fn insert_relevant_txs() {
|
||||
let (descriptor, _) =
|
||||
Descriptor::parse_descriptor(&Secp256k1::signing_only(), common::DESCRIPTORS[0])
|
||||
.expect("must be valid");
|
||||
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[0])
|
||||
.expect("must be valid");
|
||||
let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey();
|
||||
let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey();
|
||||
|
||||
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::new(
|
||||
KeychainTxOutIndex::new(10),
|
||||
);
|
||||
graph.index.add_keychain((), descriptor);
|
||||
let _ = graph.index.insert_descriptor((), descriptor.clone());
|
||||
|
||||
let tx_a = Transaction {
|
||||
output: vec![
|
||||
@@ -71,7 +71,10 @@ fn insert_relevant_txs() {
|
||||
txs: txs.iter().cloned().map(Arc::new).collect(),
|
||||
..Default::default()
|
||||
},
|
||||
indexer: keychain::ChangeSet([((), 9_u32)].into()),
|
||||
indexer: keychain::ChangeSet {
|
||||
last_revealed: [(descriptor.descriptor_id(), 9_u32)].into(),
|
||||
keychains_added: [].into(),
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
@@ -79,7 +82,16 @@ fn insert_relevant_txs() {
|
||||
changeset,
|
||||
);
|
||||
|
||||
assert_eq!(graph.initial_changeset(), changeset,);
|
||||
// The initial changeset will also contain info about the keychain we added
|
||||
let initial_changeset = indexed_tx_graph::ChangeSet {
|
||||
graph: changeset.graph,
|
||||
indexer: keychain::ChangeSet {
|
||||
last_revealed: changeset.indexer.last_revealed,
|
||||
keychains_added: [((), descriptor)].into(),
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(graph.initial_changeset(), initial_changeset);
|
||||
}
|
||||
|
||||
/// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists
|
||||
@@ -126,8 +138,8 @@ fn test_list_owned_txouts() {
|
||||
KeychainTxOutIndex::new(10),
|
||||
);
|
||||
|
||||
graph.index.add_keychain("keychain_1".into(), desc_1);
|
||||
graph.index.add_keychain("keychain_2".into(), desc_2);
|
||||
let _ = graph.index.insert_descriptor("keychain_1".into(), desc_1);
|
||||
let _ = graph.index.insert_descriptor("keychain_2".into(), desc_2);
|
||||
|
||||
// Get trusted and untrusted addresses
|
||||
|
||||
@@ -137,14 +149,20 @@ fn test_list_owned_txouts() {
|
||||
{
|
||||
// we need to scope here to take immutanble reference of the graph
|
||||
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())
|
||||
.unwrap();
|
||||
// TODO Assert indexes
|
||||
trusted_spks.push(script.to_owned());
|
||||
}
|
||||
}
|
||||
{
|
||||
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())
|
||||
.unwrap();
|
||||
untrusted_spks.push(script.to_owned());
|
||||
}
|
||||
}
|
||||
@@ -237,26 +255,18 @@ fn test_list_owned_txouts() {
|
||||
.unwrap_or_else(|| panic!("block must exist at {}", height));
|
||||
let txouts = graph
|
||||
.graph()
|
||||
.filter_chain_txouts(
|
||||
&local_chain,
|
||||
chain_tip,
|
||||
graph.index.outpoints().iter().cloned(),
|
||||
)
|
||||
.filter_chain_txouts(&local_chain, chain_tip, graph.index.outpoints())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let utxos = graph
|
||||
.graph()
|
||||
.filter_chain_unspents(
|
||||
&local_chain,
|
||||
chain_tip,
|
||||
graph.index.outpoints().iter().cloned(),
|
||||
)
|
||||
.filter_chain_unspents(&local_chain, chain_tip, graph.index.outpoints())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let balance = graph.graph().balance(
|
||||
&local_chain,
|
||||
chain_tip,
|
||||
graph.index.outpoints().iter().cloned(),
|
||||
graph.index.outpoints(),
|
||||
|_, spk: &Script| trusted_spks.contains(&spk.to_owned()),
|
||||
);
|
||||
|
||||
|
||||
@@ -6,35 +6,38 @@ use bdk_chain::{
|
||||
collections::BTreeMap,
|
||||
indexed_tx_graph::Indexer,
|
||||
keychain::{self, ChangeSet, KeychainTxOutIndex},
|
||||
Append,
|
||||
Append, DescriptorExt, DescriptorId,
|
||||
};
|
||||
|
||||
use bitcoin::{secp256k1::Secp256k1, Amount, OutPoint, ScriptBuf, Transaction, TxOut};
|
||||
use miniscript::{Descriptor, DescriptorPublicKey};
|
||||
|
||||
use crate::common::DESCRIPTORS;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
enum TestKeychain {
|
||||
External,
|
||||
Internal,
|
||||
}
|
||||
|
||||
fn parse_descriptor(descriptor: &str) -> Descriptor<DescriptorPublicKey> {
|
||||
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, descriptor)
|
||||
.unwrap()
|
||||
.0
|
||||
}
|
||||
|
||||
fn init_txout_index(
|
||||
external_descriptor: Descriptor<DescriptorPublicKey>,
|
||||
internal_descriptor: Descriptor<DescriptorPublicKey>,
|
||||
lookahead: u32,
|
||||
) -> (
|
||||
bdk_chain::keychain::KeychainTxOutIndex<TestKeychain>,
|
||||
Descriptor<DescriptorPublicKey>,
|
||||
Descriptor<DescriptorPublicKey>,
|
||||
) {
|
||||
) -> bdk_chain::keychain::KeychainTxOutIndex<TestKeychain> {
|
||||
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::new(lookahead);
|
||||
|
||||
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
|
||||
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
|
||||
let _ = txout_index.insert_descriptor(TestKeychain::External, external_descriptor);
|
||||
let _ = txout_index.insert_descriptor(TestKeychain::Internal, internal_descriptor);
|
||||
|
||||
txout_index.add_keychain(TestKeychain::External, external_descriptor.clone());
|
||||
txout_index.add_keychain(TestKeychain::Internal, internal_descriptor.clone());
|
||||
|
||||
(txout_index, external_descriptor, internal_descriptor)
|
||||
txout_index
|
||||
}
|
||||
|
||||
fn spk_at_index(descriptor: &Descriptor<DescriptorPublicKey>, index: u32) -> ScriptBuf {
|
||||
@@ -44,61 +47,136 @@ fn spk_at_index(descriptor: &Descriptor<DescriptorPublicKey>, index: u32) -> Scr
|
||||
.script_pubkey()
|
||||
}
|
||||
|
||||
// We create two empty changesets lhs and rhs, we then insert various descriptors with various
|
||||
// last_revealed, append rhs to lhs, and check that the result is consistent with these rules:
|
||||
// - Existing index doesn't update if the new index in `other` is lower than `self`.
|
||||
// - Existing index updates if the new index in `other` is higher than `self`.
|
||||
// - Existing index is unchanged if keychain doesn't exist in `other`.
|
||||
// - New keychain gets added if the keychain is in `other` but not in `self`.
|
||||
#[test]
|
||||
fn append_keychain_derivation_indices() {
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
|
||||
enum Keychain {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
let mut lhs_di = BTreeMap::<Keychain, u32>::default();
|
||||
let mut rhs_di = BTreeMap::<Keychain, u32>::default();
|
||||
lhs_di.insert(Keychain::One, 7);
|
||||
lhs_di.insert(Keychain::Two, 0);
|
||||
rhs_di.insert(Keychain::One, 3);
|
||||
rhs_di.insert(Keychain::Two, 5);
|
||||
lhs_di.insert(Keychain::Three, 3);
|
||||
rhs_di.insert(Keychain::Four, 4);
|
||||
fn append_changesets_check_last_revealed() {
|
||||
let secp = bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
let descriptor_ids: Vec<_> = DESCRIPTORS
|
||||
.iter()
|
||||
.take(4)
|
||||
.map(|d| {
|
||||
Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, d)
|
||||
.unwrap()
|
||||
.0
|
||||
.descriptor_id()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut lhs = ChangeSet(lhs_di);
|
||||
let rhs = ChangeSet(rhs_di);
|
||||
let mut lhs_di = BTreeMap::<DescriptorId, u32>::default();
|
||||
let mut rhs_di = BTreeMap::<DescriptorId, u32>::default();
|
||||
lhs_di.insert(descriptor_ids[0], 7);
|
||||
lhs_di.insert(descriptor_ids[1], 0);
|
||||
lhs_di.insert(descriptor_ids[2], 3);
|
||||
|
||||
rhs_di.insert(descriptor_ids[0], 3); // value less than lhs desc 0
|
||||
rhs_di.insert(descriptor_ids[1], 5); // value more than lhs desc 1
|
||||
lhs_di.insert(descriptor_ids[3], 4); // key doesn't exist in lhs
|
||||
|
||||
let mut lhs = ChangeSet {
|
||||
keychains_added: BTreeMap::<(), _>::new(),
|
||||
last_revealed: lhs_di,
|
||||
};
|
||||
let rhs = ChangeSet {
|
||||
keychains_added: BTreeMap::<(), _>::new(),
|
||||
last_revealed: rhs_di,
|
||||
};
|
||||
lhs.append(rhs);
|
||||
|
||||
// Exiting index doesn't update if the new index in `other` is lower than `self`.
|
||||
assert_eq!(lhs.0.get(&Keychain::One), Some(&7));
|
||||
// Existing index doesn't update if the new index in `other` is lower than `self`.
|
||||
assert_eq!(lhs.last_revealed.get(&descriptor_ids[0]), Some(&7));
|
||||
// Existing index updates if the new index in `other` is higher than `self`.
|
||||
assert_eq!(lhs.0.get(&Keychain::Two), Some(&5));
|
||||
assert_eq!(lhs.last_revealed.get(&descriptor_ids[1]), Some(&5));
|
||||
// Existing index is unchanged if keychain doesn't exist in `other`.
|
||||
assert_eq!(lhs.0.get(&Keychain::Three), Some(&3));
|
||||
assert_eq!(lhs.last_revealed.get(&descriptor_ids[2]), Some(&3));
|
||||
// New keychain gets added if the keychain is in `other` but not in `self`.
|
||||
assert_eq!(lhs.0.get(&Keychain::Four), Some(&4));
|
||||
assert_eq!(lhs.last_revealed.get(&descriptor_ids[3]), Some(&4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_changeset_with_different_descriptors_to_same_keychain() {
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
|
||||
let mut txout_index =
|
||||
init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 0);
|
||||
assert_eq!(
|
||||
txout_index.keychains().collect::<Vec<_>>(),
|
||||
vec![
|
||||
(&TestKeychain::External, &external_descriptor),
|
||||
(&TestKeychain::Internal, &internal_descriptor)
|
||||
]
|
||||
);
|
||||
|
||||
let changeset = ChangeSet {
|
||||
keychains_added: [(TestKeychain::External, internal_descriptor.clone())].into(),
|
||||
last_revealed: [].into(),
|
||||
};
|
||||
txout_index.apply_changeset(changeset);
|
||||
|
||||
assert_eq!(
|
||||
txout_index.keychains().collect::<Vec<_>>(),
|
||||
vec![
|
||||
(&TestKeychain::External, &internal_descriptor),
|
||||
(&TestKeychain::Internal, &internal_descriptor)
|
||||
]
|
||||
);
|
||||
|
||||
let changeset = ChangeSet {
|
||||
keychains_added: [(TestKeychain::Internal, external_descriptor.clone())].into(),
|
||||
last_revealed: [].into(),
|
||||
};
|
||||
txout_index.apply_changeset(changeset);
|
||||
|
||||
assert_eq!(
|
||||
txout_index.keychains().collect::<Vec<_>>(),
|
||||
vec![
|
||||
(&TestKeychain::External, &internal_descriptor),
|
||||
(&TestKeychain::Internal, &external_descriptor)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_all_derivation_indices() {
|
||||
use bdk_chain::indexed_tx_graph::Indexer;
|
||||
|
||||
let (mut txout_index, _, _) = init_txout_index(0);
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
|
||||
let mut txout_index =
|
||||
init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 0);
|
||||
let derive_to: BTreeMap<_, _> =
|
||||
[(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into();
|
||||
let last_revealed: BTreeMap<_, _> = [
|
||||
(external_descriptor.descriptor_id(), 12),
|
||||
(internal_descriptor.descriptor_id(), 24),
|
||||
]
|
||||
.into();
|
||||
assert_eq!(
|
||||
txout_index.reveal_to_target_multi(&derive_to).1.as_inner(),
|
||||
&derive_to
|
||||
txout_index.reveal_to_target_multi(&derive_to).1,
|
||||
ChangeSet {
|
||||
keychains_added: BTreeMap::new(),
|
||||
last_revealed: last_revealed.clone()
|
||||
}
|
||||
);
|
||||
assert_eq!(txout_index.last_revealed_indices(), &derive_to);
|
||||
assert_eq!(txout_index.last_revealed_indices(), derive_to);
|
||||
assert_eq!(
|
||||
txout_index.reveal_to_target_multi(&derive_to).1,
|
||||
keychain::ChangeSet::default(),
|
||||
"no changes if we set to the same thing"
|
||||
);
|
||||
assert_eq!(txout_index.initial_changeset().as_inner(), &derive_to);
|
||||
assert_eq!(txout_index.initial_changeset().last_revealed, last_revealed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookahead() {
|
||||
let (mut txout_index, external_desc, internal_desc) = init_txout_index(10);
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
|
||||
let mut txout_index =
|
||||
init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 10);
|
||||
|
||||
// given:
|
||||
// - external lookahead set to 10
|
||||
@@ -108,15 +186,16 @@ fn test_lookahead() {
|
||||
// - scripts cached in spk_txout_index should increase correctly
|
||||
// - stored scripts of external keychain should be of expected counts
|
||||
for index in (0..20).skip_while(|i| i % 2 == 1) {
|
||||
let (revealed_spks, revealed_changeset) =
|
||||
txout_index.reveal_to_target(&TestKeychain::External, index);
|
||||
let (revealed_spks, revealed_changeset) = txout_index
|
||||
.reveal_to_target(&TestKeychain::External, index)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
revealed_spks.collect::<Vec<_>>(),
|
||||
vec![(index, spk_at_index(&external_desc, index))],
|
||||
vec![(index, spk_at_index(&external_descriptor, index))],
|
||||
);
|
||||
assert_eq!(
|
||||
revealed_changeset.as_inner(),
|
||||
&[(TestKeychain::External, index)].into()
|
||||
&revealed_changeset.last_revealed,
|
||||
&[(external_descriptor.descriptor_id(), index)].into()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -158,17 +237,18 @@ fn test_lookahead() {
|
||||
// - derivation index is set ahead of current derivation index + lookahead
|
||||
// expect:
|
||||
// - scripts cached in spk_txout_index should increase correctly, a.k.a. no scripts are skipped
|
||||
let (revealed_spks, revealed_changeset) =
|
||||
txout_index.reveal_to_target(&TestKeychain::Internal, 24);
|
||||
let (revealed_spks, revealed_changeset) = txout_index
|
||||
.reveal_to_target(&TestKeychain::Internal, 24)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
revealed_spks.collect::<Vec<_>>(),
|
||||
(0..=24)
|
||||
.map(|index| (index, spk_at_index(&internal_desc, index)))
|
||||
.map(|index| (index, spk_at_index(&internal_descriptor, index)))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
assert_eq!(
|
||||
revealed_changeset.as_inner(),
|
||||
&[(TestKeychain::Internal, 24)].into()
|
||||
&revealed_changeset.last_revealed,
|
||||
&[(internal_descriptor.descriptor_id(), 24)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
txout_index.inner().all_spks().len(),
|
||||
@@ -204,14 +284,14 @@ fn test_lookahead() {
|
||||
let tx = Transaction {
|
||||
output: vec![
|
||||
TxOut {
|
||||
script_pubkey: external_desc
|
||||
script_pubkey: external_descriptor
|
||||
.at_derivation_index(external_index)
|
||||
.unwrap()
|
||||
.script_pubkey(),
|
||||
value: Amount::from_sat(10_000),
|
||||
},
|
||||
TxOut {
|
||||
script_pubkey: internal_desc
|
||||
script_pubkey: internal_descriptor
|
||||
.at_derivation_index(internal_index)
|
||||
.unwrap()
|
||||
.script_pubkey(),
|
||||
@@ -251,14 +331,17 @@ fn test_lookahead() {
|
||||
// - last used index should change as expected
|
||||
#[test]
|
||||
fn test_scan_with_lookahead() {
|
||||
let (mut txout_index, external_desc, _) = init_txout_index(10);
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
|
||||
let mut txout_index =
|
||||
init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 10);
|
||||
|
||||
let spks: BTreeMap<u32, ScriptBuf> = [0, 10, 20, 30]
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
(
|
||||
i,
|
||||
external_desc
|
||||
external_descriptor
|
||||
.at_derivation_index(i)
|
||||
.unwrap()
|
||||
.script_pubkey(),
|
||||
@@ -275,8 +358,8 @@ fn test_scan_with_lookahead() {
|
||||
|
||||
let changeset = txout_index.index_txout(op, &txout);
|
||||
assert_eq!(
|
||||
changeset.as_inner(),
|
||||
&[(TestKeychain::External, spk_i)].into()
|
||||
&changeset.last_revealed,
|
||||
&[(external_descriptor.descriptor_id(), spk_i)].into()
|
||||
);
|
||||
assert_eq!(
|
||||
txout_index.last_revealed_index(&TestKeychain::External),
|
||||
@@ -289,7 +372,7 @@ fn test_scan_with_lookahead() {
|
||||
}
|
||||
|
||||
// now try with index 41 (lookahead surpassed), we expect that the txout to not be indexed
|
||||
let spk_41 = external_desc
|
||||
let spk_41 = external_descriptor
|
||||
.at_derivation_index(41)
|
||||
.unwrap()
|
||||
.script_pubkey();
|
||||
@@ -305,11 +388,13 @@ fn test_scan_with_lookahead() {
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn test_wildcard_derivations() {
|
||||
let (mut txout_index, external_desc, _) = init_txout_index(0);
|
||||
let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey();
|
||||
let external_spk_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey();
|
||||
let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey();
|
||||
let external_spk_27 = external_desc.at_derivation_index(27).unwrap().script_pubkey();
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
|
||||
let mut txout_index = init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 0);
|
||||
let external_spk_0 = external_descriptor.at_derivation_index(0).unwrap().script_pubkey();
|
||||
let external_spk_16 = external_descriptor.at_derivation_index(16).unwrap().script_pubkey();
|
||||
let external_spk_26 = external_descriptor.at_derivation_index(26).unwrap().script_pubkey();
|
||||
let external_spk_27 = external_descriptor.at_derivation_index(27).unwrap().script_pubkey();
|
||||
|
||||
// - nothing is derived
|
||||
// - unused list is also empty
|
||||
@@ -317,13 +402,13 @@ fn test_wildcard_derivations() {
|
||||
// - next_derivation_index() == (0, true)
|
||||
// - derive_new() == ((0, <spk>), keychain::ChangeSet)
|
||||
// - next_unused() == ((0, <spk>), keychain::ChangeSet:is_empty())
|
||||
assert_eq!(txout_index.next_index(&TestKeychain::External), (0, true));
|
||||
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External);
|
||||
assert_eq!(txout_index.next_index(&TestKeychain::External).unwrap(), (0, true));
|
||||
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External).unwrap();
|
||||
assert_eq!(spk, (0_u32, external_spk_0.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[(TestKeychain::External, 0)].into());
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External);
|
||||
assert_eq!(&changeset.last_revealed, &[(external_descriptor.descriptor_id(), 0)].into());
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External).unwrap();
|
||||
assert_eq!(spk, (0_u32, external_spk_0.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[].into());
|
||||
assert_eq!(&changeset.last_revealed, &[].into());
|
||||
|
||||
// - derived till 25
|
||||
// - used all spks till 15.
|
||||
@@ -339,16 +424,16 @@ fn test_wildcard_derivations() {
|
||||
.chain([17, 20, 23])
|
||||
.for_each(|index| assert!(txout_index.mark_used(TestKeychain::External, index)));
|
||||
|
||||
assert_eq!(txout_index.next_index(&TestKeychain::External), (26, true));
|
||||
assert_eq!(txout_index.next_index(&TestKeychain::External).unwrap(), (26, true));
|
||||
|
||||
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External);
|
||||
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External).unwrap();
|
||||
assert_eq!(spk, (26, external_spk_26.as_script()));
|
||||
|
||||
assert_eq!(changeset.as_inner(), &[(TestKeychain::External, 26)].into());
|
||||
assert_eq!(&changeset.last_revealed, &[(external_descriptor.descriptor_id(), 26)].into());
|
||||
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External);
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External).unwrap();
|
||||
assert_eq!(spk, (16, external_spk_16.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[].into());
|
||||
assert_eq!(&changeset.last_revealed, &[].into());
|
||||
|
||||
// - Use all the derived till 26.
|
||||
// - next_unused() = ((27, <spk>), keychain::ChangeSet)
|
||||
@@ -356,9 +441,9 @@ fn test_wildcard_derivations() {
|
||||
txout_index.mark_used(TestKeychain::External, index);
|
||||
});
|
||||
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External);
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External).unwrap();
|
||||
assert_eq!(spk, (27, external_spk_27.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[(TestKeychain::External, 27)].into());
|
||||
assert_eq!(&changeset.last_revealed, &[(external_descriptor.descriptor_id(), 27)].into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -366,13 +451,14 @@ fn test_non_wildcard_derivations() {
|
||||
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
|
||||
|
||||
let secp = bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();
|
||||
let (no_wildcard_descriptor, _) =
|
||||
Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[6]).unwrap();
|
||||
let external_spk = no_wildcard_descriptor
|
||||
.at_derivation_index(0)
|
||||
.unwrap()
|
||||
.script_pubkey();
|
||||
|
||||
txout_index.add_keychain(TestKeychain::External, no_wildcard_descriptor);
|
||||
let _ = txout_index.insert_descriptor(TestKeychain::External, no_wildcard_descriptor.clone());
|
||||
|
||||
// given:
|
||||
// - `txout_index` with no stored scripts
|
||||
@@ -380,14 +466,24 @@ fn test_non_wildcard_derivations() {
|
||||
// - next derivation index should be new
|
||||
// - when we derive a new script, script @ index 0
|
||||
// - when we get the next unused script, script @ index 0
|
||||
assert_eq!(txout_index.next_index(&TestKeychain::External), (0, true));
|
||||
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External);
|
||||
assert_eq!(
|
||||
txout_index.next_index(&TestKeychain::External).unwrap(),
|
||||
(0, true)
|
||||
);
|
||||
let (spk, changeset) = txout_index
|
||||
.reveal_next_spk(&TestKeychain::External)
|
||||
.unwrap();
|
||||
assert_eq!(spk, (0, external_spk.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[(TestKeychain::External, 0)].into());
|
||||
assert_eq!(
|
||||
&changeset.last_revealed,
|
||||
&[(no_wildcard_descriptor.descriptor_id(), 0)].into()
|
||||
);
|
||||
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External);
|
||||
let (spk, changeset) = txout_index
|
||||
.next_unused_spk(&TestKeychain::External)
|
||||
.unwrap();
|
||||
assert_eq!(spk, (0, external_spk.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[].into());
|
||||
assert_eq!(&changeset.last_revealed, &[].into());
|
||||
|
||||
// given:
|
||||
// - the non-wildcard descriptor already has a stored and used script
|
||||
@@ -395,18 +491,26 @@ fn test_non_wildcard_derivations() {
|
||||
// - next derivation index should not be new
|
||||
// - derive new and next unused should return the old script
|
||||
// - store_up_to should not panic and return empty changeset
|
||||
assert_eq!(txout_index.next_index(&TestKeychain::External), (0, false));
|
||||
assert_eq!(
|
||||
txout_index.next_index(&TestKeychain::External).unwrap(),
|
||||
(0, false)
|
||||
);
|
||||
txout_index.mark_used(TestKeychain::External, 0);
|
||||
|
||||
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External);
|
||||
let (spk, changeset) = txout_index
|
||||
.reveal_next_spk(&TestKeychain::External)
|
||||
.unwrap();
|
||||
assert_eq!(spk, (0, external_spk.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[].into());
|
||||
assert_eq!(&changeset.last_revealed, &[].into());
|
||||
|
||||
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External);
|
||||
let (spk, changeset) = txout_index
|
||||
.next_unused_spk(&TestKeychain::External)
|
||||
.unwrap();
|
||||
assert_eq!(spk, (0, external_spk.as_script()));
|
||||
assert_eq!(changeset.as_inner(), &[].into());
|
||||
let (revealed_spks, revealed_changeset) =
|
||||
txout_index.reveal_to_target(&TestKeychain::External, 200);
|
||||
assert_eq!(&changeset.last_revealed, &[].into());
|
||||
let (revealed_spks, revealed_changeset) = txout_index
|
||||
.reveal_to_target(&TestKeychain::External, 200)
|
||||
.unwrap();
|
||||
assert_eq!(revealed_spks.count(), 0);
|
||||
assert!(revealed_changeset.is_empty());
|
||||
|
||||
@@ -470,7 +574,13 @@ fn lookahead_to_target() {
|
||||
];
|
||||
|
||||
for t in test_cases {
|
||||
let (mut index, _, _) = init_txout_index(t.lookahead);
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
|
||||
let mut index = init_txout_index(
|
||||
external_descriptor.clone(),
|
||||
internal_descriptor.clone(),
|
||||
t.lookahead,
|
||||
);
|
||||
|
||||
if let Some(last_revealed) = t.external_last_revealed {
|
||||
let _ = index.reveal_to_target(&TestKeychain::External, last_revealed);
|
||||
@@ -481,17 +591,19 @@ fn lookahead_to_target() {
|
||||
|
||||
let keychain_test_cases = [
|
||||
(
|
||||
external_descriptor.descriptor_id(),
|
||||
TestKeychain::External,
|
||||
t.external_last_revealed,
|
||||
t.external_target,
|
||||
),
|
||||
(
|
||||
internal_descriptor.descriptor_id(),
|
||||
TestKeychain::Internal,
|
||||
t.internal_last_revealed,
|
||||
t.internal_target,
|
||||
),
|
||||
];
|
||||
for (keychain, last_revealed, target) in keychain_test_cases {
|
||||
for (descriptor_id, keychain, last_revealed, target) in keychain_test_cases {
|
||||
if let Some(target) = target {
|
||||
let original_last_stored_index = match last_revealed {
|
||||
Some(last_revealed) => Some(last_revealed + t.lookahead),
|
||||
@@ -507,10 +619,10 @@ fn lookahead_to_target() {
|
||||
let keys = index
|
||||
.inner()
|
||||
.all_spks()
|
||||
.range((keychain.clone(), 0)..=(keychain.clone(), u32::MAX))
|
||||
.map(|(k, _)| k.clone())
|
||||
.range((descriptor_id, 0)..=(descriptor_id, u32::MAX))
|
||||
.map(|(k, _)| *k)
|
||||
.collect::<Vec<_>>();
|
||||
let exp_keys = core::iter::repeat(keychain)
|
||||
let exp_keys = core::iter::repeat(descriptor_id)
|
||||
.zip(0_u32..=exp_last_stored_index)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(keys, exp_keys);
|
||||
@@ -518,3 +630,67 @@ fn lookahead_to_target() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `::index_txout` should still index txouts with spks derived from descriptors without keychains.
|
||||
/// This includes properly refilling the lookahead for said descriptors.
|
||||
#[test]
|
||||
fn index_txout_after_changing_descriptor_under_keychain() {
|
||||
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
let (desc_a, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[0])
|
||||
.expect("descriptor 0 must be valid");
|
||||
let (desc_b, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[1])
|
||||
.expect("descriptor 1 must be valid");
|
||||
let desc_id_a = desc_a.descriptor_id();
|
||||
|
||||
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<()>::new(10);
|
||||
|
||||
// Introduce `desc_a` under keychain `()` and replace the descriptor.
|
||||
let _ = txout_index.insert_descriptor((), desc_a.clone());
|
||||
let _ = txout_index.insert_descriptor((), desc_b.clone());
|
||||
|
||||
// Loop through spks in intervals of `lookahead` to create outputs with. We should always be
|
||||
// able to index these outputs if `lookahead` is respected.
|
||||
let spk_indices = [9, 19, 29, 39];
|
||||
for i in spk_indices {
|
||||
let spk_at_index = desc_a
|
||||
.at_derivation_index(i)
|
||||
.expect("must derive")
|
||||
.script_pubkey();
|
||||
let index_changeset = txout_index.index_txout(
|
||||
// Use spk derivation index as vout as we just want an unique outpoint.
|
||||
OutPoint::new(h!("mock_tx"), i as _),
|
||||
&TxOut {
|
||||
value: Amount::from_sat(10_000),
|
||||
script_pubkey: spk_at_index,
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
index_changeset,
|
||||
bdk_chain::keychain::ChangeSet {
|
||||
keychains_added: BTreeMap::default(),
|
||||
last_revealed: [(desc_id_a, i)].into(),
|
||||
},
|
||||
"must always increase last active if impl respects lookahead"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_descriptor_no_change() {
|
||||
let secp = Secp256k1::signing_only();
|
||||
let (desc, _) =
|
||||
Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[0]).unwrap();
|
||||
let mut txout_index = KeychainTxOutIndex::<()>::default();
|
||||
assert_eq!(
|
||||
txout_index.insert_descriptor((), desc.clone()),
|
||||
keychain::ChangeSet {
|
||||
keychains_added: [((), desc.clone())].into(),
|
||||
last_revealed: Default::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
txout_index.insert_descriptor((), desc.clone()),
|
||||
keychain::ChangeSet::default(),
|
||||
"inserting the same descriptor for keychain should return an empty changeset",
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user