From 57f5a9d6cc92be8f3c79b22b60b0ecd652148305 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Tue, 23 Jun 2026 10:14:09 +0700 Subject: [PATCH] test(testnet): cover export without UNLReport --- .testnet/scenarios/export-suite.yml | 9 ++ .../export/export_without_unl_report.py | 87 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 .testnet/scenarios/export/export_without_unl_report.py diff --git a/.testnet/scenarios/export-suite.yml b/.testnet/scenarios/export-suite.yml index 9fc744200..0c4c7450f 100644 --- a/.testnet/scenarios/export-suite.yml +++ b/.testnet/scenarios/export-suite.yml @@ -39,6 +39,15 @@ tests: 4: XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}' + - name: export_without_unl_report + script: .testnet/scenarios/export/export_without_unl_report.py + network: + features: + - Export + track_features: + - Export + unl_report: false + # CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed - name: export_ce_one_node_down script: .testnet/scenarios/export/export_quorum.py diff --git a/.testnet/scenarios/export/export_without_unl_report.py b/.testnet/scenarios/export/export_without_unl_report.py new file mode 100644 index 000000000..8d906474f --- /dev/null +++ b/.testnet/scenarios/export/export_without_unl_report.py @@ -0,0 +1,87 @@ +""":descr: Export retries/expires without a ledger-anchored UNLReport view. + +All validators may sign, but network-mode Export must not assemble quorum +material from a node-local trusted-config view. Without UNLReport, the export +should retry until LastLedgerSequence and expire without creating a shadow +ticket. +""" + +from __future__ import annotations + +from export_helpers import require_export, assert_shadow_ticket + + +async def scenario(ctx, log): + await require_export(ctx, log) + + await ctx.fund_accounts({"alice": 10000, "bob": 1000}) + log("Accounts funded") + + alice = ctx.account("alice") + bob = ctx.account("bob") + current_seq = ctx.validated_ledger_index(0) + + log(f"Current ledger: {current_seq}") + log("UNLReport intentionally absent; export must not use local config view") + + export_start = ctx.mark("export-without-unlreport-submit-start") + result = await ctx.submit_and_wait( + { + "TransactionType": "Export", + "LastLedgerSequence": current_seq + 8, + "Fee": "1000000", + "ExportedTxn": { + "TransactionType": "Payment", + "Account": alice.address, + "Destination": bob.address, + "Amount": "1000000", + "Fee": "10", + "Sequence": 0, + "TicketSequence": 1, + "FirstLedgerSequence": current_seq + 1, + "LastLedgerSequence": current_seq + 6, + "Flags": 2147483648, + "SigningPubKey": "", + }, + }, + alice.wallet, + timeout=60, + ) + export_end = ctx.mark("export-without-unlreport-submit-end") + + final_seq = ctx.validated_ledger_index(0) + engine_result = result.get("engine_result", "") + log(f"Export completed at ledger {final_seq}, result: {engine_result}") + + if engine_result == "tesSUCCESS": + raise AssertionError( + "Export should not succeed without a ledger-anchored UNLReport view" + ) + + if engine_result != "tecEXPORT_EXPIRED": + log(f"WARNING: expected tecEXPORT_EXPIRED, got {engine_result}") + + warning_logs = ctx.assert_log( + r"Export: retrying without ledger-anchored validator view", + since=export_start, + until=export_end, + ) + log(f"Export no-UNLReport retry warnings: {warning_logs.count}") + + retry_logs = ctx.assert_log( + r"Export: insufficient signatures .*result=terRETRY_EXPORT", + since=export_start, + until=export_end, + ) + log(f"Export retry logs: {retry_logs.count}") + + expired_logs = ctx.assert_log( + r"Export: last ledger expired .*result=tecEXPORT_EXPIRED", + since=export_start, + until=export_end, + ) + log(f"Export expiry logs: {expired_logs.count}") + + assert_shadow_ticket(ctx, alice.address, log, expect_exists=False) + + log("PASS")