X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=src%2Fcli.rs;h=0bea87041252b9b0cfcca134a8cf52a2cbb2fe16;hb=1db442581294d073de85c4e9f21393f87bb0515c;hp=476ea81efd6803e393f237bd5965333767b00bc2;hpb=d10310b213f6c979e96d770963c4f5f8e9eb0212;p=ldk-sample diff --git a/src/cli.rs b/src/cli.rs index 476ea81..0bea870 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,21 +1,22 @@ use crate::disk; use crate::hex_utils; use crate::{ - ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, PaymentInfoStorage, - PeerManager, + ChannelManager, FilesystemLogger, HTLCStatus, InvoicePayer, MillisatAmount, PaymentInfo, + PaymentInfoStorage, PeerManager, }; use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::key::PublicKey; -use lightning::chain; -use lightning::chain::keysinterface::KeysManager; -use lightning::ln::features::InvoiceFeatures; +use lightning::chain::keysinterface::{KeysInterface, KeysManager}; use lightning::ln::msgs::NetAddress; -use lightning::ln::{PaymentHash, PaymentSecret}; -use lightning::routing::network_graph::NetGraphMsgHandler; +use lightning::ln::PaymentHash; +use lightning::routing::network_graph::NetworkGraph; use lightning::routing::router; -use lightning::routing::router::RouteHint; +use lightning::routing::router::{Payee, RouteParameters}; +use lightning::routing::scorer::Scorer; use lightning::util::config::{ChannelConfig, ChannelHandshakeLimits, UserConfig}; +use lightning::util::events::EventHandler; +use lightning_invoice::payment::PaymentError; use lightning_invoice::{utils, Currency, Invoice}; use std::env; use std::io; @@ -24,7 +25,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::ops::Deref; use std::path::Path; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Duration; pub(crate) struct LdkUserInfo { @@ -45,19 +46,19 @@ pub(crate) fn parse_startup_args() -> Result { return Err(()); } let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); - let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.split("@").collect(); + let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.rsplitn(2, "@").collect(); if bitcoind_rpc_info_parts.len() != 2 { println!("ERROR: bad bitcoind RPC URL provided"); return Err(()); } - let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); + let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect(); if rpc_user_and_password.len() != 2 { println!("ERROR: bad bitcoind RPC username/password combo provided"); return Err(()); } let bitcoind_rpc_username = rpc_user_and_password[0].to_string(); let bitcoind_rpc_password = rpc_user_and_password[1].to_string(); - let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect(); + let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); if bitcoind_rpc_path.len() != 2 { println!("ERROR: bad bitcoind RPC path provided"); return Err(()); @@ -139,10 +140,10 @@ pub(crate) fn parse_startup_args() -> Result { }) } -pub(crate) async fn poll_for_user_input( - peer_manager: Arc, channel_manager: Arc, - keys_manager: Arc, - router: Arc, Arc>>, +pub(crate) async fn poll_for_user_input( + invoice_payer: Arc>, peer_manager: Arc, + channel_manager: Arc, keys_manager: Arc, + network_graph: Arc, scorer: Arc>, inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, ldk_data_dir: String, logger: Arc, network: Network, ) { @@ -150,10 +151,14 @@ pub(crate) async fn poll_for_user_input( println!("LDK logs are available at /.ldk/logs"); println!("Local Node ID is {}.", channel_manager.get_our_node_id()); let stdin = io::stdin(); - print!("> "); - io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print - for line in stdin.lock().lines() { - let line = line.unwrap(); + let mut line_reader = stdin.lock().lines(); + loop { + print!("> "); + io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print + let line = match line_reader.next() { + Some(l) => l.unwrap(), + None => break, + }; let mut words = line.split_whitespace(); if let Some(word) = words.next() { match word { @@ -163,8 +168,6 @@ pub(crate) async fn poll_for_user_input( let channel_value_sat = words.next(); if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { println!("ERROR: openchannel has 2 required arguments: `openchannel pubkey@host:port channel_amt_satoshis` [--public]"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); @@ -173,8 +176,6 @@ pub(crate) async fn poll_for_user_input( Ok(info) => info, Err(e) => { println!("{:?}", e.into_inner().unwrap()); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; @@ -182,8 +183,6 @@ pub(crate) async fn poll_for_user_input( let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); if chan_amt_sat.is_err() { println!("ERROR: channel amount must be a number"); - print!("> "); - io::stdout().flush().unwrap(); continue; } @@ -191,8 +190,6 @@ pub(crate) async fn poll_for_user_input( .await .is_err() { - print!("> "); - io::stdout().flush().unwrap(); continue; }; @@ -201,8 +198,6 @@ pub(crate) async fn poll_for_user_input( Some("--public=false") => false, Some(_) => { println!("ERROR: invalid `--public` command format. Valid formats: `--public`, `--public=true` `--public=false`"); - print!("> "); - io::stdout().flush().unwrap(); continue; } None => false, @@ -227,8 +222,6 @@ pub(crate) async fn poll_for_user_input( let invoice_str = words.next(); if invoice_str.is_none() { println!("ERROR: sendpayment requires an invoice: `sendpayment `"); - print!("> "); - io::stdout().flush().unwrap(); continue; } @@ -236,41 +229,11 @@ pub(crate) async fn poll_for_user_input( Ok(inv) => inv, Err(e) => { println!("ERROR: invalid invoice: {:?}", e); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; - let last_hops = invoice.route_hints(); - - let amt_pico_btc = invoice.amount_pico_btc(); - if amt_pico_btc.is_none() { - println!("ERROR: invalid invoice: must contain amount to pay"); - print!("> "); - io::stdout().flush().unwrap(); - continue; - } - let amt_msat = amt_pico_btc.unwrap() / 10; - - let payee_pubkey = invoice.recover_payee_pub_key(); - let final_cltv = invoice.min_final_cltv_expiry() as u32; - let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner()); - let payment_secret = invoice.payment_secret().cloned(); - let invoice_features = invoice.features().cloned(); - send_payment( - payee_pubkey, - amt_msat, - final_cltv, - payment_hash, - payment_secret, - invoice_features, - last_hops, - router.clone(), - channel_manager.clone(), - outbound_payments.clone(), - logger.clone(), - ); + send_payment(&*invoice_payer, &invoice, outbound_payments.clone()); } "keysend" => { let dest_pubkey = match words.next() { @@ -278,15 +241,11 @@ pub(crate) async fn poll_for_user_input( Some(pk) => pk, None => { println!("ERROR: couldn't parse destination pubkey"); - print!("> "); - io::stdout().flush().unwrap(); continue; } }, None => { println!("ERROR: keysend requires a destination pubkey: `keysend `"); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; @@ -295,8 +254,6 @@ pub(crate) async fn poll_for_user_input( None => { println!("ERROR: keysend requires an amount in millisatoshis: `keysend `"); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; @@ -304,34 +261,29 @@ pub(crate) async fn poll_for_user_input( Ok(amt) => amt, Err(e) => { println!("ERROR: couldn't parse amount_msat: {}", e); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; keysend( dest_pubkey, amt_msat, - router.clone(), + network_graph.clone(), channel_manager.clone(), outbound_payments.clone(), logger.clone(), + scorer.clone(), ); } "getinvoice" => { let amt_str = words.next(); if amt_str.is_none() { println!("ERROR: getinvoice requires an amount in millisatoshis"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let amt_msat: Result = amt_str.unwrap().parse(); if amt_msat.is_err() { println!("ERROR: getinvoice provided payment amount was not a number"); - print!("> "); - io::stdout().flush().unwrap(); continue; } get_invoice( @@ -346,8 +298,6 @@ pub(crate) async fn poll_for_user_input( let peer_pubkey_and_ip_addr = words.next(); if peer_pubkey_and_ip_addr.is_none() { println!("ERROR: connectpeer requires peer connection info: `connectpeer pubkey@host:port`"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let (pubkey, peer_addr) = @@ -355,8 +305,6 @@ pub(crate) async fn poll_for_user_input( Ok(info) => info, Err(e) => { println!("{:?}", e.into_inner().unwrap()); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; @@ -375,15 +323,11 @@ pub(crate) async fn poll_for_user_input( let channel_id_str = words.next(); if channel_id_str.is_none() { println!("ERROR: closechannel requires a channel ID: `closechannel `"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); if channel_id_vec.is_none() { println!("ERROR: couldn't parse channel_id as hex"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let mut channel_id = [0; 32]; @@ -394,15 +338,11 @@ pub(crate) async fn poll_for_user_input( let channel_id_str = words.next(); if channel_id_str.is_none() { println!("ERROR: forceclosechannel requires a channel ID: `forceclosechannel `"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); if channel_id_vec.is_none() { println!("ERROR: couldn't parse channel_id as hex"); - print!("> "); - io::stdout().flush().unwrap(); continue; } let mut channel_id = [0; 32]; @@ -411,11 +351,23 @@ pub(crate) async fn poll_for_user_input( } "nodeinfo" => node_info(channel_manager.clone(), peer_manager.clone()), "listpeers" => list_peers(peer_manager.clone()), + "signmessage" => { + const MSG_STARTPOS: usize = "signmsg".len() + 1; + if line.as_bytes().len() <= MSG_STARTPOS { + println!("ERROR: signmsg requires a message"); + continue; + } + println!( + "{:?}", + lightning::util::message_signing::sign( + &line.as_bytes()[MSG_STARTPOS..], + &keys_manager.get_node_secret() + ) + ); + } _ => println!("Unknown command. See `\"help\" for available commands."), } } - print!("> "); - io::stdout().flush().unwrap(); } } @@ -430,13 +382,20 @@ fn help() { println!("forceclosechannel "); println!("nodeinfo"); println!("listpeers"); + println!("signmessage "); } fn node_info(channel_manager: Arc, peer_manager: Arc) { println!("\t{{"); println!("\t\t node_pubkey: {}", channel_manager.get_our_node_id()); - println!("\t\t num_channels: {}", channel_manager.list_channels().len()); - println!("\t\t num_usable_channels: {}", channel_manager.list_usable_channels().len()); + let chans = channel_manager.list_channels(); + println!("\t\t num_channels: {}", chans.len()); + println!("\t\t num_usable_channels: {}", chans.iter().filter(|c| c.is_usable).count()); + let local_balance_msat = chans + .iter() + .map(|c| c.unspendable_punishment_reserve.unwrap_or(0) * 1000 + c.outbound_capacity_msat) + .sum::(); + println!("\t\t local_balance_msat: {}", local_balance_msat); println!("\t\t num_peers: {}", peer_manager.get_peer_node_ids().len()); println!("\t}},"); } @@ -586,44 +545,36 @@ fn open_channel( } } -fn send_payment( - payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, - payment_secret: Option, payee_features: Option, - route_hints: Vec<&RouteHint>, - router: Arc, Arc>>, - channel_manager: Arc, payment_storage: PaymentInfoStorage, - logger: Arc, +fn send_payment( + invoice_payer: &InvoicePayer, invoice: &Invoice, payment_storage: PaymentInfoStorage, ) { - let network_graph = router.network_graph.read().unwrap(); - let first_hops = channel_manager.list_usable_channels(); - let payer_pubkey = channel_manager.get_our_node_id(); - - let route = router::get_route( - &payer_pubkey, - &network_graph, - &payee, - payee_features, - Some(&first_hops.iter().collect::>()), - &route_hints, - amt_msat, - final_cltv, - logger, - ); - if let Err(e) = route { - println!("ERROR: failed to find route: {}", e.err); - return; - } - let status = match channel_manager.send_payment(&route.unwrap(), payment_hash, &payment_secret) - { - Ok(()) => { - println!("EVENT: initiated sending {} msats to {}", amt_msat, payee); + let status = match invoice_payer.pay_invoice(invoice) { + Ok(_payment_id) => { + let payee_pubkey = invoice.recover_payee_pub_key(); + let amt_msat = invoice.amount_milli_satoshis().unwrap(); + println!("EVENT: initiated sending {} msats to {}", amt_msat, payee_pubkey); + print!("> "); HTLCStatus::Pending } - Err(e) => { + Err(PaymentError::Invoice(e)) => { + println!("ERROR: invalid invoice: {}", e); + print!("> "); + return; + } + Err(PaymentError::Routing(e)) => { + println!("ERROR: failed to find route: {}", e.err); + print!("> "); + return; + } + Err(PaymentError::Sending(e)) => { println!("ERROR: failed to send payment: {:?}", e); + print!("> "); HTLCStatus::Failed } }; + let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner()); + let payment_secret = Some(invoice.payment_secret().clone()); + let mut payments = payment_storage.lock().unwrap(); payments.insert( payment_hash, @@ -631,30 +582,29 @@ fn send_payment( preimage: None, secret: payment_secret, status, - amt_msat: MillisatAmount(Some(amt_msat)), + amt_msat: MillisatAmount(invoice.amount_milli_satoshis()), }, ); } fn keysend( - payee: PublicKey, amt_msat: u64, - router: Arc, Arc>>, + payee_pubkey: PublicKey, amt_msat: u64, network_graph: Arc, channel_manager: Arc, payment_storage: PaymentInfoStorage, - logger: Arc, + logger: Arc, scorer: Arc>, ) { - let network_graph = router.network_graph.read().unwrap(); let first_hops = channel_manager.list_usable_channels(); let payer_pubkey = channel_manager.get_our_node_id(); - let route = match router::get_keysend_route( + let payee = Payee::for_keysend(payee_pubkey); + let params = RouteParameters { payee, final_value_msat: amt_msat, final_cltv_expiry_delta: 40 }; + + let route = match router::find_route( &payer_pubkey, + ¶ms, &network_graph, - &payee, Some(&first_hops.iter().collect::>()), - &vec![], - amt_msat, - 40, logger, + &scorer.lock().unwrap(), ) { Ok(r) => r, Err(e) => { @@ -664,7 +614,7 @@ fn keysend( }; let mut payments = payment_storage.lock().unwrap(); - let payment_hash = channel_manager.send_spontaneous_payment(&route, None).unwrap(); + let payment_hash = channel_manager.send_spontaneous_payment(&route, None).unwrap().0; payments.insert( payment_hash, PaymentInfo { @@ -709,7 +659,7 @@ fn get_invoice( payment_hash, PaymentInfo { preimage: None, - secret: invoice.payment_secret().cloned(), + secret: Some(invoice.payment_secret().clone()), status: HTLCStatus::Pending, amt_msat: MillisatAmount(Some(amt_msat)), },