Skip to content

Commit

Permalink
Merge pull request #44 from BP-WG/runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky authored Jul 10, 2024
2 parents 76e05c1 + 30ae979 commit 418bf29
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 394 deletions.
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

0 comments on commit 418bf29

Please sign in to comment.