Skip to content

Commit 78659f3

Browse files
authored
Merge pull request #129 from TheBlueMatt/main
Use PaymentParameters creation utility and support BOLT12
2 parents 3bb8258 + a9273ad commit 78659f3

File tree

2 files changed

+169
-57
lines changed

2 files changed

+169
-57
lines changed

src/cli.rs

Lines changed: 146 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use bitcoin::secp256k1::PublicKey;
1111
use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry};
1212
use lightning::ln::msgs::SocketAddress;
1313
use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage};
14+
use lightning::offers::offer::{self, Offer};
1415
use lightning::onion_message::messenger::Destination;
1516
use lightning::onion_message::packet::OnionMessageContents;
1617
use lightning::routing::gossip::NodeId;
@@ -19,6 +20,8 @@ use lightning::sign::{EntropySource, KeysManager};
1920
use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig};
2021
use lightning::util::persist::KVStore;
2122
use lightning::util::ser::{Writeable, Writer};
23+
use lightning_invoice::payment::payment_parameters_from_invoice;
24+
use lightning_invoice::payment::payment_parameters_from_zero_amount_invoice;
2225
use lightning_invoice::{utils, Bolt11Invoice, Currency};
2326
use lightning_persister::fs_store::FilesystemStore;
2427
use std::env;
@@ -72,7 +75,7 @@ pub(crate) fn poll_for_user_input(
7275
);
7376
println!("LDK logs are available at <your-supplied-ldk-data-dir-path>/.ldk/logs");
7477
println!("Local Node ID is {}.", channel_manager.get_our_node_id());
75-
loop {
78+
'read_command: loop {
7679
print!("> ");
7780
io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print
7881
let mut line = String::new();
@@ -160,20 +163,90 @@ pub(crate) fn poll_for_user_input(
160163
continue;
161164
}
162165

163-
let invoice = match Bolt11Invoice::from_str(invoice_str.unwrap()) {
164-
Ok(inv) => inv,
165-
Err(e) => {
166-
println!("ERROR: invalid invoice: {:?}", e);
166+
let mut user_provided_amt: Option<u64> = None;
167+
if let Some(amt_msat_str) = words.next() {
168+
match amt_msat_str.parse() {
169+
Ok(amt) => user_provided_amt = Some(amt),
170+
Err(e) => {
171+
println!("ERROR: couldn't parse amount_msat: {}", e);
172+
continue;
173+
}
174+
};
175+
}
176+
177+
if let Ok(offer) = Offer::from_str(invoice_str.unwrap()) {
178+
let offer_hash = Sha256::hash(invoice_str.unwrap().as_bytes());
179+
let payment_id = PaymentId(*offer_hash.as_ref());
180+
181+
let amt_msat = match (offer.amount(), user_provided_amt) {
182+
(Some(offer::Amount::Bitcoin { amount_msats }), _) => *amount_msats,
183+
(_, Some(amt)) => amt,
184+
(amt, _) => {
185+
println!("ERROR: Cannot process non-Bitcoin-denominated offer value {:?}", amt);
186+
continue;
187+
}
188+
};
189+
if user_provided_amt.is_some() && user_provided_amt != Some(amt_msat) {
190+
println!("Amount didn't match offer of {}msat", amt_msat);
167191
continue;
168192
}
169-
};
170193

171-
send_payment(
172-
&channel_manager,
173-
&invoice,
174-
&mut outbound_payments.lock().unwrap(),
175-
Arc::clone(&fs_store),
176-
);
194+
while user_provided_amt.is_none() {
195+
print!("Paying offer for {} msat. Continue (Y/N)? >", amt_msat);
196+
io::stdout().flush().unwrap();
197+
198+
if let Err(e) = io::stdin().read_line(&mut line) {
199+
println!("ERROR: {}", e);
200+
break 'read_command;
201+
}
202+
203+
if line.len() == 0 {
204+
// We hit EOF / Ctrl-D
205+
break 'read_command;
206+
}
207+
208+
if line.starts_with("Y") {
209+
break;
210+
}
211+
if line.starts_with("N") {
212+
continue 'read_command;
213+
}
214+
}
215+
216+
outbound_payments.lock().unwrap().payments.insert(
217+
payment_id,
218+
PaymentInfo {
219+
preimage: None,
220+
secret: None,
221+
status: HTLCStatus::Pending,
222+
amt_msat: MillisatAmount(Some(amt_msat)),
223+
},
224+
);
225+
fs_store
226+
.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode())
227+
.unwrap();
228+
229+
let retry = Retry::Timeout(Duration::from_secs(10));
230+
let amt = Some(amt_msat);
231+
let pay = channel_manager
232+
.pay_for_offer(&offer, None, amt, None, payment_id, retry, None);
233+
if pay.is_err() {
234+
println!("ERROR: Failed to pay: {:?}", pay);
235+
}
236+
} else {
237+
match Bolt11Invoice::from_str(invoice_str.unwrap()) {
238+
Ok(invoice) => send_payment(
239+
&channel_manager,
240+
&invoice,
241+
user_provided_amt,
242+
&mut outbound_payments.lock().unwrap(),
243+
Arc::clone(&fs_store),
244+
),
245+
Err(e) => {
246+
println!("ERROR: invalid invoice: {:?}", e);
247+
}
248+
}
249+
}
177250
}
178251
"keysend" => {
179252
let dest_pubkey = match words.next() {
@@ -212,6 +285,34 @@ pub(crate) fn poll_for_user_input(
212285
Arc::clone(&fs_store),
213286
);
214287
}
288+
"getoffer" => {
289+
let offer_builder = channel_manager.create_offer_builder(String::new());
290+
if let Err(e) = offer_builder {
291+
println!("ERROR: Failed to initiate offer building: {:?}", e);
292+
continue;
293+
}
294+
295+
let amt_str = words.next();
296+
let offer = if amt_str.is_some() {
297+
let amt_msat: Result<u64, _> = amt_str.unwrap().parse();
298+
if amt_msat.is_err() {
299+
println!("ERROR: getoffer provided payment amount was not a number");
300+
continue;
301+
}
302+
offer_builder.unwrap().amount_msats(amt_msat.unwrap()).build()
303+
} else {
304+
offer_builder.unwrap().build()
305+
};
306+
307+
if offer.is_err() {
308+
println!("ERROR: Failed to build offer: {:?}", offer.unwrap_err());
309+
} else {
310+
// Note that unlike BOLT11 invoice creation we don't bother to add a
311+
// pending inbound payment here, as offers can be reused and don't
312+
// correspond with individual payments.
313+
println!("{}", offer.unwrap());
314+
}
315+
}
215316
"getinvoice" => {
216317
let amt_str = words.next();
217318
if amt_str.is_none() {
@@ -480,11 +581,12 @@ fn help() {
480581
println!(" disconnectpeer <peer_pubkey>");
481582
println!(" listpeers");
482583
println!("\n Payments:");
483-
println!(" sendpayment <invoice>");
584+
println!(" sendpayment <invoice|offer> [<amount_msat>]");
484585
println!(" keysend <dest_pubkey> <amt_msats>");
485586
println!(" listpayments");
486587
println!("\n Invoices:");
487588
println!(" getinvoice <amt_msats> <expiry_secs>");
589+
println!(" getoffer [<amt_msats>]");
488590
println!("\n Other:");
489591
println!(" signmessage <message>");
490592
println!(
@@ -686,12 +788,41 @@ fn open_channel(
686788
}
687789

688790
fn send_payment(
689-
channel_manager: &ChannelManager, invoice: &Bolt11Invoice,
791+
channel_manager: &ChannelManager, invoice: &Bolt11Invoice, required_amount_msat: Option<u64>,
690792
outbound_payments: &mut OutboundPaymentInfoStorage, fs_store: Arc<FilesystemStore>,
691793
) {
692794
let payment_id = PaymentId((*invoice.payment_hash()).to_byte_array());
693-
let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
694795
let payment_secret = Some(*invoice.payment_secret());
796+
let zero_amt_invoice =
797+
invoice.amount_milli_satoshis().is_none() || invoice.amount_milli_satoshis() == Some(0);
798+
let pay_params_opt = if zero_amt_invoice {
799+
if let Some(amt_msat) = required_amount_msat {
800+
payment_parameters_from_zero_amount_invoice(invoice, amt_msat)
801+
} else {
802+
println!("Need an amount for the given 0-value invoice");
803+
print!("> ");
804+
return;
805+
}
806+
} else {
807+
if required_amount_msat.is_some() && invoice.amount_milli_satoshis() != required_amount_msat
808+
{
809+
println!(
810+
"Amount didn't match invoice value of {}msat",
811+
invoice.amount_milli_satoshis().unwrap_or(0)
812+
);
813+
print!("> ");
814+
return;
815+
}
816+
payment_parameters_from_invoice(invoice)
817+
};
818+
let (payment_hash, recipient_onion, route_params) = match pay_params_opt {
819+
Ok(res) => res,
820+
Err(e) => {
821+
println!("Failed to parse invoice");
822+
print!("> ");
823+
return;
824+
}
825+
};
695826
outbound_payments.payments.insert(
696827
payment_id,
697828
PaymentInfo {
@@ -703,42 +834,6 @@ fn send_payment(
703834
);
704835
fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap();
705836

706-
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
707-
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
708-
let mut payment_params = match PaymentParameters::from_node_id(
709-
invoice.recover_payee_pub_key(),
710-
invoice.min_final_cltv_expiry_delta() as u32,
711-
)
712-
.with_expiry_time(
713-
invoice.duration_since_epoch().as_secs().saturating_add(invoice.expiry_time().as_secs()),
714-
)
715-
.with_route_hints(invoice.route_hints())
716-
{
717-
Err(e) => {
718-
println!("ERROR: Could not process invoice to prepare payment parameters, {:?}, invoice: {:?}", e, invoice);
719-
return;
720-
}
721-
Ok(p) => p,
722-
};
723-
if let Some(features) = invoice.features() {
724-
payment_params = match payment_params.with_bolt11_features(features.clone()) {
725-
Err(e) => {
726-
println!("ERROR: Could not build BOLT11 payment parameters! {:?}", e);
727-
return;
728-
}
729-
Ok(p) => p,
730-
};
731-
}
732-
let invoice_amount = match invoice.amount_milli_satoshis() {
733-
None => {
734-
println!("ERROR: An invoice with an amount is expected; {:?}", invoice);
735-
return;
736-
}
737-
Some(a) => a,
738-
};
739-
let route_params =
740-
RouteParameters::from_payment_params_and_value(payment_params, invoice_amount);
741-
742837
match channel_manager.send_payment(
743838
payment_hash,
744839
recipient_onion,

src/main.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ use std::fs;
5151
use std::fs::File;
5252
use std::io;
5353
use std::io::Write;
54+
use std::net::ToSocketAddrs;
5455
use std::path::Path;
5556
use std::sync::atomic::{AtomicBool, Ordering};
5657
use std::sync::{Arc, Mutex, RwLock};
@@ -172,11 +173,11 @@ pub(crate) type BumpTxEventHandler = BumpTransactionEventHandler<
172173
>;
173174

174175
async fn handle_ldk_events(
175-
channel_manager: &Arc<ChannelManager>, bitcoind_client: &BitcoindClient,
176+
channel_manager: Arc<ChannelManager>, bitcoind_client: &BitcoindClient,
176177
network_graph: &NetworkGraph, keys_manager: &KeysManager,
177-
bump_tx_event_handler: &BumpTxEventHandler,
178+
bump_tx_event_handler: &BumpTxEventHandler, peer_manager: Arc<PeerManager>,
178179
inbound_payments: Arc<Mutex<InboundPaymentInfoStorage>>,
179-
outbound_payments: Arc<Mutex<OutboundPaymentInfoStorage>>, fs_store: &Arc<FilesystemStore>,
180+
outbound_payments: Arc<Mutex<OutboundPaymentInfoStorage>>, fs_store: Arc<FilesystemStore>,
180181
network: Network, event: Event,
181182
) {
182183
match event {
@@ -512,7 +513,20 @@ async fn handle_ldk_events(
512513
}
513514
Event::HTLCIntercepted { .. } => {}
514515
Event::BumpTransaction(event) => bump_tx_event_handler.handle_event(&event),
515-
Event::ConnectionNeeded { .. } => {}
516+
Event::ConnectionNeeded { node_id, addresses } => {
517+
tokio::spawn(async move {
518+
for address in addresses {
519+
if let Ok(sockaddrs) = address.to_socket_addrs() {
520+
for addr in sockaddrs {
521+
let pm = Arc::clone(&peer_manager);
522+
if cli::connect_peer_if_necessary(node_id, addr, pm).await.is_ok() {
523+
return;
524+
}
525+
}
526+
}
527+
}
528+
});
529+
}
516530
}
517531
}
518532

@@ -886,6 +900,7 @@ async fn start_ldk() {
886900
let inbound_payments_event_listener = Arc::clone(&inbound_payments);
887901
let outbound_payments_event_listener = Arc::clone(&outbound_payments);
888902
let fs_store_event_listener = Arc::clone(&fs_store);
903+
let peer_manager_event_listener = Arc::clone(&peer_manager);
889904
let network = args.network;
890905
let event_handler = move |event: Event| {
891906
let channel_manager_event_listener = Arc::clone(&channel_manager_event_listener);
@@ -896,16 +911,18 @@ async fn start_ldk() {
896911
let inbound_payments_event_listener = Arc::clone(&inbound_payments_event_listener);
897912
let outbound_payments_event_listener = Arc::clone(&outbound_payments_event_listener);
898913
let fs_store_event_listener = Arc::clone(&fs_store_event_listener);
914+
let peer_manager_event_listener = Arc::clone(&peer_manager_event_listener);
899915
async move {
900916
handle_ldk_events(
901-
&channel_manager_event_listener,
917+
channel_manager_event_listener,
902918
&bitcoind_client_event_listener,
903919
&network_graph_event_listener,
904920
&keys_manager_event_listener,
905921
&bump_tx_event_handler,
922+
peer_manager_event_listener,
906923
inbound_payments_event_listener,
907924
outbound_payments_event_listener,
908-
&fs_store_event_listener,
925+
fs_store_event_listener,
909926
network,
910927
event,
911928
)

0 commit comments

Comments
 (0)