X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;ds=sidebyside;f=src%2Fcli.rs;h=02713a55ad3e2d1f7eccc98d93db0d393558c412;hb=b61dae03855accbafcbbc68c69692cbe1797b195;hp=c046d0cf6fe4771d0e7427b3c25c236a6ee112e5;hpb=1b034d63a4cb4e0b249ae6240166513b6be76737;p=ldk-sample diff --git a/src/cli.rs b/src/cli.rs index c046d0c..e348742 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,100 +1,898 @@ -use bitcoin::secp256k1::key::PublicKey; -use crate::{FilesystemLogger, PeerManager, ChannelManager}; -use crate::utils; +use crate::disk::{self, INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME}; +use crate::hex_utils; +use crate::{ + ChannelManager, HTLCStatus, InboundPaymentInfoStorage, MillisatAmount, NetworkGraph, + OnionMessenger, OutboundPaymentInfoStorage, PaymentInfo, PeerManager, +}; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::Hash; +use bitcoin::network::constants::Network; +use bitcoin::secp256k1::PublicKey; +use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry}; +use lightning::ln::msgs::SocketAddress; +use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage}; +use lightning::onion_message::messenger::Destination; +use lightning::onion_message::packet::OnionMessageContents; +use lightning::routing::gossip::NodeId; +use lightning::routing::router::{PaymentParameters, RouteParameters}; +use lightning::sign::{EntropySource, KeysManager}; +use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; +use lightning::util::persist::KVStore; +use lightning::util::ser::{Writeable, Writer}; +use lightning_invoice::{utils, Bolt11Invoice, Currency}; +use lightning_persister::fs_store::FilesystemStore; +use std::env; use std::io; -use std::io::{BufRead, Write}; -use std::net::{SocketAddr, TcpStream}; -use std::sync::Arc; -use std::thread; +use std::io::Write; +use std::net::{SocketAddr, ToSocketAddrs}; +use std::path::Path; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; use std::time::Duration; -// use tokio::runtime::Runtime; -use tokio::runtime::{Builder, Runtime}; -use tokio::sync::mpsc; - -pub fn poll_for_user_input(peer_manager: Arc, channel_manager: Arc, event_notifier: mpsc::Sender<()>) { - println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available in the `logs` folder of the current directory."); - print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print - let stdin = io::stdin(); - for line in stdin.lock().lines() { - let line = line.unwrap(); - let mut words = line.split_whitespace(); - if let Some(word) = words.next() { - match word { - "help" => handle_help(), - "openchannel" => { - let peer_pubkey_and_ip_addr = words.next(); - let channel_value_sat = words.next(); - if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { - println!("ERROR: openchannel takes 2 arguments: `openchannel pubkey@host:port channel_amt_satoshis`") - } else { - let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); - let channel_value_sat = channel_value_sat.unwrap(); - 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() { - println!("ERROR: incorrectly formatted peer info. Should be formatted as: `pubkey@host:port`"); - } else { - let pubkey = pubkey.unwrap(); - let peer_addr_str = peer_addr_str.unwrap(); - let peer_addr_res: Result = peer_addr_str.parse(); - let chan_amt: Result = channel_value_sat.parse(); - if let Some(pk) = utils::hex_to_compressed_pubkey(pubkey) { - if let Ok(addr) = peer_addr_res { - if let Ok(amt) = chan_amt { - handle_open_channel(pk, addr, amt, - peer_manager.clone(), - channel_manager.clone(), - event_notifier.clone()); - } else { - println!("ERROR: channel amount must be a number"); - } - } else { - println!("ERROR: couldn't parse - pubkey@host:port into a socket address"); - } - } else { - println!("ERROR: unable to parse given pubkey for node"); - } - } - } - }, - _ => println!("hello") - } - } - print!("> "); io::stdout().flush().unwrap(); - - } -} - -fn handle_help() { - println!("") -} - -fn handle_open_channel(peer_pubkey: PublicKey, peer_socket: SocketAddr, - channel_amt_sat: u64, peer_manager: Arc, - channel_manager: Arc, event_notifier: - mpsc::Sender<()>) -{ - // let runtime = Runtime::new().expect("Unable to create a runtime").enable_all(); - let runtime = Builder::new_multi_thread() - .worker_threads(3) - .enable_all() - .build() - .unwrap(); - match TcpStream::connect_timeout(&peer_socket, Duration::from_secs(10)) { - Ok(stream) => { - runtime.block_on(|| { - lightning_net_tokio::setup_outbound(peer_manager, - event_notifier, - peer_pubkey, - stream); - }); - match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) { - Ok(_) => println!("SUCCESS: channel created! Mine some blocks to open it."), - Err(e) => println!("ERROR: failed to open channel: {:?}", e) - } - }, - Err(e) => println!("ERROR: failed to connect to peer: {:?}", e) - } + +pub(crate) struct LdkUserInfo { + pub(crate) bitcoind_rpc_username: String, + pub(crate) bitcoind_rpc_password: String, + pub(crate) bitcoind_rpc_port: u16, + pub(crate) bitcoind_rpc_host: String, + pub(crate) ldk_storage_dir_path: String, + pub(crate) ldk_peer_listening_port: u16, + pub(crate) ldk_announced_listen_addr: Vec, + pub(crate) ldk_announced_node_name: [u8; 32], + pub(crate) network: Network, +} + +#[derive(Debug)] +struct UserOnionMessageContents { + tlv_type: u64, + data: Vec, +} + +impl OnionMessageContents for UserOnionMessageContents { + fn tlv_type(&self) -> u64 { + self.tlv_type + } +} + +impl Writeable for UserOnionMessageContents { + fn write(&self, w: &mut W) -> Result<(), std::io::Error> { + w.write_all(&self.data) + } +} + +pub(crate) fn poll_for_user_input( + peer_manager: Arc, channel_manager: Arc, + keys_manager: Arc, network_graph: Arc, + onion_messenger: Arc, inbound_payments: Arc>, + outbound_payments: Arc>, ldk_data_dir: String, + network: Network, logger: Arc, fs_store: Arc, +) { + 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()); + 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 { + "help" => help(), + "openchannel" => { + let peer_pubkey_and_ip_addr = words.next(); + 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] [--with-anchors]"); + continue; + } + let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); + let (pubkey, peer_addr) = + match parse_peer_info(peer_pubkey_and_ip_addr.to_string()) { + Ok(info) => info, + Err(e) => { + println!("{:?}", e.into_inner().unwrap()); + continue; + } + }; + + let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); + if chan_amt_sat.is_err() { + println!("ERROR: channel amount must be a number"); + continue; + } + + if tokio::runtime::Handle::current() + .block_on(connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + )) + .is_err() + { + continue; + }; + + let (mut announce_channel, mut with_anchors) = (false, false); + while let Some(word) = words.next() { + match word { + "--public" | "--public=true" => announce_channel = true, + "--public=false" => announce_channel = false, + "--with-anchors" | "--with-anchors=true" => with_anchors = true, + "--with-anchors=false" => with_anchors = false, + _ => { + println!("ERROR: invalid boolean flag format. Valid formats: `--option`, `--option=true` `--option=false`"); + continue; + } + } + } + + if open_channel( + pubkey, + chan_amt_sat.unwrap(), + announce_channel, + with_anchors, + channel_manager.clone(), + ) + .is_ok() + { + let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); + let _ = disk::persist_channel_peer( + Path::new(&peer_data_path), + peer_pubkey_and_ip_addr, + ); + } + } + "sendpayment" => { + let invoice_str = words.next(); + if invoice_str.is_none() { + println!("ERROR: sendpayment requires an invoice: `sendpayment `"); + continue; + } + + let invoice = match Bolt11Invoice::from_str(invoice_str.unwrap()) { + Ok(inv) => inv, + Err(e) => { + println!("ERROR: invalid invoice: {:?}", e); + continue; + } + }; + + send_payment( + &channel_manager, + &invoice, + &mut outbound_payments.lock().unwrap(), + Arc::clone(&fs_store), + ); + } + "keysend" => { + let dest_pubkey = match words.next() { + Some(dest) => match hex_utils::to_compressed_pubkey(dest) { + Some(pk) => pk, + None => { + println!("ERROR: couldn't parse destination pubkey"); + continue; + } + }, + None => { + println!("ERROR: keysend requires a destination pubkey: `keysend `"); + continue; + } + }; + let amt_msat_str = match words.next() { + Some(amt) => amt, + None => { + println!("ERROR: keysend requires an amount in millisatoshis: `keysend `"); + continue; + } + }; + let amt_msat: u64 = match amt_msat_str.parse() { + Ok(amt) => amt, + Err(e) => { + println!("ERROR: couldn't parse amount_msat: {}", e); + continue; + } + }; + keysend( + &channel_manager, + dest_pubkey, + amt_msat, + &*keys_manager, + &mut outbound_payments.lock().unwrap(), + Arc::clone(&fs_store), + ); + } + "getinvoice" => { + let amt_str = words.next(); + if amt_str.is_none() { + println!("ERROR: getinvoice requires an amount in millisatoshis"); + continue; + } + + let amt_msat: Result = amt_str.unwrap().parse(); + if amt_msat.is_err() { + println!("ERROR: getinvoice provided payment amount was not a number"); + 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; + } + + let mut inbound_payments = inbound_payments.lock().unwrap(); + get_invoice( + amt_msat.unwrap(), + &mut inbound_payments, + &channel_manager, + Arc::clone(&keys_manager), + network, + expiry_secs.unwrap(), + Arc::clone(&logger), + ); + fs_store + .write("", "", INBOUND_PAYMENTS_FNAME, &inbound_payments.encode()) + .unwrap(); + } + "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`"); + continue; + } + let (pubkey, peer_addr) = + match parse_peer_info(peer_pubkey_and_ip_addr.unwrap().to_string()) { + Ok(info) => info, + Err(e) => { + println!("{:?}", e.into_inner().unwrap()); + continue; + } + }; + if tokio::runtime::Handle::current() + .block_on(connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + )) + .is_ok() + { + println!("SUCCESS: connected to peer {}", pubkey); + } + } + "disconnectpeer" => { + let peer_pubkey = words.next(); + if peer_pubkey.is_none() { + println!("ERROR: disconnectpeer requires peer public key: `disconnectpeer `"); + continue; + } + + let peer_pubkey = + match bitcoin::secp256k1::PublicKey::from_str(peer_pubkey.unwrap()) { + Ok(pubkey) => pubkey, + Err(e) => { + println!("ERROR: {}", e.to_string()); + continue; + } + }; + + if do_disconnect_peer( + peer_pubkey, + peer_manager.clone(), + channel_manager.clone(), + ) + .is_ok() + { + println!("SUCCESS: disconnected from peer {}", peer_pubkey); + } + } + "listchannels" => list_channels(&channel_manager, &network_graph), + "listpayments" => list_payments( + &inbound_payments.lock().unwrap(), + &outbound_payments.lock().unwrap(), + ), + "closechannel" => { + let channel_id_str = words.next(); + if channel_id_str.is_none() { + 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() || 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()); + + 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 `"); + continue; + } + let channel_id_vec = hex_utils::to_vec(channel_id_str.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()); + + 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, &peer_manager), + "listpeers" => list_peers(peer_manager.clone()), + "signmessage" => { + const MSG_STARTPOS: usize = "signmessage".len() + 1; + if line.trim().as_bytes().len() <= MSG_STARTPOS { + println!("ERROR: signmsg requires a message"); + continue; + } + println!( + "{:?}", + lightning::util::message_signing::sign( + &line.trim().as_bytes()[MSG_STARTPOS..], + &keys_manager.get_node_secret_key() + ) + ); + } + "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 intermediate_nodes = 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; + } + }; + intermediate_nodes.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 = Destination::Node(intermediate_nodes.pop().unwrap()); + match onion_messenger.send_onion_message( + UserOnionMessageContents { tlv_type, data }, + destination, + None, + ) { + Ok(success) => { + println!("SUCCESS: forwarded onion message to first hop {:?}", success) + } + Err(e) => println!("ERROR: failed to send onion message: {:?}", e), + } + } + "quit" | "exit" => break, + _ => println!("Unknown command. See `\"help\" for available commands."), + } + } + } +} + +fn help() { + 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] [--with-anchors]"); + println!(" closechannel "); + println!(" forceclosechannel "); + println!(" listchannels"); + println!("\n Peers:"); + println!(" connectpeer pubkey@host:port"); + println!(" disconnectpeer "); + 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) { + println!("\t{{"); + println!("\t\t node_pubkey: {}", channel_manager.get_our_node_id()); + 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}},"); +} + +fn list_peers(peer_manager: Arc) { + println!("\t{{"); + for (pubkey, _) in peer_manager.get_peer_node_ids() { + println!("\t\t pubkey: {}", pubkey); + } + println!("\t}},"); +} + +fn list_channels(channel_manager: &Arc, network_graph: &Arc) { + print!("["); + for chan_info in channel_manager.list_channels() { + println!(""); + println!("\t{{"); + println!("\t\tchannel_id: {},", chan_info.channel_id); + 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_channel_ready: {},", chan_info.is_channel_ready); + println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis); + println!("\t\toutbound_capacity_msat: {},", chan_info.outbound_capacity_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); + } + println!("\t\tchannel_can_send_payments: {},", chan_info.is_usable); + println!("\t\tpublic: {},", chan_info.is_public); + println!("\t}},"); + } + println!("]"); +} + +fn list_payments( + inbound_payments: &InboundPaymentInfoStorage, outbound_payments: &OutboundPaymentInfoStorage, +) { + print!("["); + for (payment_hash, payment_info) in &inbound_payments.payments { + println!(""); + println!("\t{{"); + println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat); + println!("\t\tpayment_hash: {},", payment_hash); + println!("\t\thtlc_direction: inbound,"); + println!( + "\t\thtlc_status: {},", + match payment_info.status { + HTLCStatus::Pending => "pending", + HTLCStatus::Succeeded => "succeeded", + HTLCStatus::Failed => "failed", + } + ); + + println!("\t}},"); + } + + for (payment_hash, payment_info) in &outbound_payments.payments { + println!(""); + println!("\t{{"); + println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat); + println!("\t\tpayment_hash: {},", payment_hash); + println!("\t\thtlc_direction: outbound,"); + println!( + "\t\thtlc_status: {},", + match payment_info.status { + HTLCStatus::Pending => "pending", + HTLCStatus::Succeeded => "succeeded", + HTLCStatus::Failed => "failed", + } + ); + + println!("\t}},"); + } + println!("]"); +} + +pub(crate) async fn connect_peer_if_necessary( + pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc, +) -> Result<(), ()> { + for (node_pubkey, _) in peer_manager.get_peer_node_ids() { + if node_pubkey == pubkey { + 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) => { + let mut connection_closed_future = Box::pin(connection_closed_future); + loop { + tokio::select! { + _ = &mut connection_closed_future => return Err(()), + _ = tokio::time::sleep(Duration::from_millis(10)) => {}, + }; + if peer_manager.get_peer_node_ids().iter().find(|(id, _)| *id == pubkey).is_some() { + return Ok(()); + } + } + } + None => Err(()), + } +} + +fn do_disconnect_peer( + pubkey: bitcoin::secp256k1::PublicKey, peer_manager: Arc, + channel_manager: Arc, +) -> Result<(), ()> { + //check for open channels with peer + for channel in channel_manager.list_channels() { + if channel.counterparty.node_id == pubkey { + println!("Error: Node has an active channel with this peer, close any channels first"); + return Err(()); + } + } + + //check the pubkey matches a valid connected peer + let peers = peer_manager.get_peer_node_ids(); + if !peers.iter().any(|(pk, _)| &pubkey == pk) { + println!("Error: Could not find peer {}", pubkey); + return Err(()); + } + + peer_manager.disconnect_by_node_id(pubkey); + Ok(()) +} + +fn open_channel( + peer_pubkey: PublicKey, channel_amt_sat: u64, announced_channel: bool, with_anchors: bool, + channel_manager: Arc, +) -> Result<(), ()> { + let config = UserConfig { + 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_handshake_config: ChannelHandshakeConfig { + announced_channel, + negotiate_anchors_zero_fee_htlc_tx: with_anchors, + ..Default::default() + }, + ..Default::default() + }; + + match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None, Some(config)) { + Ok(_) => { + println!("EVENT: initiated channel with peer {}. ", peer_pubkey); + return Ok(()); + } + Err(e) => { + println!("ERROR: failed to open channel: {:?}", e); + return Err(()); + } + } +} + +fn send_payment( + channel_manager: &ChannelManager, invoice: &Bolt11Invoice, + outbound_payments: &mut OutboundPaymentInfoStorage, fs_store: Arc, +) { + let payment_id = PaymentId((*invoice.payment_hash()).to_byte_array()); + let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); + let payment_secret = Some(*invoice.payment_secret()); + outbound_payments.payments.insert( + payment_id, + PaymentInfo { + preimage: None, + secret: payment_secret, + status: HTLCStatus::Pending, + amt_msat: MillisatAmount(invoice.amount_milli_satoshis()), + }, + ); + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + + let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); + recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); + let mut payment_params = match PaymentParameters::from_node_id( + invoice.recover_payee_pub_key(), + invoice.min_final_cltv_expiry_delta() as u32, + ) + .with_expiry_time( + invoice.duration_since_epoch().as_secs().saturating_add(invoice.expiry_time().as_secs()), + ) + .with_route_hints(invoice.route_hints()) + { + Err(e) => { + println!("ERROR: Could not process invoice to prepare payment parameters, {:?}, invoice: {:?}", e, invoice); + return; + } + Ok(p) => p, + }; + if let Some(features) = invoice.features() { + payment_params = match payment_params.with_bolt11_features(features.clone()) { + Err(e) => { + println!("ERROR: Could not build BOLT11 payment parameters! {:?}", e); + return; + } + Ok(p) => p, + }; + } + let invoice_amount = match invoice.amount_milli_satoshis() { + None => { + println!("ERROR: An invoice with an amount is expected; {:?}", invoice); + return; + } + Some(a) => a, + }; + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, invoice_amount); + + match channel_manager.send_payment( + payment_hash, + recipient_onion, + payment_id, + route_params, + Retry::Timeout(Duration::from_secs(10)), + ) { + Ok(_) => { + 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!("> "); + } + Err(e) => { + println!("ERROR: failed to send payment: {:?}", e); + print!("> "); + outbound_payments.payments.get_mut(&payment_id).unwrap().status = HTLCStatus::Failed; + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + } + }; +} + +fn keysend( + channel_manager: &ChannelManager, payee_pubkey: PublicKey, amt_msat: u64, entropy_source: &E, + outbound_payments: &mut OutboundPaymentInfoStorage, fs_store: Arc, +) { + let payment_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes()); + let payment_id = PaymentId(Sha256::hash(&payment_preimage.0[..]).to_byte_array()); + + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::for_keysend(payee_pubkey, 40, false), + amt_msat, + ); + outbound_payments.payments.insert( + payment_id, + PaymentInfo { + preimage: None, + secret: None, + status: HTLCStatus::Pending, + amt_msat: MillisatAmount(Some(amt_msat)), + }, + ); + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + match channel_manager.send_spontaneous_payment_with_retry( + Some(payment_preimage), + RecipientOnionFields::spontaneous_empty(), + payment_id, + route_params, + Retry::Timeout(Duration::from_secs(10)), + ) { + Ok(_payment_hash) => { + println!("EVENT: initiated sending {} msats to {}", amt_msat, payee_pubkey); + print!("> "); + } + Err(e) => { + println!("ERROR: failed to send payment: {:?}", e); + print!("> "); + outbound_payments.payments.get_mut(&payment_id).unwrap().status = HTLCStatus::Failed; + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + } + }; +} + +fn get_invoice( + amt_msat: u64, inbound_payments: &mut InboundPaymentInfoStorage, + channel_manager: &ChannelManager, keys_manager: Arc, network: Network, + expiry_secs: u32, logger: Arc, +) { + let currency = match network { + Network::Bitcoin => Currency::Bitcoin, + Network::Regtest => Currency::Regtest, + Network::Signet => Currency::Signet, + Network::Testnet | _ => Currency::BitcoinTestnet, + }; + let invoice = match utils::create_invoice_from_channelmanager( + channel_manager, + keys_manager, + logger, + currency, + Some(amt_msat), + "ldk-tutorial-node".to_string(), + expiry_secs, + None, + ) { + Ok(inv) => { + println!("SUCCESS: generated invoice: {}", inv); + inv + } + Err(e) => { + println!("ERROR: failed to create invoice: {:?}", e); + return; + } + }; + + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + inbound_payments.payments.insert( + payment_hash, + PaymentInfo { + preimage: None, + secret: Some(invoice.payment_secret().clone()), + status: HTLCStatus::Pending, + amt_msat: MillisatAmount(Some(amt_msat)), + }, + ); +} + +fn close_channel( + channel_id: [u8; 32], counterparty_node_id: PublicKey, channel_manager: Arc, +) { + match channel_manager.close_channel(&ChannelId(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], counterparty_node_id: PublicKey, channel_manager: Arc, +) { + match channel_manager + .force_close_broadcasting_latest_txn(&ChannelId(channel_id), &counterparty_node_id) + { + Ok(()) => println!("EVENT: initiating channel force-close"), + Err(e) => println!("ERROR: failed to force-close channel: {:?}", e), + } +} + +pub(crate) fn parse_peer_info( + peer_pubkey_and_ip_addr: String, +) -> Result<(PublicKey, SocketAddr), std::io::Error> { + 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() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ERROR: incorrectly formatted peer info. Should be formatted as: `pubkey@host:port`", + )); + } + + let peer_addr = peer_addr_str.unwrap().to_socket_addrs().map(|mut r| r.next()); + if peer_addr.is_err() || peer_addr.as_ref().unwrap().is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ERROR: couldn't parse pubkey@host:port into a socket address", + )); + } + + let pubkey = hex_utils::to_compressed_pubkey(pubkey.unwrap()); + if pubkey.is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ERROR: unable to parse given pubkey for node", + )); + } + + Ok((pubkey.unwrap(), peer_addr.unwrap().unwrap())) }