tests: lnpeer: add test_payment_with_malformed_onion
What changed, and why it matters
This commit only adds a new automated test to Electrum's Lightning networking code. It simulates a multi-hop payment where the final receiver intentionally reports a malformed onion packet. There is no change to production code, no bug fix, and no security patch.
No action needed; this is a test-only addition. Reviewers may optionally verify that the existing production implementation of `on_update_fail_malformed_htlc` is already robust, but the commit itself introduces no risk.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds test_payment_with_malformed_onion in tests/test_lnpeer.py. The test sets up an Alice→Bob→Carol route, enables a test-only flag TEST_FAIL_HTLCS_AS_MALFORMED on Carol, sends a payment, and asserts that Carol returns update_fail_malformed_htlc and Bob handles on_update_fail_malformed_htlc. It is purely test coverage for existing Lightning protocol behavior.
Changed components
tests/test_lnpeer.pyInspect captured patch +39 / −0
diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py
index acc5a5a..3fe6a37 100644
--- a/tests/test_lnpeer.py
+++ b/tests/test_lnpeer.py
@@ -2866,6 +2866,45 @@ class TestPeerForwarding(TestPeer):
assert len(bob_hm.all_htlcs_ever()) == 2
assert all(bob_hm.was_htlc_failed(htlc_id=htlc.htlc_id, htlc_proposer=HTLCOwner.REMOTE) for (_, htlc) in bob_hm.all_htlcs_ever())
+ async def test_payment_with_malformed_onion(self):
+ """
+ Alice -> Bob -> Carol. Carol fails htlc with update_fail_malformed_htlc because she is unable
+ to parse the onion Alice sent to her.
+ """
+ graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['line_graph'])
+ peers = graph.peers.values()
+
+ async def pay(lnaddr, pay_req):
+ self.assertEqual(PR_UNPAID, graph.workers['carol'].get_payment_status(lnaddr.paymenthash))
+ result, log = await graph.workers['alice'].pay_invoice(pay_req)
+ self.assertEqual(OnionFailureCode.INVALID_ONION_VERSION, log[0].failure_msg.code)
+ self.assertFalse(result, msg=log)
+ raise PaymentFailure()
+
+ # this will make carol send update_fail_malformed_htlc
+ graph.workers['carol'].config.TEST_FAIL_HTLCS_AS_MALFORMED = True
+
+ async def f():
+ async with OldTaskGroup() as group:
+ for peer in peers:
+ await group.spawn(peer._message_loop())
+ await group.spawn(peer.htlc_switch())
+ for peer in peers:
+ await peer.initialized
+ lnaddr, pay_req = self.prepare_invoice(graph.workers['carol'], include_routing_hints=True)
+ await group.spawn(pay(lnaddr, pay_req))
+
+ with self.assertLogs('electrum', level='INFO') as logs:
+ with self.assertRaises(PaymentFailure):
+ await f()
+ self.assertTrue(
+ any('carol->bob' in msg and 'fail_malformed_htlc' in msg for msg in logs.output)
+ )
+ self.assertTrue(
+ any('bob->carol' in msg and 'on_update_fail_malformed_htlc' in msg for msg in logs.output)
+ )
+
+
class TestPeerDirectAnchors(TestPeerDirect):
TEST_ANCHOR_CHANNELS = True
Why this scored 12/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.