Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Runtime #44

Merged
merged 8 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/bin/bp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ extern crate serde_crate as serde;

use std::process::ExitCode;

use bpwallet::cli::{Args, BpCommand, Config, DescrStdOpts, Exec, LogLevel};
use bpwallet::RuntimeError;
use bpwallet::cli::{Args, BpCommand, Config, DescrStdOpts, Exec, ExecError, LogLevel};
use clap::Parser;

fn main() -> ExitCode {
Expand All @@ -39,7 +38,7 @@ fn main() -> ExitCode {
}
}

fn run() -> Result<(), RuntimeError> {
fn run() -> Result<(), ExecError> {
let mut args = Args::<BpCommand, DescrStdOpts>::parse();
args.process();
LogLevel::from_verbosity_flag_count(args.verbose).apply();
Expand Down
88 changes: 52 additions & 36 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ use std::fmt::Debug;
use std::path::PathBuf;
use std::process::exit;

use bpstd::XpubDerivable;
use clap::Subcommand;
use descriptors::Descriptor;
use strict_encoding::Ident;

use crate::cli::{Config, DescrStdOpts, DescriptorOpts, GeneralOpts, ResolverOpt, WalletOpts};
use crate::{AnyIndexer, Runtime, RuntimeError};
use crate::cli::{
Config, DescrStdOpts, DescriptorOpts, ExecError, GeneralOpts, ResolverOpt, WalletOpts,
};
use crate::{AnyIndexer, MayError, Wallet};

/// Command-line arguments
#[derive(Parser)]
Expand Down Expand Up @@ -89,39 +92,49 @@ impl<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts> Args<C, O> {
conf_path
}

pub fn bp_runtime<D: Descriptor>(&self, conf: &Config) -> Result<Runtime<D>, RuntimeError>
where for<'de> D: From<O::Descr> + serde::Serialize + serde::Deserialize<'de> {
pub fn bp_wallet<D: Descriptor>(
&self,
conf: &Config,
) -> Result<Wallet<XpubDerivable, D>, ExecError>
where
for<'de> D: From<O::Descr> + serde::Serialize + serde::Deserialize<'de>,
{
eprint!("Loading descriptor");
let mut runtime: Runtime<D> = if let Some(d) = self.wallet.descriptor_opts.descriptor() {
eprint!(" from command-line argument ... ");
Runtime::new_standard(d.into(), self.general.network)
} else if let Some(wallet_path) = self.wallet.wallet_path.clone() {
eprint!(" from specified wallet directory ... ");
Runtime::load_standard(wallet_path)?
} else {
let wallet_name = self
.wallet
.name
.as_ref()
.map(Ident::to_string)
.unwrap_or(conf.default_wallet.clone());
eprint!(" from wallet {wallet_name} ... ");
Runtime::load_standard(self.general.wallet_dir(wallet_name))?
};
let mut sync = self.sync;
if runtime.warnings().is_empty() {
eprintln!("success");
} else {
eprintln!("complete with warnings:");
for warning in runtime.warnings() {
eprintln!("- {warning}");
}
sync = true;
runtime.reset_warnings();
}
let mut sync = self.sync || self.wallet.descriptor_opts.is_some();

if sync || self.wallet.descriptor_opts.is_some() {
eprint!("Syncing");
let mut wallet: Wallet<XpubDerivable, D> =
if let Some(d) = self.wallet.descriptor_opts.descriptor() {
eprintln!(" from command-line argument");
eprint!("Syncing");
Wallet::new_layer1(d.into(), self.general.network)
} else {
let path = if let Some(wallet_path) = self.wallet.wallet_path.clone() {
eprint!(" from specified wallet directory ... ");
wallet_path
} else {
let wallet_name = self
.wallet
.name
.as_ref()
.map(Ident::to_string)
.unwrap_or(conf.default_wallet.clone());
eprint!(" from wallet {wallet_name} ... ");
self.general.wallet_dir(wallet_name)
};
let (wallet, warnings) = Wallet::load(&path, true)?;
if warnings.is_empty() {
eprintln!("success");
} else {
eprintln!("complete with warnings:");
for warning in warnings {
eprintln!("- {warning}");
}
sync = true;
}
wallet
};

if sync {
let indexer = match (&self.resolver.esplora, &self.resolver.electrum) {
(None, Some(url)) => AnyIndexer::Electrum(Box::new(electrum::Client::new(url)?)),
(Some(url), None) => {
Expand All @@ -135,17 +148,20 @@ impl<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts> Args<C, O> {
exit(1);
}
};
if let Err(errors) = runtime.sync(&indexer) {
eprint!("Syncing");
if let MayError {
err: Some(errors), ..
} = wallet.update(&indexer)
{
eprintln!(" partial, some requests has failed:");
for err in errors {
eprintln!("- {err}");
}
} else {
eprintln!(" success");
}
runtime.try_store()?;
}

Ok(runtime)
Ok(wallet)
}
}
94 changes: 64 additions & 30 deletions src/cli/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs;
use std::convert::Infallible;
use std::fs::File;
use std::path::PathBuf;
use std::process::exit;
use std::{error, fs};

use bpstd::psbt::{Beneficiary, TxParams};
use bpstd::{Derive, IdxBase, Keychain, NormalIndex, Sats};
use psbt::{Payment, PsbtConstructor, PsbtVer};
use psbt::{ConstructionError, Payment, PsbtConstructor, PsbtVer};
use strict_encoding::Ident;

use crate::cli::{Args, Config, DescriptorOpts, Exec};
use crate::{coinselect, OpType, RuntimeError, StoreError, WalletAddr, WalletUtxo};
use crate::wallet::fs::{LoadError, StoreError};
use crate::wallet::Save;
use crate::{coinselect, FsConfig, OpType, WalletAddr, WalletUtxo};

#[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)]
pub enum Command {
Expand Down Expand Up @@ -131,8 +134,38 @@ pub enum BpCommand {
},
}

#[derive(Debug, Display, Error, From)]
#[non_exhaustive]
#[display(inner)]
pub enum ExecError<L2: error::Error = Infallible> {
#[from]
Load(LoadError<L2>),

#[from]
Store(StoreError<L2>),

#[from]
ConstructPsbt(ConstructionError),

#[cfg(feature = "electrum")]
/// error querying electrum server.
///
/// {0}
#[from]
#[display(doc_comments)]
Electrum(electrum::Error),

#[cfg(feature = "esplora")]
/// error querying esplora server.
///
/// {0}
#[from]
#[display(doc_comments)]
Esplora(esplora::Error),
}

impl<O: DescriptorOpts> Exec for Args<Command, O> {
type Error = RuntimeError;
type Error = ExecError;
const CONF_FILE_NAME: &'static str = "bp.toml";

fn exec(self, mut config: Config, name: &'static str) -> Result<(), Self::Error> {
Expand Down Expand Up @@ -181,12 +214,15 @@ impl<O: DescriptorOpts> Exec for Args<Command, O> {
eprintln!("Error: you must provide an argument specifying wallet descriptor");
exit(1);
}
let mut runtime = self.bp_runtime::<O::Descr>(&config)?;
let name = name.to_string();
print!("Saving the wallet as '{name}' ... ");
let dir = self.general.wallet_dir(&name);
runtime.set_name(name);
if let Err(err) = runtime.store(&dir) {
let mut wallet = self.bp_wallet::<O::Descr>(&config)?;
let name = name.to_string();
wallet.set_fs_config(FsConfig {
path: self.general.wallet_dir(&name),
autosave: false,
})?;
wallet.set_name(name);
if let Err(err) = wallet.save() {
println!("error: {err}");
} else {
println!("success");
Expand All @@ -199,28 +235,27 @@ impl<O: DescriptorOpts> Exec for Args<Command, O> {
dry_run: no_shift,
count: no,
} => {
let mut runtime = self.bp_runtime::<O::Descr>(&config)?;
let mut wallet = self.bp_wallet::<O::Descr>(&config)?;
let keychain = match (change, keychain) {
(false, None) => runtime.default_keychain(),
(false, None) => wallet.default_keychain(),
(true, None) => (*change as u8).into(),
(false, Some(keychain)) => *keychain,
_ => unreachable!(),
};
if !runtime.keychains().contains(&keychain) {
if !wallet.keychains().contains(&keychain) {
eprintln!(
"Error: the specified keychain {keychain} is not a part of the descriptor"
);
exit(1);
}
let index =
index.unwrap_or_else(|| runtime.next_derivation_index(keychain, !*no_shift));
index.unwrap_or_else(|| wallet.next_derivation_index(keychain, !*no_shift));
println!("\nTerm.\tAddress");
for derived_addr in
runtime.addresses(keychain).skip(index.index() as usize).take(*no as usize)
wallet.addresses(keychain).skip(index.index() as usize).take(*no as usize)
{
println!("{}\t{}", derived_addr.terminal, derived_addr.addr);
}
runtime.try_store()?;
}
}

Expand All @@ -229,7 +264,7 @@ impl<O: DescriptorOpts> Exec for Args<Command, O> {
}

impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
type Error = RuntimeError;
type Error = ExecError;
const CONF_FILE_NAME: &'static str = "bp.toml";

fn exec(mut self, config: Config, name: &'static str) -> Result<(), Self::Error> {
Expand All @@ -239,16 +274,16 @@ impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
addr: false,
utxo: false,
} => {
let runtime = self.bp_runtime::<O::Descr>(&config)?;
let runtime = self.bp_wallet::<O::Descr>(&config)?;
println!("\nWallet total balance: {} ṩ", runtime.balance());
}
BpCommand::Balance {
addr: true,
utxo: false,
} => {
let runtime = self.bp_runtime::<O::Descr>(&config)?;
let wallet = self.bp_wallet::<O::Descr>(&config)?;
println!("\nTerm.\t{:62}\t# used\tVol., ṩ\tBalance, ṩ", "Address");
for info in runtime.address_balance() {
for info in wallet.address_balance() {
let WalletAddr {
addr,
terminal,
Expand All @@ -269,9 +304,9 @@ impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
addr: false,
utxo: true,
} => {
let runtime = self.bp_runtime::<O::Descr>(&config)?;
let wallet = self.bp_wallet::<O::Descr>(&config)?;
println!("\nHeight\t{:>12}\t{:68}\tAddress", "Amount, ṩ", "Outpoint");
for row in runtime.coins() {
for row in wallet.coins() {
println!(
"{}\t{: >12}\t{:68}\t{}",
row.height, row.amount, row.outpoint, row.address
Expand All @@ -288,9 +323,9 @@ impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
addr: true,
utxo: true,
} => {
let runtime = self.bp_runtime::<O::Descr>(&config)?;
let wallet = self.bp_wallet::<O::Descr>(&config)?;
println!("\nHeight\t{:>12}\t{:68}", "Amount, ṩ", "Outpoint");
for (derived_addr, utxos) in runtime.address_coins() {
for (derived_addr, utxos) in wallet.address_coins() {
println!("{}\t{}", derived_addr.addr, derived_addr.terminal);
for row in utxos {
println!("{}\t{: >12}\t{:68}", row.height, row.amount, row.outpoint);
Expand All @@ -305,13 +340,13 @@ impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
self.exec(config, name)?;
}
BpCommand::History { txid, details } => {
let runtime = self.bp_runtime::<O::Descr>(&config)?;
let wallet = self.bp_wallet::<O::Descr>(&config)?;
println!(
"\nHeight\t{:<1$}\t Amount, ṩ\tFee rate, ṩ/vbyte",
"Txid",
if *txid { 64 } else { 18 }
);
let mut rows = runtime.history().collect::<Vec<_>>();
let mut rows = wallet.history().collect::<Vec<_>>();
rows.sort_by_key(|row| row.height);
for row in rows {
println!(
Expand Down Expand Up @@ -358,7 +393,7 @@ impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
fee,
psbt: psbt_file,
} => {
let mut runtime = self.bp_runtime::<O::Descr>(&config)?;
let mut wallet = self.bp_wallet::<O::Descr>(&config)?;

// Do coin selection
let total_amount =
Expand All @@ -368,21 +403,20 @@ impl<O: DescriptorOpts> Exec for Args<BpCommand, O> {
});
let coins: Vec<_> = match total_amount {
Ok(sats) if sats > Sats::ZERO => {
runtime.wallet().coinselect(sats + *fee, coinselect::all).collect()
wallet.coinselect(sats + *fee, coinselect::all).collect()
}
_ => {
eprintln!(
"Warning: you are not paying to anybody but just aggregating all your \
balances to a single UTXO",
);
runtime.wallet().all_utxos().map(WalletUtxo::into_outpoint).collect()
wallet.all_utxos().map(WalletUtxo::into_outpoint).collect()
}
};

// TODO: Support lock time and RBFs
let params = TxParams::with(*fee);
let (psbt, _) =
runtime.wallet_mut().construct_psbt(coins, beneficiaries, params)?;
let (psbt, _) = wallet.construct_psbt(coins, beneficiaries, params)?;
let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 };

eprintln!("{}", serde_yaml::to_string(&psbt).unwrap());
Expand Down
2 changes: 1 addition & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod config;
mod command;

pub use args::{Args, Exec};
pub use command::{BpCommand, Command};
pub use command::{BpCommand, Command, ExecError};
pub use config::Config;
pub use loglevel::LogLevel;
pub use opts::{
Expand Down
2 changes: 1 addition & 1 deletion src/indexers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mod esplora;
mod any;

#[cfg(any(feature = "electrum", feature = "esplora"))]
pub use any::AnyIndexer;
pub use any::{AnyIndexer, AnyIndexerError};
use descriptors::Descriptor;

use crate::{Layer2, MayError, WalletCache, WalletDescr};
Expand Down
Loading
Loading