From 2609061a69c99e5b5f05b2a105751e5509055d54 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Sat, 30 Jul 2022 15:18:00 +0200 Subject: [PATCH] commands: implement getnewaddress --- src/commands/mod.rs | 23 ++++++++++++++++++++++- src/database/mod.rs | 14 ++++++++++++++ src/database/sqlite/mod.rs | 20 +++++++++++++++++++- src/lib.rs | 31 ++++++++++++++++++++++++------- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 584590d6..8069e7bb 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,7 +4,11 @@ use crate::{DaemonControl, VERSION}; -use miniscript::{bitcoin, descriptor}; +use miniscript::{ + bitcoin, + descriptor::{self, DescriptorTrait}, + TranslatePk2, +}; impl DaemonControl { /// Get information about the current state of the daemon @@ -19,6 +23,23 @@ impl DaemonControl { }, } } + + /// Get a new deposit address. This will always generate a new deposit address, regardless of + /// whether it was actually used. + pub fn get_new_address(&self) -> bitcoin::Address { + 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")); + self.config + .main_descriptor + // TODO: have a descriptor newtype along with a derived descriptor one. + .derive(index.into()) + .translate_pk2(|xpk| xpk.derive_public_key(&self.secp)) + .expect("All pubkeys were derived, no wildcard.") + .address(self.config.bitcoind_config.network) + .expect("It's a wsh() descriptor") + } } #[derive(Debug, Clone)] diff --git a/src/database/mod.rs b/src/database/mod.rs index 80312209..dac70224 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -8,6 +8,8 @@ use crate::{ database::sqlite::{schema::DbTip, SqliteConn, SqliteDb}, }; +use miniscript::bitcoin::util::bip32; + pub trait DatabaseInterface: Send { fn connection(&self) -> Box; } @@ -24,6 +26,10 @@ pub trait DatabaseConnection { /// Update our best chain seen. fn update_tip(&mut self, tip: &BlockChainTip); + + fn derivation_index(&mut self) -> bip32::ChildNumber; + + fn update_derivation_index(&mut self, index: bip32::ChildNumber); } impl DatabaseConnection for SqliteConn { @@ -41,4 +47,12 @@ impl DatabaseConnection for SqliteConn { fn update_tip(&mut self, tip: &BlockChainTip) { self.update_tip(&tip) } + + fn derivation_index(&mut self) -> bip32::ChildNumber { + self.db_wallet().deposit_derivation_index + } + + fn update_derivation_index(&mut self, index: bip32::ChildNumber) { + self.update_derivation_index(index) + } } diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index c3bad332..1bc73aa2 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -19,7 +19,10 @@ use crate::{ use std::{convert::TryInto, fmt, io, path}; -use miniscript::{bitcoin, Descriptor, DescriptorPublicKey}; +use miniscript::{ + bitcoin::{self, util::bip32}, + Descriptor, DescriptorPublicKey, +}; const DB_VERSION: i64 = 0; @@ -196,6 +199,21 @@ 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(); + db_exec(&mut self.conn, |db_tx| { + // NOTE: should be updated if we ever have multi-wallet support + db_tx + .execute( + "UPDATE wallets SET deposit_derivation_index = (?1)", + rusqlite::params![new_index], + ) + .map(|_| ()) + }) + .expect("Database must be available") + } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 526cc377..ba3da80a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,8 @@ use crate::{ use std::{error, fmt, fs, io, path, sync}; +use miniscript::bitcoin::secp256k1; + #[cfg(not(test))] use std::{panic, process}; // A panic in any thread should stop the main thread, and print the panic. @@ -146,6 +148,7 @@ pub struct DaemonControl { config: Config, bitcoin: Box, db: Box, + secp: secp256k1::Secp256k1, } impl DaemonControl { @@ -154,10 +157,12 @@ impl DaemonControl { bitcoin: Box, db: Box, ) -> DaemonControl { + let secp = secp256k1::Secp256k1::verification_only(); DaemonControl { config, bitcoin, db, + secp, } } } @@ -246,11 +251,7 @@ impl DaemonHandle { ); // Finally, set up the API. - let control = DaemonControl { - config, - bitcoin: Box::from(bitcoind), - db: Box::from(sqlite), - }; + let control = DaemonControl::new(config, Box::from(bitcoind), Box::from(sqlite)); Ok(Self { control, @@ -463,7 +464,7 @@ mod tests { }; // Create a dummy config with this bitcoind - let desc_str = "wsh(andor(pk(03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a),older(10000),pk(0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce)))#39x77spy"; + let desc_str = "wsh(andor(pk(xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/*),older(10000),pk(xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/*)))#tk6wzexy"; let desc = Descriptor::::from_str(desc_str).unwrap(); let config = Config { bitcoind_config, @@ -479,7 +480,21 @@ mod tests { let config = config.clone(); move || { let handle = DaemonHandle::start(config).unwrap(); + // TODO: avoid scope creep. We should move the bitcoind-specific checks to the + // bitcoind module, test the startup with a mocked bitcoind interface, and not test + // commands here but in the commands module. + let addr = handle.control.get_new_address(); + let addr2 = handle.control.get_new_address(); + assert_eq!( + addr, + bitcoin::Address::from_str( + "bc1qdu9dama0pwc6fd9lj4sqzq4f728y5q2ucqyj55mfzfvuxr268zks7yajm3" + ) + .unwrap() + ); + assert_ne!(addr, addr2); handle.shutdown(); + addr } }); complete_sanity_check(&server); @@ -490,11 +505,13 @@ mod tests { complete_wallet_check(&server, &wo_path); complete_desc_check(&server, desc_str); complete_sync_check(&server); - daemon_thread.join().unwrap(); + let addr = daemon_thread.join().unwrap(); // The datadir is created now, so if we restart it it won't create the wo wallet. let daemon_thread = thread::spawn(move || { let handle = DaemonHandle::start(config).unwrap(); + // TODO: avoid scope creep. See above comment. + assert_ne!(handle.control.get_new_address(), addr); handle.shutdown(); }); complete_sanity_check(&server);