pytest: test for malformed reply from first hop when using injectpaymentonion.
What changed, and why it matters
This commit adds a new test case to Core Lightning's test suite. It checks how the software handles a malformed (corrupted) payment onion message when the first hop is the node that created the payment, using a developer-only RPC command called injectpaymentonion. The test is currently marked as expected to fail (xfail), meaning the behavior it checks is not yet working correctly. The commit itself does not fix any code; it only adds a test that documents a bug or missing behavior.
Treat this as a test-case addition documenting a known issue, not as a security patch. If reviewing for security, investigate whether the unfixed behavior in injectpaymentonion's malformed onion reply handling could affect production payment processing or error reporting. Wait for a follow-up commit that removes the xfail marker and fixes the underlying behavior before reassessing security relevance.
Security signals we found
Test-only change, no production code modified
Tests malformed onion handling for injectpaymentonion
Test marked xfail strict, indicating known unfixed behavior
Uses developer-only RPC and dev-fail-process-onionpacket option
No CVE, advisory, or vendor security disclosure present in materials
Evidence from the diff
The diff modifies tests/test_misc.py. It imports two new helpers (serialize_payload_tlv, serialize_payload_final_tlv) and updates the existing test_bad_onion_immediate_peer test. The test setup is corrected so only the second node (l2) uses the dev-fail-process-onionpacket option. A new sub-test is added that builds a two-hop onion route where l1 is both origin and first hop, then injects it via l1.rpc.injectpaymentonion. The test expects an RpcError with code 218 (PAY_INJECTPAYMENTONION_FAILED) and an onionreply field in the error data. The @pytest.mark.xfail(strict=True) decorator means this test documents current broken behavior rather than verifying a fix.
Changed components
tests/test_misc.pytest_bad_onion_immediate_peerinjectpaymentonion RPConion error reply handlingInspect captured patch +25 / −3
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 0a6aba28..4508cf0f 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -8,10 +8,11 @@ from pyln.client import RpcError, Millisatoshi
from threading import Event
from pyln.testing.utils import (
TIMEOUT, VALGRIND, sync_blockheight, only_one,
- wait_for, TailableProc, env, mine_funding_to_announce
+ wait_for, TailableProc, env, mine_funding_to_announce,
)
from utils import (
- account_balance, scriptpubkey_addr, check_coin_moves, first_scid
+ account_balance, scriptpubkey_addr, check_coin_moves, first_scid,
+ serialize_payload_tlv, serialize_payload_final_tlv,
)
import copy
@@ -2118,9 +2119,10 @@ def test_bad_onion(node_factory, bitcoind):
assert err.value.error['data']['erring_channel'] == route[1]['channel']
+@pytest.mark.xfail(strict=True)
def test_bad_onion_immediate_peer(node_factory, bitcoind):
"""Test that we handle the malformed msg when we're the origin"""
- l1, l2 = node_factory.line_graph(2, opts={'dev-fail-process-onionpacket': None})
+ l1, l2 = node_factory.line_graph(2, opts=[{}, {'dev-fail-process-onionpacket': None}])
inv = l2.rpc.invoice(123000, 'test_bad_onion_immediate_peer', 'description')
route = l1.rpc.getroute(l2.info['id'], 123000, 1)['route']
@@ -2137,6 +2139,26 @@ def test_bad_onion_immediate_peer(node_factory, bitcoind):
WIRE_INVALID_ONION_HMAC = 0x8000 | 0x4000 | 5
assert err.value.error['data']['failcode'] == WIRE_INVALID_ONION_HMAC
+ # Same, but using injectpaymentonion with corrupt onion.
+ blockheight = l1.rpc.getinfo()['blockheight']
+ hops = [{'pubkey': l1.info['id'],
+ 'payload': serialize_payload_tlv(123000, 18 + 6, first_scid(l1, l2), blockheight).hex()},
+ {'pubkey': l2.info['id'],
+ 'payload': serialize_payload_final_tlv(123000, 18, 123000, blockheight, inv['payment_secret']).hex()}]
+ onion = l1.rpc.createonion(hops=hops, assocdata=inv['payment_hash'])
+
+ with pytest.raises(RpcError) as err:
+ l1.rpc.injectpaymentonion(onion=onion['onion'],
+ payment_hash=inv['payment_hash'],
+ amount_msat=123000,
+ cltv_expiry=blockheight + 18 + 6,
+ partid=1,
+ groupid=0)
+ # FIXME: PAY_INJECTPAYMENTONION_FAILED = 218
+ PAY_INJECTPAYMENTONION_FAILED = 218
+ assert err.value.error['code'] == PAY_INJECTPAYMENTONION_FAILED
+ assert 'onionreply' in err.value.error['data']
+
def test_newaddr(node_factory, chainparams):
l1 = node_factory.get_node()
Why this scored 28/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.