feat!: Rework sqlite, changesets, persistence and wallet-construction
Rework sqlite: Instead of only supported one schema (defined in `bdk_sqlite`), we have a schema per changeset type for more flexiblity. * rm `bdk_sqlite` crate (as we don't need `bdk_sqlite::Store` anymore). * add `sqlite` feature on `bdk_chain` which adds methods on each changeset type for initializing tables, loading the changeset and writing. Rework changesets: Some callers may want to use `KeychainTxOutIndex` where `K` may change per descriptor on every run. So we only want to persist the last revealed indices by `DescriptorId` (which uniquely-ish identifies the descriptor). * rm `keychain_added` field from `keychain_txout`'s changeset. * Add `keychain_added` to `CombinedChangeSet` (which is renamed to `WalletChangeSet`). Rework persistence: add back some safety and convenience when persisting our types. Working with changeset directly (as we were doing before) can be cumbersome. * Intoduce `struct Persisted<T>` which wraps a type `T` which stores staged changes to it. This adds safety when creating and or loading `T` from db. * `struct Persisted<T>` methods, `create`, `load` and `persist`, are avaliable if `trait PersistWith<Db>` is implemented for `T`. `Db` represents the database connection and `PersistWith` should be implemented per database-type. * For async, we have `trait PersistedAsyncWith<Db>`. * `Wallet` has impls of `PersistedWith<rusqlite::Connection>`, `PersistedWith<rusqlite::Transaction>` and `PersistedWith<bdk_file_store::Store>` by default. Rework wallet-construction: Before, we had multiple methods for loading and creating with different input-counts so it would be unwieldly to add more parameters in the future. This also makes it difficult to impl `PersistWith` (which has a single method for `load` that takes in `PersistWith::LoadParams` and a single method for `create` that takes in `PersistWith::CreateParams`). * Introduce a builder pattern when constructing a `Wallet`. For loading from persistence or `ChangeSet`, we have `LoadParams`. For creating a new wallet, we have `CreateParams`.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bdk_chain::{tx_graph::TxGraph, Anchor, SpkTxOutIndex};
|
||||
use bdk_chain::{spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor};
|
||||
use bitcoin::{
|
||||
locktime::absolute::LockTime, secp256k1::Secp256k1, transaction, Amount, OutPoint, ScriptBuf,
|
||||
Sequence, Transaction, TxIn, TxOut, Txid, Witness,
|
||||
|
||||
@@ -10,7 +10,7 @@ use bdk_chain::{
|
||||
indexed_tx_graph::{self, IndexedTxGraph},
|
||||
indexer::keychain_txout::KeychainTxOutIndex,
|
||||
local_chain::LocalChain,
|
||||
tx_graph, Balance, ChainPosition, ConfirmationBlockTime, DescriptorExt, Merge,
|
||||
tx_graph, Balance, ChainPosition, ConfirmationBlockTime, DescriptorExt,
|
||||
};
|
||||
use bitcoin::{
|
||||
secp256k1::Secp256k1, Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
|
||||
@@ -73,13 +73,12 @@ fn insert_relevant_txs() {
|
||||
let txs = [tx_c, tx_b, tx_a];
|
||||
|
||||
let changeset = indexed_tx_graph::ChangeSet {
|
||||
graph: tx_graph::ChangeSet {
|
||||
tx_graph: tx_graph::ChangeSet {
|
||||
txs: txs.iter().cloned().map(Arc::new).collect(),
|
||||
..Default::default()
|
||||
},
|
||||
indexer: keychain_txout::ChangeSet {
|
||||
last_revealed: [(descriptor.descriptor_id(), 9_u32)].into(),
|
||||
keychains_added: [].into(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -90,10 +89,9 @@ fn insert_relevant_txs() {
|
||||
|
||||
// The initial changeset will also contain info about the keychain we added
|
||||
let initial_changeset = indexed_tx_graph::ChangeSet {
|
||||
graph: changeset.graph,
|
||||
tx_graph: changeset.tx_graph,
|
||||
indexer: keychain_txout::ChangeSet {
|
||||
last_revealed: changeset.indexer.last_revealed,
|
||||
keychains_added: [((), descriptor)].into(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -144,16 +142,14 @@ fn test_list_owned_txouts() {
|
||||
KeychainTxOutIndex::new(10),
|
||||
);
|
||||
|
||||
assert!(!graph
|
||||
assert!(graph
|
||||
.index
|
||||
.insert_descriptor("keychain_1".into(), desc_1)
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
assert!(!graph
|
||||
.unwrap());
|
||||
assert!(graph
|
||||
.index
|
||||
.insert_descriptor("keychain_2".into(), desc_2)
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
.unwrap());
|
||||
|
||||
// Get trusted and untrusted addresses
|
||||
|
||||
@@ -532,8 +528,8 @@ fn test_list_owned_txouts() {
|
||||
#[test]
|
||||
fn test_get_chain_position() {
|
||||
use bdk_chain::local_chain::CheckPoint;
|
||||
use bdk_chain::spk_txout::SpkTxOutIndex;
|
||||
use bdk_chain::BlockId;
|
||||
use bdk_chain::SpkTxOutIndex;
|
||||
|
||||
struct TestCase<A> {
|
||||
name: &'static str,
|
||||
|
||||
@@ -81,11 +81,9 @@ fn merge_changesets_check_last_revealed() {
|
||||
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.merge(rhs);
|
||||
@@ -100,49 +98,6 @@ fn merge_changesets_check_last_revealed() {
|
||||
assert_eq!(lhs.last_revealed.get(&descriptor_ids[3]), Some(&4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_apply_contradictory_changesets_they_are_ignored() {
|
||||
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, &external_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, &external_descriptor),
|
||||
(&TestKeychain::Internal, &internal_descriptor)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_all_derivation_indices() {
|
||||
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
|
||||
@@ -159,7 +114,6 @@ fn test_set_all_derivation_indices() {
|
||||
assert_eq!(
|
||||
txout_index.reveal_to_target_multi(&derive_to),
|
||||
ChangeSet {
|
||||
keychains_added: BTreeMap::new(),
|
||||
last_revealed: last_revealed.clone()
|
||||
}
|
||||
);
|
||||
@@ -633,46 +587,29 @@ fn lookahead_to_target() {
|
||||
}
|
||||
|
||||
#[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()),
|
||||
Ok(ChangeSet {
|
||||
keychains_added: [((), desc.clone())].into(),
|
||||
last_revealed: Default::default()
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
txout_index.insert_descriptor((), desc.clone()),
|
||||
Ok(ChangeSet::default()),
|
||||
"inserting the same descriptor for keychain should return an empty changeset",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
|
||||
let desc = parse_descriptor(DESCRIPTORS[0]);
|
||||
let changesets: &[ChangeSet<TestKeychain>] = &[
|
||||
let changesets: &[ChangeSet] = &[
|
||||
ChangeSet {
|
||||
keychains_added: [(TestKeychain::Internal, desc.clone())].into(),
|
||||
last_revealed: [].into(),
|
||||
last_revealed: [(desc.descriptor_id(), 10)].into(),
|
||||
},
|
||||
ChangeSet {
|
||||
keychains_added: [(TestKeychain::External, desc.clone())].into(),
|
||||
last_revealed: [(desc.descriptor_id(), 12)].into(),
|
||||
},
|
||||
];
|
||||
|
||||
let mut indexer_a = KeychainTxOutIndex::<TestKeychain>::new(0);
|
||||
indexer_a
|
||||
.insert_descriptor(TestKeychain::External, desc.clone())
|
||||
.expect("must insert keychain");
|
||||
for changeset in changesets {
|
||||
indexer_a.apply_changeset(changeset.clone());
|
||||
}
|
||||
|
||||
let mut indexer_b = KeychainTxOutIndex::<TestKeychain>::new(0);
|
||||
indexer_b
|
||||
.insert_descriptor(TestKeychain::External, desc.clone())
|
||||
.expect("must insert keychain");
|
||||
let aggregate_changesets = changesets
|
||||
.iter()
|
||||
.cloned()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use bdk_chain::{Indexer, SpkTxOutIndex};
|
||||
use bdk_chain::{spk_txout::SpkTxOutIndex, Indexer};
|
||||
use bitcoin::{
|
||||
absolute, transaction, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user