tests: lnpeer: add test_reject_mpp_for_non_mpp_invoice
What changed, and why it matters
This commit only adds a new automated test to Electrum's Lightning networking code. It checks that a multi-part payment (MPP) is rejected when the recipient's invoice did not advertise support for MPP. There is no change to production code, no bug fix, and no security patch in the diff itself.
No action required; this is a test-only commit. If reviewing a related patch series, verify that the production-code commit(s) preceding or following this test correctly enforce MPP rejection for non-MPP invoices.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces test_reject_mpp_for_non_mpp_invoice in tests/test_lnpeer.py. The test creates two Lightning peers, forces the payer to use MPP via w1.config.TEST_FORCE_MPP = True, prepares an invoice from the payee that does not signal BASIC_MPP_OPT or BASIC_MPP_REQ, and asserts that the payment fails with PaymentFailure. It runs the scenario both with and without trampoline routing. No implementation code is modified.
Changed components
tests/test_lnpeer.pyInspect captured patch +40 / −0
diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py
index 07662f1..941a4a1 100644
--- a/tests/test_lnpeer.py
+++ b/tests/test_lnpeer.py
@@ -1071,6 +1071,46 @@ class TestPeerDirect(TestPeer):
for _test_trampoline in [False, True]:
await run_test(_test_trampoline)
+ async def test_reject_mpp_for_non_mpp_invoice(self):
+ """Test that we reject a payment if it is mpp and we didn't signal support for mpp in the invoice"""
+ async def run_test(test_trampoline):
+ alice_channel, bob_channel = create_test_channels()
+ p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
+ w1.config.TEST_FORCE_MPP = True # force alice to send mpp
+
+ if test_trampoline:
+ await self._activate_trampoline(w1)
+ await self._activate_trampoline(w2)
+ # declare bob as trampoline node
+ electrum.trampoline._TRAMPOLINE_NODES_UNITTESTS = {
+ 'bob': LNPeerAddr(host="127.0.0.1", port=9735, pubkey=w2.node_keypair.pubkey),
+ }
+
+ lnaddr, pay_req = self.prepare_invoice(w2)
+ self.assertFalse(lnaddr.get_features().supports(LnFeatures.BASIC_MPP_OPT))
+ self.assertFalse(lnaddr.get_features().supports(LnFeatures.BASIC_MPP_REQ))
+
+ async def try_pay_invoice_with_mpp(pay_req: Invoice, w1=w1):
+ result, log = await w1.pay_invoice(pay_req)
+ if not result:
+ raise PaymentFailure()
+ raise PaymentDone()
+
+ async def f():
+ async with OldTaskGroup() as group:
+ await group.spawn(p1._message_loop())
+ await group.spawn(p1.htlc_switch())
+ await group.spawn(p2._message_loop())
+ await group.spawn(p2.htlc_switch())
+ await asyncio.sleep(0.01)
+ await group.spawn(try_pay_invoice_with_mpp(pay_req))
+
+ with self.assertRaises(PaymentFailure):
+ await f()
+
+ for _test_trampoline in [False, True]:
+ await run_test(_test_trampoline)
+
async def test_reject_multiple_payments_of_same_invoice(self):
"""Tests that new htlcs paying an invoice that has already been paid will get rejected."""
async def run_test(test_trampoline):
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.