X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;ds=sidebyside;f=src%2Fcli.rs;h=8135d1e691a928c2d8d2c2edd88fff49b817fe62;hb=fbb1f231ef0e1e8362d353e5d4e3aaecba42a7f4;hp=5863ed0144430dda1fbe18231b7cbe70b0a988b6;hpb=cdca4eebd50cf480ea2a8cc5471aacb514ddfccf;p=ldk-sample diff --git a/src/cli.rs b/src/cli.rs index 5863ed0..8135d1e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,25 +1,27 @@ use crate::disk; use crate::hex_utils; use crate::{ - ChannelManager, HTLCStatus, InvoicePayer, MillisatAmount, NetworkGraph, 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::PublicKey; use lightning::chain::keysinterface::{KeysInterface, KeysManager, Recipient}; -use lightning::ln::msgs::NetAddress; +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::{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; @@ -38,125 +40,48 @@ 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(()); - } - 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(()); - } - 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("signet") => Network::Signet, - Some(net) => { - panic!("Unsupported network provided. Options are: `regtest`, `testnet`, and `signet`. 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], - }; +struct UserOnionMessageContents { + tlv_type: u64, + data: Vec, +} - 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 CustomOnionMessageContents for UserOnionMessageContents { + fn tlv_type(&self) -> u64 { + self.tlv_type } +} - 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, - }) +impl Writeable for UserOnionMessageContents { + fn write(&self, w: &mut W) -> Result<(), std::io::Error> { + w.write_all(&self.data) + } } pub(crate) async fn poll_for_user_input( invoice_payer: Arc>, peer_manager: Arc, channel_manager: Arc, keys_manager: Arc, - network_graph: Arc, inbound_payments: PaymentInfoStorage, - outbound_payments: PaymentInfoStorage, ldk_data_dir: String, network: Network, + network_graph: Arc, onion_messenger: Arc, + inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, + 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(); - 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 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 { @@ -296,11 +221,12 @@ pub(crate) async fn poll_for_user_input( 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" => { @@ -416,6 +342,64 @@ pub(crate) async fn poll_for_user_input( ) ); } + "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."), } } @@ -423,18 +407,35 @@ pub(crate) async fn poll_for_user_input( } fn help() { - println!("openchannel pubkey@host:port [--public]"); - println!("sendpayment "); - println!("keysend "); - 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) { @@ -698,8 +699,9 @@ fn keysend( } fn get_invoice( - amt_msat: u64, payment_storage: PaymentInfoStorage, channel_manager: Arc, + 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 { @@ -709,8 +711,9 @@ fn get_invoice( 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(),