From 6d171e062614029898d3289a92e77b36a74b1610 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 14 Oct 2024 00:27:14 +0000 Subject: [PATCH 1/5] Drop `sendonionmessage` custom message sending command Onion messages are now useful for various things so having a demo use for them isn't all that interesting anymore. --- src/cli.rs | 81 ------------------------------------------------------ 1 file changed, 81 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 4d64d89..902e652 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,8 +12,6 @@ use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry}; use lightning::ln::msgs::SocketAddress; use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage}; use lightning::offers::offer::{self, Offer}; -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}; @@ -45,24 +43,6 @@ pub(crate) struct LdkUserInfo { 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, @@ -496,64 +476,6 @@ pub(crate) 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 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."), } @@ -589,9 +511,6 @@ fn help() { println!(" getoffer []"); println!("\n Other:"); println!(" signmessage "); - println!( - " sendonionmessage " - ); println!(" nodeinfo"); } From d8ab0a6ffe742f1267253ffc5b29ee3503cb6719 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 14 Oct 2024 01:29:25 +0000 Subject: [PATCH 2/5] Upgrade to LDK 0.0.125/rust-bitcoin 0.32 --- Cargo.toml | 16 +++--- src/args.rs | 2 +- src/bitcoind_client.rs | 57 +++++++++++++++------ src/cli.rs | 49 +++++++++--------- src/disk.rs | 4 +- src/main.rs | 114 ++++++++++++++++++++++------------------- src/sweep.rs | 11 ++-- 7 files changed, 145 insertions(+), 108 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 172bcd1..3311131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,16 +8,16 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lightning = { version = "0.0.123", features = ["max_level_trace"] } -lightning-block-sync = { version = "0.0.123", features = [ "rpc-client", "tokio" ] } -lightning-invoice = { version = "0.31.0" } -lightning-net-tokio = { version = "0.0.123" } -lightning-persister = { version = "0.0.123" } -lightning-background-processor = { version = "0.0.123", features = [ "futures" ] } -lightning-rapid-gossip-sync = { version = "0.0.123" } +lightning = { version = "0.0.125", features = ["max_level_trace"] } +lightning-block-sync = { version = "0.0.125", features = [ "rpc-client", "tokio" ] } +lightning-invoice = { version = "0.32.0" } +lightning-net-tokio = { version = "0.0.125" } +lightning-persister = { version = "0.0.125" } +lightning-background-processor = { version = "0.0.125", features = [ "futures" ] } +lightning-rapid-gossip-sync = { version = "0.0.125" } base64 = "0.13.0" -bitcoin = "0.30.2" +bitcoin = "0.32" bitcoin-bech32 = "0.12" bech32 = "0.8" libc = "0.2" diff --git a/src/args.rs b/src/args.rs index 34d97bb..e59852e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,5 +1,5 @@ use crate::cli::LdkUserInfo; -use bitcoin::network::constants::Network; +use bitcoin::network::Network; use lightning::ln::msgs::SocketAddress; use std::collections::HashMap; use std::env; diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 57c1960..85b1217 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -5,7 +5,7 @@ use crate::convert::{ use crate::disk::FilesystemLogger; use crate::hex_utils; use base64; -use bitcoin::address::{Address, Payload, WitnessVersion}; +use bitcoin::address::Address; use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::blockdata::script::ScriptBuf; use bitcoin::blockdata::transaction::Transaction; @@ -13,7 +13,7 @@ use bitcoin::consensus::{encode, Decodable, Encodable}; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::hashes::Hash; use bitcoin::key::XOnlyPublicKey; -use bitcoin::psbt::PartiallySignedTransaction; +use bitcoin::psbt::Psbt; use bitcoin::{Network, OutPoint, TxOut, WPubkeyHash}; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning::events::bump_transaction::{Utxo, WalletSource}; @@ -80,7 +80,8 @@ impl BitcoindClient { "Failed to make initial call to bitcoind - please check your RPC user/password and access settings") })?; let mut fees: HashMap = HashMap::new(); - fees.insert(ConfirmationTarget::OnChainSweep, AtomicU32::new(5000)); + fees.insert(ConfirmationTarget::MaximumFeeEstimate, AtomicU32::new(50000)); + fees.insert(ConfirmationTarget::UrgentOnChainSweep, AtomicU32::new(5000)); fees.insert( ConfirmationTarget::MinAllowedAnchorChannelRemoteFee, AtomicU32::new(MIN_FEERATE), @@ -92,6 +93,7 @@ impl BitcoindClient { fees.insert(ConfirmationTarget::AnchorChannelFee, AtomicU32::new(MIN_FEERATE)); fees.insert(ConfirmationTarget::NonAnchorChannelFee, AtomicU32::new(2000)); fees.insert(ConfirmationTarget::ChannelCloseMinimum, AtomicU32::new(MIN_FEERATE)); + fees.insert(ConfirmationTarget::OutputSpendingFee, AtomicU32::new(MIN_FEERATE)); let client = Self { bitcoind_rpc_client: Arc::new(bitcoind_rpc_client), @@ -177,7 +179,27 @@ impl BitcoindClient { } }; - fees.get(&ConfirmationTarget::OnChainSweep) + let very_high_prio_estimate = { + let high_prio_conf_target = serde_json::json!(2); + let high_prio_estimate_mode = serde_json::json!("CONSERVATIVE"); + let resp = rpc_client + .call_method::( + "estimatesmartfee", + &vec![high_prio_conf_target, high_prio_estimate_mode], + ) + .await + .unwrap(); + + match resp.feerate_sat_per_kw { + Some(feerate) => std::cmp::max(feerate, MIN_FEERATE), + None => 50000, + } + }; + + fees.get(&ConfirmationTarget::MaximumFeeEstimate) + .unwrap() + .store(very_high_prio_estimate, Ordering::Release); + fees.get(&ConfirmationTarget::UrgentOnChainSweep) .unwrap() .store(high_prio_estimate, Ordering::Release); fees.get(&ConfirmationTarget::MinAllowedAnchorChannelRemoteFee) @@ -195,6 +217,9 @@ impl BitcoindClient { fees.get(&ConfirmationTarget::ChannelCloseMinimum) .unwrap() .store(background_estimate, Ordering::Release); + fees.get(&ConfirmationTarget::OutputSpendingFee) + .unwrap() + .store(background_estimate, Ordering::Release); tokio::time::sleep(Duration::from_secs(60)).await; } @@ -335,24 +360,26 @@ impl WalletSource for BitcoindClient { .into_iter() .filter_map(|utxo| { let outpoint = OutPoint { txid: utxo.txid, vout: utxo.vout }; - match utxo.address.payload.clone() { - Payload::WitnessProgram(wp) => match wp.version() { - WitnessVersion::V0 => WPubkeyHash::from_slice(wp.program().as_bytes()) - .map(|wpkh| Utxo::new_v0_p2wpkh(outpoint, utxo.amount, &wpkh)) - .ok(), + let value = bitcoin::Amount::from_sat(utxo.amount); + match utxo.address.witness_program() { + Some(prog) if prog.is_p2wpkh() => { + WPubkeyHash::from_slice(prog.program().as_bytes()) + .map(|wpkh| Utxo::new_v0_p2wpkh(outpoint, value, &wpkh)) + .ok() + }, + Some(prog) if prog.is_p2tr() => { // TODO: Add `Utxo::new_v1_p2tr` upstream. - WitnessVersion::V1 => XOnlyPublicKey::from_slice(wp.program().as_bytes()) + XOnlyPublicKey::from_slice(prog.program().as_bytes()) .map(|_| Utxo { outpoint, output: TxOut { - value: utxo.amount, - script_pubkey: ScriptBuf::new_witness_program(&wp), + value, + script_pubkey: utxo.address.script_pubkey(), }, satisfaction_weight: 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64 + 1 /* witness items */ + 1 /* schnorr sig len */ + 64, /* schnorr sig */ }) - .ok(), - _ => None, + .ok() }, _ => None, } @@ -366,7 +393,7 @@ impl WalletSource for BitcoindClient { }) } - fn sign_psbt(&self, tx: PartiallySignedTransaction) -> Result { + fn sign_psbt(&self, tx: Psbt) -> Result { let mut tx_bytes = Vec::new(); let _ = tx.unsigned_tx.consensus_encode(&mut tx_bytes).map_err(|_| ()); let tx_hex = hex_utils::hex_str(&tx_bytes); diff --git a/src/cli.rs b/src/cli.rs index 902e652..b528e04 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,28 +2,29 @@ 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, + OutboundPaymentInfoStorage, PaymentInfo, PeerManager, }; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; -use bitcoin::network::constants::Network; +use bitcoin::network::Network; use bitcoin::secp256k1::PublicKey; +use lightning::ln::bolt11_payment::payment_parameters_from_invoice; +use lightning::ln::bolt11_payment::payment_parameters_from_zero_amount_invoice; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry}; +use lightning::ln::invoice_utils as utils; use lightning::ln::msgs::SocketAddress; -use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage}; +use lightning::ln::types::ChannelId; use lightning::offers::offer::{self, Offer}; use lightning::routing::gossip::NodeId; use lightning::routing::router::{PaymentParameters, RouteParameters}; use lightning::sign::{EntropySource, KeysManager}; +use lightning::types::payment::{PaymentHash, PaymentPreimage}; use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; use lightning::util::persist::KVStore; -use lightning::util::ser::{Writeable, Writer}; -use lightning_invoice::payment::payment_parameters_from_invoice; -use lightning_invoice::payment::payment_parameters_from_zero_amount_invoice; -use lightning_invoice::{utils, Bolt11Invoice, Currency}; +use lightning::util::ser::Writeable; +use lightning_invoice::{Bolt11Invoice, Currency}; use lightning_persister::fs_store::FilesystemStore; use std::env; -use std::io; use std::io::Write; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::Path; @@ -46,7 +47,7 @@ pub(crate) struct LdkUserInfo { 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>, + inbound_payments: Arc>, outbound_payments: Arc>, ldk_data_dir: String, network: Network, logger: Arc, fs_store: Arc, ) { @@ -57,9 +58,9 @@ pub(crate) fn poll_for_user_input( println!("Local Node ID is {}.", channel_manager.get_our_node_id()); 'read_command: loop { print!("> "); - io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print + std::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) { + if let Err(e) = std::io::stdin().read_line(&mut line) { break println!("ERROR: {}", e); } @@ -159,7 +160,7 @@ pub(crate) fn poll_for_user_input( let payment_id = PaymentId(random_bytes); let amt_msat = match (offer.amount(), user_provided_amt) { - (Some(offer::Amount::Bitcoin { amount_msats }), _) => *amount_msats, + (Some(offer::Amount::Bitcoin { amount_msats }), _) => amount_msats, (_, Some(amt)) => amt, (amt, _) => { println!("ERROR: Cannot process non-Bitcoin-denominated offer value {:?}", amt); @@ -173,9 +174,9 @@ pub(crate) fn poll_for_user_input( while user_provided_amt.is_none() { print!("Paying offer for {} msat. Continue (Y/N)? >", amt_msat); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); - if let Err(e) = io::stdin().read_line(&mut line) { + if let Err(e) = std::io::stdin().read_line(&mut line) { println!("ERROR: {}", e); break 'read_command; } @@ -266,7 +267,7 @@ pub(crate) fn poll_for_user_input( ); }, "getoffer" => { - let offer_builder = channel_manager.create_offer_builder(); + let offer_builder = channel_manager.create_offer_builder(None); if let Err(e) = offer_builder { println!("ERROR: Failed to initiate offer building: {:?}", e); continue; @@ -554,7 +555,7 @@ fn list_channels(channel_manager: &Arc, network_graph: &Arc, network_graph: &Arc, + peer_pubkey: PublicKey, channel_amt_sat: u64, announce_for_forwarding: bool, + with_anchors: bool, channel_manager: Arc, ) -> Result<(), ()> { let config = UserConfig { channel_handshake_limits: ChannelHandshakeLimits { @@ -686,7 +687,7 @@ fn open_channel( ..Default::default() }, channel_handshake_config: ChannelHandshakeConfig { - announced_channel, + announce_for_forwarding, negotiate_anchors_zero_fee_htlc_tx: with_anchors, ..Default::default() }, @@ -870,9 +871,11 @@ fn close_channel( 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) - { + match channel_manager.force_close_broadcasting_latest_txn( + &ChannelId(channel_id), + &counterparty_node_id, + "Manually force-closed".to_string(), + ) { Ok(()) => println!("EVENT: initiating channel force-close"), Err(e) => println!("ERROR: failed to force-close channel: {:?}", e), } diff --git a/src/disk.rs b/src/disk.rs index 780a123..6db81d4 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -4,11 +4,11 @@ use bitcoin::Network; use chrono::Utc; use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringDecayParameters}; use lightning::util::logger::{Logger, Record}; -use lightning::util::ser::{Readable, ReadableArgs, Writer}; +use lightning::util::ser::{Readable, ReadableArgs}; use std::collections::HashMap; use std::fs; use std::fs::File; -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, Write}; use std::net::SocketAddr; use std::path::Path; use std::sync::Arc; diff --git a/src/main.rs b/src/main.rs index 5308585..d7e63c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,8 @@ use crate::bitcoind_client::BitcoindClient; use crate::disk::FilesystemLogger; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; -use bitcoin::network::constants::Network; +use bitcoin::io; +use bitcoin::network::Network; use bitcoin::BlockHash; use bitcoin_bech32::WitnessProgram; use disk::{INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME}; @@ -24,13 +25,14 @@ use lightning::ln::channelmanager::{ }; use lightning::ln::msgs::DecodeError; use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler, SimpleArcPeerManager}; -use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::ln::types::ChannelId; use lightning::onion_message::messenger::{DefaultMessageRouter, SimpleArcOnionMessenger}; use lightning::routing::gossip; use lightning::routing::gossip::{NodeId, P2PGossipSync}; use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::ProbabilisticScoringFeeParameters; use lightning::sign::{EntropySource, InMemorySigner, KeysManager}; +use lightning::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::util::config::UserConfig; use lightning::util::persist::{ self, KVStore, MonitorUpdatingPersister, OUTPUT_SWEEPER_PERSISTENCE_KEY, @@ -53,8 +55,7 @@ use std::convert::TryInto; use std::fmt; use std::fs; use std::fs::File; -use std::io; -use std::io::Write; +use std::io::{BufReader, Write}; use std::net::ToSocketAddrs; use std::path::Path; use std::sync::atomic::{AtomicBool, Ordering}; @@ -71,7 +72,7 @@ pub(crate) enum HTLCStatus { impl_writeable_tlv_based_enum!(HTLCStatus, (0, Pending) => {}, (1, Succeeded) => {}, - (2, Failed) => {}; + (2, Failed) => {}, ); pub(crate) struct MillisatAmount(Option); @@ -93,7 +94,7 @@ impl Readable for MillisatAmount { } impl Writeable for MillisatAmount { - fn write(&self, w: &mut W) -> Result<(), std::io::Error> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { self.0.write(w) } } @@ -140,6 +141,8 @@ type ChainMonitor = chainmonitor::ChainMonitor< Arc, Arc, Arc, + Arc, + Arc, >, >, >; @@ -231,19 +234,18 @@ async fn handle_ldk_events( encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap(); // Give the funding transaction back to LDK for opening the channel. if channel_manager - .funding_transaction_generated( - &temporary_channel_id, - &counterparty_node_id, - final_tx, - ) + .funding_transaction_generated(temporary_channel_id, counterparty_node_id, final_tx) .is_err() { println!( "\nERROR: Channel went away before we could fund it. The peer disconnected or refused the channel."); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); } }, + Event::FundingTxBroadcastSafe { .. } => { + // We don't use the manual broadcasting feature, so this event should never be seen. + }, Event::PaymentClaimable { payment_hash, purpose, @@ -260,7 +262,7 @@ async fn handle_ldk_events( payment_hash, amount_msat, ); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); let payment_preimage = match purpose { PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => payment_preimage, PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. } => payment_preimage, @@ -269,20 +271,13 @@ async fn handle_ldk_events( }; channel_manager.claim_funds(payment_preimage.unwrap()); }, - Event::PaymentClaimed { - payment_hash, - purpose, - amount_msat, - receiver_node_id: _, - htlcs: _, - sender_intended_total_msat: _, - } => { + Event::PaymentClaimed { payment_hash, purpose, amount_msat, .. } => { println!( "\nEVENT: claimed payment from payment hash {} of {} millisatoshis", payment_hash, amount_msat, ); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); let (payment_preimage, payment_secret) = match purpose { PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, .. @@ -335,7 +330,7 @@ async fn handle_ldk_events( payment_preimage ); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); } } fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound.encode()).unwrap(); @@ -367,32 +362,29 @@ async fn handle_ldk_events( ); } print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); }, Event::PaymentPathSuccessful { .. } => {}, Event::PaymentPathFailed { .. } => {}, Event::ProbeSuccessful { .. } => {}, Event::ProbeFailed { .. } => {}, Event::PaymentFailed { payment_hash, reason, payment_id, .. } => { - print!( - "\nEVENT: Failed to send payment to payment hash {}: {:?}", - payment_hash, - if let Some(r) = reason { r } else { PaymentFailureReason::RetriesExhausted } - ); - print!("> "); - io::stdout().flush().unwrap(); - - let mut outbound = outbound_payments.lock().unwrap(); - if outbound.payments.contains_key(&payment_id) { - let payment = outbound.payments.get_mut(&payment_id).unwrap(); - payment.status = HTLCStatus::Failed; + if let Some(hash) = payment_hash { + print!( + "\nEVENT: Failed to send payment to payment ID {}, payment hash {}: {:?}", + payment_id, + hash, + if let Some(r) = reason { r } else { PaymentFailureReason::RetriesExhausted } + ); + } else { + print!( + "\nEVENT: Failed fetch invoice for payment ID {}: {:?}", + payment_id, + if let Some(r) = reason { r } else { PaymentFailureReason::RetriesExhausted } + ); } - fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound.encode()).unwrap(); - }, - Event::InvoiceRequestFailed { payment_id } => { - print!("\nEVENT: Failed to request invoice to send payment with id {}", payment_id); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); let mut outbound = outbound_payments.lock().unwrap(); if outbound.payments.contains_key(&payment_id) { @@ -401,6 +393,9 @@ async fn handle_ldk_events( } fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound.encode()).unwrap(); }, + Event::InvoiceReceived { .. } => { + // We don't use the manual invoice payment logic, so this event should never be seen. + }, Event::PaymentForwarded { prev_channel_id, next_channel_id, @@ -425,7 +420,7 @@ async fn handle_ldk_events( Some(node) => match &node.announcement_info { None => "unnamed node".to_string(), Some(announcement) => { - format!("node {}", announcement.alias) + format!("node {}", announcement.alias()) }, }, } @@ -464,7 +459,7 @@ async fn handle_ldk_events( ); } print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); }, Event::HTLCHandlingFailed { .. } => {}, Event::PendingHTLCsForwardable { time_forwardable } => { @@ -486,7 +481,7 @@ async fn handle_ldk_events( hex_utils::hex_str(&counterparty_node_id.serialize()), ); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); }, Event::ChannelReady { ref channel_id, @@ -500,7 +495,7 @@ async fn handle_ldk_events( hex_utils::hex_str(&counterparty_node_id.serialize()), ); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); }, Event::ChannelClosed { channel_id, @@ -517,13 +512,21 @@ async fn handle_ldk_events( reason ); print!("> "); - io::stdout().flush().unwrap(); + std::io::stdout().flush().unwrap(); }, Event::DiscardFunding { .. } => { // A "real" node should probably "lock" the UTXOs spent in funding transactions until // the funding transaction either confirms, or this event is generated. }, Event::HTLCIntercepted { .. } => {}, + Event::OnionMessageIntercepted { .. } => { + // We don't use the onion message interception feature, so this event should never be + // seen. + }, + Event::OnionMessagePeerConnected { .. } => { + // We don't use the onion message interception feature, so we have no use for this + // event. + }, Event::BumpTransaction(event) => bump_tx_event_handler.handle_event(&event), Event::ConnectionNeeded { node_id, addresses } => { tokio::spawn(async move { @@ -617,7 +620,8 @@ async fn start_ldk() { thread_rng().fill_bytes(&mut key); match File::create(keys_seed_path.clone()) { Ok(mut f) => { - Write::write_all(&mut f, &key).expect("Failed to write node keys seed to disk"); + std::io::Write::write_all(&mut f, &key) + .expect("Failed to write node keys seed to disk"); f.sync_all().expect("Failed to sync node keys seed to disk"); }, Err(e) => { @@ -645,6 +649,8 @@ async fn start_ldk() { 1000, Arc::clone(&keys_manager), Arc::clone(&keys_manager), + Arc::clone(&bitcoind_client), + Arc::clone(&bitcoind_client), )); // Alternatively, you can use the `FilesystemStore` as a `Persist` directly, at the cost of // larger `ChannelMonitor` update writes (but no deletion or cleanup): @@ -660,9 +666,7 @@ async fn start_ldk() { )); // Step 7: Read ChannelMonitor state from disk - let mut channelmonitors = persister - .read_all_channel_monitors_with_updates(&bitcoind_client, &bitcoind_client) - .unwrap(); + let mut channelmonitors = persister.read_all_channel_monitors_with_updates().unwrap(); // If you are using the `FilesystemStore` as a `Persist` directly, use // `lightning::util::persist::read_channel_monitors` like this: //read_channel_monitors(Arc::clone(&persister), Arc::clone(&keys_manager), Arc::clone(&keys_manager)).unwrap(); @@ -701,7 +705,7 @@ async fn start_ldk() { user_config.manually_accept_inbound_channels = true; let mut restarting_node = true; let (channel_manager_blockhash, channel_manager) = { - if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { + if let Ok(f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { let mut channel_monitor_mut_references = Vec::new(); for (_, channel_monitor) in channelmonitors.iter_mut() { channel_monitor_mut_references.push(channel_monitor); @@ -718,7 +722,7 @@ async fn start_ldk() { user_config, channel_monitor_mut_references, ); - <(BlockHash, ChannelManager)>::read(&mut f, read_args).unwrap() + <(BlockHash, ChannelManager)>::read(&mut BufReader::new(f), read_args).unwrap() } else { // We're starting a fresh node. restarting_node = false; @@ -840,6 +844,7 @@ async fn start_ldk() { Arc::clone(&channel_manager), Arc::new(DefaultMessageRouter::new(Arc::clone(&network_graph), Arc::clone(&keys_manager))), Arc::clone(&channel_manager), + Arc::clone(&channel_manager), IgnoringMessageHandler {}, )); let mut ephemeral_bytes = [0; 32]; @@ -982,6 +987,7 @@ async fn start_ldk() { event, ) .await; + Ok(()) } }; @@ -995,6 +1001,7 @@ async fn start_ldk() { event_handler, chain_monitor.clone(), channel_manager.clone(), + Some(onion_messenger), GossipSync::p2p(gossip_sync.clone()), peer_manager.clone(), logger.clone(), @@ -1066,7 +1073,7 @@ async fn start_ldk() { // Don't bother trying to announce if we don't have any public channls, though our // peers should drop such an announcement anyway. Note that announcement may not // propagate until we have a channel with 6+ confirmations. - if chan_man.list_channels().iter().any(|chan| chan.is_public) { + if chan_man.list_channels().iter().any(|chan| chan.is_announced) { peer_man.broadcast_node_announcement( [0; 3], args.ldk_announced_node_name, @@ -1095,7 +1102,6 @@ async fn start_ldk() { cli_channel_manager, keys_manager, network_graph, - onion_messenger, inbound_payments, outbound_payments, ldk_data_dir, diff --git a/src/sweep.rs b/src/sweep.rs index decaca0..7ab3b82 100644 --- a/src/sweep.rs +++ b/src/sweep.rs @@ -1,4 +1,4 @@ -use std::io::{Read, Seek, SeekFrom}; +use std::io::{BufReader, Read, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::{fs, io}; @@ -73,17 +73,18 @@ pub(crate) async fn migrate_deprecated_spendable_outputs( let mut outputs: Vec = Vec::new(); if let Ok(dir_iter) = fs::read_dir(&spendables_dir) { for file_res in dir_iter { - let mut file = fs::File::open(file_res.unwrap().path()).unwrap(); + let file = fs::File::open(file_res.unwrap().path()).unwrap(); + let mut reader = BufReader::new(file); loop { // Check if there are any bytes left to read, and if so read a descriptor. - match file.read_exact(&mut [0; 1]) { + match reader.read_exact(&mut [0; 1]) { Ok(_) => { - file.seek(SeekFrom::Current(-1)).unwrap(); + reader.seek(SeekFrom::Current(-1)).unwrap(); }, Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break, Err(e) => Err(e).unwrap(), } - outputs.push(Readable::read(&mut file).unwrap()); + outputs.push(Readable::read(&mut reader).unwrap()); } } } From cfcd28b094a2ada3835c986154434d83e22165f7 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 14 Oct 2024 02:03:54 +0000 Subject: [PATCH 3/5] Use `Balance`s to calculate balances instead of `ChannelDetails` LDK has deprecated the `ChannelDetails` balance retrieval because its generally not what users want. Instead, we use the `ChainMonitor` `Balance` list. --- src/cli.rs | 41 +++++++++++++++++++++++++++++++++-------- src/main.rs | 2 ++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index b528e04..3fe10dc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,13 +1,14 @@ use crate::disk::{self, INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME}; use crate::hex_utils; use crate::{ - ChannelManager, HTLCStatus, InboundPaymentInfoStorage, MillisatAmount, NetworkGraph, - OutboundPaymentInfoStorage, PaymentInfo, PeerManager, + ChainMonitor, ChannelManager, HTLCStatus, InboundPaymentInfoStorage, MillisatAmount, + NetworkGraph, OutboundPaymentInfoStorage, PaymentInfo, PeerManager, }; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::network::Network; use bitcoin::secp256k1::PublicKey; +use lightning::chain::channelmonitor::Balance; use lightning::ln::bolt11_payment::payment_parameters_from_invoice; use lightning::ln::bolt11_payment::payment_parameters_from_zero_amount_invoice; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry}; @@ -46,8 +47,8 @@ pub(crate) struct LdkUserInfo { pub(crate) fn poll_for_user_input( peer_manager: Arc, channel_manager: Arc, - keys_manager: Arc, network_graph: Arc, - inbound_payments: Arc>, + chain_monitor: Arc, keys_manager: Arc, + network_graph: Arc, inbound_payments: Arc>, outbound_payments: Arc>, ldk_data_dir: String, network: Network, logger: Arc, fs_store: Arc, ) { @@ -461,7 +462,7 @@ pub(crate) fn poll_for_user_input( force_close_channel(channel_id, peer_pubkey, channel_manager.clone()); }, - "nodeinfo" => node_info(&channel_manager, &peer_manager), + "nodeinfo" => node_info(&channel_manager, &chain_monitor, &peer_manager), "listpeers" => list_peers(peer_manager.clone()), "signmessage" => { const MSG_STARTPOS: usize = "signmessage".len() + 1; @@ -515,14 +516,38 @@ fn help() { println!(" nodeinfo"); } -fn node_info(channel_manager: &Arc, peer_manager: &Arc) { +fn node_info( + channel_manager: &Arc, chain_monitor: &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); + let balances = chain_monitor.get_claimable_balances(&[]); + let local_balance_sat = balances.iter().map(|b| b.claimable_amount_satoshis()).sum::(); + println!("\t\t local_balance_sats: {}", local_balance_sat); + let close_fees_map = |b| match b { + &Balance::ClaimableOnChannelClose { transaction_fee_satoshis, .. } => { + transaction_fee_satoshis + }, + _ => 0, + }; + let close_fees_sats = balances.iter().map(close_fees_map).sum::(); + println!("\t\t eventual_close_fees_sats: {}", close_fees_sats); + let pending_payments_map = |b| match b { + &Balance::MaybeTimeoutClaimableHTLC { amount_satoshis, outbound_payment, .. } => { + if outbound_payment { + amount_satoshis + } else { + 0 + } + }, + _ => 0, + }; + let pending_payments = balances.iter().map(pending_payments_map).sum::(); + println!("\t\t pending_outbound_payments_sats: {}", pending_payments); println!("\t\t num_peers: {}", peer_manager.list_peers().len()); println!("\t}},"); } diff --git a/src/main.rs b/src/main.rs index d7e63c9..cfe5705 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1093,6 +1093,7 @@ async fn start_ldk() { // Start the CLI. let cli_channel_manager = Arc::clone(&channel_manager); + let cli_chain_monitor = Arc::clone(&chain_monitor); let cli_persister = Arc::clone(&persister); let cli_logger = Arc::clone(&logger); let cli_peer_manager = Arc::clone(&peer_manager); @@ -1100,6 +1101,7 @@ async fn start_ldk() { cli::poll_for_user_input( cli_peer_manager, cli_channel_manager, + cli_chain_monitor, keys_manager, network_graph, inbound_payments, From c52bf5b2e41c87142cfecaf16173ec52e3395b80 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 14 Oct 2024 02:40:36 +0000 Subject: [PATCH 4/5] Print network graph statistics in nodeinfo --- src/cli.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3fe10dc..c55623a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -462,7 +462,9 @@ pub(crate) fn poll_for_user_input( force_close_channel(channel_id, peer_pubkey, channel_manager.clone()); }, - "nodeinfo" => node_info(&channel_manager, &chain_monitor, &peer_manager), + "nodeinfo" => { + node_info(&channel_manager, &chain_monitor, &peer_manager, &network_graph) + }, "listpeers" => list_peers(peer_manager.clone()), "signmessage" => { const MSG_STARTPOS: usize = "signmessage".len() + 1; @@ -518,7 +520,7 @@ fn help() { fn node_info( channel_manager: &Arc, chain_monitor: &Arc, - peer_manager: &Arc, + peer_manager: &Arc, network_graph: &Arc, ) { println!("\t{{"); println!("\t\t node_pubkey: {}", channel_manager.get_our_node_id()); @@ -549,6 +551,9 @@ fn node_info( let pending_payments = balances.iter().map(pending_payments_map).sum::(); println!("\t\t pending_outbound_payments_sats: {}", pending_payments); println!("\t\t num_peers: {}", peer_manager.list_peers().len()); + let graph_lock = network_graph.read_only(); + println!("\t\t network_nodes: {}", graph_lock.nodes().len()); + println!("\t\t network_channels: {}", graph_lock.channels().len()); println!("\t}},"); } From 7ca108838b30f31614c9c150a57d4ff7ecbbab38 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 14 Oct 2024 13:48:31 +0000 Subject: [PATCH 5/5] Switch to `submitpackage` for broadcasting transactions --- src/bitcoind_client.rs | 60 ++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 85b1217..31b328c 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -314,32 +314,42 @@ impl FeeEstimator for BitcoindClient { impl BroadcasterInterface for BitcoindClient { fn broadcast_transactions(&self, txs: &[&Transaction]) { - // TODO: Rather than calling `sendrawtransaction` in a a loop, we should probably use - // `submitpackage` once it becomes available. - for tx in txs { - let bitcoind_rpc_client = Arc::clone(&self.bitcoind_rpc_client); - let tx_serialized = encode::serialize_hex(tx); - let tx_json = serde_json::json!(tx_serialized); - let logger = Arc::clone(&self.logger); - self.handle.spawn(async move { - // This may error due to RL calling `broadcast_transactions` with the same transaction - // multiple times, but the error is safe to ignore. - match bitcoind_rpc_client - .call_method::("sendrawtransaction", &vec![tx_json]) + // As of Bitcoin Core 28, using `submitpackage` allows us to broadcast multiple + // transactions at once and have them propagate through the network as a whole, avoiding + // some pitfalls with anchor channels where the first transaction doesn't make it into the + // mempool at all. Several older versions of Bitcoin Core also support `submitpackage`, + // however, so we just use it unconditionally here. + // Sadly, Bitcoin Core has an arbitrary restriction on `submitpackage` - it must actually + // contain a package (see https://github.com/bitcoin/bitcoin/issues/31085). + let txn = txs.iter().map(|tx| encode::serialize_hex(tx)).collect::>(); + let bitcoind_rpc_client = Arc::clone(&self.bitcoind_rpc_client); + let logger = Arc::clone(&self.logger); + self.handle.spawn(async move { + let res = if txn.len() == 1 { + let tx_json = serde_json::json!(txn[0]); + bitcoind_rpc_client + .call_method::("sendrawtransaction", &[tx_json]) .await - { - Ok(_) => {} - Err(e) => { - let err_str = e.get_ref().unwrap().to_string(); - log_error!(logger, - "Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\nTransaction: {}", - err_str, - tx_serialized); - print!("Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\n> ", err_str); - } - } - }); - } + } else { + let tx_json = serde_json::json!(txn); + bitcoind_rpc_client + .call_method::("submitpackage", &[tx_json]) + .await + }; + // This may error due to RL calling `broadcast_transactions` with the same transaction + // multiple times, but the error is safe to ignore. + match res { + Ok(_) => {} + Err(e) => { + let err_str = e.get_ref().unwrap().to_string(); + log_error!(logger, + "Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\nTransactions: {:?}", + err_str, + txn); + print!("Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\n> ", err_str); + } + } + }); } }