Skip to content

Commit 8ea292f

Browse files
committed
[wip] feat(wallet)!: add wallet birthday
1 parent 8f8a8e9 commit 8ea292f

File tree

4 files changed

+68
-5
lines changed

4 files changed

+68
-5
lines changed

src/persist_test_utils.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ where
168168
descriptor: Some(descriptor.clone()),
169169
change_descriptor: Some(change_descriptor.clone()),
170170
network: Some(Network::Testnet),
171+
birthday: None,
171172
local_chain: local_chain_changeset,
172173
tx_graph: tx_graph_changeset,
173174
indexer: keychain_txout_changeset,
@@ -227,6 +228,7 @@ where
227228
descriptor: None,
228229
change_descriptor: None,
229230
network: None,
231+
birthday: None,
230232
local_chain: local_chain_changeset,
231233
tx_graph: tx_graph_changeset,
232234
indexer: keychain_txout_changeset,

src/wallet/changeset.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use bdk_chain::{
22
indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge,
33
};
4+
use chain::BlockId;
45
use miniscript::{Descriptor, DescriptorPublicKey};
56
use serde::{Deserialize, Serialize};
67

@@ -110,6 +111,9 @@ pub struct ChangeSet {
110111
pub change_descriptor: Option<Descriptor<DescriptorPublicKey>>,
111112
/// Stores the network type of the transaction data.
112113
pub network: Option<bitcoin::Network>,
114+
/// Stores the [`Wallet`]'s birthday, defined as the first
115+
/// [`Block`] with relevant transactions to this [`Wallet`].
116+
pub birthday: Option<BlockId>,
113117
/// Changes to the [`LocalChain`](local_chain::LocalChain).
114118
pub local_chain: local_chain::ChangeSet,
115119
/// Changes to [`TxGraph`](tx_graph::TxGraph).
@@ -145,6 +149,14 @@ impl Merge for ChangeSet {
145149
);
146150
self.network = other.network;
147151
}
152+
// TODO(@luisschwab): should merging [`ChangeSet`]s with distinct birthdays be possible?
153+
if other.birthday.is_some() {
154+
debug_assert!(
155+
self.birthday.is_none() || self.birthday == other.birthday,
156+
"birthday must never change"
157+
);
158+
self.birthday = other.birthday;
159+
}
148160

149161
// merge locked outpoints
150162
self.locked_outpoints.merge(other.locked_outpoints);
@@ -158,6 +170,7 @@ impl Merge for ChangeSet {
158170
self.descriptor.is_none()
159171
&& self.change_descriptor.is_none()
160172
&& self.network.is_none()
173+
&& self.birthday.is_none()
161174
&& self.local_chain.is_empty()
162175
&& self.tx_graph.is_empty()
163176
&& self.indexer.is_empty()
@@ -174,7 +187,7 @@ impl ChangeSet {
174187
/// Name of table to store wallet locked outpoints.
175188
pub const WALLET_OUTPOINT_LOCK_TABLE_NAME: &'static str = "bdk_wallet_locked_outpoints";
176189

177-
/// Get v0 sqlite [ChangeSet] schema
190+
/// Get v0 sqlite [`ChangeSet`] schema.
178191
pub fn schema_v0() -> alloc::string::String {
179192
format!(
180193
"CREATE TABLE {} ( \
@@ -199,12 +212,19 @@ impl ChangeSet {
199212
)
200213
}
201214

215+
pub fn schema_v2() -> alloc::string::String {
216+
format!(
217+
"ALTER TABLE {} ADD COLUMN birthday TEXT",
218+
Self::WALLET_TABLE_NAME,
219+
)
220+
}
221+
202222
/// Initialize sqlite tables for wallet tables.
203223
pub fn init_sqlite_tables(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<()> {
204224
crate::rusqlite_impl::migrate_schema(
205225
db_tx,
206226
Self::WALLET_SCHEMA_NAME,
207-
&[&Self::schema_v0(), &Self::schema_v1()],
227+
&[&Self::schema_v0(), &Self::schema_v1(), &Self::schema_v2()],
208228
)?;
209229

210230
bdk_chain::local_chain::ChangeSet::init_sqlite_tables(db_tx)?;
@@ -223,7 +243,7 @@ impl ChangeSet {
223243
let mut changeset = Self::default();
224244

225245
let mut wallet_statement = db_tx.prepare(&format!(
226-
"SELECT descriptor, change_descriptor, network FROM {}",
246+
"SELECT descriptor, change_descriptor, network, birthday FROM {}",
227247
Self::WALLET_TABLE_NAME,
228248
))?;
229249
let row = wallet_statement
@@ -234,13 +254,16 @@ impl ChangeSet {
234254
"change_descriptor",
235255
)?,
236256
row.get::<_, Option<Impl<bitcoin::Network>>>("network")?,
257+
// TODO(@luisschwab): merge bdk#2097, publish new bdk_chain version and bump it here for [`BlockId`] impls.
258+
row.get::<_, Option<Impl<BlockId>>>("birthday")?,
237259
))
238260
})
239261
.optional()?;
240-
if let Some((desc, change_desc, network)) = row {
262+
if let Some((desc, change_desc, network, birthday)) = row {
241263
changeset.descriptor = desc.map(Impl::into_inner);
242264
changeset.change_descriptor = change_desc.map(Impl::into_inner);
243265
changeset.network = network.map(Impl::into_inner);
266+
changeset.birthday = birthday.map(Impl::into_inner);
244267
}
245268

246269
// Select locked outpoints.
@@ -309,6 +332,17 @@ impl ChangeSet {
309332
})?;
310333
}
311334

335+
let mut birthday_statement = db_tx.prepare_cached(&format!(
336+
"INSERT INTO {}(id, birthday) VALUES(:id, :birthday) ON CONFLICT(id) DO UPDATE SET birthday=:birthday",
337+
Self::WALLET_TABLE_NAME,
338+
))?;
339+
if let Some(birthday) = self.birthday {
340+
birthday_statement.execute(named_params! {
341+
":id": 0,
342+
":birthday": Impl(birthday),
343+
})?;
344+
}
345+
312346
// Insert or delete locked outpoints.
313347
let mut insert_stmt = db_tx.prepare_cached(&format!(
314348
"INSERT OR IGNORE INTO {}(txid, vout) VALUES(:txid, :vout)",

src/wallet/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,12 @@ impl Wallet {
449449
let genesis_hash = params
450450
.genesis_hash
451451
.unwrap_or(genesis_block(network).block_hash());
452-
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
452+
let (mut chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
453+
if let Some(birthday) = params.birthday {
454+
chain
455+
.apply_update(CheckPoint::new(birthday))
456+
.expect("wallet birthday overrided genesis hash");
457+
}
453458

454459
let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network_kind)?;
455460
check_wallet_descriptor(&descriptor)?;

src/wallet/params.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use alloc::boxed::Box;
22

33
use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD;
44
use bitcoin::{BlockHash, Network, NetworkKind};
5+
use chain::BlockId;
56
use miniscript::descriptor::KeyMap;
67

78
use crate::{
@@ -65,6 +66,7 @@ pub struct CreateParams {
6566
pub(crate) change_descriptor_keymap: KeyMap,
6667
pub(crate) network: Network,
6768
pub(crate) genesis_hash: Option<BlockHash>,
69+
pub(crate) birthday: Option<BlockId>,
6870
pub(crate) lookahead: u32,
6971
pub(crate) use_spk_cache: bool,
7072
}
@@ -88,6 +90,7 @@ impl CreateParams {
8890
change_descriptor_keymap: KeyMap::default(),
8991
network: Network::Bitcoin,
9092
genesis_hash: None,
93+
birthday: None,
9194
lookahead: DEFAULT_LOOKAHEAD,
9295
use_spk_cache: false,
9396
}
@@ -110,6 +113,7 @@ impl CreateParams {
110113
change_descriptor_keymap: KeyMap::default(),
111114
network: Network::Bitcoin,
112115
genesis_hash: None,
116+
birthday: None,
113117
lookahead: DEFAULT_LOOKAHEAD,
114118
use_spk_cache: false,
115119
}
@@ -135,6 +139,7 @@ impl CreateParams {
135139
change_descriptor_keymap: KeyMap::default(),
136140
network: Network::Bitcoin,
137141
genesis_hash: None,
142+
birthday: None,
138143
lookahead: DEFAULT_LOOKAHEAD,
139144
use_spk_cache: false,
140145
}
@@ -162,6 +167,15 @@ impl CreateParams {
162167
self
163168
}
164169

170+
/// Set the [`Wallet`]'s birthday, as a [`BlockId`].
171+
///
172+
/// The birthday can be used to limit how far back a chain oracle is queried,
173+
/// saving up time and data bandwidth in full-scans.
174+
pub fn birthday(mut self, birthday: BlockId) -> Self {
175+
self.birthday = Some(birthday);
176+
self
177+
}
178+
165179
/// Use a custom `lookahead` value.
166180
///
167181
/// The `lookahead` defines a number of script pubkeys to derive over and above the last
@@ -218,6 +232,7 @@ pub struct LoadParams {
218232
pub(crate) lookahead: u32,
219233
pub(crate) check_network: Option<Network>,
220234
pub(crate) check_genesis_hash: Option<BlockHash>,
235+
pub(crate) check_birthday: Option<BlockId>,
221236
pub(crate) check_descriptor: Option<Option<DescriptorToExtract>>,
222237
pub(crate) check_change_descriptor: Option<Option<DescriptorToExtract>>,
223238
pub(crate) extract_keys: bool,
@@ -235,6 +250,7 @@ impl LoadParams {
235250
lookahead: DEFAULT_LOOKAHEAD,
236251
check_network: None,
237252
check_genesis_hash: None,
253+
check_birthday: None,
238254
check_descriptor: None,
239255
check_change_descriptor: None,
240256
extract_keys: false,
@@ -282,6 +298,12 @@ impl LoadParams {
282298
self
283299
}
284300

301+
/// Checks that the given `birthday` matches the one loaded from persistence.
302+
pub fn check_birthday(mut self, birthday: BlockId) -> Self {
303+
self.check_birthday = Some(birthday);
304+
self
305+
}
306+
285307
/// Use a custom `lookahead` value.
286308
///
287309
/// The `lookahead` defines a number of script pubkeys to derive over and above the last

0 commit comments

Comments
 (0)