Skip to content

Commit

Permalink
Separate CLI argument handling
Browse files Browse the repository at this point in the history
  • Loading branch information
evanlinjin committed Aug 13, 2022
1 parent 3616c11 commit a0120d7
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 38 deletions.
73 changes: 73 additions & 0 deletions src/args.rs
@@ -0,0 +1,73 @@
use std::{ffi::OsString, fmt, num::ParseIntError};

use crate::{ScheduledChannel, ScheduledPayJoin};

/// CLI argument errors.
#[derive(Debug)]
pub(crate) enum ArgError {
/// Argument not UTF-8
NotUTF8(OsString),
/// Parse feerate error
FeeRateError(ParseIntError),
/// Parse node address error
InvalidNodeAddress(ln_types::p2p_address::ParseError),
/// Parse bitcoin amount error
InvalidBitcoinAmount(bitcoin::util::amount::ParseAmountError),
/// Wallet amount error
InvalidWalletAmount(bitcoin::util::amount::ParseAmountError),
}

impl fmt::Display for ArgError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this properly.
write!(f, "invalid arguments: {:?}", self)
}
}

impl std::error::Error for ArgError {}

/// Parses arguments in `[fee_rate] [(<p2p_addr>, <sats_amount>)...] [wallet_amount]`
pub(crate) fn parse_args<A>(args: A) -> Result<Option<ScheduledPayJoin>, ArgError>
where
A: Iterator<Item = OsString>,
{
// ensure all args are utf8
let args = args
.map(|arg| arg.into_string())
.collect::<Result<Vec<_>, _>>()
.map_err(ArgError::NotUTF8)?;

// first argument is fee rate
let fee_rate = match args.get(0) {
Some(fee_rate_str) => fee_rate_str.parse::<u64>().map_err(ArgError::FeeRateError)?,
None => return Ok(None),
};

// return None if no remaining args.
let mut args = match args.get(1..) {
Some(args) => args.iter(),
None => return Ok(None),
};

// parse scheduled channel arguments: pairs of (addr, amount)
let mut channels = Vec::with_capacity(args.len() / 2);

// the remaining single argument is the wallet amount in satoshis (if any)
let wallet_amount = loop {
match (args.next(), args.next()) {
// we have a pair of arguments, interpret it as a scheduled channel (p2p addr, amount)
(Some(addr), Some(amount)) => {
channels.push(ScheduledChannel::from_args(addr, amount)?);
}
// if there is a remaining single argument, it is the wallet amount
(Some(amount), None) => {
break bitcoin::Amount::from_str_in(amount, bitcoin::Denomination::Satoshi)
.map_err(ArgError::InvalidWalletAmount)?
}
// if there is no remaining single argument, the wallet amount is 0
_ => break bitcoin::Amount::ZERO,
}
};

Ok(Some(ScheduledPayJoin { wallet_amount, channels, fee_rate }))
}
56 changes: 18 additions & 38 deletions src/main.rs
@@ -1,14 +1,19 @@
mod args;

use args::ArgError;
use bitcoin::util::address::Address;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::{Script, TxOut};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use ln_types::P2PAddress;
use std::collections::HashMap;
use std::collections::{HashMap};
use std::convert::TryInto;
use std::fmt;
use std::sync::{Arc, Mutex};

use crate::args::parse_args;

#[macro_use]
extern crate serde_derive;
extern crate configure_me;
Expand All @@ -29,15 +34,13 @@ struct ScheduledChannel {
}

impl ScheduledChannel {
fn from_args(addr: std::ffi::OsString, amount: std::ffi::OsString) -> Self {
use configure_me::parse_arg::Arg;
fn from_args(addr: &str, amount: &str) -> Result<Self, ArgError> {
let node = addr.parse::<P2PAddress>().map_err(ArgError::InvalidNodeAddress)?;

let node = addr.parse().expect("invalid node address");
let amount = amount.to_str().expect("invalid channel amount");
let amount = bitcoin::Amount::from_str_in(&amount, bitcoin::Denomination::Satoshi)
.expect("invalid channel amount");
let amount = bitcoin::Amount::from_str_in(amount, bitcoin::Denomination::Satoshi)
.map_err(ArgError::InvalidBitcoinAmount)?;

ScheduledChannel { node, amount }
Ok(Self { node, amount })
}
}

Expand Down Expand Up @@ -218,52 +221,29 @@ async fn get_new_bech32_address(client: &mut tonic_lnd::Client) -> Address {

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (config, mut args) =
let (config, args) =
Config::including_optional_config_files(std::iter::empty::<&str>()).unwrap_or_exit();

let scheduled_pj = parse_args(args).expect("failed to parse remaining arguments");

let client =
tonic_lnd::connect(config.lnd_address, &config.lnd_cert_path, &config.lnd_macaroon_path)
.await
.expect("failed to connect");

let mut handler = Handler::new(client).await?;

if let Some(fee_rate) = args.next() {
let fee_rate = fee_rate.into_string().expect("fee rate is not UTF-8").parse::<u64>()?;
if let Some(payjoin) = scheduled_pj {
payjoin.test_connections(&mut handler.client).await;
let address = get_new_bech32_address(&mut handler.client).await;

let mut args = args.fuse();
let mut scheduled_channels = Vec::with_capacity(args.size_hint().0 / 2);
let mut wallet_amount = bitcoin::Amount::ZERO;
while let Some(arg) = args.next() {
match args.next() {
Some(channel_amount) => {
scheduled_channels.push(ScheduledChannel::from_args(arg, channel_amount))
}
None => {
wallet_amount = bitcoin::Amount::from_str_in(
arg.to_str().expect("wallet amount not UTF-8"),
bitcoin::Denomination::Satoshi,
)?
}
}
}

let scheduled_payjoin =
ScheduledPayJoin { wallet_amount, channels: scheduled_channels, fee_rate };

scheduled_payjoin.test_connections(&mut handler.client).await;

println!(
"bitcoin:{}?amount={}&pj=https://example.com/pj",
address,
scheduled_payjoin.total_amount().to_string_in(bitcoin::Denomination::Bitcoin)
payjoin.total_amount().to_string_in(bitcoin::Denomination::Bitcoin)
);

handler
.payjoins
.insert(&address, scheduled_payjoin)
.expect("New Handler is supposed to be empty");
handler.payjoins.insert(&address, payjoin).expect("new handler should be empty");
}

let addr = ([127, 0, 0, 1], config.bind_port).into();
Expand Down

0 comments on commit a0120d7

Please sign in to comment.