From a9273ad140da7c28fb801f0301a31e3f016c913f Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 6 Feb 2024 22:43:24 +0000 Subject: [PATCH 1/4] Support paying 0-amount offers and invoices --- src/cli.rs | 81 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 72cc484..a70d772 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,6 +21,7 @@ use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, Us 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_persister::fs_store::FilesystemStore; use std::env; @@ -162,20 +163,35 @@ pub(crate) fn poll_for_user_input( continue; } + let mut user_provided_amt: Option = None; + if let Some(amt_msat_str) = words.next() { + match amt_msat_str.parse() { + Ok(amt) => user_provided_amt = Some(amt), + Err(e) => { + println!("ERROR: couldn't parse amount_msat: {}", e); + continue; + } + }; + } + if let Ok(offer) = Offer::from_str(invoice_str.unwrap()) { let offer_hash = Sha256::hash(invoice_str.unwrap().as_bytes()); let payment_id = PaymentId(*offer_hash.as_ref()); - let amt_msat = - match offer.amount() { - Some(offer::Amount::Bitcoin { amount_msats }) => *amount_msats, - amt => { - println!("ERROR: Cannot process non-Bitcoin-denominated offer value {:?}", amt); - continue; - } - }; + let amt_msat = match (offer.amount(), user_provided_amt) { + (Some(offer::Amount::Bitcoin { amount_msats }), _) => *amount_msats, + (_, Some(amt)) => amt, + (amt, _) => { + println!("ERROR: Cannot process non-Bitcoin-denominated offer value {:?}", amt); + continue; + } + }; + if user_provided_amt.is_some() && user_provided_amt != Some(amt_msat) { + println!("Amount didn't match offer of {}msat", amt_msat); + continue; + } - loop { + while user_provided_amt.is_none() { print!("Paying offer for {} msat. Continue (Y/N)? >", amt_msat); io::stdout().flush().unwrap(); @@ -211,8 +227,9 @@ pub(crate) fn poll_for_user_input( .unwrap(); let retry = Retry::Timeout(Duration::from_secs(10)); + let amt = Some(amt_msat); let pay = channel_manager - .pay_for_offer(&offer, None, None, None, payment_id, retry, None); + .pay_for_offer(&offer, None, amt, None, payment_id, retry, None); if pay.is_err() { println!("ERROR: Failed to pay: {:?}", pay); } @@ -221,6 +238,7 @@ pub(crate) fn poll_for_user_input( Ok(invoice) => send_payment( &channel_manager, &invoice, + user_provided_amt, &mut outbound_payments.lock().unwrap(), Arc::clone(&fs_store), ), @@ -563,7 +581,7 @@ fn help() { println!(" disconnectpeer "); println!(" listpeers"); println!("\n Payments:"); - println!(" sendpayment "); + println!(" sendpayment []"); println!(" keysend "); println!(" listpayments"); println!("\n Invoices:"); @@ -770,20 +788,41 @@ fn open_channel( } fn send_payment( - channel_manager: &ChannelManager, invoice: &Bolt11Invoice, + channel_manager: &ChannelManager, invoice: &Bolt11Invoice, required_amount_msat: Option, outbound_payments: &mut OutboundPaymentInfoStorage, fs_store: Arc, ) { let payment_id = PaymentId((*invoice.payment_hash()).to_byte_array()); let payment_secret = Some(*invoice.payment_secret()); - let (payment_hash, recipient_onion, route_params) = - match payment_parameters_from_invoice(invoice) { - Ok(res) => res, - Err(e) => { - println!("Failed to parse invoice"); - print!("> "); - return; - } - }; + let zero_amt_invoice = + invoice.amount_milli_satoshis().is_none() || invoice.amount_milli_satoshis() == Some(0); + let pay_params_opt = if zero_amt_invoice { + if let Some(amt_msat) = required_amount_msat { + payment_parameters_from_zero_amount_invoice(invoice, amt_msat) + } else { + println!("Need an amount for the given 0-value invoice"); + print!("> "); + return; + } + } else { + if required_amount_msat.is_some() && invoice.amount_milli_satoshis() != required_amount_msat + { + println!( + "Amount didn't match invoice value of {}msat", + invoice.amount_milli_satoshis().unwrap_or(0) + ); + print!("> "); + return; + } + payment_parameters_from_invoice(invoice) + }; + let (payment_hash, recipient_onion, route_params) = match pay_params_opt { + Ok(res) => res, + Err(e) => { + println!("Failed to parse invoice"); + print!("> "); + return; + } + }; outbound_payments.payments.insert( payment_id, PaymentInfo { -- 2.30.2 From c7302c4615e7a6461e4858007f2cf10585df7547 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 23 Feb 2024 12:06:57 +0100 Subject: [PATCH 2/4] Align `rustfmt.toml` with `rust-lightning` Now that `rust-lightning` has added `rustfmt` support, we make sure our coding style is aligned. --- rustfmt.toml | 13 ++++++-- src/args.rs | 10 +++--- src/cli.rs | 82 +++++++++++++++++++++++++------------------------- src/convert.rs | 4 +-- src/disk.rs | 2 +- src/main.rs | 60 ++++++++++++++++++------------------ src/sweep.rs | 2 +- 7 files changed, 90 insertions(+), 83 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index c00f655..4f88472 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,12 @@ -hard_tabs = true # use tab characters for indentation, spaces for alignment +use_small_heuristics = "Max" +fn_params_layout = "Compressed" +hard_tabs = true use_field_init_shorthand = true max_width = 100 -use_small_heuristics = "Max" -fn_args_layout = "Compressed" +match_block_trailing_comma = true +# UNSTABLE: format_code_in_doc_comments = true +# UNSTABLE: overflow_delimited_expr = true +# UNSTABLE: comment_width = 100 +# UNSTABLE: format_macro_matchers = true +# UNSTABLE: format_strings = true +# UNSTABLE: group_imports = "StdExternalCrate" diff --git a/src/args.rs b/src/args.rs index 3778aa2..34d97bb 100644 --- a/src/args.rs +++ b/src/args.rs @@ -32,11 +32,11 @@ pub(crate) fn parse_startup_args() -> Result { Some(Err(_)) => { ldk_peer_port_set = false; 9735 - } + }, None => { ldk_peer_port_set = false; 9735 - } + }, }; let mut arg_idx = match ldk_peer_port_set { @@ -49,7 +49,7 @@ pub(crate) fn parse_startup_args() -> Result { Some("signet") => Network::Signet, Some(net) => { panic!("Unsupported network provided. Options are: `regtest`, `testnet`, and `signet`. Got {}", net); - } + }, None => Network::Testnet, }; @@ -78,7 +78,7 @@ pub(crate) fn parse_startup_args() -> Result { let mut bytes = [0; 32]; bytes[..s.len()].copy_from_slice(s.as_bytes()); bytes - } + }, None => [0; 32], }; @@ -89,7 +89,7 @@ pub(crate) fn parse_startup_args() -> Result { Ok(sa) => { ldk_announced_listen_addr.push(sa); arg_idx += 1; - } + }, Err(_) => panic!("Failed to parse announced-listen-addr into a socket address"), }, None => break, diff --git a/src/cli.rs b/src/cli.rs index a70d772..ee6a949 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -106,7 +106,7 @@ pub(crate) fn poll_for_user_input( Err(e) => { println!("{:?}", e.into_inner().unwrap()); continue; - } + }, }; let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); @@ -136,7 +136,7 @@ pub(crate) fn poll_for_user_input( _ => { println!("ERROR: invalid boolean flag format. Valid formats: `--option`, `--option=true` `--option=false`"); continue; - } + }, } } @@ -155,7 +155,7 @@ pub(crate) fn poll_for_user_input( peer_pubkey_and_ip_addr, ); } - } + }, "sendpayment" => { let invoice_str = words.next(); if invoice_str.is_none() { @@ -170,7 +170,7 @@ pub(crate) fn poll_for_user_input( Err(e) => { println!("ERROR: couldn't parse amount_msat: {}", e); continue; - } + }, }; } @@ -184,7 +184,7 @@ pub(crate) fn poll_for_user_input( (amt, _) => { println!("ERROR: Cannot process non-Bitcoin-denominated offer value {:?}", amt); continue; - } + }, }; if user_provided_amt.is_some() && user_provided_amt != Some(amt_msat) { println!("Amount didn't match offer of {}msat", amt_msat); @@ -244,10 +244,10 @@ pub(crate) fn poll_for_user_input( ), Err(e) => { println!("ERROR: invalid invoice: {:?}", e); - } + }, } } - } + }, "keysend" => { let dest_pubkey = match words.next() { Some(dest) => match hex_utils::to_compressed_pubkey(dest) { @@ -255,26 +255,26 @@ pub(crate) fn poll_for_user_input( 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, @@ -284,7 +284,7 @@ pub(crate) fn poll_for_user_input( &mut outbound_payments.lock().unwrap(), Arc::clone(&fs_store), ); - } + }, "getoffer" => { let offer_builder = channel_manager.create_offer_builder(String::new()); if let Err(e) = offer_builder { @@ -312,7 +312,7 @@ pub(crate) fn poll_for_user_input( // correspond with individual payments. println!("{}", offer.unwrap()); } - } + }, "getinvoice" => { let amt_str = words.next(); if amt_str.is_none() { @@ -351,7 +351,7 @@ pub(crate) fn poll_for_user_input( 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() { @@ -364,7 +364,7 @@ pub(crate) fn poll_for_user_input( Err(e) => { println!("{:?}", e.into_inner().unwrap()); continue; - } + }, }; if tokio::runtime::Handle::current() .block_on(connect_peer_if_necessary( @@ -376,7 +376,7 @@ pub(crate) fn poll_for_user_input( { println!("SUCCESS: connected to peer {}", pubkey); } - } + }, "disconnectpeer" => { let peer_pubkey = words.next(); if peer_pubkey.is_none() { @@ -390,7 +390,7 @@ pub(crate) fn poll_for_user_input( Err(e) => { println!("ERROR: {}", e.to_string()); continue; - } + }, }; if do_disconnect_peer( @@ -402,7 +402,7 @@ pub(crate) fn poll_for_user_input( { println!("SUCCESS: disconnected from peer {}", peer_pubkey); } - } + }, "listchannels" => list_channels(&channel_manager, &network_graph), "listpayments" => list_payments( &inbound_payments.lock().unwrap(), @@ -432,18 +432,18 @@ pub(crate) fn poll_for_user_input( 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() { @@ -468,18 +468,18 @@ pub(crate) fn poll_for_user_input( 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" => { @@ -495,7 +495,7 @@ pub(crate) fn poll_for_user_input( &keys_manager.get_node_secret_key() ) ); - } + }, "sendonionmessage" => { let path_pks_str = words.next(); if path_pks_str.is_none() { @@ -513,7 +513,7 @@ pub(crate) fn poll_for_user_input( 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, @@ -521,7 +521,7 @@ pub(crate) fn poll_for_user_input( println!("ERROR: couldn't parse peer_pubkey"); errored = true; break; - } + }, }; intermediate_nodes.push(node_pubkey); } @@ -533,14 +533,14 @@ pub(crate) fn poll_for_user_input( _ => { 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( @@ -550,10 +550,10 @@ pub(crate) fn poll_for_user_input( ) { 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."), } @@ -729,7 +729,7 @@ pub(crate) async fn do_connect_peer( return Ok(()); } } - } + }, None => Err(()), } } @@ -779,11 +779,11 @@ fn open_channel( Ok(_) => { println!("EVENT: initiated channel with peer {}. ", peer_pubkey); return Ok(()); - } + }, Err(e) => { println!("ERROR: failed to open channel: {:?}", e); return Err(()); - } + }, } } @@ -821,7 +821,7 @@ fn send_payment( println!("Failed to parse invoice"); print!("> "); return; - } + }, }; outbound_payments.payments.insert( payment_id, @@ -846,13 +846,13 @@ fn send_payment( 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(); - } + }, }; } @@ -887,13 +887,13 @@ fn keysend( 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(); - } + }, }; } @@ -921,11 +921,11 @@ fn get_invoice( 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()); diff --git a/src/convert.rs b/src/convert.rs index 686761f..5237332 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -67,7 +67,7 @@ impl TryInto for JsonResponse { // to convert virtual-bytes into weight units. Some(feerate_btc_per_kvbyte) => { Some((feerate_btc_per_kvbyte * 100_000_000.0 / 4.0).round() as u32) - } + }, None => None, }, }) @@ -92,7 +92,7 @@ impl TryInto for JsonResponse { // to convert virtual-bytes into weight units. Some(feerate_btc_per_kvbyte) => { Some((feerate_btc_per_kvbyte * 100_000_000.0 / 4.0).round() as u32) - } + }, None => None, }, }) diff --git a/src/disk.rs b/src/disk.rs index 9b9a72b..780a123 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -68,7 +68,7 @@ pub(crate) fn read_channel_peer_data( match cli::parse_peer_info(line.unwrap()) { Ok((pubkey, socket_addr)) => { peer_data.insert(pubkey, socket_addr); - } + }, Err(e) => return Err(e), } } diff --git a/src/main.rs b/src/main.rs index 6b4dbdd..add6990 100644 --- a/src/main.rs +++ b/src/main.rs @@ -228,7 +228,7 @@ async fn handle_ldk_events( print!("> "); io::stdout().flush().unwrap(); } - } + }, Event::PaymentClaimable { payment_hash, purpose, @@ -251,7 +251,7 @@ async fn handle_ldk_events( PaymentPurpose::SpontaneousPayment(preimage) => Some(preimage), }; channel_manager.claim_funds(payment_preimage.unwrap()); - } + }, Event::PaymentClaimed { payment_hash, purpose, @@ -269,7 +269,7 @@ async fn handle_ldk_events( let (payment_preimage, payment_secret) = match purpose { PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => { (payment_preimage, Some(payment_secret)) - } + }, PaymentPurpose::SpontaneousPayment(preimage) => (Some(preimage), None), }; let mut inbound = inbound_payments.lock().unwrap(); @@ -279,7 +279,7 @@ async fn handle_ldk_events( payment.status = HTLCStatus::Succeeded; payment.preimage = payment_preimage; payment.secret = payment_secret; - } + }, Entry::Vacant(e) => { e.insert(PaymentInfo { preimage: payment_preimage, @@ -287,10 +287,10 @@ async fn handle_ldk_events( status: HTLCStatus::Succeeded, amt_msat: MillisatAmount(Some(amount_msat)), }); - } + }, } fs_store.write("", "", INBOUND_PAYMENTS_FNAME, &inbound.encode()).unwrap(); - } + }, Event::PaymentSent { payment_preimage, payment_hash, fee_paid_msat, payment_id, .. } => { @@ -316,7 +316,7 @@ async fn handle_ldk_events( } } fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound.encode()).unwrap(); - } + }, Event::OpenChannelRequest { ref temporary_channel_id, ref counterparty_node_id, .. } => { @@ -345,11 +345,11 @@ async fn handle_ldk_events( } print!("> "); io::stdout().flush().unwrap(); - } - Event::PaymentPathSuccessful { .. } => {} - Event::PaymentPathFailed { .. } => {} - Event::ProbeSuccessful { .. } => {} - Event::ProbeFailed { .. } => {} + }, + Event::PaymentPathSuccessful { .. } => {}, + Event::PaymentPathFailed { .. } => {}, + Event::ProbeSuccessful { .. } => {}, + Event::ProbeFailed { .. } => {}, Event::PaymentFailed { payment_hash, reason, payment_id, .. } => { print!( "\nEVENT: Failed to send payment to payment hash {}: {:?}", @@ -365,7 +365,7 @@ async fn handle_ldk_events( payment.status = HTLCStatus::Failed; } 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!("> "); @@ -377,7 +377,7 @@ async fn handle_ldk_events( payment.status = HTLCStatus::Failed; } fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound.encode()).unwrap(); - } + }, Event::PaymentForwarded { prev_channel_id, next_channel_id, @@ -400,10 +400,10 @@ async fn handle_ldk_events( None => "unnamed node".to_string(), Some(announcement) => { format!("node {}", announcement.alias) - } + }, }, } - } + }, }, }; let channel_str = |channel_id: &Option| { @@ -439,8 +439,8 @@ async fn handle_ldk_events( } print!("> "); io::stdout().flush().unwrap(); - } - Event::HTLCHandlingFailed { .. } => {} + }, + Event::HTLCHandlingFailed { .. } => {}, Event::PendingHTLCsForwardable { time_forwardable } => { let forwarding_channel_manager = channel_manager.clone(); let min = time_forwardable.as_millis() as u64; @@ -449,7 +449,7 @@ async fn handle_ldk_events( tokio::time::sleep(Duration::from_millis(millis_to_sleep)).await; forwarding_channel_manager.process_pending_htlc_forwards(); }); - } + }, Event::SpendableOutputs { outputs, channel_id: _ } => { // SpendableOutputDescriptors, of which outputs is a vec of, are critical to keep track // of! While a `StaticOutput` descriptor is just an output to a static, well-known key, @@ -466,7 +466,7 @@ async fn handle_ldk_events( let output: SpendableOutputDescriptor = output; fs_store.write(PENDING_SPENDABLE_OUTPUT_DIR, "", &key, &output.encode()).unwrap(); } - } + }, Event::ChannelPending { channel_id, counterparty_node_id, .. } => { println!( "\nEVENT: Channel {} with peer {} is pending awaiting funding lock-in!", @@ -475,7 +475,7 @@ async fn handle_ldk_events( ); print!("> "); io::stdout().flush().unwrap(); - } + }, Event::ChannelReady { ref channel_id, user_channel_id: _, @@ -489,7 +489,7 @@ async fn handle_ldk_events( ); print!("> "); io::stdout().flush().unwrap(); - } + }, Event::ChannelClosed { channel_id, reason, @@ -506,12 +506,12 @@ async fn handle_ldk_events( ); print!("> "); 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::HTLCIntercepted { .. } => {}, Event::BumpTransaction(event) => bump_tx_event_handler.handle_event(&event), Event::ConnectionNeeded { node_id, addresses } => { tokio::spawn(async move { @@ -526,7 +526,7 @@ async fn handle_ldk_events( } } }); - } + }, } } @@ -560,7 +560,7 @@ async fn start_ldk() { Err(e) => { println!("Failed to connect to bitcoind client: {}", e); return; - } + }, }; // Check that the bitcoind we've connected to is running the network we expect @@ -607,11 +607,11 @@ async fn start_ldk() { Ok(mut f) => { 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) => { println!("ERROR: Unable to create keys seed file {}: {}", keys_seed_path, e); return; - } + }, } key }; @@ -990,7 +990,7 @@ async fn start_ldk() { } } } - } + }, Err(e) => println!("ERROR: errored reading channel peer info from disk: {:?}", e), } } diff --git a/src/sweep.rs b/src/sweep.rs index b4ee017..fbfa62c 100644 --- a/src/sweep.rs +++ b/src/sweep.rs @@ -101,7 +101,7 @@ pub(crate) async fn periodic_sweep( match file.read_exact(&mut [0; 1]) { Ok(_) => { file.seek(SeekFrom::Current(-1)).unwrap(); - } + }, Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break, Err(e) => Err(e).unwrap(), } -- 2.30.2 From 4a1a645819fa18609ccaf9f6a9c4ddfd55519dd5 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 8 Mar 2024 14:32:39 +0100 Subject: [PATCH 3/4] Generate random `PaymentId` for outbound offers Previously, we'd deterministically derive the `offer_hash` as a `PaymentId` for outbound BOLT 12 payments. However, as offers may be paid multiple times, this could result in collisions in our `outbound_payments` store. Here, we therefore use random `PaymentId`s to avoid collisions, even if offers are paid multiple times. --- src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index a70d772..b9fbf77 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -175,8 +175,8 @@ pub(crate) fn poll_for_user_input( } if let Ok(offer) = Offer::from_str(invoice_str.unwrap()) { - let offer_hash = Sha256::hash(invoice_str.unwrap().as_bytes()); - let payment_id = PaymentId(*offer_hash.as_ref()); + let random_bytes = keys_manager.get_secure_random_bytes(); + let payment_id = PaymentId(random_bytes); let amt_msat = match (offer.amount(), user_provided_amt) { (Some(offer::Amount::Bitcoin { amount_msats }), _) => *amount_msats, -- 2.30.2 From f58bb8471fcd2be32afbf95ccefee6d4538ae6e5 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 8 Mar 2024 14:35:19 +0100 Subject: [PATCH 4/4] Fix warning by including error in error prompt --- src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index b9fbf77..04c5898 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -818,7 +818,7 @@ fn send_payment( let (payment_hash, recipient_onion, route_params) = match pay_params_opt { Ok(res) => res, Err(e) => { - println!("Failed to parse invoice"); + println!("Failed to parse invoice: {:?}", e); print!("> "); return; } -- 2.30.2