X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=src%2Fcli.rs;h=da6d97fb2587ae1e990d4203b0542a3261e7ee00;hb=d072085ead48969f2e752764f9609a75f0228487;hp=54b2eab7c5f46fda4ba7f60c39ee85a09c3fc6fb;hpb=7a39a4df612ae282fcf2329be8fc1bf8da8a84db;p=ldk-sample diff --git a/src/cli.rs b/src/cli.rs index 54b2eab..da6d97f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,31 +1,31 @@ use crate::disk; use crate::hex_utils; use crate::{ - ChannelManager, FilesystemLogger, HTLCStatus, InvoicePayer, MillisatAmount, PaymentInfo, - PaymentInfoStorage, PeerManager, + ChannelManager, HTLCStatus, InvoicePayer, MillisatAmount, NetworkGraph, OnionMessenger, + PaymentInfo, PaymentInfoStorage, PeerManager, }; +use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; -use bitcoin::secp256k1::key::PublicKey; -use lightning::chain::keysinterface::{KeysInterface, KeysManager}; -use lightning::ln::msgs::NetAddress; -use lightning::ln::PaymentHash; -use lightning::routing::network_graph::NetworkGraph; -use lightning::routing::router; -use lightning::routing::router::{Payee, RouteParameters}; -use lightning::routing::scorer::Scorer; -use lightning::util::config::{ChannelConfig, ChannelHandshakeLimits, UserConfig}; +use bitcoin::secp256k1::PublicKey; +use lightning::chain::keysinterface::{KeysInterface, KeysManager, Recipient}; +use lightning::ln::msgs::{DecodeError, NetAddress}; +use lightning::ln::{PaymentHash, PaymentPreimage}; +use lightning::onion_message::{CustomOnionMessageContents, Destination, OnionMessageContents}; +use lightning::routing::gossip::NodeId; +use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; use lightning::util::events::EventHandler; +use lightning::util::ser::{MaybeReadableArgs, Writeable, Writer}; use lightning_invoice::payment::PaymentError; use lightning_invoice::{utils, Currency, Invoice}; use std::env; use std::io; -use std::io::{BufRead, Write}; -use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use std::io::Write; +use std::net::{SocketAddr, ToSocketAddrs}; use std::ops::Deref; use std::path::Path; use std::str::FromStr; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::time::Duration; pub(crate) struct LdkUserInfo { @@ -40,121 +40,54 @@ pub(crate) struct LdkUserInfo { pub(crate) network: Network, } -pub(crate) fn parse_startup_args() -> Result { - if env::args().len() < 3 { - println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [] [bitcoin-network] [announced-node-name announced-listen-addr*]`"); - return Err(()); - } - let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); - 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[1].split(":").collect(); - if rpc_user_and_password.len() != 2 { - println!("ERROR: bad bitcoind RPC username/password combo provided"); - return Err(()); +struct UserOnionMessageContents { + tlv_type: u64, + data: Vec, +} + +impl CustomOnionMessageContents for UserOnionMessageContents { + fn tlv_type(&self) -> u64 { + self.tlv_type } - 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[0].split(":").collect(); - if bitcoind_rpc_path.len() != 2 { - println!("ERROR: bad bitcoind RPC path provided"); - return Err(()); +} +impl MaybeReadableArgs for UserOnionMessageContents { + fn read(_r: &mut R, _args: u64) -> Result, DecodeError> { + // UserOnionMessageContents is only ever passed to `send_onion_message`, never to an + // `OnionMessageHandler`, thus it does not need to implement the read side here. + unreachable!(); } - let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string(); - let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::().unwrap(); - - let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); - - let mut ldk_peer_port_set = true; - let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { - Some(Ok(p)) => p, - Some(Err(_)) => { - ldk_peer_port_set = false; - 9735 - } - None => { - ldk_peer_port_set = false; - 9735 - } - }; - - let mut arg_idx = match ldk_peer_port_set { - true => 4, - false => 3, - }; - let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) { - Some("testnet") => Network::Testnet, - Some("regtest") => Network::Regtest, - Some(net) => { - panic!("Unsupported network provided. Options are: `regtest`, `testnet`. Got {}", net); - } - None => Network::Testnet, - }; - - let ldk_announced_node_name = match env::args().skip(arg_idx + 1).next().as_ref() { - Some(s) => { - if s.len() > 32 { - panic!("Node Alias can not be longer than 32 bytes"); - } - arg_idx += 1; - let mut bytes = [0; 32]; - bytes[..s.len()].copy_from_slice(s.as_bytes()); - bytes - } - None => [0; 32], - }; - - let mut ldk_announced_listen_addr = Vec::new(); - loop { - match env::args().skip(arg_idx + 1).next().as_ref() { - Some(s) => match IpAddr::from_str(s) { - Ok(IpAddr::V4(a)) => { - ldk_announced_listen_addr - .push(NetAddress::IPv4 { addr: a.octets(), port: ldk_peer_listening_port }); - arg_idx += 1; - } - Ok(IpAddr::V6(a)) => { - ldk_announced_listen_addr - .push(NetAddress::IPv6 { addr: a.octets(), port: ldk_peer_listening_port }); - arg_idx += 1; - } - Err(_) => panic!("Failed to parse announced-listen-addr into an IP address"), - }, - None => break, - } +} +impl Writeable for UserOnionMessageContents { + fn write(&self, w: &mut W) -> Result<(), std::io::Error> { + w.write_all(&self.data) } - - Ok(LdkUserInfo { - bitcoind_rpc_username, - bitcoind_rpc_password, - bitcoind_rpc_host, - bitcoind_rpc_port, - ldk_storage_dir_path, - ldk_peer_listening_port, - ldk_announced_listen_addr, - ldk_announced_node_name, - network, - }) } 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>, + network_graph: Arc, onion_messenger: Arc, inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, - ldk_data_dir: String, logger: Arc, network: Network, + ldk_data_dir: String, network: Network, logger: Arc, ) { - println!("LDK startup successful. To view available commands: \"help\"."); + println!( + "LDK startup successful. Enter \"help\" to view available commands. Press Ctrl-D to quit." + ); 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(); + loop { + print!("> "); + io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print + let mut line = String::new(); + if let Err(e) = io::stdin().read_line(&mut line) { + break println!("ERROR: {}", e); + } + + if line.len() == 0 { + // We hit EOF / Ctrl-D + break; + } + let mut words = line.split_whitespace(); if let Some(word) = words.next() { match word { @@ -164,8 +97,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(); @@ -174,8 +105,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; } }; @@ -183,8 +112,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; } @@ -192,8 +119,6 @@ pub(crate) async fn poll_for_user_input( .await .is_err() { - print!("> "); - io::stdout().flush().unwrap(); continue; }; @@ -202,8 +127,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, @@ -228,8 +151,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; } @@ -237,8 +158,6 @@ pub(crate) async fn poll_for_user_input( Ok(inv) => inv, Err(e) => { println!("ERROR: invalid invoice: {:?}", e); - print!("> "); - io::stdout().flush().unwrap(); continue; } }; @@ -251,15 +170,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; } }; @@ -267,9 +182,6 @@ pub(crate) async fn poll_for_user_input( Some(amt) => amt, None => { println!("ERROR: keysend requires an amount in millisatoshis: `keysend `"); - - print!("> "); - io::stdout().flush().unwrap(); continue; } }; @@ -277,51 +189,56 @@ 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( + &*invoice_payer, dest_pubkey, amt_msat, - network_graph.clone(), - channel_manager.clone(), + &*keys_manager, 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; } + + let expiry_secs_str = words.next(); + if expiry_secs_str.is_none() { + println!("ERROR: getinvoice requires an expiry in seconds"); + continue; + } + + let expiry_secs: Result = expiry_secs_str.unwrap().parse(); + if expiry_secs.is_err() { + println!("ERROR: getinvoice provided expiry was not a number"); + continue; + } + get_invoice( amt_msat.unwrap(), - inbound_payments.clone(), - channel_manager.clone(), - keys_manager.clone(), + Arc::clone(&inbound_payments), + &*channel_manager, + Arc::clone(&keys_manager), network, + expiry_secs.unwrap(), + Arc::clone(&logger), ); } "connectpeer" => { 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) = @@ -329,8 +246,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; } }; @@ -341,95 +256,202 @@ pub(crate) async fn poll_for_user_input( println!("SUCCESS: connected to peer {}", pubkey); } } - "listchannels" => list_channels(channel_manager.clone()), + "listchannels" => list_channels(&channel_manager, &network_graph), "listpayments" => { list_payments(inbound_payments.clone(), outbound_payments.clone()) } "closechannel" => { 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(); + println!("ERROR: closechannel requires a channel ID: `closechannel `"); 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(); + if channel_id_vec.is_none() || channel_id_vec.as_ref().unwrap().len() != 32 { + println!("ERROR: couldn't parse channel_id"); continue; } let mut channel_id = [0; 32]; channel_id.copy_from_slice(&channel_id_vec.unwrap()); - close_channel(channel_id, channel_manager.clone()); + + let peer_pubkey_str = words.next(); + if peer_pubkey_str.is_none() { + println!("ERROR: closechannel requires a peer pubkey: `closechannel `"); + continue; + } + let peer_pubkey_vec = match hex_utils::to_vec(peer_pubkey_str.unwrap()) { + Some(peer_pubkey_vec) => peer_pubkey_vec, + None => { + println!("ERROR: couldn't parse peer_pubkey"); + continue; + } + }; + let peer_pubkey = match PublicKey::from_slice(&peer_pubkey_vec) { + Ok(peer_pubkey) => peer_pubkey, + Err(_) => { + println!("ERROR: couldn't parse peer_pubkey"); + continue; + } + }; + + close_channel(channel_id, peer_pubkey, channel_manager.clone()); } "forceclosechannel" => { 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(); + println!("ERROR: forceclosechannel requires a channel ID: `forceclosechannel `"); 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(); + if channel_id_vec.is_none() || channel_id_vec.as_ref().unwrap().len() != 32 { + println!("ERROR: couldn't parse channel_id"); continue; } let mut channel_id = [0; 32]; channel_id.copy_from_slice(&channel_id_vec.unwrap()); - force_close_channel(channel_id, channel_manager.clone()); + + let peer_pubkey_str = words.next(); + if peer_pubkey_str.is_none() { + println!("ERROR: forceclosechannel requires a peer pubkey: `forceclosechannel `"); + continue; + } + let peer_pubkey_vec = match hex_utils::to_vec(peer_pubkey_str.unwrap()) { + Some(peer_pubkey_vec) => peer_pubkey_vec, + None => { + println!("ERROR: couldn't parse peer_pubkey"); + continue; + } + }; + let peer_pubkey = match PublicKey::from_slice(&peer_pubkey_vec) { + Ok(peer_pubkey) => peer_pubkey, + Err(_) => { + println!("ERROR: couldn't parse peer_pubkey"); + continue; + } + }; + + force_close_channel(channel_id, peer_pubkey, channel_manager.clone()); } - "nodeinfo" => node_info(channel_manager.clone(), peer_manager.clone()), + "nodeinfo" => node_info(&channel_manager, &peer_manager), "listpeers" => list_peers(peer_manager.clone()), "signmessage" => { - const MSG_STARTPOS: usize = "signmsg".len() + 1; + const MSG_STARTPOS: usize = "signmessage".len() + 1; if line.as_bytes().len() <= MSG_STARTPOS { println!("ERROR: signmsg requires a message"); - print!("> "); - io::stdout().flush().unwrap(); continue; } println!( "{:?}", lightning::util::message_signing::sign( &line.as_bytes()[MSG_STARTPOS..], - &keys_manager.get_node_secret() + &keys_manager.get_node_secret(Recipient::Node).unwrap() ) ); - print!("> "); - io::stdout().flush().unwrap(); } + "sendonionmessage" => { + let path_pks_str = words.next(); + if path_pks_str.is_none() { + println!( + "ERROR: sendonionmessage requires at least one node id for the path" + ); + continue; + } + let mut node_pks = Vec::new(); + let mut errored = false; + for pk_str in path_pks_str.unwrap().split(",") { + let node_pubkey_vec = match hex_utils::to_vec(pk_str) { + Some(peer_pubkey_vec) => peer_pubkey_vec, + None => { + println!("ERROR: couldn't parse peer_pubkey"); + errored = true; + break; + } + }; + let node_pubkey = match PublicKey::from_slice(&node_pubkey_vec) { + Ok(peer_pubkey) => peer_pubkey, + Err(_) => { + println!("ERROR: couldn't parse peer_pubkey"); + errored = true; + break; + } + }; + node_pks.push(node_pubkey); + } + if errored { + continue; + } + let tlv_type = match words.next().map(|ty_str| ty_str.parse()) { + Some(Ok(ty)) if ty >= 64 => ty, + _ => { + println!("Need an integral message type above 64"); + continue; + } + }; + let data = match words.next().map(|s| hex_utils::to_vec(s)) { + Some(Some(data)) => data, + _ => { + println!("Need a hex data string"); + continue; + } + }; + let destination_pk = node_pks.pop().unwrap(); + match onion_messenger.send_onion_message( + &node_pks, + Destination::Node(destination_pk), + OnionMessageContents::Custom(UserOnionMessageContents { tlv_type, data }), + None, + ) { + Ok(()) => println!("SUCCESS: forwarded onion message to first hop"), + Err(e) => println!("ERROR: failed to send onion message: {:?}", e), + } + } + "quit" | "exit" => break, _ => println!("Unknown command. See `\"help\" for available commands."), } } - print!("> "); - io::stdout().flush().unwrap(); } } fn help() { - println!("openchannel pubkey@host:port "); - println!("sendpayment "); - println!("getinvoice "); - println!("connectpeer pubkey@host:port"); - println!("listchannels"); - println!("listpayments"); - println!("closechannel "); - println!("forceclosechannel "); - println!("nodeinfo"); - println!("listpeers"); - println!("signmessage "); + let package_version = env!("CARGO_PKG_VERSION"); + let package_name = env!("CARGO_PKG_NAME"); + println!("\nVERSION:"); + println!(" {} v{}", package_name, package_version); + println!("\nUSAGE:"); + println!(" Command [arguments]"); + println!("\nCOMMANDS:"); + println!(" help\tShows a list of commands."); + println!(" quit\tClose the application."); + println!("\n Channels:"); + println!(" openchannel pubkey@host:port [--public]"); + println!(" closechannel "); + println!(" forceclosechannel "); + println!(" listchannels"); + println!("\n Peers:"); + println!(" connectpeer pubkey@host:port"); + println!(" listpeers"); + println!("\n Payments:"); + println!(" sendpayment "); + println!(" keysend "); + println!(" listpayments"); + println!("\n Invoices:"); + println!(" getinvoice "); + println!("\n Other:"); + println!(" signmessage "); + println!( + " sendonionmessage " + ); + println!(" nodeinfo"); } -fn node_info(channel_manager: Arc, peer_manager: Arc) { +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.balance_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}},"); } @@ -442,7 +464,7 @@ fn list_peers(peer_manager: Arc) { println!("\t}},"); } -fn list_channels(channel_manager: Arc) { +fn list_channels(channel_manager: &Arc, network_graph: &Arc) { print!("["); for chan_info in channel_manager.list_channels() { println!(""); @@ -451,20 +473,27 @@ fn list_channels(channel_manager: Arc) { if let Some(funding_txo) = chan_info.funding_txo { println!("\t\tfunding_txid: {},", funding_txo.txid); } + println!( "\t\tpeer_pubkey: {},", hex_utils::hex_str(&chan_info.counterparty.node_id.serialize()) ); + if let Some(node_info) = network_graph + .read_only() + .nodes() + .get(&NodeId::from_pubkey(&chan_info.counterparty.node_id)) + { + if let Some(announcement) = &node_info.announcement_info { + println!("\t\tpeer_alias: {}", announcement.alias); + } + } + if let Some(id) = chan_info.short_channel_id { println!("\t\tshort_channel_id: {},", id); } - println!("\t\tis_confirmed_onchain: {},", chan_info.is_funding_locked); + println!("\t\tis_channel_ready: {},", chan_info.is_channel_ready); println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis); - println!( - "\t\tlocal_balance_msat: {},", - chan_info.outbound_capacity_msat - + chan_info.unspendable_punishment_reserve.unwrap_or(0) * 1000 - ); + println!("\t\tlocal_balance_msat: {},", chan_info.balance_msat); if chan_info.is_usable { println!("\t\tavailable_balance_for_send_msat: {},", chan_info.outbound_capacity_msat); println!("\t\tavailable_balance_for_recv_msat: {},", chan_info.inbound_capacity_msat); @@ -526,6 +555,16 @@ pub(crate) async fn connect_peer_if_necessary( return Ok(()); } } + let res = do_connect_peer(pubkey, peer_addr, peer_manager).await; + if res.is_err() { + println!("ERROR: failed to connect to peer"); + } + res +} + +pub(crate) async fn do_connect_peer( + pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc, +) -> Result<(), ()> { match lightning_net_tokio::connect_outbound(Arc::clone(&peer_manager), pubkey, peer_addr).await { Some(connection_closed_future) => { @@ -533,24 +572,19 @@ pub(crate) async fn connect_peer_if_necessary( loop { match futures::poll!(&mut connection_closed_future) { std::task::Poll::Ready(_) => { - println!("ERROR: Peer disconnected before we finished the handshake"); return Err(()); } std::task::Poll::Pending => {} } // Avoid blocking the tokio context by sleeping a bit match peer_manager.get_peer_node_ids().iter().find(|id| **id == pubkey) { - Some(_) => break, + Some(_) => return Ok(()), None => tokio::time::sleep(Duration::from_millis(10)).await, } } } - None => { - println!("ERROR: failed to connect to peer"); - return Err(()); - } + None => Err(()), } - Ok(()) } fn open_channel( @@ -558,12 +592,15 @@ fn open_channel( channel_manager: Arc, ) -> Result<(), ()> { let config = UserConfig { - peer_channel_config_limits: ChannelHandshakeLimits { + channel_handshake_limits: ChannelHandshakeLimits { // lnd's max to_self_delay is 2016, so we want to be compatible. their_to_self_delay: 2016, ..Default::default() }, - channel_options: ChannelConfig { announced_channel, ..Default::default() }, + channel_handshake_config: ChannelHandshakeConfig { + announced_channel, + ..Default::default() + }, ..Default::default() }; @@ -621,62 +658,72 @@ fn send_payment( ); } -fn keysend( - payee_pubkey: PublicKey, amt_msat: u64, network_graph: Arc, - channel_manager: Arc, payment_storage: PaymentInfoStorage, - logger: Arc, scorer: Arc>, +fn keysend( + invoice_payer: &InvoicePayer, payee_pubkey: PublicKey, amt_msat: u64, keys: &K, + payment_storage: PaymentInfoStorage, ) { - let first_hops = channel_manager.list_usable_channels(); - let payer_pubkey = channel_manager.get_our_node_id(); + let payment_preimage = keys.get_secure_random_bytes(); - 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, - Some(&first_hops.iter().collect::>()), - logger, - &scorer.lock().unwrap(), + let status = match invoice_payer.pay_pubkey( + payee_pubkey, + PaymentPreimage(payment_preimage), + amt_msat, + 40, ) { - Ok(r) => r, - Err(e) => { + Ok(_payment_id) => { + println!("EVENT: initiated sending {} msats to {}", amt_msat, payee_pubkey); + print!("> "); + HTLCStatus::Pending + } + Err(PaymentError::Invoice(e)) => { + println!("ERROR: invalid payee: {}", 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 mut payments = payment_storage.lock().unwrap(); - let payment_hash = channel_manager.send_spontaneous_payment(&route, None).unwrap().0; payments.insert( - payment_hash, + PaymentHash(Sha256::hash(&payment_preimage).into_inner()), PaymentInfo { preimage: None, secret: None, - status: HTLCStatus::Pending, + status, amt_msat: MillisatAmount(Some(amt_msat)), }, ); } fn get_invoice( - amt_msat: u64, payment_storage: PaymentInfoStorage, channel_manager: Arc, - keys_manager: Arc, network: Network, + amt_msat: u64, payment_storage: PaymentInfoStorage, channel_manager: &ChannelManager, + keys_manager: Arc, network: Network, expiry_secs: u32, + logger: Arc, ) { let mut payments = payment_storage.lock().unwrap(); let currency = match network { Network::Bitcoin => Currency::Bitcoin, Network::Testnet => Currency::BitcoinTestnet, Network::Regtest => Currency::Regtest, - Network::Signet => panic!("Signet unsupported"), + Network::Signet => Currency::Signet, }; let invoice = match utils::create_invoice_from_channelmanager( - &channel_manager, + channel_manager, keys_manager, + logger, currency, Some(amt_msat), "ldk-tutorial-node".to_string(), + expiry_secs, ) { Ok(inv) => { println!("SUCCESS: generated invoice: {}", inv); @@ -700,15 +747,19 @@ fn get_invoice( ); } -fn close_channel(channel_id: [u8; 32], channel_manager: Arc) { - match channel_manager.close_channel(&channel_id) { +fn close_channel( + channel_id: [u8; 32], counterparty_node_id: PublicKey, channel_manager: Arc, +) { + match channel_manager.close_channel(&channel_id, &counterparty_node_id) { Ok(()) => println!("EVENT: initiating channel close"), Err(e) => println!("ERROR: failed to close channel: {:?}", e), } } -fn force_close_channel(channel_id: [u8; 32], channel_manager: Arc) { - match channel_manager.force_close_channel(&channel_id) { +fn force_close_channel( + channel_id: [u8; 32], counterparty_node_id: PublicKey, channel_manager: Arc, +) { + match channel_manager.force_close_broadcasting_latest_txn(&channel_id, &counterparty_node_id) { Ok(()) => println!("EVENT: initiating channel force-close"), Err(e) => println!("ERROR: failed to force-close channel: {:?}", e), } @@ -720,7 +771,7 @@ pub(crate) fn parse_peer_info( let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@"); let pubkey = pubkey_and_addr.next(); let peer_addr_str = pubkey_and_addr.next(); - if peer_addr_str.is_none() || peer_addr_str.is_none() { + if peer_addr_str.is_none() { return Err(std::io::Error::new( std::io::ErrorKind::Other, "ERROR: incorrectly formatted peer info. Should be formatted as: `pubkey@host:port`",