Skip to content

Commit d9f905a

Browse files
committed
db: track the next unused derivation index for change, too
1 parent 58a0e57 commit d9f905a

File tree

6 files changed

+139
-42
lines changed

6 files changed

+139
-42
lines changed

src/commands/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ impl DaemonControl {
200200
/// whether it was actually used.
201201
pub fn get_new_address(&self) -> GetAddressResult {
202202
let mut db_conn = self.db.connection();
203-
let index = db_conn.derivation_index();
203+
let index = db_conn.receive_index();
204204
// TODO: should we wrap around instead of failing?
205-
db_conn.increment_derivation_index(&self.secp);
205+
db_conn.increment_receive_index(&self.secp);
206206
let address = self
207207
.derived_desc(index)
208208
.address(self.config.bitcoin_config.network);

src/database/mod.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@ pub trait DatabaseConnection {
4545
/// Update our best chain seen.
4646
fn update_tip(&mut self, tip: &BlockChainTip);
4747

48-
fn derivation_index(&mut self) -> bip32::ChildNumber;
48+
fn receive_index(&mut self) -> bip32::ChildNumber;
4949

50-
fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
50+
fn change_index(&mut self) -> bip32::ChildNumber;
51+
52+
fn increment_receive_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
53+
54+
fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
5155

5256
/// Get the derivation index for this address, as well as whether this address is change.
5357
fn derivation_index_by_address(
@@ -114,12 +118,20 @@ impl DatabaseConnection for SqliteConn {
114118
self.update_tip(tip)
115119
}
116120

117-
fn derivation_index(&mut self) -> bip32::ChildNumber {
121+
fn receive_index(&mut self) -> bip32::ChildNumber {
118122
self.db_wallet().deposit_derivation_index
119123
}
120124

121-
fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
122-
self.increment_derivation_index(secp)
125+
fn change_index(&mut self) -> bip32::ChildNumber {
126+
self.db_wallet().change_derivation_index
127+
}
128+
129+
fn increment_receive_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
130+
self.increment_deposit_index(secp)
131+
}
132+
133+
fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
134+
self.increment_change_index(secp)
123135
}
124136

125137
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {

src/database/sqlite/mod.rs

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
database::{
1515
sqlite::{
1616
schema::{DbAddress, DbCoin, DbSpendTransaction, DbTip, DbWallet},
17-
utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT},
17+
utils::{create_fresh_db, db_exec, db_query, db_tx_query, populate_address_mapping},
1818
},
1919
Coin,
2020
},
@@ -210,10 +210,7 @@ impl SqliteConn {
210210
.expect("Database must be available")
211211
}
212212

213-
pub fn increment_derivation_index(
214-
&mut self,
215-
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
216-
) {
213+
pub fn increment_deposit_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
217214
let network = self.db_tip().network;
218215

219216
db_exec(&mut self.conn, |db_tx| {
@@ -235,24 +232,41 @@ impl SqliteConn {
235232
rusqlite::params![next_index],
236233
)?;
237234

238-
// Update the address to derivation index mapping.
239-
// TODO: have this as a helper in descriptors.rs
240-
let next_la_index = next_index + LOOK_AHEAD_LIMIT - 1;
241-
let next_receive_address = db_wallet
242-
.main_descriptor
243-
.receive_descriptor()
244-
.derive(next_la_index.into(), secp)
245-
.address(network);
246-
let next_change_address = db_wallet
247-
.main_descriptor
248-
.change_descriptor()
249-
.derive(next_la_index.into(), secp)
250-
.address(network);
235+
if next_index > db_wallet.change_derivation_index.into() {
236+
populate_address_mapping(db_tx, &db_wallet, next_index, network, secp)?;
237+
}
238+
239+
Ok(())
240+
})
241+
.expect("Database must be available")
242+
}
243+
244+
pub fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
245+
let network = self.db_tip().network;
246+
247+
db_exec(&mut self.conn, |db_tx| {
248+
let db_wallet: DbWallet =
249+
db_tx_query(db_tx, "SELECT * FROM wallets", rusqlite::params![], |row| {
250+
row.try_into()
251+
})
252+
.expect("Db must not fail")
253+
.pop()
254+
.expect("There is always a row in the wallet table");
255+
let next_index: u32 = db_wallet
256+
.change_derivation_index
257+
.increment()
258+
.expect("Must not get in hardened territory")
259+
.into();
260+
// NOTE: should be updated if we ever have multi-wallet support
251261
db_tx.execute(
252-
"INSERT INTO addresses (receive_address, change_address, derivation_index) VALUES (?1, ?2, ?3)",
253-
rusqlite::params![next_receive_address.to_string(), next_change_address.to_string(), next_la_index],
262+
"UPDATE wallets SET change_derivation_index = (?1)",
263+
rusqlite::params![next_index],
254264
)?;
255265

266+
if next_index > db_wallet.deposit_derivation_index.into() {
267+
populate_address_mapping(db_tx, &db_wallet, next_index, network, secp)?;
268+
}
269+
256270
Ok(())
257271
})
258272
.expect("Database must be available")
@@ -756,18 +770,42 @@ mod tests {
756770
assert!(conn.db_address(&addr).is_none());
757771

758772
// But if we increment the deposit derivation index, the 200th one will be there.
759-
conn.increment_derivation_index(&secp);
773+
conn.increment_deposit_index(&secp);
760774
let db_addr = conn.db_address(&addr).unwrap();
761775
assert_eq!(db_addr.derivation_index, 200.into());
762776

763-
// Same for the change descriptor.
777+
// It will also be there for the change descriptor.
764778
let addr = options
765779
.main_descriptor
766780
.change_descriptor()
767781
.derive(200.into(), &secp)
768782
.address(options.bitcoind_network);
769783
let db_addr = conn.db_address(&addr).unwrap();
770784
assert_eq!(db_addr.derivation_index, 200.into());
785+
786+
// But not for the 201th.
787+
let addr = options
788+
.main_descriptor
789+
.change_descriptor()
790+
.derive(201.into(), &secp)
791+
.address(options.bitcoind_network);
792+
assert!(conn.db_address(&addr).is_none());
793+
794+
// If we increment the *change* derivation index to 1, it will still not be there.
795+
conn.increment_change_index(&secp);
796+
assert!(conn.db_address(&addr).is_none());
797+
798+
// But doing it once again it will be there for both change and receive.
799+
conn.increment_change_index(&secp);
800+
let db_addr = conn.db_address(&addr).unwrap();
801+
assert_eq!(db_addr.derivation_index, 201.into());
802+
let addr = options
803+
.main_descriptor
804+
.receive_descriptor()
805+
.derive(201.into(), &secp)
806+
.address(options.bitcoind_network);
807+
let db_addr = conn.db_address(&addr).unwrap();
808+
assert_eq!(db_addr.derivation_index, 201.into());
771809
}
772810

773811
fs::remove_dir_all(&tmp_dir).unwrap();

src/database/sqlite/schema.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ CREATE TABLE wallets (
2727
id INTEGER PRIMARY KEY NOT NULL,
2828
timestamp INTEGER NOT NULL,
2929
main_descriptor TEXT NOT NULL,
30-
deposit_derivation_index INTEGER NOT NULL
30+
deposit_derivation_index INTEGER NOT NULL,
31+
change_derivation_index INTEGER NOT NULL
3132
);
3233
3334
/* Our (U)TxOs.
@@ -107,6 +108,7 @@ pub struct DbWallet {
107108
pub timestamp: u32,
108109
pub main_descriptor: MultipathDescriptor,
109110
pub deposit_derivation_index: bip32::ChildNumber,
111+
pub change_derivation_index: bip32::ChildNumber,
110112
}
111113

112114
impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
@@ -122,12 +124,15 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
122124

123125
let der_idx: u32 = row.get(3)?;
124126
let deposit_derivation_index = bip32::ChildNumber::from(der_idx);
127+
let der_idx: u32 = row.get(4)?;
128+
let change_derivation_index = bip32::ChildNumber::from(der_idx);
125129

126130
Ok(DbWallet {
127131
id,
128132
timestamp,
129133
main_descriptor,
130134
deposit_derivation_index,
135+
change_derivation_index,
131136
})
132137
}
133138
}

src/database/sqlite/utils.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
use crate::database::sqlite::{schema::SCHEMA, FreshDbOptions, SqliteDbError, DB_VERSION};
1+
use crate::database::sqlite::{
2+
schema::{DbWallet, SCHEMA},
3+
FreshDbOptions, SqliteDbError, DB_VERSION,
4+
};
25

36
use std::{convert::TryInto, fs, path, time};
47

5-
use miniscript::bitcoin::secp256k1;
8+
use miniscript::bitcoin::{self, secp256k1};
69

710
pub const LOOK_AHEAD_LIMIT: u32 = 200;
811

@@ -123,9 +126,9 @@ pub fn create_fresh_db(
123126
rusqlite::params![options.bitcoind_network.to_string()],
124127
)?;
125128
tx.execute(
126-
"INSERT INTO wallets (timestamp, main_descriptor, deposit_derivation_index) \
127-
VALUES (?1, ?2, ?3)",
128-
rusqlite::params![timestamp, options.main_descriptor.to_string(), 0,],
129+
"INSERT INTO wallets (timestamp, main_descriptor, deposit_derivation_index, change_derivation_index) \
130+
VALUES (?1, ?2, ?3, ?4)",
131+
rusqlite::params![timestamp, options.main_descriptor.to_string(), 0, 0],
129132
)?;
130133
tx.execute_batch(&query)?;
131134

@@ -134,3 +137,31 @@ pub fn create_fresh_db(
134137

135138
Ok(())
136139
}
140+
141+
/// Insert the deposit and change addresses for this index in the address->index mapping table
142+
pub fn populate_address_mapping(
143+
db_tx: &rusqlite::Transaction,
144+
db_wallet: &DbWallet,
145+
next_index: u32,
146+
network: bitcoin::Network,
147+
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
148+
) -> rusqlite::Result<()> {
149+
// Update the address to derivation index mapping.
150+
let next_la_index = next_index + LOOK_AHEAD_LIMIT - 1;
151+
let next_receive_address = db_wallet
152+
.main_descriptor
153+
.receive_descriptor()
154+
.derive(next_la_index.into(), secp)
155+
.address(network);
156+
let next_change_address = db_wallet
157+
.main_descriptor
158+
.change_descriptor()
159+
.derive(next_la_index.into(), secp)
160+
.address(network);
161+
db_tx.execute(
162+
"INSERT INTO addresses (receive_address, change_address, derivation_index) VALUES (?1, ?2, ?3)",
163+
rusqlite::params![next_receive_address.to_string(), next_change_address.to_string(), next_la_index],
164+
)?;
165+
166+
Ok(())
167+
}

src/testutils.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ impl BitcoinInterface for DummyBitcoind {
7777
}
7878

7979
pub struct DummyDb {
80-
curr_index: bip32::ChildNumber,
80+
deposit_index: bip32::ChildNumber,
81+
change_index: bip32::ChildNumber,
8182
curr_tip: Option<BlockChainTip>,
8283
coins: HashMap<bitcoin::OutPoint, Coin>,
8384
spend_txs: HashMap<bitcoin::Txid, Psbt>,
@@ -86,7 +87,8 @@ pub struct DummyDb {
8687
impl DummyDb {
8788
pub fn new() -> DummyDb {
8889
DummyDb {
89-
curr_index: 0.into(),
90+
deposit_index: 0.into(),
91+
change_index: 0.into(),
9092
curr_tip: None,
9193
coins: HashMap::new(),
9294
spend_txs: HashMap::new(),
@@ -123,13 +125,22 @@ impl DatabaseConnection for DummyDbConn {
123125
self.db.write().unwrap().curr_tip = Some(*tip);
124126
}
125127

126-
fn derivation_index(&mut self) -> bip32::ChildNumber {
127-
self.db.read().unwrap().curr_index
128+
fn receive_index(&mut self) -> bip32::ChildNumber {
129+
self.db.read().unwrap().deposit_index
128130
}
129131

130-
fn increment_derivation_index(&mut self, _: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
131-
let next_index = self.db.write().unwrap().curr_index.increment().unwrap();
132-
self.db.write().unwrap().curr_index = next_index;
132+
fn change_index(&mut self) -> bip32::ChildNumber {
133+
self.db.read().unwrap().deposit_index
134+
}
135+
136+
fn increment_receive_index(&mut self, _: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
137+
let next_index = self.db.write().unwrap().deposit_index.increment().unwrap();
138+
self.db.write().unwrap().deposit_index = next_index;
139+
}
140+
141+
fn increment_change_index(&mut self, _: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
142+
let next_index = self.db.write().unwrap().change_index.increment().unwrap();
143+
self.db.write().unwrap().change_index = next_index;
133144
}
134145

135146
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {

0 commit comments

Comments
 (0)