Skip to content

Commit 3afb324

Browse files
committed
feat: add multi_keychain module
- Add rusqlite support for `Wallet<DescriptorId>`.
1 parent 4d3116a commit 3afb324

File tree

5 files changed

+641
-0
lines changed

5 files changed

+641
-0
lines changed

wallet/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub mod test_utils;
3333
mod types;
3434
mod wallet;
3535

36+
pub mod multi_keychain;
37+
3638
pub(crate) use bdk_chain::collections;
3739
#[cfg(feature = "rusqlite")]
3840
pub use bdk_chain::rusqlite;

wallet/src/multi_keychain.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//! Module containing the multi-keychain [`Wallet`].
2+
3+
mod changeset;
4+
pub mod keyring;
5+
mod wallet;
6+
7+
pub use changeset::*;
8+
pub use keyring::KeyRing;
9+
pub use wallet::*;
10+
11+
/// Alias for [`DescriptorId`](bdk_chain::DescriptorId).
12+
pub(crate) type Did = bdk_chain::DescriptorId;
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use bdk_chain::{
2+
indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, DescriptorId,
3+
Merge,
4+
};
5+
use bitcoin::Network;
6+
use miniscript::{Descriptor, DescriptorPublicKey};
7+
use serde::{Deserialize, Serialize};
8+
9+
use crate::multi_keychain::keyring;
10+
11+
/// Change set.
12+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
13+
pub struct ChangeSet<K: Ord> {
14+
/// Keyring changeset.
15+
pub keyring: keyring::ChangeSet<K>,
16+
/// Changes to the [`LocalChain`](local_chain::LocalChain).
17+
pub local_chain: local_chain::ChangeSet,
18+
/// Changes to [`TxGraph`](tx_graph::TxGraph).
19+
pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>,
20+
/// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
21+
pub indexer: keychain_txout::ChangeSet,
22+
}
23+
24+
impl<K: Ord> Default for ChangeSet<K> {
25+
fn default() -> Self {
26+
Self {
27+
keyring: Default::default(),
28+
local_chain: Default::default(),
29+
tx_graph: Default::default(),
30+
indexer: Default::default(),
31+
}
32+
}
33+
}
34+
35+
impl<K: Ord> Merge for ChangeSet<K> {
36+
fn merge(&mut self, other: Self) {
37+
// merge keyring
38+
self.keyring.merge(other.keyring);
39+
40+
// merge local chain, tx-graph, indexer
41+
Merge::merge(&mut self.local_chain, other.local_chain);
42+
Merge::merge(&mut self.tx_graph, other.tx_graph);
43+
Merge::merge(&mut self.indexer, other.indexer);
44+
}
45+
46+
fn is_empty(&self) -> bool {
47+
self.keyring.is_empty()
48+
&& self.local_chain.is_empty()
49+
&& self.tx_graph.is_empty()
50+
&& self.indexer.is_empty()
51+
}
52+
}
53+
54+
#[cfg(feature = "rusqlite")]
55+
use bdk_chain::rusqlite;
56+
57+
#[cfg(feature = "rusqlite")]
58+
impl ChangeSet<DescriptorId> {
59+
/// Schema name for wallet.
60+
pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet";
61+
/// Name of table to store wallet metainformation.
62+
pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet";
63+
/// Name of table to store wallet descriptors.
64+
pub const DESCRIPTORS_TABLE_NAME: &'static str = "bdk_descriptor";
65+
66+
/// Get v0 sqlite [ChangeSet] schema.
67+
pub fn schema_v0() -> alloc::string::String {
68+
format!(
69+
"CREATE TABLE {} ( \
70+
id INTEGER PRIMARY KEY NOT NULL, \
71+
network TEXT NOT NULL \
72+
); \
73+
CREATE TABLE {} ( \
74+
descriptor_id TEXT PRIMARY KEY NOT NULL, \
75+
descriptor BLOB NOT NULL \
76+
);",
77+
Self::WALLET_TABLE_NAME,
78+
Self::DESCRIPTORS_TABLE_NAME,
79+
)
80+
}
81+
82+
/// Initializes tables and returns the aggregate data if the database is non-empty
83+
/// otherwise returns `Ok(None)`.
84+
pub fn initialize(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Option<Self>> {
85+
Self::init_sqlite_tables(db_tx)?;
86+
let changeset = Self::from_sqlite(db_tx)?;
87+
88+
if changeset.is_empty() {
89+
Ok(None)
90+
} else {
91+
Ok(Some(changeset))
92+
}
93+
}
94+
95+
/// Initialize SQLite tables.
96+
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
97+
crate::rusqlite_impl::migrate_schema(
98+
db_tx,
99+
Self::WALLET_SCHEMA_NAME,
100+
&[&Self::schema_v0()],
101+
)?;
102+
103+
local_chain::ChangeSet::init_sqlite_tables(db_tx)?;
104+
tx_graph::ChangeSet::<ConfirmationBlockTime>::init_sqlite_tables(db_tx)?;
105+
keychain_txout::ChangeSet::init_sqlite_tables(db_tx)?;
106+
107+
Ok(())
108+
}
109+
110+
/// Construct self by reading all of the SQLite data. This should succeed
111+
/// even if attempting to read an empty database.
112+
fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
113+
use bdk_chain::Impl;
114+
use rusqlite::OptionalExtension;
115+
let mut changeset = Self::default();
116+
117+
let mut keyring = keyring::ChangeSet::default();
118+
119+
// Read network
120+
let mut network_stmt = db_tx.prepare(&format!(
121+
"SELECT network FROM {} WHERE id = 0",
122+
Self::WALLET_TABLE_NAME,
123+
))?;
124+
let row = network_stmt
125+
.query_row([], |row| row.get::<_, Impl<Network>>("network"))
126+
.optional()?;
127+
if let Some(Impl(network)) = row {
128+
keyring.network = Some(network);
129+
}
130+
131+
// Read descriptors
132+
let mut descriptor_stmt = db_tx.prepare(&format!(
133+
"SELECT descriptor_id, descriptor FROM {}",
134+
Self::DESCRIPTORS_TABLE_NAME
135+
))?;
136+
let rows = descriptor_stmt.query_map([], |row| {
137+
Ok((
138+
row.get::<_, Impl<DescriptorId>>("descriptor_id")?,
139+
row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("descriptor")?,
140+
))
141+
})?;
142+
for row in rows {
143+
let (Impl(did), Impl(descriptor)) = row?;
144+
keyring.descriptors.insert(did, descriptor);
145+
}
146+
147+
changeset.keyring = keyring;
148+
changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?;
149+
changeset.tx_graph = tx_graph::ChangeSet::from_sqlite(db_tx)?;
150+
changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?;
151+
152+
Ok(changeset)
153+
}
154+
155+
/// Persist self to SQLite.
156+
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
157+
use chain::rusqlite::named_params;
158+
use chain::Impl;
159+
160+
let keyring = &self.keyring;
161+
162+
// Write network
163+
let mut network_stmt = db_tx.prepare_cached(&format!(
164+
"REPLACE INTO {}(id, network) VALUES(:id, :network)",
165+
Self::WALLET_TABLE_NAME,
166+
))?;
167+
if let Some(network) = keyring.network {
168+
network_stmt.execute(named_params! {
169+
":id": 0,
170+
":network": Impl(network),
171+
})?;
172+
}
173+
174+
// Write descriptors
175+
let mut descriptor_stmt = db_tx.prepare_cached(&format!(
176+
"INSERT OR IGNORE INTO {}(descriptor_id, descriptor) VALUES(:descriptor_id, :descriptor)",
177+
Self::DESCRIPTORS_TABLE_NAME,
178+
))?;
179+
for (&did, descriptor) in &keyring.descriptors {
180+
descriptor_stmt.execute(named_params! {
181+
":descriptor_id": Impl(did),
182+
":descriptor": Impl(descriptor.clone()),
183+
})?;
184+
}
185+
186+
self.local_chain.persist_to_sqlite(db_tx)?;
187+
self.tx_graph.persist_to_sqlite(db_tx)?;
188+
self.indexer.persist_to_sqlite(db_tx)?;
189+
190+
Ok(())
191+
}
192+
}
193+
194+
impl<K: Ord> From<local_chain::ChangeSet> for ChangeSet<K> {
195+
fn from(local_chain: local_chain::ChangeSet) -> Self {
196+
Self {
197+
local_chain,
198+
..Default::default()
199+
}
200+
}
201+
}
202+
203+
impl<K: Ord> From<indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>>
204+
for ChangeSet<K>
205+
{
206+
fn from(
207+
indexed_tx_graph: indexed_tx_graph::ChangeSet<
208+
ConfirmationBlockTime,
209+
keychain_txout::ChangeSet,
210+
>,
211+
) -> Self {
212+
Self {
213+
tx_graph: indexed_tx_graph.tx_graph,
214+
indexer: indexed_tx_graph.indexer,
215+
..Default::default()
216+
}
217+
}
218+
}
219+
220+
impl<K: Ord> From<keychain_txout::ChangeSet> for ChangeSet<K> {
221+
fn from(indexer: keychain_txout::ChangeSet) -> Self {
222+
Self {
223+
indexer,
224+
..Default::default()
225+
}
226+
}
227+
}

wallet/src/multi_keychain/keyring.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! [`KeyRing`].
2+
3+
use bdk_chain::{DescriptorExt, Merge};
4+
use bitcoin::{
5+
secp256k1::{All, Secp256k1},
6+
Network,
7+
};
8+
use miniscript::{Descriptor, DescriptorPublicKey};
9+
use serde::{Deserialize, Serialize};
10+
11+
use crate::collections::BTreeMap;
12+
use crate::descriptor::{check_wallet_descriptor, IntoWalletDescriptor};
13+
use crate::multi_keychain::Did;
14+
15+
/// KeyRing.
16+
#[derive(Debug, Clone)]
17+
pub struct KeyRing<K> {
18+
pub(crate) secp: Secp256k1<All>,
19+
pub(crate) network: Network,
20+
pub(crate) descriptors: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
21+
}
22+
23+
impl<K> KeyRing<K>
24+
where
25+
K: Ord + Clone,
26+
{
27+
/// Construct new [`KeyRing`] with the provided `network`.
28+
pub fn new(network: Network) -> Self {
29+
Self {
30+
secp: Secp256k1::new(),
31+
network,
32+
descriptors: BTreeMap::default(),
33+
}
34+
}
35+
36+
/// Add descriptor, must not be [multipath](miniscript::Descriptor::is_multipath).
37+
pub fn add_descriptor(&mut self, keychain: K, descriptor: impl IntoWalletDescriptor) {
38+
let descriptor = descriptor
39+
.into_wallet_descriptor(&self.secp, self.network)
40+
.expect("err: invalid descriptor")
41+
.0;
42+
assert!(
43+
!descriptor.is_multipath(),
44+
"err: Use `add_multipath_descriptor` instead"
45+
);
46+
check_wallet_descriptor(&descriptor).expect("err: failed `check_wallet_descriptor`");
47+
48+
self.descriptors.insert(keychain, descriptor);
49+
}
50+
51+
/// Initial changeset.
52+
pub fn initial_changeset(&self) -> ChangeSet<K> {
53+
ChangeSet {
54+
network: Some(self.network),
55+
descriptors: self.descriptors.clone(),
56+
}
57+
}
58+
59+
/// Construct from changeset.
60+
pub fn from_changeset(changeset: ChangeSet<K>) -> Option<Self> {
61+
Some(Self {
62+
secp: Secp256k1::new(),
63+
network: changeset.network?,
64+
descriptors: changeset.descriptors,
65+
})
66+
}
67+
}
68+
69+
impl KeyRing<Did> {
70+
/// Add multipath descriptor.
71+
pub fn add_multipath_descriptor(&mut self, descriptor: impl IntoWalletDescriptor) {
72+
let descriptor = descriptor
73+
.into_wallet_descriptor(&self.secp, self.network)
74+
.expect("err: invalid descriptor")
75+
.0;
76+
assert!(
77+
descriptor.is_multipath(),
78+
"err: Use `add_descriptor` instead"
79+
);
80+
let descriptors = descriptor
81+
.into_single_descriptors()
82+
.expect("err: invalid descriptor");
83+
for descriptor in descriptors {
84+
let did = descriptor.descriptor_id();
85+
self.descriptors.insert(did, descriptor);
86+
}
87+
}
88+
}
89+
90+
/// Represents changes to the `KeyRing`.
91+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92+
pub struct ChangeSet<K: Ord> {
93+
/// Network.
94+
pub network: Option<Network>,
95+
/// Added descriptors.
96+
pub descriptors: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
97+
}
98+
99+
impl<K: Ord> Default for ChangeSet<K> {
100+
fn default() -> Self {
101+
Self {
102+
network: None,
103+
descriptors: Default::default(),
104+
}
105+
}
106+
}
107+
108+
impl<K: Ord> Merge for ChangeSet<K> {
109+
fn merge(&mut self, other: Self) {
110+
// merge network
111+
if other.network.is_some() && self.network.is_none() {
112+
self.network = other.network;
113+
}
114+
// merge descriptors
115+
self.descriptors.extend(other.descriptors);
116+
}
117+
118+
fn is_empty(&self) -> bool {
119+
self.network.is_none() && self.descriptors.is_empty()
120+
}
121+
}

0 commit comments

Comments
 (0)