Skip to content

Commit 9fa0d70

Browse files
committed
f Allow overpayment, improve "zero-amount" invoicing
1 parent d7eec89 commit 9fa0d70

File tree

3 files changed

+121
-23
lines changed

3 files changed

+121
-23
lines changed

src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum Error {
1313
ConnectionFailed,
1414
/// Payment of the given invoice has already been intiated.
1515
NonUniquePaymentHash,
16+
/// The given amount is invalid.
17+
InvalidAmount,
1618
/// The given invoice is invalid.
1719
InvalidInvoice,
1820
/// Invoice creation failed.
@@ -47,6 +49,7 @@ impl fmt::Display for Error {
4749
}
4850
Self::ConnectionFailed => write!(f, "Network connection closed."),
4951
Self::NonUniquePaymentHash => write!(f, "An invoice must not get payed twice."),
52+
Self::InvalidAmount => write!(f, "The given amount is invalid."),
5053
Self::InvalidInvoice => write!(f, "The given invoice is invalid."),
5154
Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."),
5255
Self::InsufficientFunds => {

src/lib.rs

+71-21
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ impl Node {
887887
Ok(_payment_id) => {
888888
let payee_pubkey = invoice.recover_payee_pub_key();
889889
let amt_msat = invoice.amount_milli_satoshis().unwrap();
890-
log_info!(self.logger, "Initiated sending {} msats to {}", amt_msat, payee_pubkey);
890+
log_info!(self.logger, "Initiated sending {}msat to {}", amt_msat, payee_pubkey);
891891

892892
outbound_payments_lock.insert(
893893
payment_hash,
@@ -922,9 +922,13 @@ impl Node {
922922
}
923923
}
924924

925-
/// Send a payement given a so-called "zero amount" invoice, i.e., an invoice that leaves the
925+
/// Send a payment given an invoice and an amount in millisatoshi.
926+
///
927+
/// This will fail if the amount given is less than the value required by the given invoice.
928+
///
929+
/// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the
926930
/// amount paid to be determined by the user.
927-
pub fn send_adjustable_value_payment(
931+
pub fn send_payment_using_amount(
928932
&self, invoice: Invoice, amount_msat: u64,
929933
) -> Result<PaymentHash, Error> {
930934
if self.running.read().unwrap().is_none() {
@@ -933,27 +937,58 @@ impl Node {
933937

934938
let mut outbound_payments_lock = self.outbound_payments.lock().unwrap();
935939

940+
if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
941+
if amount_msat < invoice_amount_msat {
942+
log_error!(
943+
self.logger,
944+
"Failed to pay as the given amount needs to be larger or equal to the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat);
945+
return Err(Error::InvalidAmount);
946+
}
947+
}
948+
949+
let payment_id = PaymentId(invoice.payment_hash().into_inner());
936950
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
937951
let payment_secret = Some(*invoice.payment_secret());
938-
939-
if invoice.amount_milli_satoshis().is_some() {
940-
log_error!(
941-
self.logger,
942-
"Failed to pay the given invoice: expected \"zero-amount\" invoice, please use send_payment."
943-
);
944-
return Err(Error::InvalidInvoice);
952+
let expiry_time = invoice
953+
.clone()
954+
.into_signed_raw()
955+
.raw_invoice()
956+
.data
957+
.timestamp
958+
.as_duration_since_epoch()
959+
+ invoice.expiry_time();
960+
let mut payment_params = PaymentParameters::from_node_id(
961+
invoice.recover_payee_pub_key(),
962+
invoice.min_final_cltv_expiry_delta() as u32,
963+
)
964+
.with_expiry_time(expiry_time.as_secs())
965+
.with_route_hints(invoice.route_hints());
966+
if let Some(features) = invoice.features() {
967+
payment_params = payment_params.with_features(features.clone());
945968
}
946-
947-
match lightning_invoice::payment::pay_zero_value_invoice(
948-
&invoice,
949-
amount_msat,
950-
Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT),
951-
self.channel_manager.as_ref(),
952-
) {
969+
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
970+
971+
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
972+
973+
match self
974+
.channel_manager
975+
.send_payment_with_retry(
976+
payment_hash,
977+
&payment_secret,
978+
payment_id,
979+
route_params,
980+
retry_strategy,
981+
)
982+
.map_err(payment::PaymentError::Sending)
983+
{
953984
Ok(_payment_id) => {
954985
let payee_pubkey = invoice.recover_payee_pub_key();
955-
let amt_msat = invoice.amount_milli_satoshis().unwrap();
956-
log_info!(self.logger, "Initiated sending {} msats to {}", amt_msat, payee_pubkey);
986+
log_info!(
987+
self.logger,
988+
"Initiated sending {} msat to {}",
989+
amount_msat,
990+
payee_pubkey
991+
);
957992

958993
outbound_payments_lock.insert(
959994
payment_hash,
@@ -1018,7 +1053,7 @@ impl Node {
10181053
Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT),
10191054
) {
10201055
Ok(_payment_id) => {
1021-
log_info!(self.logger, "Initiated sending {} msats to {}.", amount_msat, node_id);
1056+
log_info!(self.logger, "Initiated sending {}msat to {}.", amount_msat, node_id);
10221057
outbound_payments_lock.insert(
10231058
payment_hash,
10241059
PaymentInfo {
@@ -1046,8 +1081,23 @@ impl Node {
10461081
}
10471082
}
10481083

1049-
/// Returns a payable invoice that can be used to request and receive a payment.
1084+
/// Returns a payable invoice that can be used to request and receive a payment of the amount
1085+
/// given.
10501086
pub fn receive_payment(
1087+
&self, amount_msat: u64, description: &str, expiry_secs: u32,
1088+
) -> Result<Invoice, Error> {
1089+
self.receive_payment_inner(Some(amount_msat), description, expiry_secs)
1090+
}
1091+
1092+
/// Returns a payable invoice that can be used to request and receive a payment for which the
1093+
/// amount is to be determined by the user, also known as a "zero-amount" invoice.
1094+
pub fn receive_variable_amount_payment(
1095+
&self, description: &str, expiry_secs: u32,
1096+
) -> Result<Invoice, Error> {
1097+
self.receive_payment_inner(None, description, expiry_secs)
1098+
}
1099+
1100+
fn receive_payment_inner(
10511101
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
10521102
) -> Result<Invoice, Error> {
10531103
let mut inbound_payments_lock = self.inbound_payments.lock().unwrap();

src/tests/functional_tests.rs

+47-2
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,59 @@ fn channel_full_cycle() {
197197
};
198198

199199
println!("\nB receive_payment");
200-
let invoice = node_b.receive_payment(Some(1000000), &"asdf", 9217).unwrap();
200+
let invoice = node_b.receive_payment(1000000, &"asdf", 9217).unwrap();
201201

202202
println!("\nA send_payment");
203203
node_a.send_payment(invoice).unwrap();
204204

205205
expect_event!(node_a, PaymentSuccessful);
206206
expect_event!(node_b, PaymentReceived);
207207

208+
// Test under-/overpayment
209+
let invoice_amount = 1000000;
210+
let invoice = node_b.receive_payment(invoice_amount, &"asdf", 9217).unwrap();
211+
212+
let underpaid_amount = invoice_amount - 1;
213+
assert_eq!(
214+
Err(Error::InvalidAmount),
215+
node_a.send_payment_using_amount(invoice, underpaid_amount)
216+
);
217+
218+
let invoice = node_b.receive_payment(invoice_amount, &"asdf", 9217).unwrap();
219+
let overpaid_amount = invoice_amount + 100;
220+
node_a.send_payment_using_amount(invoice, overpaid_amount).unwrap();
221+
expect_event!(node_a, PaymentSuccessful);
222+
let received_amount = match node_b.next_event() {
223+
ref e @ Event::PaymentReceived { amount_msat, .. } => {
224+
println!("{} got event {:?}", std::stringify!(node_b), e);
225+
node_b.event_handled();
226+
amount_msat
227+
}
228+
ref e => {
229+
panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e);
230+
}
231+
};
232+
assert_eq!(received_amount, overpaid_amount);
233+
234+
// Test "zero-amount" invoice payment
235+
let variable_amount_invoice = node_b.receive_variable_amount_payment(&"asdf", 9217).unwrap();
236+
let determined_amount = 1234567;
237+
assert_eq!(Err(Error::InvalidInvoice), node_a.send_payment(variable_amount_invoice.clone()));
238+
node_a.send_payment_using_amount(variable_amount_invoice, determined_amount).unwrap();
239+
240+
expect_event!(node_a, PaymentSuccessful);
241+
let received_amount = match node_b.next_event() {
242+
ref e @ Event::PaymentReceived { amount_msat, .. } => {
243+
println!("{} got event {:?}", std::stringify!(node_b), e);
244+
node_b.event_handled();
245+
amount_msat
246+
}
247+
ref e => {
248+
panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e);
249+
}
250+
};
251+
assert_eq!(received_amount, determined_amount);
252+
208253
node_b.close_channel(&channel_id, &node_a.node_id()).unwrap();
209254
expect_event!(node_a, ChannelClosed);
210255
expect_event!(node_b, ChannelClosed);
@@ -217,7 +262,7 @@ fn channel_full_cycle() {
217262
node_b.sync_wallets().unwrap();
218263

219264
assert!(node_a.on_chain_balance().unwrap().get_spendable() > 90000);
220-
assert_eq!(node_b.on_chain_balance().unwrap().get_spendable(), 101000);
265+
assert_eq!(node_b.on_chain_balance().unwrap().get_spendable(), 103234);
221266

222267
node_a.stop().unwrap();
223268
println!("\nA stopped");

0 commit comments

Comments
 (0)