From 12c0b516cb6dd91ae3d8f9baea04cb1695449aa6 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 27 Jun 2024 21:08:08 +0200 Subject: [PATCH 1/8] wallet: rename Runtime to StoredWallet --- src/bin/bp.rs | 4 ++-- src/cli/args.rs | 13 +++++++------ src/cli/command.rs | 22 +++++++++++----------- src/lib.rs | 4 ++-- src/{runtime.rs => store.rs} | 24 ++++++++++++------------ 5 files changed, 34 insertions(+), 33 deletions(-) rename src/{runtime.rs => store.rs} (91%) diff --git a/src/bin/bp.rs b/src/bin/bp.rs index b0078f5..be106c9 100644 --- a/src/bin/bp.rs +++ b/src/bin/bp.rs @@ -27,7 +27,7 @@ extern crate serde_crate as serde; use std::process::ExitCode; use bpwallet::cli::{Args, BpCommand, Config, DescrStdOpts, Exec, LogLevel}; -use bpwallet::RuntimeError; +use bpwallet::WalletError; use clap::Parser; fn main() -> ExitCode { @@ -39,7 +39,7 @@ fn main() -> ExitCode { } } -fn run() -> Result<(), RuntimeError> { +fn run() -> Result<(), WalletError> { let mut args = Args::::parse(); args.process(); LogLevel::from_verbosity_flag_count(args.verbose).apply(); diff --git a/src/cli/args.rs b/src/cli/args.rs index 6fe23ff..ac5a1c9 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -29,7 +29,7 @@ use descriptors::Descriptor; use strict_encoding::Ident; use crate::cli::{Config, DescrStdOpts, DescriptorOpts, GeneralOpts, ResolverOpt, WalletOpts}; -use crate::{AnyIndexer, Runtime, RuntimeError}; +use crate::{AnyIndexer, StoredWallet, WalletError}; /// Command-line arguments #[derive(Parser)] @@ -89,15 +89,16 @@ impl Args { conf_path } - pub fn bp_runtime(&self, conf: &Config) -> Result, RuntimeError> + pub fn bp_wallet(&self, conf: &Config) -> Result, WalletError> where for<'de> D: From + serde::Serialize + serde::Deserialize<'de> { eprint!("Loading descriptor"); - let mut runtime: Runtime = if let Some(d) = self.wallet.descriptor_opts.descriptor() { + let mut runtime: StoredWallet = if let Some(d) = self.wallet.descriptor_opts.descriptor() + { eprint!(" from command-line argument ... "); - Runtime::new_standard(d.into(), self.general.network) + StoredWallet::new_standard(d.into(), self.general.network) } else if let Some(wallet_path) = self.wallet.wallet_path.clone() { eprint!(" from specified wallet directory ... "); - Runtime::load_standard(wallet_path)? + StoredWallet::load_standard(wallet_path)? } else { let wallet_name = self .wallet @@ -106,7 +107,7 @@ impl Args { .map(Ident::to_string) .unwrap_or(conf.default_wallet.clone()); eprint!(" from wallet {wallet_name} ... "); - Runtime::load_standard(self.general.wallet_dir(wallet_name))? + StoredWallet::load_standard(self.general.wallet_dir(wallet_name))? }; let mut sync = self.sync; if runtime.warnings().is_empty() { diff --git a/src/cli/command.rs b/src/cli/command.rs index 54cb34e..859f33b 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -31,7 +31,7 @@ use psbt::{Payment, PsbtConstructor, PsbtVer}; use strict_encoding::Ident; use crate::cli::{Args, Config, DescriptorOpts, Exec}; -use crate::{coinselect, OpType, RuntimeError, StoreError, WalletAddr, WalletUtxo}; +use crate::{coinselect, OpType, StoreError, WalletAddr, WalletError, WalletUtxo}; #[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] pub enum Command { @@ -132,7 +132,7 @@ pub enum BpCommand { } impl Exec for Args { - type Error = RuntimeError; + type Error = WalletError; const CONF_FILE_NAME: &'static str = "bp.toml"; fn exec(self, mut config: Config, name: &'static str) -> Result<(), Self::Error> { @@ -181,7 +181,7 @@ impl Exec for Args { eprintln!("Error: you must provide an argument specifying wallet descriptor"); exit(1); } - let mut runtime = self.bp_runtime::(&config)?; + let mut runtime = self.bp_wallet::(&config)?; let name = name.to_string(); print!("Saving the wallet as '{name}' ... "); let dir = self.general.wallet_dir(&name); @@ -199,7 +199,7 @@ impl Exec for Args { dry_run: no_shift, count: no, } => { - let mut runtime = self.bp_runtime::(&config)?; + let mut runtime = self.bp_wallet::(&config)?; let keychain = match (change, keychain) { (false, None) => runtime.default_keychain(), (true, None) => (*change as u8).into(), @@ -229,7 +229,7 @@ impl Exec for Args { } impl Exec for Args { - type Error = RuntimeError; + type Error = WalletError; const CONF_FILE_NAME: &'static str = "bp.toml"; fn exec(mut self, config: Config, name: &'static str) -> Result<(), Self::Error> { @@ -239,14 +239,14 @@ impl Exec for Args { addr: false, utxo: false, } => { - let runtime = self.bp_runtime::(&config)?; + let runtime = self.bp_wallet::(&config)?; println!("\nWallet total balance: {} ṩ", runtime.balance()); } BpCommand::Balance { addr: true, utxo: false, } => { - let runtime = self.bp_runtime::(&config)?; + let runtime = self.bp_wallet::(&config)?; println!("\nTerm.\t{:62}\t# used\tVol., ṩ\tBalance, ṩ", "Address"); for info in runtime.address_balance() { let WalletAddr { @@ -269,7 +269,7 @@ impl Exec for Args { addr: false, utxo: true, } => { - let runtime = self.bp_runtime::(&config)?; + let runtime = self.bp_wallet::(&config)?; println!("\nHeight\t{:>12}\t{:68}\tAddress", "Amount, ṩ", "Outpoint"); for row in runtime.coins() { println!( @@ -288,7 +288,7 @@ impl Exec for Args { addr: true, utxo: true, } => { - let runtime = self.bp_runtime::(&config)?; + let runtime = self.bp_wallet::(&config)?; println!("\nHeight\t{:>12}\t{:68}", "Amount, ṩ", "Outpoint"); for (derived_addr, utxos) in runtime.address_coins() { println!("{}\t{}", derived_addr.addr, derived_addr.terminal); @@ -305,7 +305,7 @@ impl Exec for Args { self.exec(config, name)?; } BpCommand::History { txid, details } => { - let runtime = self.bp_runtime::(&config)?; + let runtime = self.bp_wallet::(&config)?; println!( "\nHeight\t{:<1$}\t Amount, ṩ\tFee rate, ṩ/vbyte", "Txid", @@ -358,7 +358,7 @@ impl Exec for Args { fee, psbt: psbt_file, } => { - let mut runtime = self.bp_runtime::(&config)?; + let mut runtime = self.bp_wallet::(&config)?; // Do coin selection let total_amount = diff --git a/src/lib.rs b/src/lib.rs index 73d0c71..801ee9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ extern crate log; mod indexers; #[cfg(feature = "fs")] -mod runtime; +mod store; mod util; mod data; mod rows; @@ -55,6 +55,6 @@ pub use layer2::{ }; pub use rows::{CoinRow, Counterparty, OpType, TxRow}; #[cfg(feature = "fs")] -pub use runtime::{LoadError, Runtime, RuntimeError, StoreError}; +pub use store::{LoadError, StoreError, StoredWallet, WalletError}; pub use util::MayError; pub use wallet::{Wallet, WalletCache, WalletData, WalletDescr}; diff --git a/src/runtime.rs b/src/store.rs similarity index 91% rename from src/runtime.rs rename to src/store.rs index 2eaf46a..34615f9 100644 --- a/src/runtime.rs +++ b/src/store.rs @@ -36,7 +36,7 @@ use crate::{Indexer, Layer2, NoLayer2, Wallet}; #[derive(Debug, Display, Error, From)] #[non_exhaustive] #[display(inner)] -pub enum RuntimeError { +pub enum WalletError { #[from] Load(LoadError), @@ -108,25 +108,25 @@ pub enum StoreError { } #[derive(Getters, Debug)] -pub struct Runtime = StdDescr, K = XpubDerivable, L2: Layer2 = NoLayer2> { +pub struct StoredWallet = StdDescr, K = XpubDerivable, L2: Layer2 = NoLayer2> { path: Option, #[getter(as_mut)] wallet: Wallet, warnings: Vec, } -impl, L2: Layer2> Deref for Runtime { +impl, L2: Layer2> Deref for StoredWallet { type Target = Wallet; fn deref(&self) -> &Self::Target { &self.wallet } } -impl, L2: Layer2> DerefMut for Runtime { +impl, L2: Layer2> DerefMut for StoredWallet { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wallet } } -impl> Runtime { +impl> StoredWallet { pub fn new_standard(descr: D, network: Network) -> Self { - Runtime { + StoredWallet { path: None, wallet: Wallet::new_standard(descr, network), warnings: none!(), @@ -134,9 +134,9 @@ impl> Runtime { } } -impl, L2: Layer2> Runtime { +impl, L2: Layer2> StoredWallet { pub fn new_layer2(descr: D, l2_descr: L2::Descr, layer2: L2, network: Network) -> Self { - Runtime { + StoredWallet { path: None, wallet: Wallet::new_layer2(descr, l2_descr, layer2, network), warnings: none!(), @@ -163,12 +163,12 @@ impl, L2: Layer2> Runtime { pub fn reset_warnings(&mut self) { self.warnings.clear() } } -impl> Runtime +impl> StoredWallet where for<'de> D: serde::Serialize + serde::Deserialize<'de> { pub fn load_standard(path: PathBuf) -> Result { let (wallet, warnings) = Wallet::load(&path)?; - Ok(Runtime { + Ok(StoredWallet { path: Some(path.clone()), wallet, warnings, @@ -190,7 +190,7 @@ where for<'de> D: serde::Serialize + serde::Deserialize<'de> } } -impl, L2: Layer2> Runtime +impl, L2: Layer2> StoredWallet where for<'de> D: serde::Serialize + serde::Deserialize<'de>, for<'de> L2: serde::Serialize + serde::Deserialize<'de>, @@ -200,7 +200,7 @@ where { pub fn load_layer2(path: PathBuf) -> Result> { let (wallet, warnings) = Wallet::load(&path)?; - Ok(Runtime { + Ok(StoredWallet { path: Some(path.clone()), wallet, warnings, From e6a02cfb07531e4f733198656d367bfb109599af Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 2 Jul 2024 23:05:29 +0200 Subject: [PATCH 2/8] wallet: refactor wallet - remove stored wrapper, streamline APIs --- src/bin/bp.rs | 5 +- src/cli/args.rs | 129 ++++++++++++--------- src/cli/command.rs | 88 ++++++++++----- src/cli/mod.rs | 2 +- src/indexers/any.rs | 3 + src/indexers/mod.rs | 2 +- src/lib.rs | 6 +- src/store.rs | 248 ---------------------------------------- src/wallet.rs | 269 +++++++++++++++++++++++++++++++------------- 9 files changed, 338 insertions(+), 414 deletions(-) delete mode 100644 src/store.rs diff --git a/src/bin/bp.rs b/src/bin/bp.rs index be106c9..5e55e65 100644 --- a/src/bin/bp.rs +++ b/src/bin/bp.rs @@ -26,8 +26,7 @@ extern crate serde_crate as serde; use std::process::ExitCode; -use bpwallet::cli::{Args, BpCommand, Config, DescrStdOpts, Exec, LogLevel}; -use bpwallet::WalletError; +use bpwallet::cli::{Args, BpCommand, Config, DescrStdOpts, Exec, ExecError, LogLevel}; use clap::Parser; fn main() -> ExitCode { @@ -39,7 +38,7 @@ fn main() -> ExitCode { } } -fn run() -> Result<(), WalletError> { +fn run() -> Result<(), ExecError> { let mut args = Args::::parse(); args.process(); LogLevel::from_verbosity_flag_count(args.verbose).apply(); diff --git a/src/cli/args.rs b/src/cli/args.rs index ac5a1c9..947f7af 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -24,12 +24,16 @@ use std::fmt::Debug; use std::path::PathBuf; use std::process::exit; +use bpstd::XpubDerivable; use clap::Subcommand; use descriptors::Descriptor; use strict_encoding::Ident; -use crate::cli::{Config, DescrStdOpts, DescriptorOpts, GeneralOpts, ResolverOpt, WalletOpts}; -use crate::{AnyIndexer, StoredWallet, WalletError}; +use crate::cli::{ + Config, DescrStdOpts, DescriptorOpts, ExecError, GeneralOpts, ResolverOpt, WalletOpts, +}; +use crate::indexers::AnyIndexerError; +use crate::{AnyIndexer, MayError, Wallet}; /// Command-line arguments #[derive(Parser)] @@ -89,54 +93,15 @@ impl Args { conf_path } - pub fn bp_wallet(&self, conf: &Config) -> Result, WalletError> - where for<'de> D: From + serde::Serialize + serde::Deserialize<'de> { - eprint!("Loading descriptor"); - let mut runtime: StoredWallet = if let Some(d) = self.wallet.descriptor_opts.descriptor() - { - eprint!(" from command-line argument ... "); - StoredWallet::new_standard(d.into(), self.general.network) - } else if let Some(wallet_path) = self.wallet.wallet_path.clone() { - eprint!(" from specified wallet directory ... "); - StoredWallet::load_standard(wallet_path)? - } else { - let wallet_name = self - .wallet - .name - .as_ref() - .map(Ident::to_string) - .unwrap_or(conf.default_wallet.clone()); - eprint!(" from wallet {wallet_name} ... "); - StoredWallet::load_standard(self.general.wallet_dir(wallet_name))? - }; - let mut sync = self.sync; - if runtime.warnings().is_empty() { - eprintln!("success"); - } else { - eprintln!("complete with warnings:"); - for warning in runtime.warnings() { - eprintln!("- {warning}"); - } - sync = true; - runtime.reset_warnings(); - } - - if sync || self.wallet.descriptor_opts.is_some() { - eprint!("Syncing"); - let indexer = match (&self.resolver.esplora, &self.resolver.electrum) { - (None, Some(url)) => AnyIndexer::Electrum(Box::new(electrum::Client::new(url)?)), - (Some(url), None) => { - AnyIndexer::Esplora(Box::new(esplora::Builder::new(url).build_blocking()?)) - } - _ => { - eprintln!( - " - error: no blockchain indexer specified; use either --esplora or \ - --electrum argument" - ); - exit(1); - } - }; - if let Err(errors) = runtime.sync(&indexer) { + pub fn bp_wallet( + &self, + conf: &Config, + ) -> Result, ExecError> + where + for<'de> D: From + serde::Serialize + serde::Deserialize<'de>, + { + fn print_errors(errors: Option>) { + if let Some(errors) = errors { eprintln!(" partial, some requests has failed:"); for err in errors { eprintln!("- {err}"); @@ -144,9 +109,69 @@ impl Args { } else { eprintln!(" success"); } - runtime.try_store()?; } - Ok(runtime) + eprint!("Loading descriptor"); + let mut sync = self.sync || self.wallet.descriptor_opts.is_some(); + + let indexer = match (&self.resolver.esplora, &self.resolver.electrum) { + (None, Some(url)) => AnyIndexer::Electrum(Box::new(electrum::Client::new(url)?)), + (Some(url), None) => { + AnyIndexer::Esplora(Box::new(esplora::Builder::new(url).build_blocking()?)) + } + _ if sync => { + eprintln!( + " - error: no blockchain indexer specified; use either --esplora or \ + --electrum argument" + ); + exit(1); + } + _ => AnyIndexer::None, + }; + + let mut wallet: Wallet = + if let Some(d) = self.wallet.descriptor_opts.descriptor() { + eprintln!(" from command-line argument"); + eprint!("Syncing"); + let MayError { + ok: wallet, + err: errors, + } = Wallet::new_layer1(d.into(), self.general.network, indexer); + print_errors(errors); + wallet + } else { + let path = if let Some(wallet_path) = self.wallet.wallet_path.clone() { + eprint!(" from specified wallet directory ... "); + wallet_path + } else { + let wallet_name = self + .wallet + .name + .as_ref() + .map(Ident::to_string) + .unwrap_or(conf.default_wallet.clone()); + eprint!(" from wallet {wallet_name} ... "); + self.general.wallet_dir(wallet_name) + }; + let (wallet, warnings) = Wallet::load(&path, true, indexer, false)?; + if warnings.is_empty() { + eprintln!("success"); + } else { + eprintln!("complete with warnings:"); + for warning in warnings { + eprintln!("- {warning}"); + } + sync = true; + } + wallet + }; + + if sync { + eprint!("Syncing"); + let MayError { err, .. } = wallet.update(); + print_errors(err); + } + + Ok(wallet) } } diff --git a/src/cli/command.rs b/src/cli/command.rs index 859f33b..ac4429d 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -20,18 +20,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fs; +use std::convert::Infallible; use std::fs::File; use std::path::PathBuf; use std::process::exit; +use std::{error, fs}; use bpstd::psbt::{Beneficiary, TxParams}; use bpstd::{Derive, IdxBase, Keychain, NormalIndex, Sats}; -use psbt::{Payment, PsbtConstructor, PsbtVer}; +use psbt::{ConstructionError, Payment, PsbtConstructor, PsbtVer}; use strict_encoding::Ident; use crate::cli::{Args, Config, DescriptorOpts, Exec}; -use crate::{coinselect, OpType, StoreError, WalletAddr, WalletError, WalletUtxo}; +use crate::wallet::fs::{LoadError, StoreError}; +use crate::wallet::Save; +use crate::{coinselect, OpType, WalletAddr, WalletUtxo}; #[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] pub enum Command { @@ -131,8 +134,38 @@ pub enum BpCommand { }, } +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] +#[display(inner)] +pub enum ExecError { + #[from] + Load(LoadError), + + #[from] + Store(StoreError), + + #[from] + ConstructPsbt(ConstructionError), + + #[cfg(feature = "electrum")] + /// error querying electrum server. + /// + /// {0} + #[from] + #[display(doc_comments)] + Electrum(electrum::Error), + + #[cfg(feature = "esplora")] + /// error querying esplora server. + /// + /// {0} + #[from] + #[display(doc_comments)] + Esplora(esplora::Error), +} + impl Exec for Args { - type Error = WalletError; + type Error = ExecError; const CONF_FILE_NAME: &'static str = "bp.toml"; fn exec(self, mut config: Config, name: &'static str) -> Result<(), Self::Error> { @@ -181,12 +214,11 @@ impl Exec for Args { eprintln!("Error: you must provide an argument specifying wallet descriptor"); exit(1); } - let mut runtime = self.bp_wallet::(&config)?; - let name = name.to_string(); print!("Saving the wallet as '{name}' ... "); - let dir = self.general.wallet_dir(&name); - runtime.set_name(name); - if let Err(err) = runtime.store(&dir) { + let mut wallet = self.bp_wallet::(&config)?; + let name = name.to_string(); + wallet.set_name(name); + if let Err(err) = wallet.save() { println!("error: {err}"); } else { println!("success"); @@ -199,28 +231,27 @@ impl Exec for Args { dry_run: no_shift, count: no, } => { - let mut runtime = self.bp_wallet::(&config)?; + let mut wallet = self.bp_wallet::(&config)?; let keychain = match (change, keychain) { - (false, None) => runtime.default_keychain(), + (false, None) => wallet.default_keychain(), (true, None) => (*change as u8).into(), (false, Some(keychain)) => *keychain, _ => unreachable!(), }; - if !runtime.keychains().contains(&keychain) { + if !wallet.keychains().contains(&keychain) { eprintln!( "Error: the specified keychain {keychain} is not a part of the descriptor" ); exit(1); } let index = - index.unwrap_or_else(|| runtime.next_derivation_index(keychain, !*no_shift)); + index.unwrap_or_else(|| wallet.next_derivation_index(keychain, !*no_shift)); println!("\nTerm.\tAddress"); for derived_addr in - runtime.addresses(keychain).skip(index.index() as usize).take(*no as usize) + wallet.addresses(keychain).skip(index.index() as usize).take(*no as usize) { println!("{}\t{}", derived_addr.terminal, derived_addr.addr); } - runtime.try_store()?; } } @@ -229,7 +260,7 @@ impl Exec for Args { } impl Exec for Args { - type Error = WalletError; + type Error = ExecError; const CONF_FILE_NAME: &'static str = "bp.toml"; fn exec(mut self, config: Config, name: &'static str) -> Result<(), Self::Error> { @@ -246,9 +277,9 @@ impl Exec for Args { addr: true, utxo: false, } => { - let runtime = self.bp_wallet::(&config)?; + let wallet = self.bp_wallet::(&config)?; println!("\nTerm.\t{:62}\t# used\tVol., ṩ\tBalance, ṩ", "Address"); - for info in runtime.address_balance() { + for info in wallet.address_balance() { let WalletAddr { addr, terminal, @@ -269,9 +300,9 @@ impl Exec for Args { addr: false, utxo: true, } => { - let runtime = self.bp_wallet::(&config)?; + let wallet = self.bp_wallet::(&config)?; println!("\nHeight\t{:>12}\t{:68}\tAddress", "Amount, ṩ", "Outpoint"); - for row in runtime.coins() { + for row in wallet.coins() { println!( "{}\t{: >12}\t{:68}\t{}", row.height, row.amount, row.outpoint, row.address @@ -288,9 +319,9 @@ impl Exec for Args { addr: true, utxo: true, } => { - let runtime = self.bp_wallet::(&config)?; + let wallet = self.bp_wallet::(&config)?; println!("\nHeight\t{:>12}\t{:68}", "Amount, ṩ", "Outpoint"); - for (derived_addr, utxos) in runtime.address_coins() { + for (derived_addr, utxos) in wallet.address_coins() { println!("{}\t{}", derived_addr.addr, derived_addr.terminal); for row in utxos { println!("{}\t{: >12}\t{:68}", row.height, row.amount, row.outpoint); @@ -305,13 +336,13 @@ impl Exec for Args { self.exec(config, name)?; } BpCommand::History { txid, details } => { - let runtime = self.bp_wallet::(&config)?; + let wallet = self.bp_wallet::(&config)?; println!( "\nHeight\t{:<1$}\t Amount, ṩ\tFee rate, ṩ/vbyte", "Txid", if *txid { 64 } else { 18 } ); - let mut rows = runtime.history().collect::>(); + let mut rows = wallet.history().collect::>(); rows.sort_by_key(|row| row.height); for row in rows { println!( @@ -358,7 +389,7 @@ impl Exec for Args { fee, psbt: psbt_file, } => { - let mut runtime = self.bp_wallet::(&config)?; + let mut wallet = self.bp_wallet::(&config)?; // Do coin selection let total_amount = @@ -368,21 +399,20 @@ impl Exec for Args { }); let coins: Vec<_> = match total_amount { Ok(sats) if sats > Sats::ZERO => { - runtime.wallet().coinselect(sats + *fee, coinselect::all).collect() + wallet.coinselect(sats + *fee, coinselect::all).collect() } _ => { eprintln!( "Warning: you are not paying to anybody but just aggregating all your \ balances to a single UTXO", ); - runtime.wallet().all_utxos().map(WalletUtxo::into_outpoint).collect() + wallet.all_utxos().map(WalletUtxo::into_outpoint).collect() } }; // TODO: Support lock time and RBFs let params = TxParams::with(*fee); - let (psbt, _) = - runtime.wallet_mut().construct_psbt(coins, beneficiaries, params)?; + let (psbt, _) = wallet.construct_psbt(coins, beneficiaries, params)?; let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; eprintln!("{}", serde_yaml::to_string(&psbt).unwrap()); diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0731052..f11d110 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -27,7 +27,7 @@ mod config; mod command; pub use args::{Args, Exec}; -pub use command::{BpCommand, Command}; +pub use command::{BpCommand, Command, ExecError}; pub use config::Config; pub use loglevel::LogLevel; pub use opts::{ diff --git a/src/indexers/any.rs b/src/indexers/any.rs index bd69938..bb4ecbc 100644 --- a/src/indexers/any.rs +++ b/src/indexers/any.rs @@ -35,6 +35,7 @@ pub enum AnyIndexer { #[from] /// Esplora indexer Esplora(Box), + None, } #[allow(clippy::large_enum_variant)] @@ -73,6 +74,7 @@ impl Indexer for AnyIndexer { err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()), } } + _ => unreachable!(), } } @@ -98,6 +100,7 @@ impl Indexer for AnyIndexer { err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()), } } + _ => unreachable!(), } } } diff --git a/src/indexers/mod.rs b/src/indexers/mod.rs index bd9b81e..e9a5e3e 100644 --- a/src/indexers/mod.rs +++ b/src/indexers/mod.rs @@ -28,7 +28,7 @@ mod esplora; mod any; #[cfg(any(feature = "electrum", feature = "esplora"))] -pub use any::AnyIndexer; +pub use any::{AnyIndexer, AnyIndexerError}; use descriptors::Descriptor; use crate::{Layer2, MayError, WalletCache, WalletDescr}; diff --git a/src/lib.rs b/src/lib.rs index 801ee9c..c610fc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,8 +32,6 @@ extern crate clap; extern crate log; mod indexers; -#[cfg(feature = "fs")] -mod store; mod util; mod data; mod rows; @@ -54,7 +52,7 @@ pub use layer2::{ Layer2, Layer2Cache, Layer2Coin, Layer2Data, Layer2Descriptor, Layer2Tx, NoLayer2, }; pub use rows::{CoinRow, Counterparty, OpType, TxRow}; -#[cfg(feature = "fs")] -pub use store::{LoadError, StoreError, StoredWallet, WalletError}; pub use util::MayError; +#[cfg(feature = "fs")] +pub use wallet::FsConfig; pub use wallet::{Wallet, WalletCache, WalletData, WalletDescr}; diff --git a/src/store.rs b/src/store.rs deleted file mode 100644 index 34615f9..0000000 --- a/src/store.rs +++ /dev/null @@ -1,248 +0,0 @@ -// Modern, minimalistic & standard-compliant cold wallet library. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2020-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2020-2023 LNP/BP Standards Association. All rights reserved. -// Copyright (C) 2020-2023 Dr Maxim Orlovsky. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::convert::Infallible; -use std::ops::{Deref, DerefMut}; -use std::path::PathBuf; -use std::{error, io}; - -use amplify::IoError; -use bpstd::{Network, XpubDerivable}; -use descriptors::{Descriptor, StdDescr}; -use psbt::ConstructionError; - -use crate::wallet::fs::Warning; -use crate::{Indexer, Layer2, NoLayer2, Wallet}; - -#[derive(Debug, Display, Error, From)] -#[non_exhaustive] -#[display(inner)] -pub enum WalletError { - #[from] - Load(LoadError), - - #[from] - Store(StoreError), - - #[from] - ConstructPsbt(ConstructionError), - - #[cfg(feature = "electrum")] - /// error querying electrum server. - /// - /// {0} - #[from] - #[display(doc_comments)] - Electrum(electrum::Error), - - #[cfg(feature = "esplora")] - /// error querying esplora server. - /// - /// {0} - #[from] - #[display(doc_comments)] - Esplora(esplora::Error), -} - -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum LoadError { - /// I/O error loading wallet - {0} - #[from] - #[from(io::Error)] - Io(IoError), - - /// unable to parse TOML file - {0} - #[from] - Toml(toml::de::Error), - - #[display(inner)] - Layer2(L2), - - #[display(inner)] - #[from] - Custom(String), -} - -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum StoreError { - /// I/O error storing wallet - {0} - #[from] - #[from(io::Error)] - Io(IoError), - - /// unable to serialize wallet data as TOML file - {0} - #[from] - Toml(toml::ser::Error), - - /// unable to serialize wallet cache as YAML file - {0} - #[from] - Yaml(serde_yaml::Error), - - #[display(inner)] - Layer2(L2), - - #[display(inner)] - #[from] - Custom(String), -} - -#[derive(Getters, Debug)] -pub struct StoredWallet = StdDescr, K = XpubDerivable, L2: Layer2 = NoLayer2> { - path: Option, - #[getter(as_mut)] - wallet: Wallet, - warnings: Vec, -} - -impl, L2: Layer2> Deref for StoredWallet { - type Target = Wallet; - fn deref(&self) -> &Self::Target { &self.wallet } -} - -impl, L2: Layer2> DerefMut for StoredWallet { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wallet } -} - -impl> StoredWallet { - pub fn new_standard(descr: D, network: Network) -> Self { - StoredWallet { - path: None, - wallet: Wallet::new_standard(descr, network), - warnings: none!(), - } - } -} - -impl, L2: Layer2> StoredWallet { - pub fn new_layer2(descr: D, l2_descr: L2::Descr, layer2: L2, network: Network) -> Self { - StoredWallet { - path: None, - wallet: Wallet::new_layer2(descr, l2_descr, layer2, network), - warnings: none!(), - } - } - pub fn set_name(&mut self, name: String) { self.wallet.set_name(name) } - - pub fn sync(&mut self, indexer: &I) -> Result<(), Vec> { - self.wallet.update(indexer).into_result() - } - - #[inline] - pub fn attach(wallet: Wallet) -> Self { - Self { - path: None, - wallet, - warnings: none!(), - } - } - - #[inline] - pub fn detach(self) -> Wallet { self.wallet } - - pub fn reset_warnings(&mut self) { self.warnings.clear() } -} - -impl> StoredWallet -where for<'de> D: serde::Serialize + serde::Deserialize<'de> -{ - pub fn load_standard(path: PathBuf) -> Result { - let (wallet, warnings) = Wallet::load(&path)?; - Ok(StoredWallet { - path: Some(path.clone()), - wallet, - warnings, - }) - } - - pub fn load_standard_or_init( - data_dir: PathBuf, - network: Network, - init: impl FnOnce(LoadError) -> Result, - ) -> Result - where - LoadError: From, - { - Self::load_standard(data_dir).or_else(|err| { - let descriptor = init(err)?; - Ok(Self::new_standard(descriptor, network)) - }) - } -} - -impl, L2: Layer2> StoredWallet -where - for<'de> D: serde::Serialize + serde::Deserialize<'de>, - for<'de> L2: serde::Serialize + serde::Deserialize<'de>, - for<'de> L2::Descr: serde::Serialize + serde::Deserialize<'de>, - for<'de> L2::Data: serde::Serialize + serde::Deserialize<'de>, - for<'de> L2::Cache: serde::Serialize + serde::Deserialize<'de>, -{ - pub fn load_layer2(path: PathBuf) -> Result> { - let (wallet, warnings) = Wallet::load(&path)?; - Ok(StoredWallet { - path: Some(path.clone()), - wallet, - warnings, - }) - } - - pub fn load_layer2_or_init( - data_dir: PathBuf, - network: Network, - init: impl FnOnce(LoadError) -> Result, - init_l2: impl FnOnce() -> Result<(L2, L2::Descr), E2>, - ) -> Result> - where - LoadError: From, - LoadError: From, - { - Self::load_layer2(data_dir).or_else(|err| { - let descriptor = init(err)?; - let (layer2, l2_descr) = init_l2()?; - Ok(Self::new_layer2(descriptor, l2_descr, layer2, network)) - }) - } - - pub fn try_store(&self) -> Result> { - let Some(path) = &self.path else { - return Ok(false); - }; - - self.wallet.store(path)?; - - Ok(true) - } - - pub fn store_as(&mut self, path: PathBuf) -> Result<(), StoreError> { - self.path = None; - self.store_default_path(path) - } - - pub fn store_default_path(&mut self, path: PathBuf) -> Result<(), StoreError> { - self.path = Some(path); - let res = self.try_store()?; - debug_assert!(res); - Ok(()) - } -} diff --git a/src/wallet.rs b/src/wallet.rs index 2a948fc..f52c4d5 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -20,10 +20,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::cmp; use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::error::Error; use std::marker::PhantomData; use std::ops::{AddAssign, Deref, DerefMut}; +use std::path::PathBuf; +use std::{cmp, mem}; use bpstd::{ Address, AddressNetwork, DerivedAddr, Descriptor, Idx, IdxBase, Keychain, Network, NormalIndex, @@ -84,10 +86,10 @@ where D: Descriptor, L2: Layer2Descriptor, { - pub(crate) generator: D, + generator: D, #[getter(as_copy)] - pub(crate) network: Network, - pub(crate) layer2: L2, + network: Network, + layer2: L2, #[cfg_attr(feature = "serde", serde(skip))] _phantom: PhantomData, } @@ -245,25 +247,43 @@ impl WalletCache { } } +#[cfg(feature = "fs")] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct FsConfig { + pub path: PathBuf, + pub autosave: bool, +} + +pub trait Save { + type SaveErr: Error; + fn save(&self) -> Result; +} + #[derive(Clone, Eq, PartialEq, Debug)] -pub struct Wallet, L2: Layer2 = NoLayer2> { - pub(crate) descr: WalletDescr, - pub(crate) data: WalletData, - pub(crate) cache: WalletCache, - pub(crate) layer2: L2, +pub struct Wallet, I: Indexer, L2: Layer2 = NoLayer2> +where Self: Save +{ + descr: WalletDescr, + data: WalletData, + cache: WalletCache, + layer2: L2, + indexer: I, + #[cfg(feature = "fs")] + fs: Option, + dirty: bool, } -impl, L2: Layer2> Deref for Wallet { +impl, I: Indexer, L2: Layer2> Deref for Wallet +where Self: Save +{ type Target = WalletDescr; fn deref(&self) -> &Self::Target { &self.descr } } -impl, L2: Layer2> DerefMut for Wallet { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.descr } -} - -impl, L2: Layer2> PsbtConstructor for Wallet { +impl, I: Indexer, L2: Layer2> PsbtConstructor for Wallet +where Self: Save +{ type Key = K; type Descr = D; @@ -282,80 +302,77 @@ impl, L2: Layer2> PsbtConstructor for Wallet { idx = cmp::max(*last_index, idx); if shift { *last_index = idx.saturating_add(1u32); + self.set_dirty(); } idx } } -impl> Wallet { - pub fn new_standard(descr: D, network: Network) -> Self { - Wallet { +impl, I: Indexer> Wallet +where Self: Save +{ + pub fn new_layer1(descr: D, network: Network, indexer: I) -> MayError> { + let mut wallet = Wallet { descr: WalletDescr::new_standard(descr, network), data: empty!(), cache: WalletCache::new(), layer2: None, - } + indexer, + dirty: false, + fs: None, + }; + wallet.update().map(|_| wallet) } +} - pub fn with_standard( +impl, I: Indexer, L2: Layer2> Wallet +where Self: Save +{ + pub fn new_layer2( descr: D, + l2_descr: L2::Descr, + layer2: L2, network: Network, - indexer: &I, + indexer: I, ) -> MayError> { - let mut wallet = Wallet::new_standard(descr, network); - wallet.update(indexer).map(|_| wallet) - } -} - -impl, L2: Layer2> Wallet { - pub fn new_layer2(descr: D, l2_descr: L2::Descr, layer2: L2, network: Network) -> Self { - Wallet { + let mut wallet = Wallet { descr: WalletDescr::new_layer2(descr, l2_descr, network), data: empty!(), cache: WalletCache::new(), layer2, - } + indexer, + dirty: false, + fs: None, + }; + wallet.update().map(|_| wallet) } - pub fn with_layer2( - descr: D, - l2_descr: L2::Descr, - layer2: L2, - network: Network, - indexer: &I, - ) -> MayError> { - let mut wallet = Wallet::new_layer2(descr, l2_descr, layer2, network); - wallet.update(indexer).map(|_| wallet) - } + #[cfg(feature = "fs")] + pub fn fs_config(&self) -> Option<&FsConfig> { self.fs.as_ref() } - pub fn restore( - descr: WalletDescr, - data: WalletData, - cache: WalletCache, - layer2: L2, - ) -> Self { - Wallet { - descr, - data, - cache, - layer2, - } + #[cfg(feature = "fs")] + pub fn set_fs_config(&mut self, config: FsConfig) -> Result, fs::StoreError> { + let mut last = Some(config); + mem::swap(&mut self.fs, &mut last); + self.set_dirty(); + Ok(last) } - pub fn detach( - self, - ) -> (WalletDescr, WalletData, WalletCache, L2) { - (self.descr, self.data, self.cache, self.layer2) - } + pub fn set_dirty(&mut self) { self.dirty = true; } - pub fn set_name(&mut self, name: String) { self.data.name = name; } + pub fn set_name(&mut self, name: String) { + self.data.name = name; + self.set_dirty(); + } - pub fn update(&mut self, indexer: &B) -> MayError<(), Vec> { - let result = - WalletCache::with::<_, K, _, L2>(&self.descr, indexer).map(|cache| self.cache = cache); + pub fn update(&mut self) -> MayError<(), Vec> { // Not yet implemented: - // self.cache.update::(&self.descr, indexer) - result + // self.cache.update::(&self.descr, &self.indexer) + + WalletCache::with::<_, K, _, L2>(&self.descr, &self.indexer).map(|cache| { + self.cache = cache; + self.set_dirty(); + }) } pub fn to_deriver(&self) -> D @@ -447,11 +464,59 @@ impl, L2: Layer2> Wallet { #[cfg(feature = "fs")] pub(crate) mod fs { - use std::fs; + use std::convert::Infallible; + use std::error::Error; use std::path::{Path, PathBuf}; + use std::{fs, io}; + + use amplify::IoError; use super::*; + #[derive(Debug, Display, Error, From)] + #[display(doc_comments)] + pub enum LoadError { + /// I/O error loading wallet - {0} + #[from] + #[from(io::Error)] + Io(IoError), + + /// unable to parse TOML file - {0} + #[from] + Toml(toml::de::Error), + + #[display(inner)] + Layer2(L2), + + #[display(inner)] + #[from] + Custom(String), + } + + #[derive(Debug, Display, Error, From)] + #[display(doc_comments)] + pub enum StoreError { + /// I/O error storing wallet - {0} + #[from] + #[from(io::Error)] + Io(IoError), + + /// unable to serialize wallet data as TOML file - {0} + #[from] + Toml(toml::ser::Error), + + /// unable to serialize wallet cache as YAML file - {0} + #[from] + Yaml(serde_yaml::Error), + + #[display(inner)] + Layer2(L2), + + #[display(inner)] + #[from] + Custom(String), + } + #[derive(Debug, Display)] #[display(doc_comments)] pub enum Warning { @@ -482,7 +547,7 @@ pub(crate) mod fs { } } - impl, L2: Layer2> Wallet + impl, I: Indexer, L2: Layer2> Wallet where for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, for<'de> D: serde::Serialize + serde::Deserialize<'de>, @@ -491,7 +556,12 @@ pub(crate) mod fs { for<'de> L2::Data: serde::Serialize + serde::Deserialize<'de>, for<'de> L2::Cache: serde::Serialize + serde::Deserialize<'de>, { - pub fn load(path: &Path) -> Result<(Self, Vec), crate::LoadError> { + pub fn load( + path: &Path, + autosave: bool, + indexer: I, + update: bool, + ) -> Result<(Self, Vec), LoadError> { let mut warnings = Vec::new(); let files = WalletFiles::new(path); @@ -512,26 +582,73 @@ pub(crate) mod fs { WalletCache::default() }); - let layer2 = L2::load(path).map_err(crate::LoadError::Layer2)?; + let layer2 = L2::load(path).map_err(LoadError::Layer2)?; - let wallet = Wallet:: { + let fs = Some(FsConfig { + path: path.to_owned(), + autosave, + }); + + let mut wallet = Wallet:: { descr, data, cache, layer2, + indexer, + dirty: false, + fs, }; + if update { + wallet.update(); + } Ok((wallet, warnings)) } + } - pub fn store(&self, path: &Path) -> Result<(), crate::StoreError> { - fs::create_dir_all(path)?; - let files = WalletFiles::new(path); - fs::write(files.descr, toml::to_string_pretty(&self.descr)?)?; - fs::write(files.data, toml::to_string_pretty(&self.data)?)?; - fs::write(files.cache, serde_yaml::to_string(&self.cache)?)?; - self.layer2.store(path).map_err(crate::StoreError::Layer2)?; + impl, I: Indexer, L2: Layer2> Save for Wallet + where + for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, + for<'de> D: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2::Descr: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2::Data: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2::Cache: serde::Serialize + serde::Deserialize<'de>, + { + type SaveErr = StoreError; + + fn save(&self) -> Result> { + let Some(path) = self.fs.as_ref().map(|fs| &fs.path) else { + return Ok(false); + }; + if self.dirty { + fs::create_dir_all(path)?; + let files = WalletFiles::new(path); + fs::write(files.descr, toml::to_string_pretty(&self.descr)?)?; + fs::write(files.data, toml::to_string_pretty(&self.data)?)?; + fs::write(files.cache, serde_yaml::to_string(&self.cache)?)?; + self.layer2.store(path).map_err(StoreError::Layer2)?; + } - Ok(()) + Ok(true) } } + + impl, I: Indexer, L2: Layer2> Drop for Wallet + where Wallet: Save + { + fn drop(&mut self) { + if self.dirty && self.fs.as_ref().map(|fs| fs.autosave).unwrap_or_default() { + let _ = self.save(); + } + } + } +} + +#[cfg(not(feature = "fs"))] +impl, I: Indexer, L2: Layer2> Save for Wallet { + type SaveErr = (); + + fn save(&self) -> Result { + // Do nothing + } } From aecefa21b9009f88f3d704b0cf5610b67dee78a0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 2 Jul 2024 23:23:51 +0200 Subject: [PATCH 3/8] wallet: separate indexer from wallet structure --- src/cli/args.rs | 66 +++++++++++++++++++-------------------------- src/indexers/any.rs | 3 --- src/wallet.rs | 55 +++++++++++++------------------------ 3 files changed, 47 insertions(+), 77 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 947f7af..1fadef0 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -32,7 +32,6 @@ use strict_encoding::Ident; use crate::cli::{ Config, DescrStdOpts, DescriptorOpts, ExecError, GeneralOpts, ResolverOpt, WalletOpts, }; -use crate::indexers::AnyIndexerError; use crate::{AnyIndexer, MayError, Wallet}; /// Command-line arguments @@ -96,49 +95,18 @@ impl Args { pub fn bp_wallet( &self, conf: &Config, - ) -> Result, ExecError> + ) -> Result, ExecError> where for<'de> D: From + serde::Serialize + serde::Deserialize<'de>, { - fn print_errors(errors: Option>) { - if let Some(errors) = errors { - eprintln!(" partial, some requests has failed:"); - for err in errors { - eprintln!("- {err}"); - } - } else { - eprintln!(" success"); - } - } - eprint!("Loading descriptor"); let mut sync = self.sync || self.wallet.descriptor_opts.is_some(); - let indexer = match (&self.resolver.esplora, &self.resolver.electrum) { - (None, Some(url)) => AnyIndexer::Electrum(Box::new(electrum::Client::new(url)?)), - (Some(url), None) => { - AnyIndexer::Esplora(Box::new(esplora::Builder::new(url).build_blocking()?)) - } - _ if sync => { - eprintln!( - " - error: no blockchain indexer specified; use either --esplora or \ - --electrum argument" - ); - exit(1); - } - _ => AnyIndexer::None, - }; - - let mut wallet: Wallet = + let mut wallet: Wallet = if let Some(d) = self.wallet.descriptor_opts.descriptor() { eprintln!(" from command-line argument"); eprint!("Syncing"); - let MayError { - ok: wallet, - err: errors, - } = Wallet::new_layer1(d.into(), self.general.network, indexer); - print_errors(errors); - wallet + Wallet::new_layer1(d.into(), self.general.network) } else { let path = if let Some(wallet_path) = self.wallet.wallet_path.clone() { eprint!(" from specified wallet directory ... "); @@ -153,7 +121,7 @@ impl Args { eprint!(" from wallet {wallet_name} ... "); self.general.wallet_dir(wallet_name) }; - let (wallet, warnings) = Wallet::load(&path, true, indexer, false)?; + let (wallet, warnings) = Wallet::load(&path, true)?; if warnings.is_empty() { eprintln!("success"); } else { @@ -167,9 +135,31 @@ impl Args { }; if sync { + let indexer = match (&self.resolver.esplora, &self.resolver.electrum) { + (None, Some(url)) => AnyIndexer::Electrum(Box::new(electrum::Client::new(url)?)), + (Some(url), None) => { + AnyIndexer::Esplora(Box::new(esplora::Builder::new(url).build_blocking()?)) + } + _ => { + eprintln!( + " - error: no blockchain indexer specified; use either --esplora or \ + --electrum argument" + ); + exit(1); + } + }; eprint!("Syncing"); - let MayError { err, .. } = wallet.update(); - print_errors(err); + if let MayError { + err: Some(errors), .. + } = wallet.update(&indexer) + { + eprintln!(" partial, some requests has failed:"); + for err in errors { + eprintln!("- {err}"); + } + } else { + eprintln!(" success"); + } } Ok(wallet) diff --git a/src/indexers/any.rs b/src/indexers/any.rs index bb4ecbc..bd69938 100644 --- a/src/indexers/any.rs +++ b/src/indexers/any.rs @@ -35,7 +35,6 @@ pub enum AnyIndexer { #[from] /// Esplora indexer Esplora(Box), - None, } #[allow(clippy::large_enum_variant)] @@ -74,7 +73,6 @@ impl Indexer for AnyIndexer { err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()), } } - _ => unreachable!(), } } @@ -100,7 +98,6 @@ impl Indexer for AnyIndexer { err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()), } } - _ => unreachable!(), } } } diff --git a/src/wallet.rs b/src/wallet.rs index f52c4d5..056f180 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -260,20 +260,19 @@ pub trait Save { } #[derive(Clone, Eq, PartialEq, Debug)] -pub struct Wallet, I: Indexer, L2: Layer2 = NoLayer2> +pub struct Wallet, L2: Layer2 = NoLayer2> where Self: Save { descr: WalletDescr, data: WalletData, cache: WalletCache, layer2: L2, - indexer: I, #[cfg(feature = "fs")] fs: Option, dirty: bool, } -impl, I: Indexer, L2: Layer2> Deref for Wallet +impl, L2: Layer2> Deref for Wallet where Self: Save { type Target = WalletDescr; @@ -281,7 +280,7 @@ where Self: Save fn deref(&self) -> &Self::Target { &self.descr } } -impl, I: Indexer, L2: Layer2> PsbtConstructor for Wallet +impl, L2: Layer2> PsbtConstructor for Wallet where Self: Save { type Key = K; @@ -308,43 +307,33 @@ where Self: Save } } -impl, I: Indexer> Wallet +impl> Wallet where Self: Save { - pub fn new_layer1(descr: D, network: Network, indexer: I) -> MayError> { - let mut wallet = Wallet { + pub fn new_layer1(descr: D, network: Network) -> Self { + Wallet { descr: WalletDescr::new_standard(descr, network), data: empty!(), cache: WalletCache::new(), layer2: None, - indexer, dirty: false, fs: None, - }; - wallet.update().map(|_| wallet) + } } } -impl, I: Indexer, L2: Layer2> Wallet +impl, L2: Layer2> Wallet where Self: Save { - pub fn new_layer2( - descr: D, - l2_descr: L2::Descr, - layer2: L2, - network: Network, - indexer: I, - ) -> MayError> { - let mut wallet = Wallet { + pub fn new_layer2(descr: D, l2_descr: L2::Descr, layer2: L2, network: Network) -> Self { + Wallet { descr: WalletDescr::new_layer2(descr, l2_descr, network), data: empty!(), cache: WalletCache::new(), layer2, - indexer, dirty: false, fs: None, - }; - wallet.update().map(|_| wallet) + } } #[cfg(feature = "fs")] @@ -365,11 +354,11 @@ where Self: Save self.set_dirty(); } - pub fn update(&mut self) -> MayError<(), Vec> { + pub fn update(&mut self, indexer: &I) -> MayError<(), Vec> { // Not yet implemented: // self.cache.update::(&self.descr, &self.indexer) - WalletCache::with::<_, K, _, L2>(&self.descr, &self.indexer).map(|cache| { + WalletCache::with::<_, K, _, L2>(&self.descr, indexer).map(|cache| { self.cache = cache; self.set_dirty(); }) @@ -547,7 +536,7 @@ pub(crate) mod fs { } } - impl, I: Indexer, L2: Layer2> Wallet + impl, L2: Layer2> Wallet where for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, for<'de> D: serde::Serialize + serde::Deserialize<'de>, @@ -559,8 +548,6 @@ pub(crate) mod fs { pub fn load( path: &Path, autosave: bool, - indexer: I, - update: bool, ) -> Result<(Self, Vec), LoadError> { let mut warnings = Vec::new(); @@ -589,23 +576,19 @@ pub(crate) mod fs { autosave, }); - let mut wallet = Wallet:: { + let wallet = Wallet:: { descr, data, cache, layer2, - indexer, dirty: false, fs, }; - if update { - wallet.update(); - } Ok((wallet, warnings)) } } - impl, I: Indexer, L2: Layer2> Save for Wallet + impl, L2: Layer2> Save for Wallet where for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, for<'de> D: serde::Serialize + serde::Deserialize<'de>, @@ -633,8 +616,8 @@ pub(crate) mod fs { } } - impl, I: Indexer, L2: Layer2> Drop for Wallet - where Wallet: Save + impl, L2: Layer2> Drop for Wallet + where Wallet: Save { fn drop(&mut self) { if self.dirty && self.fs.as_ref().map(|fs| fs.autosave).unwrap_or_default() { @@ -645,7 +628,7 @@ pub(crate) mod fs { } #[cfg(not(feature = "fs"))] -impl, I: Indexer, L2: Layer2> Save for Wallet { +impl, L2: Layer2> Save for Wallet { type SaveErr = (); fn save(&self) -> Result { From 89672bfe44669964f630e4631fe1a67b85b0656f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 4 Jul 2024 16:37:12 +0200 Subject: [PATCH 4/8] wallet: fix build issues when no fs feature is used --- src/lib.rs | 4 ++-- src/wallet.rs | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c610fc5..1d765d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,9 +45,9 @@ pub use data::{ BlockHeight, BlockInfo, MiningInfo, Party, TxCredit, TxDebit, TxStatus, WalletAddr, WalletTx, WalletUtxo, }; -#[cfg(any(feature = "electrum", feature = "esplora"))] -pub use indexers::AnyIndexer; pub use indexers::Indexer; +#[cfg(any(feature = "electrum", feature = "esplora"))] +pub use indexers::{AnyIndexer, AnyIndexerError}; pub use layer2::{ Layer2, Layer2Cache, Layer2Coin, Layer2Data, Layer2Descriptor, Layer2Tx, NoLayer2, }; diff --git a/src/wallet.rs b/src/wallet.rs index 056f180..6f00f4c 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -20,12 +20,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::cmp; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::error::Error; use std::marker::PhantomData; use std::ops::{AddAssign, Deref, DerefMut}; +#[cfg(feature = "fs")] use std::path::PathBuf; -use std::{cmp, mem}; use bpstd::{ Address, AddressNetwork, DerivedAddr, Descriptor, Idx, IdxBase, Keychain, Network, NormalIndex, @@ -317,6 +318,7 @@ where Self: Save cache: WalletCache::new(), layer2: None, dirty: false, + #[cfg(feature = "fs")] fs: None, } } @@ -332,6 +334,7 @@ where Self: Save cache: WalletCache::new(), layer2, dirty: false, + #[cfg(feature = "fs")] fs: None, } } @@ -342,7 +345,7 @@ where Self: Save #[cfg(feature = "fs")] pub fn set_fs_config(&mut self, config: FsConfig) -> Result, fs::StoreError> { let mut last = Some(config); - mem::swap(&mut self.fs, &mut last); + std::mem::swap(&mut self.fs, &mut last); self.set_dirty(); Ok(last) } @@ -629,9 +632,9 @@ pub(crate) mod fs { #[cfg(not(feature = "fs"))] impl, L2: Layer2> Save for Wallet { - type SaveErr = (); + type SaveErr = std::convert::Infallible; fn save(&self) -> Result { - // Do nothing + panic!("Attempt to save wallet with no file system support during compilation"); } } From 3f121c000de793a2fe0494d211621611d4967619 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 5 Jul 2024 22:45:59 +0200 Subject: [PATCH 5/8] wallet: re-export Save trait --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1d765d4..b616129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,4 +55,4 @@ pub use rows::{CoinRow, Counterparty, OpType, TxRow}; pub use util::MayError; #[cfg(feature = "fs")] pub use wallet::FsConfig; -pub use wallet::{Wallet, WalletCache, WalletData, WalletDescr}; +pub use wallet::{Save, Wallet, WalletCache, WalletData, WalletDescr}; From b63f66766e72f01a93d98c39af64eb940f3e6958 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 6 Jul 2024 08:12:45 +0200 Subject: [PATCH 6/8] wallet: add operations on mutable descriptor saving the result --- src/wallet.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/wallet.rs b/src/wallet.rs index 6f00f4c..5c23e36 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -350,13 +350,28 @@ where Self: Save Ok(last) } - pub fn set_dirty(&mut self) { self.dirty = true; } + pub fn set_dirty(&mut self) { + self.dirty = true; + #[cfg(feature = "fs")] + if self.fs.as_ref().map(|fs| fs.autosave).unwrap_or_default() { + let _ = self.save(); + } + } pub fn set_name(&mut self, name: String) { self.data.name = name; self.set_dirty(); } + pub fn descriptor_mut( + &mut self, + f: impl FnOnce(&mut WalletDescr) -> R, + ) -> R { + let res = f(&mut self.descr); + self.set_dirty(); + res + } + pub fn update(&mut self, indexer: &I) -> MayError<(), Vec> { // Not yet implemented: // self.cache.update::(&self.descr, &self.indexer) From b3cdda624394f6ade6f83076a43bc66078895c2a Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 6 Jul 2024 08:16:55 +0200 Subject: [PATCH 7/8] wallet: re-export fs module --- src/lib.rs | 2 +- src/wallet.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b616129..9fc81ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,5 +54,5 @@ pub use layer2::{ pub use rows::{CoinRow, Counterparty, OpType, TxRow}; pub use util::MayError; #[cfg(feature = "fs")] -pub use wallet::FsConfig; +pub use wallet::{fs, FsConfig}; pub use wallet::{Save, Wallet, WalletCache, WalletData, WalletDescr}; diff --git a/src/wallet.rs b/src/wallet.rs index 5c23e36..443fdb3 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -470,7 +470,7 @@ where Self: Save } #[cfg(feature = "fs")] -pub(crate) mod fs { +pub mod fs { use std::convert::Infallible; use std::error::Error; use std::path::{Path, PathBuf}; From 30ae979f658b5b4643352387a3e5e36f8f817098 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 8 Jul 2024 22:56:00 +0200 Subject: [PATCH 8/8] wallet: fix saving of newly created wallet --- src/cli/command.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cli/command.rs b/src/cli/command.rs index ac4429d..85063e9 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -34,7 +34,7 @@ use strict_encoding::Ident; use crate::cli::{Args, Config, DescriptorOpts, Exec}; use crate::wallet::fs::{LoadError, StoreError}; use crate::wallet::Save; -use crate::{coinselect, OpType, WalletAddr, WalletUtxo}; +use crate::{coinselect, FsConfig, OpType, WalletAddr, WalletUtxo}; #[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] pub enum Command { @@ -217,6 +217,10 @@ impl Exec for Args { print!("Saving the wallet as '{name}' ... "); let mut wallet = self.bp_wallet::(&config)?; let name = name.to_string(); + wallet.set_fs_config(FsConfig { + path: self.general.wallet_dir(&name), + autosave: false, + })?; wallet.set_name(name); if let Err(err) = wallet.save() { println!("error: {err}");