X-Git-Url: http://git.bitcoin.ninja/index.cgi?p=ldk-sample;a=blobdiff_plain;f=src%2Fcli.rs;h=a70d772769e931bed7e7bfd5312b97136ec646b6;hp=72cc484220d818e8cd2a54e4cdd9537dc6383648;hb=HEAD;hpb=a81a739005b900649bb74f7397c040ddd27c0c11 diff --git a/src/cli.rs b/src/cli.rs index 72cc484..fe1b286 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; @@ -105,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(); @@ -135,7 +136,7 @@ pub(crate) fn poll_for_user_input( _ => { println!("ERROR: invalid boolean flag format. Valid formats: `--option`, `--option=true` `--option=false`"); continue; - } + }, } } @@ -154,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() { @@ -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; - } - }; - - loop { + 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, + (_, 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; + } + + 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,15 +238,16 @@ 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), ), Err(e) => { println!("ERROR: invalid invoice: {:?}", e); - } + }, } } - } + }, "keysend" => { let dest_pubkey = match words.next() { Some(dest) => match hex_utils::to_compressed_pubkey(dest) { @@ -237,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, @@ -266,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 { @@ -294,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() { @@ -333,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() { @@ -346,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( @@ -358,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() { @@ -372,7 +390,7 @@ pub(crate) fn poll_for_user_input( Err(e) => { println!("ERROR: {}", e.to_string()); continue; - } + }, }; if do_disconnect_peer( @@ -384,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(), @@ -414,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() { @@ -450,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" => { @@ -477,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() { @@ -495,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, @@ -503,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); } @@ -515,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( @@ -532,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."), } @@ -563,7 +581,7 @@ fn help() { println!(" disconnectpeer "); println!(" listpeers"); println!("\n Payments:"); - println!(" sendpayment "); + println!(" sendpayment []"); println!(" keysend "); println!(" listpayments"); println!("\n Invoices:"); @@ -711,7 +729,7 @@ pub(crate) async fn do_connect_peer( return Ok(()); } } - } + }, None => Err(()), } } @@ -761,29 +779,50 @@ 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(()); - } + }, } } 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: {:?}", e); + print!("> "); + return; + }, + }; outbound_payments.payments.insert( payment_id, PaymentInfo { @@ -807,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(); - } + }, }; } @@ -848,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(); - } + }, }; } @@ -882,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());