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

Clean up argument parsing. #5

Merged
merged 2 commits into from Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rustfmt commit can fix this non-change

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