diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f4c56fc8..1ecef7be 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -31,7 +31,7 @@ impl DaemonControl { let mut db_conn = self.db.connection(); let index = db_conn.derivation_index(); // TODO: handle should we wrap around instead of failing? - db_conn.update_derivation_index(index.increment().expect("TODO: handle wraparound")); + db_conn.increment_derivation_index(&self.secp); let address = self .config .main_descriptor diff --git a/src/database/mod.rs b/src/database/mod.rs index f395c364..6fd27ee4 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -13,7 +13,7 @@ use crate::{ use std::{collections::HashMap, sync}; -use miniscript::bitcoin::{self, util::bip32}; +use miniscript::bitcoin::{self, secp256k1, util::bip32}; pub trait DatabaseInterface: Send { fn connection(&self) -> Box; @@ -44,7 +44,7 @@ pub trait DatabaseConnection { fn derivation_index(&mut self) -> bip32::ChildNumber; - fn update_derivation_index(&mut self, index: bip32::ChildNumber); + fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1); /// Get all UTxOs. fn unspent_coins(&mut self) -> HashMap; @@ -83,8 +83,8 @@ impl DatabaseConnection for SqliteConn { self.db_wallet().deposit_derivation_index } - fn update_derivation_index(&mut self, index: bip32::ChildNumber) { - self.update_derivation_index(index) + fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1) { + self.increment_derivation_index(secp) } fn unspent_coins(&mut self) -> HashMap { diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index 1e4a70fd..91315ba5 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -14,7 +14,7 @@ use crate::{ database::{ sqlite::{ schema::{DbAddress, DbCoin, DbTip, DbWallet}, - utils::{create_fresh_db, db_exec, db_query}, + utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT}, }, Coin, }, @@ -23,8 +23,8 @@ use crate::{ use std::{convert::TryInto, fmt, io, path}; use miniscript::{ - bitcoin::{self, secp256k1, util::bip32}, - Descriptor, DescriptorPublicKey, + bitcoin::{self, secp256k1}, + Descriptor, DescriptorPublicKey, DescriptorTrait, TranslatePk2, }; const DB_VERSION: i64 = 0; @@ -207,15 +207,47 @@ impl SqliteConn { .expect("Database must be available") } - /// Update the deposit derivation index. - pub fn update_derivation_index(&mut self, index: bip32::ChildNumber) { - let new_index: u32 = index.into(); + pub fn increment_derivation_index( + &mut self, + secp: &secp256k1::Secp256k1, + ) { + 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 + .deposit_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( + "UPDATE wallets SET deposit_derivation_index = (?1)", + 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_la_address = db_wallet + .main_descriptor + .derive(next_la_index) + .translate_pk2(|xpk| xpk.derive_public_key(secp)) + .expect("All pubkeys were derived, no wildcard.") + .address(network) + .expect("It's a wsh() descriptor"); db_tx .execute( - "UPDATE wallets SET deposit_derivation_index = (?1)", - rusqlite::params![new_index], + "INSERT INTO addresses (address, derivation_index) VALUES (?1, ?2)", + rusqlite::params![next_la_address.to_string(), next_la_index], ) .map(|_| ()) }) @@ -311,7 +343,7 @@ mod tests { use crate::testutils::*; use std::{collections::HashSet, fs, path, str::FromStr}; - use bitcoin::hashes::Hash; + use bitcoin::{hashes::Hash, util::bip32}; use miniscript::{DescriptorTrait, TranslatePk2}; fn dummy_options() -> FreshDbOptions { @@ -518,6 +550,11 @@ mod tests { .address(options.bitcoind_network) .expect("Always a P2WSH address"); 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); + let db_addr = conn.db_address(&addr).unwrap(); + assert_eq!(db_addr.derivation_index, 200.into()); } fs::remove_dir_all(&tmp_dir).unwrap(); diff --git a/src/database/sqlite/utils.rs b/src/database/sqlite/utils.rs index c42bdb76..e744ce4b 100644 --- a/src/database/sqlite/utils.rs +++ b/src/database/sqlite/utils.rs @@ -4,7 +4,7 @@ use std::{convert::TryInto, fs, path, time}; use miniscript::{bitcoin::secp256k1, DescriptorTrait, TranslatePk2}; -const LOOK_AHEAD_LIMIT: u32 = 200; +pub const LOOK_AHEAD_LIMIT: u32 = 200; /// Perform a set of modifications to the database inside a single transaction pub fn db_exec(conn: &mut rusqlite::Connection, modifications: F) -> Result<(), rusqlite::Error> @@ -16,6 +16,27 @@ where tx.commit() } +/// Internal helper for queries boilerplate +pub fn db_tx_query( + tx: &rusqlite::Transaction, + stmt_str: &str, + params: P, + f: F, +) -> Result, rusqlite::Error> +where + P: IntoIterator + rusqlite::Params, + P::Item: rusqlite::ToSql, + F: FnMut(&rusqlite::Row<'_>) -> rusqlite::Result, +{ + // rustc says 'borrowed value does not live long enough' + let x = tx + .prepare(stmt_str)? + .query_map(params, f)? + .collect::>>(); + + x +} + /// Internal helper for queries boilerplate pub fn db_query( conn: &mut rusqlite::Connection, diff --git a/src/testutils.rs b/src/testutils.rs index 8df893f4..989cafc8 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -8,7 +8,7 @@ use crate::{ use std::{collections::HashMap, env, fs, io, path, process, str::FromStr, sync, thread, time}; use miniscript::{ - bitcoin::{self, util::bip32}, + bitcoin::{self, secp256k1, util::bip32}, descriptor, }; @@ -97,8 +97,9 @@ impl DatabaseConnection for DummyDbConn { self.db.read().unwrap().curr_index } - fn update_derivation_index(&mut self, index: bip32::ChildNumber) { - self.db.write().unwrap().curr_index = index; + fn increment_derivation_index(&mut self, _: &secp256k1::Secp256k1) { + let next_index = self.db.write().unwrap().curr_index.increment().unwrap(); + self.db.write().unwrap().curr_index = next_index; } fn unspent_coins(&mut self) -> HashMap {