The exit-certificate pipeline is explicitly designed to run step by step — the operator can run --step 0, then --step a, then --step b, etc. Each step reads the output of the previous step from disk and writes its own output.
targetBlock is the block number at which the L2 state is captured for the certificate.
The problem: targetBlock is read from parameters.json at the start of each step, but it is never embedded in the intermediate output files. There is no mechanism for a later step to verify that the files it is reading were produced with the same targetBlock it is currently using.
Failure modes:
Mode 1 — Config change between steps: operator runs Step 0 with targetBlock=32000000, then updates parameters.json to targetBlock=32100000 before running Step A. Step A scans addresses at block 32100000, but the LBT from Step 0 was built at block 32000000. The certificate mixes LBT data from one block with balance data from another. No error, no warning.
Mode 2 — Interrupted run resumed with different targetBlock: the pipeline runs --step all, fails midway through Step B (e.g. RPC timeout after 200K addresses). Operator restarts with a corrected targetBlock. Steps 0 and A outputs from the previous run are reused silently. Only Step B and later are re-executed with the new targetBlock.
Mode 3 — Parallel investigation: operator runs a test pipeline with a different targetBlock to investigate a token, then accidentally uses those intermediate files in a production run.
In all modes, the resulting certificate is built from inconsistent state. The NewLocalExitRoot computed in Step G reflects a chain state that never existed at any single block. This could over-exit or under-exit individual tokens depending on which direction token balances moved between the two blocks.
The exit-certificate pipeline is explicitly designed to run step by step — the operator can run --step 0, then --step a, then --step b, etc. Each step reads the output of the previous step from disk and writes its own output.
targetBlock is the block number at which the L2 state is captured for the certificate.
The problem: targetBlock is read from parameters.json at the start of each step, but it is never embedded in the intermediate output files. There is no mechanism for a later step to verify that the files it is reading were produced with the same targetBlock it is currently using.
Failure modes:
Mode 1 — Config change between steps: operator runs Step 0 with targetBlock=32000000, then updates parameters.json to targetBlock=32100000 before running Step A. Step A scans addresses at block 32100000, but the LBT from Step 0 was built at block 32000000. The certificate mixes LBT data from one block with balance data from another. No error, no warning.
Mode 2 — Interrupted run resumed with different targetBlock: the pipeline runs --step all, fails midway through Step B (e.g. RPC timeout after 200K addresses). Operator restarts with a corrected targetBlock. Steps 0 and A outputs from the previous run are reused silently. Only Step B and later are re-executed with the new targetBlock.
Mode 3 — Parallel investigation: operator runs a test pipeline with a different targetBlock to investigate a token, then accidentally uses those intermediate files in a production run.
In all modes, the resulting certificate is built from inconsistent state. The NewLocalExitRoot computed in Step G reflects a chain state that never existed at any single block. This could over-exit or under-exit individual tokens depending on which direction token balances moved between the two blocks.