+fn list_channels(channel_manager: Arc<ChannelManager>) {
+ print!("[");
+ for chan_info in channel_manager.list_channels() {
+ println!("");
+ println!("\t{{");
+ println!("\t\tchannel_id: {},", hex_utils::hex_str(&chan_info.channel_id[..]));
+ println!(
+ "\t\tpeer_pubkey: {},",
+ hex_utils::hex_str(&chan_info.remote_network_id.serialize())
+ );
+ let mut pending_channel = false;
+ match chan_info.short_channel_id {
+ Some(id) => println!("\t\tshort_channel_id: {},", id),
+ None => {
+ pending_channel = true;
+ }
+ }
+ println!("\t\tpending_open: {},", pending_channel);
+ println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis);
+ println!("\t\tchannel_can_send_payments: {},", chan_info.is_live);
+ println!("\t}},");
+ }
+ println!("]");
+}
+
+fn list_payments(inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage) {
+ let inbound = inbound_payments.lock().unwrap();
+ let outbound = outbound_payments.lock().unwrap();
+ print!("[");
+ for (payment_hash, payment_info) in inbound.deref() {
+ println!("");
+ println!("\t{{");
+ println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat);
+ println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0));
+ 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.deref() {
+ println!("");
+ println!("\t{{");
+ println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat);
+ println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0));
+ 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) fn connect_peer_if_necessary(
+ pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc<PeerManager>,
+ event_notifier: mpsc::Sender<()>,
+) -> Result<(), ()> {
+ for node_pubkey in peer_manager.get_peer_node_ids() {
+ if node_pubkey == pubkey {
+ return Ok(());
+ }
+ }
+ match TcpStream::connect_timeout(&peer_addr, Duration::from_secs(10)) {
+ Ok(stream) => {
+ let peer_mgr = peer_manager.clone();
+ let event_ntfns = event_notifier.clone();
+ tokio::spawn(async move {
+ lightning_net_tokio::setup_outbound(peer_mgr, event_ntfns, pubkey, stream).await;
+ });
+ let mut peer_connected = false;
+ while !peer_connected {
+ for node_pubkey in peer_manager.get_peer_node_ids() {
+ if node_pubkey == pubkey {
+ peer_connected = true;
+ }
+ }
+ }
+ }
+ Err(e) => {
+ println!("ERROR: failed to connect to peer: {:?}", e);
+ return Err(());
+ }
+ }
+ Ok(())
+}
+
+fn open_channel(
+ peer_pubkey: PublicKey, channel_amt_sat: u64, announce_channel: bool,
+ channel_manager: Arc<ChannelManager>,
+) -> Result<(), ()> {
+ let mut config = UserConfig::default();
+ if announce_channel {
+ config.channel_options.announced_channel = true;
+ }
+ // lnd's max to_self_delay is 2016, so we want to be compatible.
+ config.peer_channel_config_limits.their_to_self_delay = 2016;
+ match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) {
+ 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(
+ payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash,
+ payment_secret: Option<PaymentSecret>, payee_features: Option<InvoiceFeatures>,
+ mut route_hints: Vec<lightning_invoice::Route>,
+ router: Arc<NetGraphMsgHandler<Arc<dyn chain::Access>, Arc<FilesystemLogger>>>,
+ channel_manager: Arc<ChannelManager>, payment_storage: PaymentInfoStorage,
+ logger: Arc<FilesystemLogger>,
+) {
+ 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 mut hints: Vec<RouteHint> = Vec::new();
+ for route in route_hints.drain(..) {
+ let route_hops = route.into_inner();
+ let last_hop = &route_hops[route_hops.len() - 1];
+ hints.push(RouteHint {
+ src_node_id: last_hop.pubkey,
+ short_channel_id: u64::from_be_bytes(last_hop.short_channel_id),
+ fees: RoutingFees {
+ base_msat: last_hop.fee_base_msat,
+ proportional_millionths: last_hop.fee_proportional_millionths,
+ },
+ cltv_expiry_delta: last_hop.cltv_expiry_delta,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ })
+ }
+ let route = router::get_route(
+ &payer_pubkey,
+ &network_graph,
+ &payee,
+ payee_features,
+ Some(&first_hops.iter().collect::<Vec<_>>()),
+ &hints.iter().collect::<Vec<_>>(),
+ 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);
+ HTLCStatus::Pending
+ }
+ Err(e) => {
+ println!("ERROR: failed to send payment: {:?}", e);
+ HTLCStatus::Failed
+ }
+ };
+ let mut payments = payment_storage.lock().unwrap();
+ payments.insert(
+ payment_hash,
+ PaymentInfo {
+ preimage: None,
+ secret: payment_secret,
+ status,
+ amt_msat: MillisatAmount(Some(amt_msat)),
+ },
+ );
+}
+
+fn get_invoice(
+ amt_msat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey,
+ channel_manager: Arc<ChannelManager>, network: Network,
+) {
+ let mut payments = payment_storage.lock().unwrap();
+ let secp_ctx = Secp256k1::new();
+
+ let mut preimage = [0; 32];
+ rand::thread_rng().fill_bytes(&mut preimage);
+ let payment_hash = Sha256Hash::hash(&preimage);
+
+ let our_node_pubkey = channel_manager.get_our_node_id();
+ let mut invoice = lightning_invoice::InvoiceBuilder::new(match network {
+ Network::Bitcoin => lightning_invoice::Currency::Bitcoin,
+ Network::Testnet => lightning_invoice::Currency::BitcoinTestnet,
+ Network::Regtest => lightning_invoice::Currency::Regtest,
+ Network::Signet => panic!("Signet invoices not supported"),
+ })
+ .payment_hash(payment_hash)
+ .description("rust-lightning-bitcoinrpc invoice".to_string())
+ .amount_pico_btc(amt_msat * 10)
+ .current_timestamp()
+ .payee_pub_key(our_node_pubkey);
+
+ // Add route hints to the invoice.
+ let our_channels = channel_manager.list_usable_channels();
+ let mut min_final_cltv_expiry = 9;
+ for channel in our_channels {
+ let short_channel_id = match channel.short_channel_id {
+ Some(id) => id.to_be_bytes(),
+ None => continue,
+ };
+ let forwarding_info = match channel.counterparty_forwarding_info {
+ Some(info) => info,
+ None => continue,
+ };
+ if forwarding_info.cltv_expiry_delta > min_final_cltv_expiry {
+ min_final_cltv_expiry = forwarding_info.cltv_expiry_delta;
+ }
+ invoice = invoice.route(vec![lightning_invoice::RouteHop {
+ pubkey: channel.remote_network_id,
+ short_channel_id,
+ fee_base_msat: forwarding_info.fee_base_msat,
+ fee_proportional_millionths: forwarding_info.fee_proportional_millionths,
+ cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
+ }]);
+ }
+ invoice = invoice.min_final_cltv_expiry(min_final_cltv_expiry.into());
+
+ // Sign the invoice.
+ let invoice =
+ invoice.build_signed(|msg_hash| secp_ctx.sign_recoverable(msg_hash, &our_node_privkey));
+
+ match invoice {
+ Ok(invoice) => println!("SUCCESS: generated invoice: {}", invoice),
+ Err(e) => println!("ERROR: failed to create invoice: {:?}", e),
+ }
+
+ payments.insert(
+ PaymentHash(payment_hash.into_inner()),
+ PaymentInfo {
+ preimage: Some(PaymentPreimage(preimage)),
+ // We can't add payment secrets to invoices until we support features in invoices.
+ // Otherwise lnd errors with "destination hop doesn't understand payment addresses"
+ // (for context, lnd calls payment secrets "payment addresses").
+ secret: None,
+ status: HTLCStatus::Pending,
+ amt_msat: MillisatAmount(Some(amt_msat)),
+ },
+ );
+}
+
+fn close_channel(channel_id: [u8; 32], channel_manager: Arc<ChannelManager>) {
+ match channel_manager.close_channel(&channel_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<ChannelManager>) {
+ match channel_manager.force_close_channel(&channel_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() || 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: Result<SocketAddr, _> = peer_addr_str.unwrap().parse();
+ if peer_addr.is_err() {
+ 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()))