Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/payment/bolt12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,13 @@ impl Bolt12Payment {
},
};

let payment_amount_msat = match quantity {
Some(quantity) => {
offer_amount_msat.checked_mul(quantity).ok_or(Error::InvalidAmount)?
Comment on lines +325 to +326

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The other amount/offer checks in send() log before returning, but this one fails silently, and before this change, an overflow got logged via the LDK error path. Could we log here too?

},
None => offer_amount_msat,
};

let params = OptionalOfferPaymentParams {
payer_note: payer_note.clone(),
retry_strategy,
Expand All @@ -339,7 +346,7 @@ impl Bolt12Payment {
log_info!(
self.logger,
"Initiated sending {}msat to {:?}",
offer_amount_msat,
payment_amount_msat,
payee_pubkey
);

Expand All @@ -354,7 +361,7 @@ impl Bolt12Payment {
let payment = PaymentDetails::new(
payment_id,
kind,
Some(offer_amount_msat),
Some(payment_amount_msat),
None,
PaymentDirection::Outbound,
PaymentStatus::Pending,
Expand All @@ -379,7 +386,7 @@ impl Bolt12Payment {
let payment = PaymentDetails::new(
payment_id,
kind,
Some(offer_amount_msat),
Some(payment_amount_msat),
None,
PaymentDirection::Outbound,
PaymentStatus::Failed,
Expand Down
60 changes: 60 additions & 0 deletions tests/integration_tests_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5121,6 +5121,66 @@ async fn simple_bolt12_send_receive() {
assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(overpaid_amount));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn bolt12_send_with_quantity_records_total_amount() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let chain_source = random_chain_source(&bitcoind, &electrsd);
let (node_a, node_b) = setup_two_nodes(&chain_source, false, false);

let address_a = node_a.onchain_payment().new_address().unwrap();
let premine_amount_sat = 5_000_000;
premine_and_distribute_funds(
&bitcoind.client,
&electrsd.client,
vec![address_a],
Amount::from_sat(premine_amount_sat),
)
.await;

node_a.sync_wallets().unwrap();
open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await;

generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;

node_a.sync_wallets().unwrap();
node_b.sync_wallets().unwrap();

expect_channel_ready_event!(node_a, node_b.node_id());
expect_channel_ready_event!(node_b, node_a.node_id());

// Wait until node_b broadcasted a node announcement and node_a received it.
let has_node_announcement = |node: &ldk_node::Node, node_id: bitcoin::secp256k1::PublicKey| {
node.network_graph()
.node(&NodeId::from_pubkey(&node_id))
.map_or(false, |info| info.announcement_info.is_some())
};
while node_b.status().latest_node_announcement_broadcast_timestamp.is_none()
|| !has_node_announcement(&node_a, node_b.node_id())
{
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}

// Paying for two items of a per-item offer must pay and record the total, not the unit price.
let unit_amount_msat = 100_000_000;
let quantity = 2;
let expected_amount_msat = unit_amount_msat * quantity;
let offer = node_b
.bolt12_payment()
.receive(unit_amount_msat, "multi-item", None, Some(quantity))
.unwrap();
let payment_id = node_a.bolt12_payment().send(&offer, Some(quantity), None, None).unwrap();

expect_payment_successful_event!(node_a, payment_id, None);
expect_payment_received_event!(node_b, expected_amount_msat);

let node_a_payment = node_a.payment(&payment_id).unwrap().unwrap();
assert_eq!(node_a_payment.amount_msat, Some(expected_amount_msat));
assert!(matches!(
node_a_payment.kind,
PaymentKind::Bolt12Offer { quantity: Some(q), .. } if q == quantity
));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn async_payment() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
Expand Down
Loading