From a7de8de5a28405332fa978322728145824e05032 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 2 Sep 2025 18:09:59 +0200 Subject: [PATCH] tests: lnpeer: test_reject_multiple_payments_of_same_invoice Test that lnpeer rejects incoming htlcs for payments that have already been paid so invoices cannot be paid twice. --- tests/test_lnpeer.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index e5f98ec12..7cf778f94 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -1046,6 +1046,47 @@ class TestPeerDirect(TestPeer): 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): + alice_channel, bob_channel = create_test_channels() + p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel) + + lnaddr, _pay_req = self.prepare_invoice(w2) + + async def try_pay_invoice_twice(pay_req: Invoice, w1=w1): + result, log = await w1.pay_invoice(pay_req) + assert result is True + # now pay the same invoice again, the payment should be rejected by w2 + w1.set_payment_status(pay_req._lnaddr.paymenthash, PR_UNPAID) + result, log = await w1.pay_invoice(pay_req) + if not result: + # w1.pay_invoice returned a payment failure as the payment got rejected by w2 + raise SuccessfulTest() + raise PaymentDone() + + if test_trampoline: + await self._activate_trampoline(w1) + # declare bob as trampoline node + electrum.trampoline._TRAMPOLINE_NODES_UNITTESTS = { + 'bob': LNPeerAddr(host="127.0.0.1", port=9735, pubkey=w2.node_keypair.pubkey), + } + + 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_twice(_pay_req)) + + with self.assertRaises(SuccessfulTest): + await f() + + for _test_trampoline in [False, True]: + await run_test(_test_trampoline) + async def test_payment_race(self): """Alice and Bob pay each other simultaneously. They both send 'update_add_htlc' and receive each other's update