pytest: test for xpay waiting when the destination complains about blockheight.
What changed, and why it matters
This commit adds a new automated test for the xpay payment feature. It checks that when a payer's node is behind on the latest blockchain block height, xpay will wait briefly for the node to catch up instead of immediately failing. The test is marked as expected to fail for now, meaning it documents desired behavior that may not yet be fully implemented. There is no code fix or security vulnerability visible in this change.
No security action required. Treat as normal test coverage addition. If reviewing a related patch series, ensure the corresponding production fix for blockheight synchronization in xpay is reviewed separately.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds a single pytest test, test_xpay_blockheight_mismatch, to tests/test_xpay.py. The test simulates l1 being pinned at an old block height by mocking bitcoind’s getblockhash RPC to return a ‘Block height out of range’ error. It then verifies that l1.rpc.xpay() waits for the blockchain to catch up to the destination node’s reported block height before retrying the payment. The test is decorated with @pytest.mark.xfail(strict=True), indicating it is a regression/behavioral test for functionality that is expected to exist but currently fails. No production code is modified.
Changed components
tests/test_xpay.pyInspect captured patch +39 / −0
diff --git a/tests/test_xpay.py b/tests/test_xpay.py
index 71e6e8bc..2b94dcc1 100644
--- a/tests/test_xpay.py
+++ b/tests/test_xpay.py
@@ -1018,3 +1018,42 @@ def test_xpay_bip353(node_factory):
node_factory.join_nodes([l2, l1])
l2.rpc.xpay('fake@fake.com', 100)
+
+
+@pytest.mark.xfail(strict=True)
+def test_xpay_blockheight_mismatch(node_factory, bitcoind, executor):
+ """We should wait a (reasonable) amount if the final node gives us a blockheight that would explain our failure."""
+ l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)
+ sync_blockheight(bitcoind, [l1, l2, l3])
+
+ # Pin `send` at the current height. by not returning the next
+ # blockhash. This error is special-cased not to count as the
+ # backend failing since it is used to poll for the next block.
+ def mock_getblockhash(req):
+ return {
+ "id": req['id'],
+ "error": {
+ "code": -8,
+ "message": "Block height out of range"
+ }
+ }
+
+ l1.daemon.rpcproxy.mock_rpc('getblockhash', mock_getblockhash)
+ bitcoind.generate_block(4)
+ sync_blockheight(bitcoind, [l2, l3])
+ l1_height = l1.rpc.getinfo()['blockheight']
+ l3_height = l3.rpc.getinfo()['blockheight']
+
+ inv = l3.rpc.invoice(42, 'lbl', 'desc')['bolt11']
+
+ # This will wait, then fail.
+ with pytest.raises(RpcError, match=f'Timed out waiting for blockheight {l3_height}'):
+ l1.rpc.xpay(invstring=inv, retry_for=10)
+
+ # This will succeed, because we wait for the blocks.
+ fut = executor.submit(l1.rpc.xpay, invstring=inv, retry_for=60)
+ l1.daemon.wait_for_log(fr"Our blockheight may be too low: waiting .* seconds for height {l3_height} \(we are at {l1_height}\)")
+
+ # Now let it catch up, and it will retry, and succeed.
+ l1.daemon.rpcproxy.mock_rpc('getblockhash')
+ fut.result(TIMEOUT)
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.