db: track the next unused derivation index for change, too

This commit is contained in:
Antoine Poinsot 2022-10-24 12:01:06 +02:00
parent 58a0e57c59
commit d9f905a19a
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
6 changed files with 139 additions and 42 deletions

View File

@ -200,9 +200,9 @@ impl DaemonControl {
/// whether it was actually used.
pub fn get_new_address(&self) -> GetAddressResult {
let mut db_conn = self.db.connection();
let index = db_conn.derivation_index();
let index = db_conn.receive_index();
// TODO: should we wrap around instead of failing?
db_conn.increment_derivation_index(&self.secp);
db_conn.increment_receive_index(&self.secp);
let address = self
.derived_desc(index)
.address(self.config.bitcoin_config.network);

View File

@ -45,9 +45,13 @@ pub trait DatabaseConnection {
/// Update our best chain seen.
fn update_tip(&mut self, tip: &BlockChainTip);
fn derivation_index(&mut self) -> bip32::ChildNumber;
fn receive_index(&mut self) -> bip32::ChildNumber;
fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
fn change_index(&mut self) -> bip32::ChildNumber;
fn increment_receive_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
/// Get the derivation index for this address, as well as whether this address is change.
fn derivation_index_by_address(
@ -114,12 +118,20 @@ impl DatabaseConnection for SqliteConn {
self.update_tip(tip)
}
fn derivation_index(&mut self) -> bip32::ChildNumber {
fn receive_index(&mut self) -> bip32::ChildNumber {
self.db_wallet().deposit_derivation_index
}
fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
self.increment_derivation_index(secp)
fn change_index(&mut self) -> bip32::ChildNumber {
self.db_wallet().change_derivation_index
}
fn increment_receive_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
self.increment_deposit_index(secp)
}
fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
self.increment_change_index(secp)
}
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {

View File

@ -14,7 +14,7 @@ use crate::{
database::{
sqlite::{
schema::{DbAddress, DbCoin, DbSpendTransaction, DbTip, DbWallet},
utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT},
utils::{create_fresh_db, db_exec, db_query, db_tx_query, populate_address_mapping},
},
Coin,
},
@ -210,10 +210,7 @@ impl SqliteConn {
.expect("Database must be available")
}
pub fn increment_derivation_index(
&mut self,
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
) {
pub fn increment_deposit_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
let network = self.db_tip().network;
db_exec(&mut self.conn, |db_tx| {
@ -235,24 +232,41 @@ impl SqliteConn {
rusqlite::params![next_index],
)?;
// Update the address to derivation index mapping.
// TODO: have this as a helper in descriptors.rs
let next_la_index = next_index + LOOK_AHEAD_LIMIT - 1;
let next_receive_address = db_wallet
.main_descriptor
.receive_descriptor()
.derive(next_la_index.into(), secp)
.address(network);
let next_change_address = db_wallet
.main_descriptor
.change_descriptor()
.derive(next_la_index.into(), secp)
.address(network);
if next_index > db_wallet.change_derivation_index.into() {
populate_address_mapping(db_tx, &db_wallet, next_index, network, secp)?;
}
Ok(())
})
.expect("Database must be available")
}
pub fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
let network = self.db_tip().network;
db_exec(&mut self.conn, |db_tx| {
let db_wallet: DbWallet =
db_tx_query(db_tx, "SELECT * FROM wallets", rusqlite::params![], |row| {
row.try_into()
})
.expect("Db must not fail")
.pop()
.expect("There is always a row in the wallet table");
let next_index: u32 = db_wallet
.change_derivation_index
.increment()
.expect("Must not get in hardened territory")
.into();
// NOTE: should be updated if we ever have multi-wallet support
db_tx.execute(
"INSERT INTO addresses (receive_address, change_address, derivation_index) VALUES (?1, ?2, ?3)",
rusqlite::params![next_receive_address.to_string(), next_change_address.to_string(), next_la_index],
"UPDATE wallets SET change_derivation_index = (?1)",
rusqlite::params![next_index],
)?;
if next_index > db_wallet.deposit_derivation_index.into() {
populate_address_mapping(db_tx, &db_wallet, next_index, network, secp)?;
}
Ok(())
})
.expect("Database must be available")
@ -756,11 +770,11 @@ mod tests {
assert!(conn.db_address(&addr).is_none());
// But if we increment the deposit derivation index, the 200th one will be there.
conn.increment_derivation_index(&secp);
conn.increment_deposit_index(&secp);
let db_addr = conn.db_address(&addr).unwrap();
assert_eq!(db_addr.derivation_index, 200.into());
// Same for the change descriptor.
// It will also be there for the change descriptor.
let addr = options
.main_descriptor
.change_descriptor()
@ -768,6 +782,30 @@ mod tests {
.address(options.bitcoind_network);
let db_addr = conn.db_address(&addr).unwrap();
assert_eq!(db_addr.derivation_index, 200.into());
// But not for the 201th.
let addr = options
.main_descriptor
.change_descriptor()
.derive(201.into(), &secp)
.address(options.bitcoind_network);
assert!(conn.db_address(&addr).is_none());
// If we increment the *change* derivation index to 1, it will still not be there.
conn.increment_change_index(&secp);
assert!(conn.db_address(&addr).is_none());
// But doing it once again it will be there for both change and receive.
conn.increment_change_index(&secp);
let db_addr = conn.db_address(&addr).unwrap();
assert_eq!(db_addr.derivation_index, 201.into());
let addr = options
.main_descriptor
.receive_descriptor()
.derive(201.into(), &secp)
.address(options.bitcoind_network);
let db_addr = conn.db_address(&addr).unwrap();
assert_eq!(db_addr.derivation_index, 201.into());
}
fs::remove_dir_all(&tmp_dir).unwrap();

View File

@ -27,7 +27,8 @@ CREATE TABLE wallets (
id INTEGER PRIMARY KEY NOT NULL,
timestamp INTEGER NOT NULL,
main_descriptor TEXT NOT NULL,
deposit_derivation_index INTEGER NOT NULL
deposit_derivation_index INTEGER NOT NULL,
change_derivation_index INTEGER NOT NULL
);
/* Our (U)TxOs.
@ -107,6 +108,7 @@ pub struct DbWallet {
pub timestamp: u32,
pub main_descriptor: MultipathDescriptor,
pub deposit_derivation_index: bip32::ChildNumber,
pub change_derivation_index: bip32::ChildNumber,
}
impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
@ -122,12 +124,15 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
let der_idx: u32 = row.get(3)?;
let deposit_derivation_index = bip32::ChildNumber::from(der_idx);
let der_idx: u32 = row.get(4)?;
let change_derivation_index = bip32::ChildNumber::from(der_idx);
Ok(DbWallet {
id,
timestamp,
main_descriptor,
deposit_derivation_index,
change_derivation_index,
})
}
}

View File

@ -1,8 +1,11 @@
use crate::database::sqlite::{schema::SCHEMA, FreshDbOptions, SqliteDbError, DB_VERSION};
use crate::database::sqlite::{
schema::{DbWallet, SCHEMA},
FreshDbOptions, SqliteDbError, DB_VERSION,
};
use std::{convert::TryInto, fs, path, time};
use miniscript::bitcoin::secp256k1;
use miniscript::bitcoin::{self, secp256k1};
pub const LOOK_AHEAD_LIMIT: u32 = 200;
@ -123,9 +126,9 @@ pub fn create_fresh_db(
rusqlite::params![options.bitcoind_network.to_string()],
)?;
tx.execute(
"INSERT INTO wallets (timestamp, main_descriptor, deposit_derivation_index) \
VALUES (?1, ?2, ?3)",
rusqlite::params![timestamp, options.main_descriptor.to_string(), 0,],
"INSERT INTO wallets (timestamp, main_descriptor, deposit_derivation_index, change_derivation_index) \
VALUES (?1, ?2, ?3, ?4)",
rusqlite::params![timestamp, options.main_descriptor.to_string(), 0, 0],
)?;
tx.execute_batch(&query)?;
@ -134,3 +137,31 @@ pub fn create_fresh_db(
Ok(())
}
/// Insert the deposit and change addresses for this index in the address->index mapping table
pub fn populate_address_mapping(
db_tx: &rusqlite::Transaction,
db_wallet: &DbWallet,
next_index: u32,
network: bitcoin::Network,
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
) -> rusqlite::Result<()> {
// Update the address to derivation index mapping.
let next_la_index = next_index + LOOK_AHEAD_LIMIT - 1;
let next_receive_address = db_wallet
.main_descriptor
.receive_descriptor()
.derive(next_la_index.into(), secp)
.address(network);
let next_change_address = db_wallet
.main_descriptor
.change_descriptor()
.derive(next_la_index.into(), secp)
.address(network);
db_tx.execute(
"INSERT INTO addresses (receive_address, change_address, derivation_index) VALUES (?1, ?2, ?3)",
rusqlite::params![next_receive_address.to_string(), next_change_address.to_string(), next_la_index],
)?;
Ok(())
}

View File

@ -77,7 +77,8 @@ impl BitcoinInterface for DummyBitcoind {
}
pub struct DummyDb {
curr_index: bip32::ChildNumber,
deposit_index: bip32::ChildNumber,
change_index: bip32::ChildNumber,
curr_tip: Option<BlockChainTip>,
coins: HashMap<bitcoin::OutPoint, Coin>,
spend_txs: HashMap<bitcoin::Txid, Psbt>,
@ -86,7 +87,8 @@ pub struct DummyDb {
impl DummyDb {
pub fn new() -> DummyDb {
DummyDb {
curr_index: 0.into(),
deposit_index: 0.into(),
change_index: 0.into(),
curr_tip: None,
coins: HashMap::new(),
spend_txs: HashMap::new(),
@ -123,13 +125,22 @@ impl DatabaseConnection for DummyDbConn {
self.db.write().unwrap().curr_tip = Some(*tip);
}
fn derivation_index(&mut self) -> bip32::ChildNumber {
self.db.read().unwrap().curr_index
fn receive_index(&mut self) -> bip32::ChildNumber {
self.db.read().unwrap().deposit_index
}
fn increment_derivation_index(&mut self, _: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
let next_index = self.db.write().unwrap().curr_index.increment().unwrap();
self.db.write().unwrap().curr_index = next_index;
fn change_index(&mut self) -> bip32::ChildNumber {
self.db.read().unwrap().deposit_index
}
fn increment_receive_index(&mut self, _: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
let next_index = self.db.write().unwrap().deposit_index.increment().unwrap();
self.db.write().unwrap().deposit_index = next_index;
}
fn increment_change_index(&mut self, _: &secp256k1::Secp256k1<secp256k1::VerifyOnly>) {
let next_index = self.db.write().unwrap().change_index.increment().unwrap();
self.db.write().unwrap().change_index = next_index;
}
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {