Skip to content

Commit

Permalink
f don't manually create message contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinewallace committed Dec 6, 2024
1 parent 00fceb2 commit 6cbb34b
Showing 1 changed file with 105 additions and 85 deletions.
190 changes: 105 additions & 85 deletions lightning/src/ln/blinded_payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,18 @@ use crate::util::ser::WithoutLength;
use crate::util::test_utils;
use lightning_invoice::RawBolt11Invoice;
#[cfg(async_payments)] use {
crate::blinded_path::BlindedHop,
crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath, OffersContext},
crate::ln::channelmanager::Verification,
crate::blinded_path::message::{BlindedMessagePath, MessageContext, OffersContext},
crate::ln::inbound_payment,
crate::ln::msgs::OnionMessageHandler,
crate::offers::nonce::Nonce,
crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc},
crate::onion_message::offers::{OffersMessage, OffersMessageHandler},
crate::onion_message::messenger::{Destination, MessageSendInstructions, MessageRouter, PeeledOnion},
crate::onion_message::offers::OffersMessage,
crate::onion_message::packet::ParsedOnionMessageContents,
crate::types::features::Bolt12InvoiceFeatures,
crate::types::payment::PaymentPreimage,

core::convert::Infallible,
};

fn blinded_payment_path(
Expand Down Expand Up @@ -109,6 +111,24 @@ pub fn get_blinded_route_parameters(
)
}

fn extract_invoice_request_reply_path<'a, 'b, 'c>(
invreq_recipient: &Node<'a, 'b, 'c>, message: &msgs::OnionMessage
) -> BlindedMessagePath {
match invreq_recipient.onion_messenger.peel_onion_message(message) {
Ok(PeeledOnion::Receive(invreq, context, reply_path)) => {
assert!(
matches!(invreq, ParsedOnionMessageContents::Offers(OffersMessage::InvoiceRequest(_)))
);
assert!(
matches!(context, Some(MessageContext::Offers(OffersContext::InvoiceRequest { .. })))
);
reply_path.unwrap()
},
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
Err(e) => panic!("Failed to process onion message {:?}", e),
}
}

#[test]
fn one_hop_blinded_path() {
do_one_hop_blinded_path(true);
Expand Down Expand Up @@ -1436,13 +1456,13 @@ fn static_invoice_unknown_required_features() {
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);

// Use a dummy blinded path because we don't support retrieving the static invoice from the
// recipient's LSP yet.
let dummy_blinded_path_to_always_online_node = BlindedMessagePath::from_raw(
nodes[1].node.get_our_node_id(), test_utils::pubkey(42),
vec![BlindedHop { blinded_node_id: test_utils::pubkey(42), encrypted_payload: vec![42; 32] }]
);
let (offer_builder, nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node]).unwrap();
let blinded_paths_to_always_online_node = nodes[1].message_router.create_blinded_paths(
nodes[1].node.get_our_node_id(),
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
Vec::new(), &secp_ctx
).unwrap();
let (offer_builder, nonce) =
nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node).unwrap();
let offer = offer_builder.build().unwrap();
let static_invoice_unknown_req_features = nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
&offer, nonce, None
Expand All @@ -1453,24 +1473,19 @@ fn static_invoice_unknown_required_features() {

let amt_msat = 5000;
let payment_id = PaymentId([1; 32]);
// Set the random bytes so we can predict the offer outbound payment context nonce.
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([42; 32]);
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();

// Don't forward the invreq since we don't support retrieving the static invoice from the
// recipient's LSP yet, instead just provide the invoice directly to the payer.
let _invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();

let inbound_payment_key = inbound_payment::ExpandedKey::new(
&nodes[0].keys_manager.get_inbound_payment_key_material()
);
let offer_outbound_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
let hmac = payment_id.hmac_for_offer_payment(offer_outbound_context_nonce, &inbound_payment_key);
if nodes[0].node.handle_message(
OffersMessage::StaticInvoice(static_invoice_unknown_req_features),
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
).is_some() { panic!() }
// recipient's LSP yet, instead manually construct the response.
let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
let invreq_reply_path = extract_invoice_request_reply_path(&nodes[1], &invreq_om);
nodes[1].onion_messenger.send_onion_message(
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(static_invoice_unknown_req_features)),
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path) }
).unwrap();

let static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
Expand All @@ -1497,25 +1512,25 @@ fn ignore_unexpected_static_invoice() {
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);

// Initiate payment to the sender's intended offer.
let dummy_blinded_path_to_always_online_node = BlindedMessagePath::from_raw(
nodes[1].node.get_our_node_id(), test_utils::pubkey(42),
vec![BlindedHop { blinded_node_id: test_utils::pubkey(42), encrypted_payload: vec![42; 32] }]
);
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node.clone()]).unwrap();
let blinded_paths_to_always_online_node = nodes[1].message_router.create_blinded_paths(
nodes[1].node.get_our_node_id(),
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
Vec::new(), &secp_ctx
).unwrap();
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node.clone()).unwrap();
let offer = offer_builder.build().unwrap();
let amt_msat = 5000;
let payment_id = PaymentId([1; 32]);
// Set the random bytes so we can predict the offer outbound payment context nonce.
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([42; 32]);
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();

// Don't forward the invreq since we don't support retrieving the static invoice from the
// recipient's LSP yet, instead just provide the invoice directly to the payer.
let _invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
// recipient's LSP yet, instead manually construct the responses below.
let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
let invreq_reply_path = extract_invoice_request_reply_path(&nodes[1], &invreq_om);

// Create a static invoice with the same payment_id but corresponding to a different offer.
let unexpected_static_invoice = {
let (offer_builder, nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node]).unwrap();
let (offer_builder, nonce) = nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node).unwrap();
let sender_unintended_offer = offer_builder.build().unwrap();

nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
Expand All @@ -1524,15 +1539,12 @@ fn ignore_unexpected_static_invoice() {
};

// Check that we'll ignore the unexpected static invoice.
let inbound_payment_key = inbound_payment::ExpandedKey::new(
&nodes[0].keys_manager.get_inbound_payment_key_material()
);
let offer_outbound_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
let hmac = payment_id.hmac_for_offer_payment(offer_outbound_context_nonce, &inbound_payment_key);
assert!(nodes[0].node.handle_message(
OffersMessage::StaticInvoice(unexpected_static_invoice),
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
).is_none());
nodes[1].onion_messenger.send_onion_message(
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(unexpected_static_invoice)),
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path.clone()) }
).unwrap();
let unexpected_static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &unexpected_static_invoice_om);
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
assert!(async_pmts_msgs.is_empty());
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
Expand All @@ -1543,19 +1555,23 @@ fn ignore_unexpected_static_invoice() {
&offer, offer_nonce, None
).unwrap().build_and_sign(&secp_ctx).unwrap();

assert!(nodes[0].node.handle_message(
OffersMessage::StaticInvoice(valid_static_invoice.clone()),
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
).is_none());
nodes[1].onion_messenger.send_onion_message(
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(valid_static_invoice.clone())),
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path.clone()) }
).unwrap();
let static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
assert!(!async_pmts_msgs.is_empty());
assert!(async_pmts_msgs.into_iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));

// Receiving a duplicate invoice will have no effect.
assert!(nodes[0].node.handle_message(
OffersMessage::StaticInvoice(valid_static_invoice),
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
).is_none());
nodes[1].onion_messenger.send_onion_message(
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(valid_static_invoice)),
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path) }
).unwrap();
let dup_static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &dup_static_invoice_om);
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
assert!(async_pmts_msgs.is_empty());
}
Expand All @@ -1573,57 +1589,61 @@ fn pays_static_invoice() {
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);

let dummy_blinded_path_to_always_online_node = BlindedMessagePath::from_raw(
nodes[1].node.get_our_node_id(), test_utils::pubkey(42),
vec![BlindedHop { blinded_node_id: test_utils::pubkey(42), encrypted_payload: vec![42; 32] }]
);
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node.clone()]).unwrap();
let blinded_paths_to_always_online_node = nodes[1].message_router.create_blinded_paths(
nodes[1].node.get_our_node_id(),
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
Vec::new(), &secp_ctx
).unwrap();
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node).unwrap();
let offer = offer_builder.build().unwrap();
let amt_msat = 5000;
let payment_id = PaymentId([1; 32]);
// Set the random bytes so we can predict the offer outbound payment context nonce.
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([42; 32]);
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();

// Don't forward the invreq since we don't support retrieving the static invoice from the
// recipient's LSP yet, instead just provide the invoice directly to the payer.
let _invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();

let inbound_payment_key = inbound_payment::ExpandedKey::new(
&nodes[0].keys_manager.get_inbound_payment_key_material()
);
let offer_outbound_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
let hmac = payment_id.hmac_for_offer_payment(offer_outbound_context_nonce, &inbound_payment_key);

let static_invoice = nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
&offer, offer_nonce, None
).unwrap().build_and_sign(&secp_ctx).unwrap();

assert!(nodes[0].node.handle_message(
OffersMessage::StaticInvoice(static_invoice),
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
).is_none());
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();

// Don't forward the invreq since we don't support retrieving the static invoice from the
// recipient's LSP yet, instead manually construct the response.
let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
let invreq_reply_path = extract_invoice_request_reply_path(&nodes[1], &invreq_om);

nodes[1].onion_messenger.send_onion_message(
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(static_invoice)),
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path) }
).unwrap();
let static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
let mut async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
assert!(!async_pmts_msgs.is_empty());
assert!(async_pmts_msgs.into_iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
assert!(async_pmts_msgs.iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));

// Manually create the message and context releasing the HTLC since the recipient doesn't support
// Manually send the message and context releasing the HTLC since the recipient doesn't support
// responding themselves yet.
let outbound_async_payment_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
let outbound_async_payment_context = AsyncPaymentsContext::OutboundPayment {
payment_id,
nonce: outbound_async_payment_context_nonce,
hmac: payment_id.hmac_for_async_payment(outbound_async_payment_context_nonce, &inbound_payment_key),
let held_htlc_avail_reply_path = match async_pmts_msgs.pop().unwrap().1 {
MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => reply_path,
_ => panic!()
};
nodes[0].node.handle_release_held_htlc(ReleaseHeldHtlc {}, outbound_async_payment_context.clone());
nodes[2].onion_messenger.send_onion_message(
ParsedOnionMessageContents::<Infallible>::AsyncPayments(
AsyncPaymentsMessage::ReleaseHeldHtlc(ReleaseHeldHtlc {}),
),
MessageSendInstructions::WithoutReplyPath {
destination: Destination::BlindedPath(held_htlc_avail_reply_path)
}
).unwrap();

let release_held_htlc_om = nodes[2].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
nodes[0].onion_messenger.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);

// Check that we've queued the HTLCs of the async keysend payment.
let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
assert_eq!(htlc_updates.update_add_htlcs.len(), 1);
check_added_monitors!(nodes[0], 1);

// Receiving a duplicate release_htlc message doesn't result in duplicate payment.
nodes[0].node.handle_release_held_htlc(ReleaseHeldHtlc {}, outbound_async_payment_context.clone());
nodes[0].onion_messenger.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
}

Expand Down

0 comments on commit 6cbb34b

Please sign in to comment.