From dd2f7ff84e52bfb5b1e3761532e1fe9df8f3b915 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 29 Aug 2024 17:21:37 +0200 Subject: [PATCH 01/14] wallet: abstract persistence with PersistenceProvider --- src/cli/args.rs | 18 +-- src/cli/command.rs | 28 ++-- src/layer2.rs | 71 ++++----- src/lib.rs | 8 +- src/persistence.rs | 241 ++++++++++++++++++++++++++++++ src/wallet.rs | 365 ++++++++++++++++----------------------------- 6 files changed, 419 insertions(+), 312 deletions(-) create mode 100644 src/persistence.rs diff --git a/src/cli/args.rs b/src/cli/args.rs index 91f1f7d..69acc61 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -32,8 +32,9 @@ use strict_encoding::Ident; use crate::cli::{ Config, DescrStdOpts, DescriptorOpts, ExecError, GeneralOpts, ResolverOpt, WalletOpts, }; +use crate::fs::FsTextStore; use crate::indexers::esplora; -use crate::{AnyIndexer, MayError, Wallet}; +use crate::{AnyIndexer, Wallet}; /// Command-line arguments #[derive(Parser)] @@ -123,7 +124,7 @@ impl Args { for<'de> D: From + serde::Serialize + serde::Deserialize<'de>, { eprint!("Loading descriptor"); - let mut sync = self.sync || self.wallet.descriptor_opts.is_some(); + let sync = self.sync || self.wallet.descriptor_opts.is_some(); let mut wallet: Wallet = if let Some(d) = self.wallet.descriptor_opts.descriptor() { @@ -144,16 +145,9 @@ impl Args { eprint!(" from wallet {wallet_name} ... "); self.general.wallet_dir(wallet_name) }; - let (wallet, warnings) = Wallet::load(&path, true)?; - if warnings.is_empty() { - eprintln!("success"); - } else { - eprintln!("complete with warnings:"); - for warning in warnings { - eprintln!("- {warning}"); - } - sync = true; - } + let provider = FsTextStore::new(path); + let wallet = Wallet::load(provider, true)?; + eprintln!("success"); wallet }; diff --git a/src/cli/command.rs b/src/cli/command.rs index 9b1f395..27eff5f 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -20,11 +20,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::Infallible; use std::fs::File; use std::path::{Path, PathBuf}; use std::process::exit; -use std::{error, fs, io}; +use std::{fs, io}; use amplify::IoError; use bpstd::psbt::{Beneficiary, TxParams}; @@ -35,10 +34,9 @@ use psbt::{ConstructionError, Payment, Psbt, PsbtConstructor, PsbtVer, Unfinaliz use strict_encoding::Ident; use crate::cli::{Args, Config, DescriptorOpts, Exec}; -use crate::wallet::fs::{LoadError, StoreError}; -use crate::wallet::Save; +use crate::fs::FsTextStore; use crate::{ - coinselect, AnyIndexerError, FsConfig, Indexer, OpType, Wallet, WalletAddr, WalletUtxo, + coinselect, AnyIndexerError, Indexer, OpType, PersistenceError, Wallet, WalletAddr, WalletUtxo, }; #[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] @@ -180,16 +178,13 @@ pub enum BpCommand { #[derive(Debug, Display, Error, From)] #[non_exhaustive] #[display(inner)] -pub enum ExecError { +pub enum ExecError { #[from] #[from(io::Error)] Io(IoError), #[from] - Load(LoadError), - - #[from] - Store(StoreError), + Store(PersistenceError), #[from] ConstructPsbt(ConstructionError), @@ -241,9 +236,8 @@ impl Exec for Args { "{name}{}", if config.default_wallet == name { "\t[default]" } else { "\t\t" } ); - let Ok((wallet, _warnings)) = - Wallet::::load(&entry.path(), true) - else { + let provider = FsTextStore::new(entry.path().clone()); + let Ok(wallet) = Wallet::::load(provider, true) else { println!("# broken wallet descriptor"); continue; }; @@ -270,12 +264,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: true, - })?; + let provider = FsTextStore::new(self.general.wallet_dir(&name)); + wallet.make_persistent(provider, true)?; wallet.set_name(name); - if let Err(err) = wallet.save() { + if let Err(err) = wallet.store() { println!("error: {err}"); } else { println!("success"); diff --git a/src/layer2.rs b/src/layer2.rs index c41142e..db5d325 100644 --- a/src/layer2.rs +++ b/src/layer2.rs @@ -23,36 +23,25 @@ use std::convert::Infallible; use std::error; use std::fmt::Debug; -use std::path::Path; -pub trait Layer2: Debug { +use crate::{Persistence, Persisting}; + +pub trait Layer2: Debug + Persisting { type Descr: Layer2Descriptor; type Data: Layer2Data; type Cache: Layer2Cache; type LoadError: error::Error; type StoreError: error::Error; - - fn load(path: &Path) -> Result - where Self: Sized; - fn store(&self, path: &Path) -> Result<(), Self::StoreError>; } pub trait Layer2Descriptor: Debug { type LoadError: error::Error; type StoreError: error::Error; - - fn load(path: &Path) -> Result - where Self: Sized; - fn store(&self, path: &Path) -> Result<(), Self::StoreError>; } pub trait Layer2Data: Debug + Default { type LoadError: error::Error; type StoreError: error::Error; - - fn load(path: &Path) -> Result - where Self: Sized; - fn store(&self, path: &Path) -> Result<(), Self::StoreError>; } pub trait Layer2Cache: Debug + Default { @@ -61,38 +50,48 @@ pub trait Layer2Cache: Debug + Default { type Tx: Layer2Tx; type Coin: Layer2Coin; - - fn load(path: &Path) -> Result - where Self: Sized; - fn store(&self, path: &Path) -> Result<(), Self::StoreError>; } #[cfg(not(feature = "serde"))] pub trait Layer2Tx: Debug + Default {} #[cfg(feature = "serde")] -pub trait Layer2Tx: - Clone + Debug + Default + serde::Serialize + for<'de> serde::Deserialize<'de> -{ -} +pub trait Layer2Tx: Debug + Default + serde::Serialize + for<'de> serde::Deserialize<'de> {} #[cfg(not(feature = "serde"))] pub trait Layer2Coin: Debug + Default {} #[cfg(feature = "serde")] pub trait Layer2Coin: - Clone + Debug + Default + serde::Serialize + for<'de> serde::Deserialize<'de> + Debug + Default + serde::Serialize + for<'de> serde::Deserialize<'de> { } -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(crate = "serde_crate") +)] +pub struct Empty; + +#[derive(Debug, Default)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(crate = "serde_crate") )] -pub enum ImpossibleLayer2 {} -pub type NoLayer2 = Option; +pub struct NoLayer2 { + #[cfg_attr(feature = "serde", serde(skip))] + persistence: Option>, +} + +impl Persisting for NoLayer2 { + #[inline] + fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } + #[inline] + fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } +} impl Layer2 for NoLayer2 { type Descr = NoLayer2; @@ -100,37 +99,25 @@ impl Layer2 for NoLayer2 { type Cache = NoLayer2; type LoadError = Infallible; type StoreError = Infallible; - - fn load(_: &Path) -> Result { Ok(None) } - fn store(&self, _: &Path) -> Result<(), Self::StoreError> { Ok(()) } } impl Layer2Descriptor for NoLayer2 { type LoadError = Infallible; type StoreError = Infallible; - - fn load(_: &Path) -> Result { Ok(None) } - fn store(&self, _: &Path) -> Result<(), Self::StoreError> { Ok(()) } } impl Layer2Data for NoLayer2 { type LoadError = Infallible; type StoreError = Infallible; - - fn load(_: &Path) -> Result { Ok(None) } - fn store(&self, _: &Path) -> Result<(), Self::StoreError> { Ok(()) } } impl Layer2Cache for NoLayer2 { - type Tx = NoLayer2; - type Coin = NoLayer2; + type Tx = Empty; + type Coin = Empty; type LoadError = Infallible; type StoreError = Infallible; - - fn load(_: &Path) -> Result { Ok(None) } - fn store(&self, _: &Path) -> Result<(), Self::StoreError> { Ok(()) } } -impl Layer2Tx for NoLayer2 {} -impl Layer2Coin for NoLayer2 {} +impl Layer2Tx for Empty {} +impl Layer2Coin for Empty {} diff --git a/src/lib.rs b/src/lib.rs index 770d915..132984e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ pub mod cli; #[cfg(feature = "hot")] pub mod hot; mod bip43; +mod persistence; pub use bip43::{Bip43, DerivationStandard, ParseBip43Error}; pub use data::{ @@ -59,8 +60,9 @@ pub use indexers::{AnyIndexer, AnyIndexerError}; pub use layer2::{ Layer2, Layer2Cache, Layer2Coin, Layer2Data, Layer2Descriptor, Layer2Tx, NoLayer2, }; +#[cfg(feature = "fs")] +pub use persistence::fs; +pub use persistence::{Persistence, PersistenceError, PersistenceProvider, Persisting}; pub use rows::{CoinRow, Counterparty, OpType, TxRow}; pub use util::MayError; -#[cfg(feature = "fs")] -pub use wallet::{fs, FsConfig}; -pub use wallet::{Save, Wallet, WalletCache, WalletData, WalletDescr}; +pub use wallet::{Wallet, WalletCache, WalletData, WalletDescr}; diff --git a/src/persistence.rs b/src/persistence.rs new file mode 100644 index 0000000..121dd9e --- /dev/null +++ b/src/persistence.rs @@ -0,0 +1,241 @@ +// Modern, minimalistic & standard-compliant cold wallet library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2020-2024 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2020-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2020-2024 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::error::Error; +use std::fmt::Debug; + +#[derive(Debug, Display, Error)] +#[display(inner)] +pub struct PersistenceError(pub Box); + +impl PersistenceError { + pub fn with(e: E) -> Self { Self(Box::new(e)) } +} + +pub trait PersistenceProvider: Send + Debug { + fn load(&self) -> Result; + fn store(&self, object: &T) -> Result<(), PersistenceError>; +} + +#[derive(Debug)] +pub struct Persistence { + pub dirty: bool, + pub autosave: bool, + pub provider: Box>, +} + +impl Persistence { + pub fn load( + provider: impl PersistenceProvider + 'static, + autosave: bool, + ) -> Result { + let mut obj: T = provider.load()?; + let mut me = Self { + dirty: false, + autosave, + provider: Box::new(provider), + }; + obj.persistence_mut().replace(&mut me); + Ok(obj) + } +} + +pub trait Persisting: Sized { + #[inline] + fn load( + provider: impl PersistenceProvider + 'static, + autosave: bool, + ) -> Result { + Persistence::load(provider, autosave) + } + + fn persistence(&self) -> Option<&Persistence>; + + fn persistence_mut(&mut self) -> Option<&mut Persistence>; + + fn is_persisted(&self) -> bool { self.persistence().is_some() } + + fn is_dirty(&self) -> bool { self.persistence().map(|p| p.autosave).unwrap_or(true) } + + fn mark_dirty(&mut self) { + if let Some(p) = self.persistence_mut() { + p.dirty = true; + } + if let Some(p) = self.persistence() { + if p.autosave { + if let Err(e) = p.provider.store(self) { + #[cfg(feature = "log")] + log::error!( + "Unable to autosave a dirty object on Persisting::mark_dirty call. \ + Details: {e}" + ); + } + } + } + } + + fn is_autosave(&self) -> bool { self.persistence().map(|p| p.autosave).unwrap_or_default() } + + fn set_autosave(&mut self) { + if let Err(e) = self.store() { + #[cfg(feature = "log")] + log::error!( + "Unable to autosave a dirty object on Persisting::set_autosave call. Details: {e}" + ); + } + } + + /// Returns whether the object was persisting before this method. + fn make_persistent( + &mut self, + provider: impl PersistenceProvider + 'static, + autosave: bool, + ) -> Result { + let was_persisted = self.is_persisted(); + let mut me = Persistence { + dirty: false, + autosave, + provider: Box::new(provider), + }; + self.persistence_mut().replace(&mut me); + self.mark_dirty(); + Ok(was_persisted) + } + + fn store(&mut self) -> Result<(), PersistenceError> { + if self.is_dirty() { + if let Some(p) = self.persistence() { + p.provider.store(self)?; + } + if let Some(p) = self.persistence_mut() { + p.dirty = false; + } + } + Ok(()) + } +} + +#[cfg(feature = "fs")] +pub mod fs { + use std::fs; + use std::path::PathBuf; + + use descriptors::Descriptor; + + use super::*; + use crate::{ + Layer2Cache, Layer2Data, Layer2Descriptor, NoLayer2, WalletCache, WalletData, WalletDescr, + }; + + #[derive(Clone, Eq, PartialEq, Debug)] + pub struct FsTextStore { + pub descr: PathBuf, + pub data: PathBuf, + pub cache: PathBuf, + pub l2: PathBuf, + } + + impl FsTextStore { + pub fn new(path: PathBuf) -> Self { + let mut descr = path.clone(); + descr.push("descriptor.toml"); + let mut data = path.clone(); + data.push("data.toml"); + let mut cache = path.clone(); + cache.push("cache.yaml"); + let mut l2 = path; + l2.push("layer2.yaml"); + + Self { + descr, + data, + cache, + l2, + } + } + } + + impl, L2: Layer2Descriptor> PersistenceProvider> + for FsTextStore + 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>, + { + fn load(&self) -> Result, PersistenceError> { + let descr = fs::read_to_string(&self.descr).map_err(PersistenceError::with)?; + Ok(toml::from_str(&descr).map_err(PersistenceError::with)?) + } + + fn store(&self, object: &WalletDescr) -> Result<(), PersistenceError> { + let s = toml::to_string_pretty(object).map_err(PersistenceError::with)?; + fs::write(&self.descr, s).map_err(PersistenceError::with)?; + Ok(()) + } + } + + impl PersistenceProvider> for FsTextStore + where + for<'de> WalletCache: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2: serde::Serialize + serde::Deserialize<'de>, + { + fn load(&self) -> Result, PersistenceError> { + let file = fs::File::open(&self.cache).map_err(PersistenceError::with)?; + Ok(serde_yaml::from_reader(file).map_err(PersistenceError::with)?) + } + + fn store(&self, object: &WalletCache) -> Result<(), PersistenceError> { + let file = fs::File::create(&self.cache).map_err(PersistenceError::with)?; + serde_yaml::to_writer(file, object).map_err(PersistenceError::with)?; + Ok(()) + } + } + + impl PersistenceProvider> for FsTextStore + where + for<'de> WalletData: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2: serde::Serialize + serde::Deserialize<'de>, + { + fn load(&self) -> Result, PersistenceError> { + let data = fs::read_to_string(&self.data).map_err(PersistenceError::with)?; + Ok(toml::from_str(&data).map_err(PersistenceError::with)?) + } + + fn store(&self, object: &WalletData) -> Result<(), PersistenceError> { + let s = toml::to_string_pretty(object).map_err(PersistenceError::with)?; + fs::write(&self.data, s).map_err(PersistenceError::with)?; + Ok(()) + } + } + + impl PersistenceProvider for FsTextStore { + fn load(&self) -> Result { + // Nothing to do + Ok(none!()) + } + + fn store(&self, _: &NoLayer2) -> Result<(), PersistenceError> { + // Nothing to do + Ok(()) + } + } +} diff --git a/src/wallet.rs b/src/wallet.rs index a4a1f89..6bf4592 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -22,11 +22,8 @@ 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 bpstd::{ Address, AddressNetwork, DerivedAddr, Descriptor, Idx, IdxBase, Keychain, Network, NormalIndex, @@ -36,7 +33,8 @@ use psbt::{PsbtConstructor, Utxo}; use crate::{ BlockInfo, CoinRow, Indexer, Layer2, Layer2Cache, Layer2Data, Layer2Descriptor, MayError, - MiningInfo, NoLayer2, TxRow, WalletAddr, WalletTx, WalletUtxo, + MiningInfo, NoLayer2, Persistence, PersistenceError, PersistenceProvider, Persisting, TxRow, + WalletAddr, WalletTx, WalletUtxo, }; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] @@ -81,12 +79,16 @@ impl<'descr, K, D: Descriptor> Iterator for AddrIter<'descr, K, D> { ) ) )] -#[derive(Getters, Clone, Eq, PartialEq, Debug)] +#[derive(Getters, Debug)] pub struct WalletDescr where D: Descriptor, L2: Layer2Descriptor, { + #[getter(skip)] + #[cfg_attr(feature = "serde", serde(skip))] + persistence: Option>, + generator: D, #[getter(as_copy)] network: Network, @@ -98,9 +100,10 @@ where impl> WalletDescr { pub fn new_standard(descr: D, network: Network) -> Self { WalletDescr { + persistence: None, generator: descr, network, - layer2: None, + layer2: none!(), _phantom: PhantomData, } } @@ -109,6 +112,7 @@ impl> WalletDescr { impl, L2: Layer2Descriptor> WalletDescr { pub fn new_layer2(descr: D, layer2: L2, network: Network) -> Self { WalletDescr { + persistence: None, generator: descr, network, layer2, @@ -137,7 +141,28 @@ impl, L2: Layer2Descriptor> DerefMut for WalletDescr &mut Self::Target { &mut self.generator } } -#[derive(Clone, Eq, PartialEq, Debug, Default)] +impl, L2: Layer2Descriptor> Persisting for WalletDescr { + #[inline] + fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } + #[inline] + fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } +} + +impl, L2: Layer2Descriptor> Drop for WalletDescr { + fn drop(&mut self) { + if self.is_autosave() { + if let Err(e) = self.store() { + #[cfg(feature = "log")] + log::error!( + "impossible to automatically-save wallet descriptor during the Drop \ + operation: {e}" + ); + } + } + } +} + +#[derive(Debug, Default)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -148,6 +173,9 @@ impl, L2: Layer2Descriptor> DerefMut for WalletDescr { + #[cfg_attr(feature = "serde", serde(skip))] + persistence: Option>, + pub name: String, pub tx_annotations: BTreeMap, pub txout_annotations: BTreeMap, @@ -157,6 +185,26 @@ pub struct WalletData { pub last_used: BTreeMap, } +impl Persisting for WalletData { + #[inline] + fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } + #[inline] + fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } +} + +impl Drop for WalletData { + fn drop(&mut self) { + if self.is_autosave() { + if let Err(e) = self.store() { + #[cfg(feature = "log")] + log::error!( + "impossible to automatically-save wallet data during the Drop operation: {e}" + ); + } + } + } +} + #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -166,8 +214,11 @@ pub struct WalletData { bound(serialize = "L2: serde::Serialize", deserialize = "L2: serde::Deserialize<'de>") ) )] -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Debug)] pub struct WalletCache { + #[cfg_attr(feature = "serde", serde(skip))] + persistence: Option>, + pub last_block: MiningInfo, pub last_change: NormalIndex, pub headers: BTreeSet, @@ -184,6 +235,7 @@ impl Default for WalletCache { impl WalletCache { pub(crate) fn new() -> Self { WalletCache { + persistence: None, last_block: MiningInfo::genesis(), last_change: NormalIndex::ZERO, headers: none!(), @@ -248,42 +300,41 @@ impl WalletCache { } } -#[cfg(feature = "fs")] -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct FsConfig { - pub path: PathBuf, - pub autosave: bool, +impl Persisting for WalletCache { + #[inline] + fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } + #[inline] + fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } } -pub trait Save { - type SaveErr: Error; - fn save(&self) -> Result; +impl Drop for WalletCache { + fn drop(&mut self) { + if self.is_autosave() { + if let Err(e) = self.store() { + #[cfg(feature = "log")] + log::error!( + "impossible to automatically-save wallet cache during the Drop operation: {e}" + ); + } + } + } } -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct Wallet, L2: Layer2 = NoLayer2> -where Self: Save -{ +#[derive(Debug)] +pub struct Wallet, L2: Layer2 = NoLayer2> { descr: WalletDescr, data: WalletData, cache: WalletCache, layer2: L2, - #[cfg(feature = "fs")] - fs: Option, - dirty: bool, } -impl, L2: Layer2> Deref for Wallet -where Self: Save -{ +impl, L2: Layer2> Deref for Wallet { type Target = WalletDescr; fn deref(&self) -> &Self::Target { &self.descr } } -impl, L2: Layer2> PsbtConstructor for Wallet -where Self: Save -{ +impl, L2: Layer2> PsbtConstructor for Wallet { type Key = K; type Descr = D; @@ -302,65 +353,36 @@ where Self: Save idx = cmp::max(*last_index, idx); if shift { *last_index = idx.saturating_add(1u32); - self.set_dirty(); + self.data.mark_dirty(); } idx } } -impl> Wallet -where Self: Save -{ +impl> Wallet { pub fn new_layer1(descr: D, network: Network) -> Self { Wallet { descr: WalletDescr::new_standard(descr, network), data: empty!(), cache: WalletCache::new(), - layer2: None, - dirty: false, - #[cfg(feature = "fs")] - fs: None, + layer2: none!(), } } } -impl, L2: Layer2> Wallet -where Self: Save -{ +impl, L2: Layer2> 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, - dirty: false, - #[cfg(feature = "fs")] - fs: None, - } - } - - #[cfg(feature = "fs")] - pub fn fs_config(&self) -> Option<&FsConfig> { self.fs.as_ref() } - - #[cfg(feature = "fs")] - pub fn set_fs_config(&mut self, config: FsConfig) -> Result, fs::StoreError> { - let mut last = Some(config); - std::mem::swap(&mut self.fs, &mut last); - self.set_dirty(); - Ok(last) - } - - 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(); + self.data.mark_dirty(); } pub fn descriptor_mut( @@ -368,7 +390,7 @@ where Self: Save f: impl FnOnce(&mut WalletDescr) -> R, ) -> R { let res = f(&mut self.descr); - self.set_dirty(); + self.descr.mark_dirty(); res } @@ -378,7 +400,7 @@ where Self: Save WalletCache::with::<_, K, _, L2>(&self.descr, indexer).map(|cache| { self.cache = cache; - self.set_dirty(); + self.cache.mark_dirty(); }) } @@ -468,185 +490,54 @@ where Self: Save } } -#[cfg(feature = "fs")] -pub mod 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 { - /// no cache file is found, initializing with empty cache - CacheAbsent, - /// wallet cache damaged or has invalid version; resetting ({0}) - CacheDamaged(serde_yaml::Error), - } - - struct WalletFiles { - pub descr: PathBuf, - pub data: PathBuf, - pub cache: PathBuf, - } - - impl WalletFiles { - pub fn new(path: &Path) -> Self { - let mut descr = path.to_owned(); - descr.push("descriptor.toml"); - - let mut data = path.to_owned(); - data.push("data.toml"); - - let mut cache = path.to_owned(); - cache.push("cache.yaml"); - - WalletFiles { descr, data, cache } - } - } - - impl, L2: Layer2> 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>, - { - pub fn load( - path: &Path, - autosave: bool, - ) -> Result<(Self, Vec), LoadError> { - let mut warnings = Vec::new(); - - let files = WalletFiles::new(path); - - let descr = fs::read_to_string(files.descr)?; - let descr = toml::from_str(&descr)?; - - let data = fs::read_to_string(files.data)?; - let data = toml::from_str(&data)?; - - let cache = fs::read_to_string(files.cache) - .map_err(|_| Warning::CacheAbsent) - .and_then(|cache| serde_yaml::from_str(&cache).map_err(Warning::CacheDamaged)) - .unwrap_or_else(|warn| { - warnings.push(warn); - WalletCache::default() - }); - - let layer2 = L2::load(path).map_err(LoadError::Layer2)?; - - let fs = Some(FsConfig { - path: path.to_owned(), - autosave, - }); - - let wallet = Wallet:: { - descr, - data, - cache, - layer2, - dirty: false, - fs, - }; - Ok((wallet, warnings)) - } +impl, L2: Layer2> Wallet { + pub fn load

(provider: P, autosave: bool) -> Result, PersistenceError> + where P: Clone + + PersistenceProvider> + + PersistenceProvider> + + PersistenceProvider> + + PersistenceProvider + + 'static { + let descr = WalletDescr::::load(provider.clone(), autosave)?; + let data = WalletData::::load(provider.clone(), autosave)?; + let cache = WalletCache::::load(provider.clone(), autosave)?; + let layer2 = L2::load(provider, autosave)?; + + Ok(Wallet { + descr, + data, + cache, + layer2, + }) } - impl, L2: Layer2> Save for Wallet + pub fn make_persistent

( + &mut self, + provider: P, + autosave: bool, + ) -> Result 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>, + P: Clone + + PersistenceProvider> + + PersistenceProvider> + + PersistenceProvider> + + PersistenceProvider + + 'static, { - 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(true) - } + Ok(self.descr.make_persistent(provider.clone(), autosave)? + && self.data.make_persistent(provider.clone(), autosave)? + && self.cache.make_persistent(provider.clone(), autosave)? + && self.layer2.make_persistent(provider, autosave)?) } - 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() { - let _ = self.save(); - } - } - } -} + pub fn store(&mut self) -> Result<(), PersistenceError> { + // TODO: Revert on failure -#[cfg(not(feature = "fs"))] -impl, L2: Layer2> Save for Wallet { - type SaveErr = std::convert::Infallible; + self.descr.store()?; + self.data.store()?; + self.cache.store()?; + self.layer2.store()?; - fn save(&self) -> Result { - panic!("Attempt to save wallet with no file system support during compilation"); + Ok(()) } } From 10626885a30091ea93c027ef4dce2e3c6fb8aa83 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 29 Aug 2024 22:12:54 +0200 Subject: [PATCH 02/14] wallet: mark wallet cache dirty after indexer --- src/wallet.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wallet.rs b/src/wallet.rs index 6bf4592..bc73c74 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -258,7 +258,9 @@ impl WalletCache { descriptor: &WalletDescr, indexer: &I, ) -> MayError> { - indexer.update::(descriptor, self) + let res = indexer.update::(descriptor, self); + self.mark_dirty(); + res } pub fn addresses_on(&self, keychain: Keychain) -> &BTreeSet { From ad68caa6b6a374cd13994d7b4fbf7ca5a55a08bb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 29 Aug 2024 22:13:16 +0200 Subject: [PATCH 03/14] wallet: remove DerefMut for the descriptor this is required in order to prevent unsaved changes to the descriptor --- src/wallet.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/wallet.rs b/src/wallet.rs index bc73c74..b8b1f07 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -23,7 +23,7 @@ use std::cmp; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::marker::PhantomData; -use std::ops::{AddAssign, Deref, DerefMut}; +use std::ops::{AddAssign, Deref}; use bpstd::{ Address, AddressNetwork, DerivedAddr, Descriptor, Idx, IdxBase, Keychain, Network, NormalIndex, @@ -137,10 +137,6 @@ impl, L2: Layer2Descriptor> Deref for WalletDescr fn deref(&self) -> &Self::Target { &self.generator } } -impl, L2: Layer2Descriptor> DerefMut for WalletDescr { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.generator } -} - impl, L2: Layer2Descriptor> Persisting for WalletDescr { #[inline] fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } From 69e63c41b357b911b904b25d4b2796a261e8b9a4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 29 Aug 2024 22:57:00 +0200 Subject: [PATCH 04/14] chore: fix clippy lints --- src/indexers/electrum.rs | 6 +++--- src/persistence.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/indexers/electrum.rs b/src/indexers/electrum.rs index 8d2767c..14b264c 100644 --- a/src/indexers/electrum.rs +++ b/src/indexers/electrum.rs @@ -174,7 +174,7 @@ impl Indexer for Client { } // build the WalletTx - return Ok(WalletTx { + Ok(WalletTx { txid, status, inputs, @@ -184,7 +184,7 @@ impl Indexer for Client { weight, version: tx.version, locktime: tx.lock_time, - }); + }) }; // build wallet transactions from script tx history, collecting indexer errors @@ -193,7 +193,7 @@ impl Indexer for Client { Ok(tx) => { cache.tx.insert(tx.txid, tx); } - Err(e) => errors.push(e.into()), + Err(e) => errors.push(e), } } diff --git a/src/persistence.rs b/src/persistence.rs index 121dd9e..f1a1dc8 100644 --- a/src/persistence.rs +++ b/src/persistence.rs @@ -183,7 +183,7 @@ pub mod fs { { fn load(&self) -> Result, PersistenceError> { let descr = fs::read_to_string(&self.descr).map_err(PersistenceError::with)?; - Ok(toml::from_str(&descr).map_err(PersistenceError::with)?) + toml::from_str(&descr).map_err(PersistenceError::with) } fn store(&self, object: &WalletDescr) -> Result<(), PersistenceError> { @@ -200,7 +200,7 @@ pub mod fs { { fn load(&self) -> Result, PersistenceError> { let file = fs::File::open(&self.cache).map_err(PersistenceError::with)?; - Ok(serde_yaml::from_reader(file).map_err(PersistenceError::with)?) + serde_yaml::from_reader(file).map_err(PersistenceError::with) } fn store(&self, object: &WalletCache) -> Result<(), PersistenceError> { @@ -217,7 +217,7 @@ pub mod fs { { fn load(&self) -> Result, PersistenceError> { let data = fs::read_to_string(&self.data).map_err(PersistenceError::with)?; - Ok(toml::from_str(&data).map_err(PersistenceError::with)?) + toml::from_str(&data).map_err(PersistenceError::with) } fn store(&self, object: &WalletData) -> Result<(), PersistenceError> { From bf54d1bb937c52aca70d2dc251808ff31f250a4d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 29 Aug 2024 23:32:25 +0200 Subject: [PATCH 05/14] chore: move persistence types into a dedicated lib --- Cargo.lock | 51 +++++++--- Cargo.toml | 5 + src/cli/command.rs | 5 +- src/fs.rs | 125 +++++++++++++++++++++++ src/layer2.rs | 2 +- src/lib.rs | 6 +- src/persistence.rs | 241 --------------------------------------------- src/wallet.rs | 4 +- 8 files changed, 173 insertions(+), 266 deletions(-) create mode 100644 src/fs.rs delete mode 100644 src/persistence.rs diff --git a/Cargo.lock b/Cargo.lock index 24de82d..93efdb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "amplify" +version = "5.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41888768802fc62c27b46427127b119e8a16e1f1f59495aced93a340f55eb25" +dependencies = [ + "amplify_derive", + "amplify_num", + "ascii", + "wasm-bindgen", +] + [[package]] name = "amplify_apfloat" version = "0.3.1" @@ -224,7 +236,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95dabc2759e01e2c382968639868a701f384a18890934f9e75d4feb4d6623794" dependencies = [ - "amplify", + "amplify 4.7.0", "base64", "mnemonic", "sha2", @@ -303,7 +315,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23ea438647522d1f1a8fc8fa1420cc56321433d5d5964636294991f18f9e0c9" dependencies = [ - "amplify", + "amplify 4.7.0", "chrono", "commit_verify", "secp256k1", @@ -317,7 +329,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5a1a38a6324920d6aa183245be6f13acde5aef1008b24fecf5f8e4036f33fe8" dependencies = [ - "amplify", + "amplify 4.7.0", "bp-consensus", "bp-invoice", "commit_verify", @@ -333,7 +345,7 @@ version = "0.11.0-beta.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760cbe0955ad8b3531e113b9c7917ab78646b979a0c1a450ce3c6356c7bd80e5" dependencies = [ - "amplify", + "amplify 4.7.0", "bp-std", "byteorder", "libc", @@ -352,7 +364,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b894bcb5df729d67e0d2a802fabbf889ff41cf8820cd35a468df36ac407f3630" dependencies = [ - "amplify", + "amplify 4.7.0", "bp-std", "log", "serde", @@ -367,7 +379,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddcb55fe081418fc6e508370eaf8431001274727862e57e6672b6c892b8d3d66" dependencies = [ - "amplify", + "amplify 4.7.0", "bech32", "bp-consensus", "commit_verify", @@ -380,7 +392,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac66f7e326e203f228fd60f1fb1f5629f6ec20f37c61b580a627c3194e85d7fc" dependencies = [ - "amplify", + "amplify 4.7.0", "bp-consensus", "bp-derive", "bp-invoice", @@ -397,7 +409,7 @@ name = "bp-wallet" version = "0.11.0-beta.7" dependencies = [ "aes-gcm", - "amplify", + "amplify 4.7.0", "base64", "bip39", "bp-electrum", @@ -408,6 +420,7 @@ dependencies = [ "descriptors", "env_logger", "log", + "nonasync", "psbt", "rand", "rpassword", @@ -565,7 +578,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca41bd14a6c400486463a5b0e7e8916b1c7bad554a382f62c3b11bd58dea5934" dependencies = [ - "amplify", + "amplify 4.7.0", "amplify_syn", "proc-macro2", "quote", @@ -578,7 +591,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "538b69bbb2f7259c1d07334fa8adae0006c8b559efbdb6daafacb6df249b897b" dependencies = [ - "amplify", + "amplify 4.7.0", "commit_encoding_derive", "ripemd", "sha2", @@ -688,7 +701,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b6883b7b23aedc5a2ff149a54b0f57496935be3fd4b453f0c76361b08f5649" dependencies = [ - "amplify", + "amplify 4.7.0", "bp-derive", "indexmap 2.4.0", "serde", @@ -1107,6 +1120,14 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonasync" +version = "0.1.0" +source = "git+https://github.com/rust-amplify/amplify-nonasync#31e8b00cc791b7ab64f2b0b97ae9e16a8bc5e8f6" +dependencies = [ + "amplify 5.0.0-beta.1", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1204,7 +1225,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2afd0eef00260666da5c2c72e61c23bd2e07076e0791ea4cc3e79ab408246a17" dependencies = [ - "amplify", + "amplify 4.7.0", "base64", "bp-derive", "chrono", @@ -1559,7 +1580,7 @@ version = "2.7.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "662b8c3ff360ff33370e6875dd5bdcbf3cecc992241f30e5f7071227ef693451" dependencies = [ - "amplify", + "amplify 4.7.0", "half", "strict_encoding_derive", "wasm-bindgen", @@ -1584,7 +1605,7 @@ version = "2.7.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e182f593e9c4f02ccfcea7929bef866cff12a3f8e213338ce48a706bb263c1" dependencies = [ - "amplify", + "amplify 4.7.0", "baid64", "half", "indexmap 2.4.0", @@ -1835,7 +1856,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f72ebd3b32f16ee8ace2bd3058c2bfa0f4820992bd4ea86e73ba228bb13dd2b0" dependencies = [ - "amplify", + "amplify 4.7.0", "strict_encoding", ] diff --git a/Cargo.toml b/Cargo.toml index d632e42..a74b473 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ license = "Apache-2.0" [workspace.dependencies] amplify = "4.7.0" +nonasync = "0.1.0" bp-std = "0.11.0-beta.7" psbt = "0.11.0-beta.7" descriptors = "0.11.0-beta.7" @@ -54,6 +55,7 @@ name = "bpwallet" [dependencies] amplify = { workspace = true } +nonasync = { workspace = true } strict_encoding = "2.7.0-beta.4" bp-std = { workspace = true } bp-esplora = { workspace = true, optional = true } @@ -91,3 +93,6 @@ esplora = ["bp-esplora"] mempool = ["esplora"] fs = ["serde"] serde = ["serde_crate", "serde_yaml", "toml", "bp-std/serde"] + +[patch.crates-io] +nonasync = { git = "https://github.com/rust-amplify/amplify-nonasync" } diff --git a/src/cli/command.rs b/src/cli/command.rs index 27eff5f..3fd6de6 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -30,14 +30,13 @@ use bpstd::psbt::{Beneficiary, TxParams}; use bpstd::{ConsensusEncode, Derive, IdxBase, Keychain, NormalIndex, Sats, Tx, XpubDerivable}; use colored::Colorize; use descriptors::{Descriptor, StdDescr}; +use nonasync::persistence::PersistenceError; use psbt::{ConstructionError, Payment, Psbt, PsbtConstructor, PsbtVer, UnfinalizedInputs}; use strict_encoding::Ident; use crate::cli::{Args, Config, DescriptorOpts, Exec}; use crate::fs::FsTextStore; -use crate::{ - coinselect, AnyIndexerError, Indexer, OpType, PersistenceError, Wallet, WalletAddr, WalletUtxo, -}; +use crate::{coinselect, AnyIndexerError, Indexer, OpType, Wallet, WalletAddr, WalletUtxo}; #[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] pub enum Command { diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..281d4ee --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,125 @@ +// Modern, minimalistic & standard-compliant cold wallet library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2020-2024 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2020-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2020-2024 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::fs; +use std::path::PathBuf; + +use descriptors::Descriptor; +use nonasync::persistence::{PersistenceError, PersistenceProvider}; + +use super::*; +use crate::{ + Layer2Cache, Layer2Data, Layer2Descriptor, NoLayer2, WalletCache, WalletData, WalletDescr, +}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct FsTextStore { + pub descr: PathBuf, + pub data: PathBuf, + pub cache: PathBuf, + pub l2: PathBuf, +} + +impl FsTextStore { + pub fn new(path: PathBuf) -> Self { + let mut descr = path.clone(); + descr.push("descriptor.toml"); + let mut data = path.clone(); + data.push("data.toml"); + let mut cache = path.clone(); + cache.push("cache.yaml"); + let mut l2 = path; + l2.push("layer2.yaml"); + + Self { + descr, + data, + cache, + l2, + } + } +} + +impl, L2: Layer2Descriptor> PersistenceProvider> + for FsTextStore +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>, +{ + fn load(&self) -> Result, PersistenceError> { + let descr = fs::read_to_string(&self.descr).map_err(PersistenceError::with)?; + toml::from_str(&descr).map_err(PersistenceError::with) + } + + fn store(&self, object: &WalletDescr) -> Result<(), PersistenceError> { + let s = toml::to_string_pretty(object).map_err(PersistenceError::with)?; + fs::write(&self.descr, s).map_err(PersistenceError::with)?; + Ok(()) + } +} + +impl PersistenceProvider> for FsTextStore +where + for<'de> WalletCache: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2: serde::Serialize + serde::Deserialize<'de>, +{ + fn load(&self) -> Result, PersistenceError> { + let file = fs::File::open(&self.cache).map_err(PersistenceError::with)?; + serde_yaml::from_reader(file).map_err(PersistenceError::with) + } + + fn store(&self, object: &WalletCache) -> Result<(), PersistenceError> { + let file = fs::File::create(&self.cache).map_err(PersistenceError::with)?; + serde_yaml::to_writer(file, object).map_err(PersistenceError::with)?; + Ok(()) + } +} + +impl PersistenceProvider> for FsTextStore +where + for<'de> WalletData: serde::Serialize + serde::Deserialize<'de>, + for<'de> L2: serde::Serialize + serde::Deserialize<'de>, +{ + fn load(&self) -> Result, PersistenceError> { + let data = fs::read_to_string(&self.data).map_err(PersistenceError::with)?; + toml::from_str(&data).map_err(PersistenceError::with) + } + + fn store(&self, object: &WalletData) -> Result<(), PersistenceError> { + let s = toml::to_string_pretty(object).map_err(PersistenceError::with)?; + fs::write(&self.data, s).map_err(PersistenceError::with)?; + Ok(()) + } +} + +impl PersistenceProvider for FsTextStore { + fn load(&self) -> Result { + // Nothing to do + Ok(none!()) + } + + fn store(&self, _: &NoLayer2) -> Result<(), PersistenceError> { + // Nothing to do + Ok(()) + } +} diff --git a/src/layer2.rs b/src/layer2.rs index db5d325..6b7c67f 100644 --- a/src/layer2.rs +++ b/src/layer2.rs @@ -24,7 +24,7 @@ use std::convert::Infallible; use std::error; use std::fmt::Debug; -use crate::{Persistence, Persisting}; +use nonasync::persistence::{Persistence, Persisting}; pub trait Layer2: Debug + Persisting { type Descr: Layer2Descriptor; diff --git a/src/lib.rs b/src/lib.rs index 132984e..c6d82a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,8 @@ pub mod cli; #[cfg(feature = "hot")] pub mod hot; mod bip43; -mod persistence; +#[cfg(feature = "fs")] +pub mod fs; pub use bip43::{Bip43, DerivationStandard, ParseBip43Error}; pub use data::{ @@ -60,9 +61,6 @@ pub use indexers::{AnyIndexer, AnyIndexerError}; pub use layer2::{ Layer2, Layer2Cache, Layer2Coin, Layer2Data, Layer2Descriptor, Layer2Tx, NoLayer2, }; -#[cfg(feature = "fs")] -pub use persistence::fs; -pub use persistence::{Persistence, PersistenceError, PersistenceProvider, Persisting}; pub use rows::{CoinRow, Counterparty, OpType, TxRow}; pub use util::MayError; pub use wallet::{Wallet, WalletCache, WalletData, WalletDescr}; diff --git a/src/persistence.rs b/src/persistence.rs deleted file mode 100644 index f1a1dc8..0000000 --- a/src/persistence.rs +++ /dev/null @@ -1,241 +0,0 @@ -// Modern, minimalistic & standard-compliant cold wallet library. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2020-2024 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2020-2024 LNP/BP Standards Association. All rights reserved. -// Copyright (C) 2020-2024 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::error::Error; -use std::fmt::Debug; - -#[derive(Debug, Display, Error)] -#[display(inner)] -pub struct PersistenceError(pub Box); - -impl PersistenceError { - pub fn with(e: E) -> Self { Self(Box::new(e)) } -} - -pub trait PersistenceProvider: Send + Debug { - fn load(&self) -> Result; - fn store(&self, object: &T) -> Result<(), PersistenceError>; -} - -#[derive(Debug)] -pub struct Persistence { - pub dirty: bool, - pub autosave: bool, - pub provider: Box>, -} - -impl Persistence { - pub fn load( - provider: impl PersistenceProvider + 'static, - autosave: bool, - ) -> Result { - let mut obj: T = provider.load()?; - let mut me = Self { - dirty: false, - autosave, - provider: Box::new(provider), - }; - obj.persistence_mut().replace(&mut me); - Ok(obj) - } -} - -pub trait Persisting: Sized { - #[inline] - fn load( - provider: impl PersistenceProvider + 'static, - autosave: bool, - ) -> Result { - Persistence::load(provider, autosave) - } - - fn persistence(&self) -> Option<&Persistence>; - - fn persistence_mut(&mut self) -> Option<&mut Persistence>; - - fn is_persisted(&self) -> bool { self.persistence().is_some() } - - fn is_dirty(&self) -> bool { self.persistence().map(|p| p.autosave).unwrap_or(true) } - - fn mark_dirty(&mut self) { - if let Some(p) = self.persistence_mut() { - p.dirty = true; - } - if let Some(p) = self.persistence() { - if p.autosave { - if let Err(e) = p.provider.store(self) { - #[cfg(feature = "log")] - log::error!( - "Unable to autosave a dirty object on Persisting::mark_dirty call. \ - Details: {e}" - ); - } - } - } - } - - fn is_autosave(&self) -> bool { self.persistence().map(|p| p.autosave).unwrap_or_default() } - - fn set_autosave(&mut self) { - if let Err(e) = self.store() { - #[cfg(feature = "log")] - log::error!( - "Unable to autosave a dirty object on Persisting::set_autosave call. Details: {e}" - ); - } - } - - /// Returns whether the object was persisting before this method. - fn make_persistent( - &mut self, - provider: impl PersistenceProvider + 'static, - autosave: bool, - ) -> Result { - let was_persisted = self.is_persisted(); - let mut me = Persistence { - dirty: false, - autosave, - provider: Box::new(provider), - }; - self.persistence_mut().replace(&mut me); - self.mark_dirty(); - Ok(was_persisted) - } - - fn store(&mut self) -> Result<(), PersistenceError> { - if self.is_dirty() { - if let Some(p) = self.persistence() { - p.provider.store(self)?; - } - if let Some(p) = self.persistence_mut() { - p.dirty = false; - } - } - Ok(()) - } -} - -#[cfg(feature = "fs")] -pub mod fs { - use std::fs; - use std::path::PathBuf; - - use descriptors::Descriptor; - - use super::*; - use crate::{ - Layer2Cache, Layer2Data, Layer2Descriptor, NoLayer2, WalletCache, WalletData, WalletDescr, - }; - - #[derive(Clone, Eq, PartialEq, Debug)] - pub struct FsTextStore { - pub descr: PathBuf, - pub data: PathBuf, - pub cache: PathBuf, - pub l2: PathBuf, - } - - impl FsTextStore { - pub fn new(path: PathBuf) -> Self { - let mut descr = path.clone(); - descr.push("descriptor.toml"); - let mut data = path.clone(); - data.push("data.toml"); - let mut cache = path.clone(); - cache.push("cache.yaml"); - let mut l2 = path; - l2.push("layer2.yaml"); - - Self { - descr, - data, - cache, - l2, - } - } - } - - impl, L2: Layer2Descriptor> PersistenceProvider> - for FsTextStore - 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>, - { - fn load(&self) -> Result, PersistenceError> { - let descr = fs::read_to_string(&self.descr).map_err(PersistenceError::with)?; - toml::from_str(&descr).map_err(PersistenceError::with) - } - - fn store(&self, object: &WalletDescr) -> Result<(), PersistenceError> { - let s = toml::to_string_pretty(object).map_err(PersistenceError::with)?; - fs::write(&self.descr, s).map_err(PersistenceError::with)?; - Ok(()) - } - } - - impl PersistenceProvider> for FsTextStore - where - for<'de> WalletCache: serde::Serialize + serde::Deserialize<'de>, - for<'de> L2: serde::Serialize + serde::Deserialize<'de>, - { - fn load(&self) -> Result, PersistenceError> { - let file = fs::File::open(&self.cache).map_err(PersistenceError::with)?; - serde_yaml::from_reader(file).map_err(PersistenceError::with) - } - - fn store(&self, object: &WalletCache) -> Result<(), PersistenceError> { - let file = fs::File::create(&self.cache).map_err(PersistenceError::with)?; - serde_yaml::to_writer(file, object).map_err(PersistenceError::with)?; - Ok(()) - } - } - - impl PersistenceProvider> for FsTextStore - where - for<'de> WalletData: serde::Serialize + serde::Deserialize<'de>, - for<'de> L2: serde::Serialize + serde::Deserialize<'de>, - { - fn load(&self) -> Result, PersistenceError> { - let data = fs::read_to_string(&self.data).map_err(PersistenceError::with)?; - toml::from_str(&data).map_err(PersistenceError::with) - } - - fn store(&self, object: &WalletData) -> Result<(), PersistenceError> { - let s = toml::to_string_pretty(object).map_err(PersistenceError::with)?; - fs::write(&self.data, s).map_err(PersistenceError::with)?; - Ok(()) - } - } - - impl PersistenceProvider for FsTextStore { - fn load(&self) -> Result { - // Nothing to do - Ok(none!()) - } - - fn store(&self, _: &NoLayer2) -> Result<(), PersistenceError> { - // Nothing to do - Ok(()) - } - } -} diff --git a/src/wallet.rs b/src/wallet.rs index b8b1f07..b55e409 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -29,12 +29,12 @@ use bpstd::{ Address, AddressNetwork, DerivedAddr, Descriptor, Idx, IdxBase, Keychain, Network, NormalIndex, Outpoint, Sats, Txid, Vout, }; +use nonasync::persistence::{Persistence, PersistenceError, PersistenceProvider, Persisting}; use psbt::{PsbtConstructor, Utxo}; use crate::{ BlockInfo, CoinRow, Indexer, Layer2, Layer2Cache, Layer2Data, Layer2Descriptor, MayError, - MiningInfo, NoLayer2, Persistence, PersistenceError, PersistenceProvider, Persisting, TxRow, - WalletAddr, WalletTx, WalletUtxo, + MiningInfo, NoLayer2, TxRow, WalletAddr, WalletTx, WalletUtxo, }; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] From 01a6a165c03ff9ebd5e9e38fed87326265c6b6e0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 30 Aug 2024 01:56:18 +0200 Subject: [PATCH 06/14] wallet: add WalletDescr::with_descriptor_mut method --- src/wallet.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/wallet.rs b/src/wallet.rs index b55e409..cb9a70d 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -129,6 +129,15 @@ impl, L2: Layer2Descriptor> WalletDescr { _phantom: PhantomData, } } + + pub fn with_descriptor_mut( + &mut self, + f: impl FnOnce(&mut D) -> Result<(), E>, + ) -> Result<(), E> { + f(&mut self.generator)?; + self.mark_dirty(); + Ok(()) + } } impl, L2: Layer2Descriptor> Deref for WalletDescr { From bfcaebd91d820484d101342ad448b1434fd21a8a Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 30 Aug 2024 04:02:24 +0200 Subject: [PATCH 07/14] chore: update nonasync --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 93efdb3..31ecec0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,7 +1123,7 @@ dependencies = [ [[package]] name = "nonasync" version = "0.1.0" -source = "git+https://github.com/rust-amplify/amplify-nonasync#31e8b00cc791b7ab64f2b0b97ae9e16a8bc5e8f6" +source = "git+https://github.com/rust-amplify/amplify-nonasync#9c6ab8f0e19d80cc787633bad328e7817c256de4" dependencies = [ "amplify 5.0.0-beta.1", ] From 2aad13e1b97cb1936d1e1de429f8c65a18176895 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 30 Aug 2024 14:32:26 +0200 Subject: [PATCH 08/14] wallet: impl CloneNoPersistence for wallet structures --- Cargo.lock | 88 ++++++++++++++++++++-------------------- src/cli/args.rs | 2 +- src/cli/opts.rs | 2 +- src/fs.rs | 2 +- src/indexers/any.rs | 4 +- src/indexers/electrum.rs | 4 +- src/indexers/esplora.rs | 4 +- src/indexers/mod.rs | 4 +- src/layer2.rs | 14 ++++--- src/wallet.rs | 87 ++++++++++++++++++++++++++++++++------- 10 files changed, 136 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee793dc..462482d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,10 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -282,7 +282,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.75", + "syn 2.0.76", "which", ] @@ -338,7 +338,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b43a6b5389fd99298fca47f4566dda754fa10bdb41200b5cdfc16147400f5951" dependencies = [ - "amplify", + "amplify 4.7.0", "bp-consensus", "bp-dbc", "bp-seals", @@ -355,7 +355,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f70bd407dfcbeadfbc012959e04decbe63e7dc6f30a4f705b804b699b37a0a" dependencies = [ - "amplify", + "amplify 4.7.0", "base85", "bp-consensus", "commit_verify", @@ -432,7 +432,7 @@ version = "0.11.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497bb5989b9b549cd8e4ebfabc50a5d57cf318ab56affd3d4d7b7749fcd780da" dependencies = [ - "amplify", + "amplify 4.7.0", "baid64", "bp-consensus", "bp-dbc", @@ -504,9 +504,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.13" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ "jobserver", "libc", @@ -595,7 +595,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -729,7 +729,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -740,7 +740,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -850,9 +850,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide", @@ -1149,11 +1149,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1181,7 +1181,7 @@ dependencies = [ [[package]] name = "nonasync" version = "0.1.0" -source = "git+https://github.com/rust-amplify/amplify-nonasync#9c6ab8f0e19d80cc787633bad328e7817c256de4" +source = "git+https://github.com/rust-amplify/amplify-nonasync#d52db387df2282a73984d2d5ef238135d5267930" dependencies = [ "amplify 5.0.0-beta.1", ] @@ -1260,12 +1260,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1336,9 +1336,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -1427,9 +1427,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags", "errno", @@ -1462,9 +1462,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" dependencies = [ "aws-lc-rs", "ring", @@ -1500,29 +1500,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", @@ -1576,7 +1576,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1720,9 +1720,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -1746,7 +1746,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1958,7 +1958,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", "wasm-bindgen-shared", ] @@ -1980,7 +1980,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2209,7 +2209,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2229,5 +2229,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] diff --git a/src/cli/args.rs b/src/cli/args.rs index 69acc61..d503397 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -116,7 +116,7 @@ impl Args { } #[allow(clippy::multiple_bound_locations)] - pub fn bp_wallet( + pub fn bp_wallet( &self, conf: &Config, ) -> Result, ExecError> diff --git a/src/cli/opts.rs b/src/cli/opts.rs index 224931e..44868ae 100644 --- a/src/cli/opts.rs +++ b/src/cli/opts.rs @@ -90,7 +90,7 @@ pub struct ResolverOpt { } pub trait DescriptorOpts: clap::Args + Clone + Eq + Debug { - type Descr: Descriptor + Display + serde::Serialize + for<'de> serde::Deserialize<'de>; + type Descr: Descriptor + Clone + Display + serde::Serialize + for<'de> serde::Deserialize<'de>; fn is_some(&self) -> bool; fn descriptor(&self) -> Option; } diff --git a/src/fs.rs b/src/fs.rs index 281d4ee..ebacf10 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -59,7 +59,7 @@ impl FsTextStore { } } -impl, L2: Layer2Descriptor> PersistenceProvider> +impl + Clone, L2: Layer2Descriptor> PersistenceProvider> for FsTextStore where for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, diff --git a/src/indexers/any.rs b/src/indexers/any.rs index 950def4..19e4699 100644 --- a/src/indexers/any.rs +++ b/src/indexers/any.rs @@ -72,7 +72,7 @@ pub enum AnyIndexerError { impl Indexer for AnyIndexer { type Error = AnyIndexerError; - fn create, L2: Layer2>( + fn create + Clone, L2: Layer2>( &self, descr: &WalletDescr, ) -> MayError, Vec> { @@ -104,7 +104,7 @@ impl Indexer for AnyIndexer { } } - fn update, L2: Layer2>( + fn update + Clone, L2: Layer2>( &self, descr: &WalletDescr, cache: &mut WalletCache, diff --git a/src/indexers/electrum.rs b/src/indexers/electrum.rs index 14b264c..5f0202f 100644 --- a/src/indexers/electrum.rs +++ b/src/indexers/electrum.rs @@ -62,7 +62,7 @@ pub enum ElectrumError { impl Indexer for Client { type Error = ElectrumError; - fn create, L2: Layer2>( + fn create + Clone, L2: Layer2>( &self, descriptor: &WalletDescr, ) -> MayError, Vec> { @@ -274,7 +274,7 @@ impl Indexer for Client { if errors.is_empty() { MayError::ok(cache) } else { MayError::err(cache, errors) } } - fn update, L2: Layer2>( + fn update + Clone, L2: Layer2>( &self, _descr: &WalletDescr, _cache: &mut WalletCache, diff --git a/src/indexers/esplora.rs b/src/indexers/esplora.rs index b0cb46d..2274cb2 100644 --- a/src/indexers/esplora.rs +++ b/src/indexers/esplora.rs @@ -192,7 +192,7 @@ fn get_scripthash_txs_all( impl Indexer for Client { type Error = Error; - fn create, L2: Layer2>( + fn create + Clone, L2: Layer2>( &self, descriptor: &WalletDescr, ) -> MayError, Vec> { @@ -305,7 +305,7 @@ impl Indexer for Client { if errors.is_empty() { MayError::ok(cache) } else { MayError::err(cache, errors) } } - fn update, L2: Layer2>( + fn update + Clone, L2: Layer2>( &self, _descr: &WalletDescr, _cache: &mut WalletCache, diff --git a/src/indexers/mod.rs b/src/indexers/mod.rs index a120ec6..2db027c 100644 --- a/src/indexers/mod.rs +++ b/src/indexers/mod.rs @@ -42,12 +42,12 @@ const BATCH_SIZE: usize = 10; pub trait Indexer { type Error; - fn create, L2: Layer2>( + fn create + Clone, L2: Layer2>( &self, descr: &WalletDescr, ) -> MayError, Vec>; - fn update, L2: Layer2>( + fn update + Clone, L2: Layer2>( &self, descr: &WalletDescr, cache: &mut WalletCache, diff --git a/src/layer2.rs b/src/layer2.rs index 6b7c67f..153cbf3 100644 --- a/src/layer2.rs +++ b/src/layer2.rs @@ -24,9 +24,9 @@ use std::convert::Infallible; use std::error; use std::fmt::Debug; -use nonasync::persistence::{Persistence, Persisting}; +use nonasync::persistence::{CloneNoPersistence, Persistence, Persisting}; -pub trait Layer2: Debug + Persisting { +pub trait Layer2: Debug + CloneNoPersistence + Persisting { type Descr: Layer2Descriptor; type Data: Layer2Data; type Cache: Layer2Cache; @@ -34,17 +34,17 @@ pub trait Layer2: Debug + Persisting { type StoreError: error::Error; } -pub trait Layer2Descriptor: Debug { +pub trait Layer2Descriptor: Debug + CloneNoPersistence { type LoadError: error::Error; type StoreError: error::Error; } -pub trait Layer2Data: Debug + Default { +pub trait Layer2Data: Debug + CloneNoPersistence + Default { type LoadError: error::Error; type StoreError: error::Error; } -pub trait Layer2Cache: Debug + Default { +pub trait Layer2Cache: Debug + CloneNoPersistence + Default { type LoadError: error::Error; type StoreError: error::Error; @@ -86,6 +86,10 @@ pub struct NoLayer2 { persistence: Option>, } +impl CloneNoPersistence for NoLayer2 { + fn clone_no_persistence(&self) -> Self { none!() } +} + impl Persisting for NoLayer2 { #[inline] fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } diff --git a/src/wallet.rs b/src/wallet.rs index cb9a70d..aa8c3bc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -29,7 +29,9 @@ use bpstd::{ Address, AddressNetwork, DerivedAddr, Descriptor, Idx, IdxBase, Keychain, Network, NormalIndex, Outpoint, Sats, Txid, Vout, }; -use nonasync::persistence::{Persistence, PersistenceError, PersistenceProvider, Persisting}; +use nonasync::persistence::{ + CloneNoPersistence, Persistence, PersistenceError, PersistenceProvider, Persisting, +}; use psbt::{PsbtConstructor, Utxo}; use crate::{ @@ -82,7 +84,7 @@ impl<'descr, K, D: Descriptor> Iterator for AddrIter<'descr, K, D> { #[derive(Getters, Debug)] pub struct WalletDescr where - D: Descriptor, + D: Descriptor + Clone, L2: Layer2Descriptor, { #[getter(skip)] @@ -97,7 +99,7 @@ where _phantom: PhantomData, } -impl> WalletDescr { +impl + Clone> WalletDescr { pub fn new_standard(descr: D, network: Network) -> Self { WalletDescr { persistence: None, @@ -109,7 +111,7 @@ impl> WalletDescr { } } -impl, L2: Layer2Descriptor> WalletDescr { +impl + Clone, L2: Layer2Descriptor> WalletDescr { pub fn new_layer2(descr: D, layer2: L2, network: Network) -> Self { WalletDescr { persistence: None, @@ -140,20 +142,34 @@ impl, L2: Layer2Descriptor> WalletDescr { } } -impl, L2: Layer2Descriptor> Deref for WalletDescr { +impl + Clone, L2: Layer2Descriptor> Deref for WalletDescr { type Target = D; fn deref(&self) -> &Self::Target { &self.generator } } -impl, L2: Layer2Descriptor> Persisting for WalletDescr { +impl + Clone, L2: Layer2Descriptor> CloneNoPersistence + for WalletDescr +{ + fn clone_no_persistence(&self) -> Self { + Self { + persistence: None, + generator: self.generator.clone(), + network: self.network, + layer2: self.layer2.clone_no_persistence(), + _phantom: PhantomData, + } + } +} + +impl + Clone, L2: Layer2Descriptor> Persisting for WalletDescr { #[inline] fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } #[inline] fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } } -impl, L2: Layer2Descriptor> Drop for WalletDescr { +impl + Clone, L2: Layer2Descriptor> Drop for WalletDescr { fn drop(&mut self) { if self.is_autosave() { if let Err(e) = self.store() { @@ -190,6 +206,21 @@ pub struct WalletData { pub last_used: BTreeMap, } +impl CloneNoPersistence for WalletData { + fn clone_no_persistence(&self) -> Self { + Self { + persistence: None, + name: self.name.clone(), + tx_annotations: self.tx_annotations.clone(), + txout_annotations: self.txout_annotations.clone(), + txin_annotations: self.txin_annotations.clone(), + addr_annotations: self.addr_annotations.clone(), + layer2_annotations: self.layer2_annotations.clone_no_persistence(), + last_used: self.last_used.clone(), + } + } +} + impl Persisting for WalletData { #[inline] fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } @@ -251,14 +282,14 @@ impl WalletCache { } } - pub fn with, L2: Layer2>( + pub fn with + Clone, L2: Layer2>( descriptor: &WalletDescr, indexer: &I, ) -> MayError> { indexer.create::(descriptor) } - pub fn update, L2: Layer2>( + pub fn update + Clone, L2: Layer2>( &mut self, descriptor: &WalletDescr, indexer: &I, @@ -307,6 +338,21 @@ impl WalletCache { } } +impl CloneNoPersistence for WalletCache { + fn clone_no_persistence(&self) -> Self { + Self { + persistence: None, + last_block: self.last_block.clone(), + last_change: self.last_change.clone(), + headers: self.headers.clone(), + tx: self.tx.clone(), + utxo: self.utxo.clone(), + addr: self.addr.clone(), + layer2: self.layer2.clone_no_persistence(), + } + } +} + impl Persisting for WalletCache { #[inline] fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } @@ -328,20 +374,31 @@ impl Drop for WalletCache { } #[derive(Debug)] -pub struct Wallet, L2: Layer2 = NoLayer2> { +pub struct Wallet + Clone, L2: Layer2 = NoLayer2> { descr: WalletDescr, data: WalletData, cache: WalletCache, layer2: L2, } -impl, L2: Layer2> Deref for Wallet { +impl + Clone, L2: Layer2> Deref for Wallet { type Target = WalletDescr; fn deref(&self) -> &Self::Target { &self.descr } } -impl, L2: Layer2> PsbtConstructor for Wallet { +impl + Clone, L2: Layer2> CloneNoPersistence for Wallet { + fn clone_no_persistence(&self) -> Self { + Self { + descr: self.descr.clone_no_persistence(), + data: self.data.clone_no_persistence(), + cache: self.cache.clone_no_persistence(), + layer2: self.layer2.clone_no_persistence(), + } + } +} + +impl + Clone, L2: Layer2> PsbtConstructor for Wallet { type Key = K; type Descr = D; @@ -366,7 +423,7 @@ impl, L2: Layer2> PsbtConstructor for Wallet { } } -impl> Wallet { +impl + Clone> Wallet { pub fn new_layer1(descr: D, network: Network) -> Self { Wallet { descr: WalletDescr::new_standard(descr, network), @@ -377,7 +434,7 @@ impl> Wallet { } } -impl, L2: Layer2> Wallet { +impl + Clone, L2: Layer2> 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), @@ -497,7 +554,7 @@ impl, L2: Layer2> Wallet { } } -impl, L2: Layer2> Wallet { +impl + Clone, L2: Layer2> Wallet { pub fn load

(provider: P, autosave: bool) -> Result, PersistenceError> where P: Clone + PersistenceProvider> From 27e89851af1d3cb4c5aaeaeec68425bf42cd50d7 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 31 Aug 2024 17:53:11 +0200 Subject: [PATCH 09/14] persistence: fix invalid persistence replacement --- Cargo.lock | 2 +- src/layer2.rs | 2 ++ src/wallet.rs | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 462482d..81f70c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,7 @@ dependencies = [ [[package]] name = "nonasync" version = "0.1.0" -source = "git+https://github.com/rust-amplify/amplify-nonasync#d52db387df2282a73984d2d5ef238135d5267930" +source = "git+https://github.com/rust-amplify/amplify-nonasync#b1bf3542060beabe3dcdf66517be6ee3eb2ac302" dependencies = [ "amplify 5.0.0-beta.1", ] diff --git a/src/layer2.rs b/src/layer2.rs index 153cbf3..3d7f2d6 100644 --- a/src/layer2.rs +++ b/src/layer2.rs @@ -95,6 +95,8 @@ impl Persisting for NoLayer2 { fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } #[inline] fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } + #[inline] + fn as_mut_persistence(&mut self) -> &mut Option> { &mut self.persistence } } impl Layer2 for NoLayer2 { diff --git a/src/wallet.rs b/src/wallet.rs index aa8c3bc..35843a2 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -167,6 +167,8 @@ impl + Clone, L2: Layer2Descriptor> Persisting for WalletDes fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } #[inline] fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } + #[inline] + fn as_mut_persistence(&mut self) -> &mut Option> { &mut self.persistence } } impl + Clone, L2: Layer2Descriptor> Drop for WalletDescr { @@ -226,6 +228,8 @@ impl Persisting for WalletData { fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } #[inline] fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } + #[inline] + fn as_mut_persistence(&mut self) -> &mut Option> { &mut self.persistence } } impl Drop for WalletData { @@ -358,6 +362,8 @@ impl Persisting for WalletCache { fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } #[inline] fn persistence_mut(&mut self) -> Option<&mut Persistence> { self.persistence.as_mut() } + #[inline] + fn as_mut_persistence(&mut self) -> &mut Option> { &mut self.persistence } } impl Drop for WalletCache { From 25edc2c2d0b66481fcae40e6039ed5b6cbe65627 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 31 Aug 2024 18:06:29 +0200 Subject: [PATCH 10/14] persistence: fix invalid make_persistence implementation --- src/wallet.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/wallet.rs b/src/wallet.rs index 35843a2..77f2b86 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -594,10 +594,11 @@ impl + Clone, L2: Layer2> Wallet { + PersistenceProvider + 'static, { - Ok(self.descr.make_persistent(provider.clone(), autosave)? - && self.data.make_persistent(provider.clone(), autosave)? - && self.cache.make_persistent(provider.clone(), autosave)? - && self.layer2.make_persistent(provider, autosave)?) + let a = self.descr.make_persistent(provider.clone(), autosave)?; + let b = self.data.make_persistent(provider.clone(), autosave)?; + let c = self.cache.make_persistent(provider.clone(), autosave)?; + let d = self.layer2.make_persistent(provider, autosave)?; + Ok(a && b && c && d) } pub fn store(&mut self) -> Result<(), PersistenceError> { From 6630037a99b3500873d844823fa3d50852303c96 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 1 Sep 2024 11:34:45 +0200 Subject: [PATCH 11/14] wallet: remove Clone+Display bounds for descriptors (moved to bp-std) --- Cargo.lock | 10 +++++----- Cargo.toml | 10 +++++----- src/cli/args.rs | 2 +- src/cli/opts.rs | 4 ++-- src/fs.rs | 2 +- src/indexers/any.rs | 4 ++-- src/indexers/electrum.rs | 4 ++-- src/indexers/esplora.rs | 4 ++-- src/indexers/mod.rs | 4 ++-- src/wallet.rs | 34 ++++++++++++++++------------------ 10 files changed, 38 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b0ffc3..aaace4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,7 @@ dependencies = [ [[package]] name = "bp-derive" version = "0.11.0-beta.7" -source = "git+https://github.com/BP-WG/bp-std?branch=master#c3d2b8f8ca356c37f42829eb0f3e811b0ff3ceb5" +source = "git+https://github.com/BP-WG/bp-std?branch=store#efb5b6779b134208e4bca79cb55828de8094b821" dependencies = [ "amplify 4.7.0", "bp-consensus", @@ -415,7 +415,7 @@ dependencies = [ [[package]] name = "bp-invoice" version = "0.11.0-beta.7" -source = "git+https://github.com/BP-WG/bp-std?branch=master#c3d2b8f8ca356c37f42829eb0f3e811b0ff3ceb5" +source = "git+https://github.com/BP-WG/bp-std?branch=store#efb5b6779b134208e4bca79cb55828de8094b821" dependencies = [ "amplify 4.7.0", "bech32", @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "bp-std" version = "0.11.0-beta.7" -source = "git+https://github.com/BP-WG/bp-std?branch=master#c3d2b8f8ca356c37f42829eb0f3e811b0ff3ceb5" +source = "git+https://github.com/BP-WG/bp-std?branch=store#efb5b6779b134208e4bca79cb55828de8094b821" dependencies = [ "amplify 4.7.0", "bp-consensus", @@ -753,7 +753,7 @@ dependencies = [ [[package]] name = "descriptors" version = "0.11.0-beta.7" -source = "git+https://github.com/BP-WG/bp-std?branch=master#c3d2b8f8ca356c37f42829eb0f3e811b0ff3ceb5" +source = "git+https://github.com/BP-WG/bp-std?branch=store#efb5b6779b134208e4bca79cb55828de8094b821" dependencies = [ "amplify 4.7.0", "bp-derive", @@ -1276,7 +1276,7 @@ dependencies = [ [[package]] name = "psbt" version = "0.11.0-beta.7" -source = "git+https://github.com/BP-WG/bp-std?branch=master#c3d2b8f8ca356c37f42829eb0f3e811b0ff3ceb5" +source = "git+https://github.com/BP-WG/bp-std?branch=store#efb5b6779b134208e4bca79cb55828de8094b821" dependencies = [ "amplify 4.7.0", "base64", diff --git a/Cargo.toml b/Cargo.toml index 1b7639c..60167cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,8 +98,8 @@ serde = ["serde_crate", "serde_yaml", "toml", "bp-std/serde", "psbt/serde", "des [patch.crates-io] nonasync = { git = "https://github.com/rust-amplify/amplify-nonasync" } -bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "master" } -bp-derive = { git = "https://github.com/BP-WG/bp-std", branch = "master" } -descriptors = { git = "https://github.com/BP-WG/bp-std", branch = "master" } -psbt = { git = "https://github.com/BP-WG/bp-std", branch = "master" } -bp-std = { git = "https://github.com/BP-WG/bp-std", branch = "master" } \ No newline at end of file +bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "store" } +bp-derive = { git = "https://github.com/BP-WG/bp-std", branch = "store" } +descriptors = { git = "https://github.com/BP-WG/bp-std", branch = "store" } +psbt = { git = "https://github.com/BP-WG/bp-std", branch = "store" } +bp-std = { git = "https://github.com/BP-WG/bp-std", branch = "store" } \ No newline at end of file diff --git a/src/cli/args.rs b/src/cli/args.rs index d503397..69acc61 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -116,7 +116,7 @@ impl Args { } #[allow(clippy::multiple_bound_locations)] - pub fn bp_wallet( + pub fn bp_wallet( &self, conf: &Config, ) -> Result, ExecError> diff --git a/src/cli/opts.rs b/src/cli/opts.rs index 44868ae..67ff9b0 100644 --- a/src/cli/opts.rs +++ b/src/cli/opts.rs @@ -20,7 +20,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::path::{Path, PathBuf}; use bpstd::{Network, XpubDerivable}; @@ -90,7 +90,7 @@ pub struct ResolverOpt { } pub trait DescriptorOpts: clap::Args + Clone + Eq + Debug { - type Descr: Descriptor + Clone + Display + serde::Serialize + for<'de> serde::Deserialize<'de>; + type Descr: Descriptor + serde::Serialize + for<'de> serde::Deserialize<'de>; fn is_some(&self) -> bool; fn descriptor(&self) -> Option; } diff --git a/src/fs.rs b/src/fs.rs index ebacf10..281d4ee 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -59,7 +59,7 @@ impl FsTextStore { } } -impl + Clone, L2: Layer2Descriptor> PersistenceProvider> +impl, L2: Layer2Descriptor> PersistenceProvider> for FsTextStore where for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, diff --git a/src/indexers/any.rs b/src/indexers/any.rs index 19e4699..950def4 100644 --- a/src/indexers/any.rs +++ b/src/indexers/any.rs @@ -72,7 +72,7 @@ pub enum AnyIndexerError { impl Indexer for AnyIndexer { type Error = AnyIndexerError; - fn create + Clone, L2: Layer2>( + fn create, L2: Layer2>( &self, descr: &WalletDescr, ) -> MayError, Vec> { @@ -104,7 +104,7 @@ impl Indexer for AnyIndexer { } } - fn update + Clone, L2: Layer2>( + fn update, L2: Layer2>( &self, descr: &WalletDescr, cache: &mut WalletCache, diff --git a/src/indexers/electrum.rs b/src/indexers/electrum.rs index 5f0202f..14b264c 100644 --- a/src/indexers/electrum.rs +++ b/src/indexers/electrum.rs @@ -62,7 +62,7 @@ pub enum ElectrumError { impl Indexer for Client { type Error = ElectrumError; - fn create + Clone, L2: Layer2>( + fn create, L2: Layer2>( &self, descriptor: &WalletDescr, ) -> MayError, Vec> { @@ -274,7 +274,7 @@ impl Indexer for Client { if errors.is_empty() { MayError::ok(cache) } else { MayError::err(cache, errors) } } - fn update + Clone, L2: Layer2>( + fn update, L2: Layer2>( &self, _descr: &WalletDescr, _cache: &mut WalletCache, diff --git a/src/indexers/esplora.rs b/src/indexers/esplora.rs index 2274cb2..b0cb46d 100644 --- a/src/indexers/esplora.rs +++ b/src/indexers/esplora.rs @@ -192,7 +192,7 @@ fn get_scripthash_txs_all( impl Indexer for Client { type Error = Error; - fn create + Clone, L2: Layer2>( + fn create, L2: Layer2>( &self, descriptor: &WalletDescr, ) -> MayError, Vec> { @@ -305,7 +305,7 @@ impl Indexer for Client { if errors.is_empty() { MayError::ok(cache) } else { MayError::err(cache, errors) } } - fn update + Clone, L2: Layer2>( + fn update, L2: Layer2>( &self, _descr: &WalletDescr, _cache: &mut WalletCache, diff --git a/src/indexers/mod.rs b/src/indexers/mod.rs index 2db027c..a120ec6 100644 --- a/src/indexers/mod.rs +++ b/src/indexers/mod.rs @@ -42,12 +42,12 @@ const BATCH_SIZE: usize = 10; pub trait Indexer { type Error; - fn create + Clone, L2: Layer2>( + fn create, L2: Layer2>( &self, descr: &WalletDescr, ) -> MayError, Vec>; - fn update + Clone, L2: Layer2>( + fn update, L2: Layer2>( &self, descr: &WalletDescr, cache: &mut WalletCache, diff --git a/src/wallet.rs b/src/wallet.rs index 77f2b86..ee517e3 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -84,7 +84,7 @@ impl<'descr, K, D: Descriptor> Iterator for AddrIter<'descr, K, D> { #[derive(Getters, Debug)] pub struct WalletDescr where - D: Descriptor + Clone, + D: Descriptor, L2: Layer2Descriptor, { #[getter(skip)] @@ -99,7 +99,7 @@ where _phantom: PhantomData, } -impl + Clone> WalletDescr { +impl> WalletDescr { pub fn new_standard(descr: D, network: Network) -> Self { WalletDescr { persistence: None, @@ -111,7 +111,7 @@ impl + Clone> WalletDescr { } } -impl + Clone, L2: Layer2Descriptor> WalletDescr { +impl, L2: Layer2Descriptor> WalletDescr { pub fn new_layer2(descr: D, layer2: L2, network: Network) -> Self { WalletDescr { persistence: None, @@ -142,15 +142,13 @@ impl + Clone, L2: Layer2Descriptor> WalletDescr { } } -impl + Clone, L2: Layer2Descriptor> Deref for WalletDescr { +impl, L2: Layer2Descriptor> Deref for WalletDescr { type Target = D; fn deref(&self) -> &Self::Target { &self.generator } } -impl + Clone, L2: Layer2Descriptor> CloneNoPersistence - for WalletDescr -{ +impl, L2: Layer2Descriptor> CloneNoPersistence for WalletDescr { fn clone_no_persistence(&self) -> Self { Self { persistence: None, @@ -162,7 +160,7 @@ impl + Clone, L2: Layer2Descriptor> CloneNoPersistence } } -impl + Clone, L2: Layer2Descriptor> Persisting for WalletDescr { +impl, L2: Layer2Descriptor> Persisting for WalletDescr { #[inline] fn persistence(&self) -> Option<&Persistence> { self.persistence.as_ref() } #[inline] @@ -171,7 +169,7 @@ impl + Clone, L2: Layer2Descriptor> Persisting for WalletDes fn as_mut_persistence(&mut self) -> &mut Option> { &mut self.persistence } } -impl + Clone, L2: Layer2Descriptor> Drop for WalletDescr { +impl, L2: Layer2Descriptor> Drop for WalletDescr { fn drop(&mut self) { if self.is_autosave() { if let Err(e) = self.store() { @@ -286,14 +284,14 @@ impl WalletCache { } } - pub fn with + Clone, L2: Layer2>( + pub fn with, L2: Layer2>( descriptor: &WalletDescr, indexer: &I, ) -> MayError> { indexer.create::(descriptor) } - pub fn update + Clone, L2: Layer2>( + pub fn update, L2: Layer2>( &mut self, descriptor: &WalletDescr, indexer: &I, @@ -380,20 +378,20 @@ impl Drop for WalletCache { } #[derive(Debug)] -pub struct Wallet + Clone, L2: Layer2 = NoLayer2> { +pub struct Wallet, L2: Layer2 = NoLayer2> { descr: WalletDescr, data: WalletData, cache: WalletCache, layer2: L2, } -impl + Clone, L2: Layer2> Deref for Wallet { +impl, L2: Layer2> Deref for Wallet { type Target = WalletDescr; fn deref(&self) -> &Self::Target { &self.descr } } -impl + Clone, L2: Layer2> CloneNoPersistence for Wallet { +impl, L2: Layer2> CloneNoPersistence for Wallet { fn clone_no_persistence(&self) -> Self { Self { descr: self.descr.clone_no_persistence(), @@ -404,7 +402,7 @@ impl + Clone, L2: Layer2> CloneNoPersistence for Wallet + Clone, L2: Layer2> PsbtConstructor for Wallet { +impl, L2: Layer2> PsbtConstructor for Wallet { type Key = K; type Descr = D; @@ -429,7 +427,7 @@ impl + Clone, L2: Layer2> PsbtConstructor for Wallet + Clone> Wallet { +impl> Wallet { pub fn new_layer1(descr: D, network: Network) -> Self { Wallet { descr: WalletDescr::new_standard(descr, network), @@ -440,7 +438,7 @@ impl + Clone> Wallet { } } -impl + Clone, L2: Layer2> Wallet { +impl, L2: Layer2> 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), @@ -560,7 +558,7 @@ impl + Clone, L2: Layer2> Wallet { } } -impl + Clone, L2: Layer2> Wallet { +impl, L2: Layer2> Wallet { pub fn load

(provider: P, autosave: bool) -> Result, PersistenceError> where P: Clone + PersistenceProvider> From 5dc38aa2ca7377755c0bf3a73620c89e4bf0a396 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 1 Sep 2024 11:46:05 +0200 Subject: [PATCH 12/14] wallet: save descriptor inside wallet cache and data this is required for identifying which data/cache belongs to each wallet, when stored separately using persistence providers --- src/indexers/electrum.rs | 2 +- src/indexers/esplora.rs | 2 +- src/wallet.rs | 17 ++++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/indexers/electrum.rs b/src/indexers/electrum.rs index 14b264c..a2cd2d1 100644 --- a/src/indexers/electrum.rs +++ b/src/indexers/electrum.rs @@ -66,7 +66,7 @@ impl Indexer for Client { &self, descriptor: &WalletDescr, ) -> MayError, Vec> { - let mut cache = WalletCache::new(); + let mut cache = WalletCache::new_nonsync(descriptor.generator()); let mut errors = Vec::::new(); let mut address_index = BTreeMap::new(); diff --git a/src/indexers/esplora.rs b/src/indexers/esplora.rs index b0cb46d..6e6545b 100644 --- a/src/indexers/esplora.rs +++ b/src/indexers/esplora.rs @@ -196,7 +196,7 @@ impl Indexer for Client { &self, descriptor: &WalletDescr, ) -> MayError, Vec> { - let mut cache = WalletCache::new(); + let mut cache = WalletCache::new_nonsync(descriptor.generator()); let mut errors = vec![]; let mut address_index = BTreeMap::new(); diff --git a/src/wallet.rs b/src/wallet.rs index ee517e3..325f3f1 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -197,6 +197,8 @@ pub struct WalletData { #[cfg_attr(feature = "serde", serde(skip))] persistence: Option>, + #[cfg_attr(feature = "serde", serde(skip))] + pub descriptor: String, pub name: String, pub tx_annotations: BTreeMap, pub txout_annotations: BTreeMap, @@ -210,6 +212,7 @@ impl CloneNoPersistence for WalletData { fn clone_no_persistence(&self) -> Self { Self { persistence: None, + descriptor: self.descriptor.clone(), name: self.name.clone(), tx_annotations: self.tx_annotations.clone(), txout_annotations: self.txout_annotations.clone(), @@ -257,6 +260,8 @@ pub struct WalletCache { #[cfg_attr(feature = "serde", serde(skip))] persistence: Option>, + #[cfg_attr(feature = "serde", serde(skip))] + pub descriptor: String, pub last_block: MiningInfo, pub last_change: NormalIndex, pub headers: BTreeSet, @@ -266,14 +271,11 @@ pub struct WalletCache { pub layer2: L2, } -impl Default for WalletCache { - fn default() -> Self { WalletCache::new() } -} - impl WalletCache { - pub(crate) fn new() -> Self { + pub(crate) fn new_nonsync>(descriptor: &D) -> Self { WalletCache { persistence: None, + descriptor: descriptor.to_string(), last_block: MiningInfo::genesis(), last_change: NormalIndex::ZERO, headers: none!(), @@ -344,6 +346,7 @@ impl CloneNoPersistence for WalletCache { fn clone_no_persistence(&self) -> Self { Self { persistence: None, + descriptor: self.descriptor.clone(), last_block: self.last_block.clone(), last_change: self.last_change.clone(), headers: self.headers.clone(), @@ -430,9 +433,9 @@ impl, L2: Layer2> PsbtConstructor for Wallet { impl> Wallet { pub fn new_layer1(descr: D, network: Network) -> Self { Wallet { + cache: WalletCache::new_nonsync(&descr), descr: WalletDescr::new_standard(descr, network), data: empty!(), - cache: WalletCache::new(), layer2: none!(), } } @@ -441,9 +444,9 @@ impl> Wallet { impl, L2: Layer2> Wallet { pub fn new_layer2(descr: D, l2_descr: L2::Descr, layer2: L2, network: Network) -> Self { Wallet { + cache: WalletCache::new_nonsync(&descr), descr: WalletDescr::new_layer2(descr, l2_descr, network), data: empty!(), - cache: WalletCache::new(), layer2, } } From 617daad6c8b108cb41148ca0914f1655b3133622 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 2 Sep 2024 21:54:09 +0200 Subject: [PATCH 13/14] fs: create directories on FsTextStore construction --- src/cli/args.rs | 2 +- src/cli/command.rs | 4 ++-- src/fs.rs | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 69acc61..5f68710 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -145,7 +145,7 @@ impl Args { eprint!(" from wallet {wallet_name} ... "); self.general.wallet_dir(wallet_name) }; - let provider = FsTextStore::new(path); + let provider = FsTextStore::new(path)?; let wallet = Wallet::load(provider, true)?; eprintln!("success"); wallet diff --git a/src/cli/command.rs b/src/cli/command.rs index 3fd6de6..0d04b0c 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -235,7 +235,7 @@ impl Exec for Args { "{name}{}", if config.default_wallet == name { "\t[default]" } else { "\t\t" } ); - let provider = FsTextStore::new(entry.path().clone()); + let provider = FsTextStore::new(entry.path().clone())?; let Ok(wallet) = Wallet::::load(provider, true) else { println!("# broken wallet descriptor"); continue; @@ -263,7 +263,7 @@ impl Exec for Args { print!("Saving the wallet as '{name}' ... "); let mut wallet = self.bp_wallet::(&config)?; let name = name.to_string(); - let provider = FsTextStore::new(self.general.wallet_dir(&name)); + let provider = FsTextStore::new(self.general.wallet_dir(&name))?; wallet.make_persistent(provider, true)?; wallet.set_name(name); if let Err(err) = wallet.store() { diff --git a/src/fs.rs b/src/fs.rs index 281d4ee..0d1f17a 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -20,8 +20,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fs; use std::path::PathBuf; +use std::{fs, io}; use descriptors::Descriptor; use nonasync::persistence::{PersistenceError, PersistenceProvider}; @@ -40,7 +40,9 @@ pub struct FsTextStore { } impl FsTextStore { - pub fn new(path: PathBuf) -> Self { + pub fn new(path: PathBuf) -> io::Result { + fs::create_dir_all(&path)?; + let mut descr = path.clone(); descr.push("descriptor.toml"); let mut data = path.clone(); @@ -50,12 +52,12 @@ impl FsTextStore { let mut l2 = path; l2.push("layer2.yaml"); - Self { + Ok(Self { descr, data, cache, l2, - } + }) } } From 95a3a3726ef0675085ab22384b0e16ffd3ee3c3d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 3 Sep 2024 22:43:40 +0200 Subject: [PATCH 14/14] wallet: print error on unsuccessful saving on Drop if no log feature --- src/wallet.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/wallet.rs b/src/wallet.rs index 325f3f1..02d8795 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -174,10 +174,9 @@ impl, L2: Layer2Descriptor> Drop for WalletDescr { if self.is_autosave() { if let Err(e) = self.store() { #[cfg(feature = "log")] - log::error!( - "impossible to automatically-save wallet descriptor during the Drop \ - operation: {e}" - ); + log::error!("impossible to automatically-save wallet descriptor on Drop: {e}"); + #[cfg(not(feature = "log"))] + eprintln!("impossible to automatically-save wallet descriptor on Drop: {e}") } } } @@ -238,9 +237,9 @@ impl Drop for WalletData { if self.is_autosave() { if let Err(e) = self.store() { #[cfg(feature = "log")] - log::error!( - "impossible to automatically-save wallet data during the Drop operation: {e}" - ); + log::error!("impossible to automatically-save wallet data on Drop: {e}"); + #[cfg(not(feature = "log"))] + eprintln!("impossible to automatically-save wallet data on Drop: {e}") } } } @@ -372,9 +371,9 @@ impl Drop for WalletCache { if self.is_autosave() { if let Err(e) = self.store() { #[cfg(feature = "log")] - log::error!( - "impossible to automatically-save wallet cache during the Drop operation: {e}" - ); + log::error!("impossible to automatically-save wallet cache on Drop: {e}"); + #[cfg(not(feature = "log"))] + eprintln!("impossible to automatically-save wallet cache on Drop: {e}") } } }