Skip to content

feat: coverage-replayer — minimal mainnet block set maximizing mega-evm branch coverage - #222

Open
flyq wants to merge 26 commits into
mainfrom
liquan/coverage-replayer-rebuilt
Open

flyq wants to merge 26 commits into
mainfrom
liquan/coverage-replayer-rebuilt

Conversation

@flyq

@flyq flyq commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds coverage-replayer, an offline tool that answers "which mainnet blocks, replayed, exercise every execution path — in mega-evm and in the revm execution engine it drives — that we have ever seen taken?" — the fixture set a stateless validator wants for regression coverage, without replaying the whole chain each time.

Every mainnet block from 1 to 27,814,972 has been replayed, all matching their headers, and 84 blocks reproduce all 13,419 coverage items those replays observed under mega-evm v1.7.0 — 61.8% of the branch arms and 63.6% of the regions in the measured code; the rest are paths mainnet has never taken.

Supersedes #153, which was opened before several large refactors landed on main and whose diff had become dominated by content main already carries. This branch is main plus the tool only, and the port to mega-evm v1.7.0 needed a single argument dropped from two replay_block call sites (the EIP-3155 trace writer removed in #195, which this tool always passed as None).

How it works

backfill replays blocks under LLVM branch instrumentation. Resident worker subprocesses reset counters, replay one block, and capture a profraw — having first built the per-hardfork precompile tables, which mega-evm and op-revm construct once per process and would otherwise be credited to whichever block a worker happened to replay first; a judge dedups the resulting per-block bitmaps into patterns in a redb store, keeping the lightest block of each pattern as its representative (bin/coverage-replayer/src/backfill.rs, worker.rs). Nothing block-sized is retained — the only per-pattern artifact kept is a small sparse profdata for report.

A bit is an evaluated coverage item, not a physical counter. rustc minimizes physical counters: an if/else gets two (entry, then-arm) and the else-arm exists only as the expression entry - then. Over physical counters an else-only block reads {entry} — a strict subset of a then-only block's {entry, then} — so it looks dominated, is never archived, gets pruned, and the cover silently loses a branch arm the scan had covered. No -Z coverage-options value turns the minimization off. So each block's profile goes through llvm-cov export --format=text --skip-functions, scoped to the source dirs, and the items are its region entries and branch arms with a non-zero count, keyed by source span (llvm.rs) — the same arithmetic report runs. It costs ~0.6 s per block. An unscoped export of this binary segfaults llvm-cov, so the scope is not optional.

  • set-cover — greedy cover over the patterns with antichain pruning and a redundancy-elimination pass. The completeness contract is that the selected set always covers the full observed universe; no selected block can be dropped, but the set is not guaranteed minimum-cardinality (setcover.rs:1). Gain ties go to the higher block number, so a store always yields the same selection.
  • report — llvm-cov summary for the selected set.
  • inspect — read-only store statistics. The one subcommand that skips the binary-id namespace check, so a store from another build can be analyzed.

Kept out of this PR to keep it reviewable, and recoverable from the branch history when needed: a merge subcommand for combining per-machine shard stores (the workflow is now a pool re-sweep on one machine; pools from several stores still union as plain block lists), reading witnesses straight from R2 (the witness RPC is the gateway in front of the same bucket), inspect's rarity and growth statistics and --pool-siblings, and set-cover's --incumbent-manifest and --prune-profiles.

Carrying a scan across a mega-evm bump

Counter ids belong to one instrumented build and one item definition, so binary_id (mega-evm rev, toolchain, measured crates and the lockfile) and a universe stamp (item definition + source scope) namespace every store and the write paths refuse a mismatch (store.rs:1). That would strand a scan at every mega-evm bump, since a full sweep costs weeks of machine time. What survives a bump is the block numbers, so the tool carries those over instead of the bitmaps:

inspect --dump-pool pool.txt        (old build; read-only, no binary-id check)
  └─ cat pool*.txt                  (union across stores; sorted and deduped on read)
       └─ backfill --blocks-file    (new build, fresh data-dir)
            └─ set-cover → report

The pool is the antichain's representatives, not the previous minimal set: a cover is minimal only for the universe that produced it and carries no slack once a new build splits patterns the old one merged. Everything the antichain leaves out was strictly dominated — its coverage a subset of a kept block's — which is the closest available stand-in for "adds nothing".

This branch's store carries its own pool for the next bump: 35,678 blocks, the antichain of its 226,158 patterns (30,123 of them from blocks up to 20,980,000, 5,555 from the extension past it), and the next bump re-sweeps only the pool. Kept next to it are the block numbers of all 226,158 pattern representatives: bitmaps, profiles and stores can all be rebuilt by replaying that list, the list itself only by replaying the whole range again.

Results: every block from 1 to 27,814,972

Every mainnet block from 1 to 27,814,972 has been replayed and matched its header, in three passes:

  1. The full-history scan (mega-evm v1.6.1, four machines, the physical-counter universe described below) replayed 20,954,362 blocks and reduced them to a candidate pool of 44,514.
  2. This branch's v1.7.0 build replayed that pool, 7,826 blocks from outside it as a sufficiency check, and, in full, the one stretch no shard of the scan had been assigned, 6,624,362–6,649,999 (25,638 blocks): 77,915 distinct blocks, 585.4M transactions, divergent=0 error=0 (63 of the sampled blocks fall inside that stretch).
  3. The final build (06b5732; its binary_id also keys the rustc commit, so earlier stores do not open under it) re-swept those 77,915 blocks into a fresh store — the same 84 blocks in the cover, with an identical per-file report over them — then replayed every block from 20,980,001 to 27,814,972, the finalized head when the run started: 6,912,887 blocks, 762.9M transactions, divergent=0 error=0.

mega-evm v1.7.0 (this branch's build)

84 blocks cover 13,419/13,419 items. Coverage of that set, by measured crate:

crate regions covered region cov. branch arms covered branch cov.
mega-evm 9,739 6,016 61.8% 1,190 732 61.5%
revm-interpreter 3,735 2,570 68.8% 336 230 68.5%
revm-handler 1,805 1,080 59.8% 186 114 61.3%
op-revm 1,371 837 61.1% 151 86 57.0%
revm-context 2,085 1,441 69.1% 136 71 52.2%
revm-context-interface 629 363 57.7% 28 19 67.9%
total 19,364 12,307 63.6% 2,027 1,252 61.8%

The table is llvm-cov's count; the tool's 13,419 items tally slightly differently from the table's 12,307 regions and 1,252 branch arms (13,559 together): an item is keyed by source position, so regions that start at one position are one item and a generic function's instantiations are OR-ed, while llvm-cov counts regions per function and summarizes a generic by its best single instantiation.

The extension past 20,980,000 added 413 items (13,006 → 13,419), 392 of them from blocks 24,829,789–24,850,253, and lifted branch-arm coverage from 58.4% to 61.8% (mega-evm alone from 56.1% to 61.5%). The cover stayed at 84 blocks, 35 of them replaced, 27 by blocks from the extension.

In mega-evm the largest gap is sandbox/, with 37 of its 212 branch arms covered (17.5%): mainnet barely exercises it.

  • The cover is complete where the tool measures. On the store up to 20,980,000 (not repeated after the extension), report over the selected blocks matched report over the union of every archived profile (35,188) exactly on lines, functions and branches, and on regions for 124 of 126 files. The other two differ by one region each, both in const-generic families (push::<N> in revm, one in mega-evm's instructions): llvm-cov summarizes a generic function by its best single instantiation, and comparing region by region across instantiations the selected set misses none.
  • The pool is sufficient in practice. It was derived under the biased universe described below, so blocks outside it were replayed too, and none added an item. The 7,826 distinct sampled ones (827 other blocks sharing a pool pattern, and 7,000 uniform samples: 5,000 over the whole range, 2,000 over the stress-test range) produced thousands of new patterns and no new item under the previous definition (13,060 items, and, scoped to mega-evm only, the same 6,331), which differs from the current one only by the run-once initializers below, which no block covers any more. All 25,638 blocks of 6,624,362–6,649,999, replayed under the current definition, produced 2,479 new patterns and no new item either. So the pool alone reaches the universe all 77,915 blocks do. The 413 items the extension added come from blocks past the scanned range, so they say nothing against the pool.
  • A block's coverage no longer depends on scheduling. mega-evm and op-revm build each hardfork's precompile table once per process, inside OnceBox::get_or_init. Those closures used to be credited to the first block each worker replayed, and to its first block of each later table, so about 2.5% of blocks recorded different coverage from run to run and re-deriving the cover gave a different selection each time. Workers now build every table before capturing anything (worker.rs), and the item universe moved to v3, so stores, shards and manifests from before are refused. Verified: the same 401 blocks replayed twice by 30 workers in fetch-completion order give identical patterns block for block; a REX-era block replayed alone and after a MINI_REX block gives the same pattern, where the previous build differed by 55 items; and the two definitions do not mix — backfill will not resume a v2 store and report will not read a v2 manifest. Against the previous run of the same 52,340 blocks, the per-file report changes in the two precompiles.rs files only — 54 regions of table construction (13 in mega-evm, 41 in op-revm), now reported uncovered, matching the 54 items the universe lost — and the cover is 84 blocks instead of 86 (71 shared) with branch coverage identical. Three further full re-sweeps selected the same 84 patterns — the last with every witness fetched through the RPC gateway, down to the same blocks in the same order; which block stands for a pattern can still differ between runs, since a pattern's representative is its fastest-replaying block (one of the 84 did once, for an equivalent block with the identical bitmap). Adding the 25,638 blocks of 6,624,362–6,649,999 left the selection unchanged, down to the block numbers.
  • mega-evm v1.7.0 logged sandbox::execution: keyless deploy nonce read failed error=Metadata not in witness for a handful of stress-range blocks whose replays nevertheless matched their headers — worth a look on the mega-evm side.
Per-file coverage of the 84 selected blocks — llvm-cov report over their profiles, 126 files (paths relative to each source root: mega-evm/ is the v1.7.0 checkout's crate, the revm crates are at their locked versions)
Filename                                                           Regions  Missed Regions    Cover  Functions  Missed Functions  Executed  Lines  Missed Lines    Cover  Branches  Missed Branches    Cover
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
mega-evm/src/external/gas.rs                                           131              36   72.52%         14                 4    71.43%     73            16   78.08%         6                1   83.33%
mega-evm/src/external/mod.rs                                             6               6    0.00%          2                 2     0.00%      6             6    0.00%         0                0        -
mega-evm/src/external/oracle.rs                                          5               5    0.00%          2                 2     0.00%      4             4    0.00%         0                0        -
mega-evm/src/external/salt.rs                                            9               3   66.67%          3                 1    66.67%      9             3   66.67%         0                0        -
mega-evm/src/sandbox/state.rs                                          125              84   32.80%         16                12    25.00%     87            52   40.23%        22               15   31.82%
mega-evm/src/sandbox/tx.rs                                              32               5   84.38%          3                 0   100.00%     24             3   87.50%         8                5   37.50%
mega-evm/src/sandbox/execution.rs                                      711             408   42.62%         26                17    34.62%    503           316   37.18%        98               72   26.53%
mega-evm/src/sandbox/error.rs                                          177             171    3.39%          2                 1    50.00%    122           118    3.28%        42               42    0.00%
mega-evm/src/sandbox/state_merge.rs                                    302             285    5.63%         15                13    13.33%    207           191    7.73%        42               41    2.38%
mega-evm/src/block/helpers.rs                                           80              72   10.00%         46                44     4.35%     75            67   10.67%         0                0        -
mega-evm/src/block/executor.rs                                         448             101   77.46%         27                13    51.85%    356            92   74.16%        28                8   71.43%
mega-evm/src/block/chain.rs                                             78              78    0.00%          4                 4     0.00%     47            47    0.00%         0                0        -
mega-evm/src/block/result.rs                                            42              42    0.00%          6                 6     0.00%     36            36    0.00%         0                0        -
mega-evm/src/block/factory.rs                                           71              43   39.44%          8                 5    37.50%     69            42   39.13%         0                0        -
mega-evm/src/block/eips.rs                                             115              40   65.22%          3                 0   100.00%     89            26   70.79%        16                8   50.00%
mega-evm/src/block/limit.rs                                            213             104   51.17%         26                17    34.62%    300           155   48.33%        24               10   58.33%
mega-evm/src/block/hardfork.rs                                         316             187   40.82%         40                25    37.50%    199           127   36.18%        24                5   79.17%
mega-evm/src/access/tracker.rs                                         106              38   64.15%         20                 8    60.00%     85            28   67.06%        10                5   50.00%
mega-evm/src/access/volatile.rs                                         36              32   11.11%          9                 8    11.11%     28            25   10.71%         0                0        -
mega-evm/src/evm/spec.rs                                                62              46   25.81%          8                 4    50.00%     45            32   28.89%         0                0        -
mega-evm/src/evm/instructions.rs                                      1101             298   72.93%         38                 2    94.74%    851           172   79.79%       122               55   54.92%
mega-evm/src/evm/result.rs                                              14              14    0.00%          3                 3     0.00%     11            11    0.00%         0                0        -
mega-evm/src/evm/state.rs                                              106              82   22.64%          6                 5    16.67%     72            60   16.67%        10                9   10.00%
mega-evm/src/evm/execution.rs                                         1327             420   68.35%         56                19    66.07%    837           246   70.61%       230               62   73.04%
mega-evm/src/evm/factory.rs                                             61              21   65.57%          8                 5    37.50%     54            22   59.26%         0                0        -
mega-evm/src/evm/precompiles.rs                                        144              54   62.50%         17                 9    47.06%    103            39   62.14%        18                6   66.67%
mega-evm/src/evm/mod.rs                                                138              80   42.03%         19                13    31.58%    129            71   44.96%         2                1   50.00%
mega-evm/src/evm/host.rs                                               636             141   77.83%         58                14    75.86%    369            69   81.30%        66               14   78.79%
mega-evm/src/evm/interfaces.rs                                         136              68   50.00%         19                10    47.37%    105            43   59.05%         2                1   50.00%
mega-evm/src/evm/limit.rs                                               58              24   58.62%         15                 6    60.00%     90            30   66.67%         0                0        -
mega-evm/src/evm/context.rs                                            377             118   68.70%         48                15    68.75%    289            99   65.74%        28                3   89.29%
mega-evm/src/limit/compute_gas.rs                                      132               7   94.70%         16                 1    93.75%     92             4   95.65%        24                5   79.17%
mega-evm/src/limit/kv_update.rs                                        175              30   82.86%         18                 3    83.33%    133            23   82.71%        36                7   80.56%
mega-evm/src/limit/data_size.rs                                        227              38   83.26%         24                 5    79.17%    169            27   84.02%        42                7   83.33%
mega-evm/src/limit/mod.rs                                               57              18   68.42%          8                 1    87.50%     45            16   64.44%         0                0        -
mega-evm/src/limit/frame_limit.rs                                      265              20   92.45%         36                 6    83.33%    225            21   90.67%        44                5   88.64%
mega-evm/src/limit/storage_call_stipend.rs                             144              14   90.28%         10                 0   100.00%     93             5   94.62%        34                5   85.29%
mega-evm/src/limit/state_growth.rs                                     177              30   83.05%         18                 3    83.33%    123            14   88.62%        24                4   83.33%
mega-evm/src/limit/limit.rs                                            579             146   74.78%         47                12    74.47%    420            97   76.90%        52                8   84.62%
mega-evm/src/system/control.rs                                          25              15   40.00%          5                 3    40.00%     27            17   37.04%         0                0        -
mega-evm/src/system/tx.rs                                               39               1   97.44%          5                 0   100.00%     29             1   96.55%        10                0  100.00%
mega-evm/src/system/limit_control.rs                                    20              10   50.00%          4                 2    50.00%     19            10   47.37%         0                0        -
mega-evm/src/system/sequencer_registry.rs                              269              76   71.75%         11                 5    54.55%    219            94   57.08%        42               17   59.52%
mega-evm/src/system/keyless_deploy.rs                                   20              10   50.00%          4                 2    50.00%     20            10   50.00%         0                0        -
mega-evm/src/system/oracle.rs                                           53              21   60.38%          7                 4    42.86%     46            19   58.70%         6                1   83.33%
mega-evm/src/system/intercept.rs                                       308             177   42.53%         12                 6    50.00%    244           140   42.62%        70               36   48.57%
mega-evm/src/system/deploy.rs                                           86               4   95.35%          5                 0   100.00%     56             1   98.21%         8                0  100.00%
op-revm-8.1.0/src/api/builder.rs                                        10              10    0.00%          2                 2     0.00%      6             6    0.00%         0                0        -
op-revm-8.1.0/src/api/exec.rs                                           67              67    0.00%          9                 9     0.00%     45            45    0.00%         0                0        -
op-revm-8.1.0/src/api/default_ctx.rs                                    11               0  100.00%          1                 0   100.00%      6             0  100.00%         0                0        -
op-revm-8.1.0/src/spec.rs                                               28              16   42.86%          4                 1    75.00%     27            16   40.74%         0                0        -
op-revm-8.1.0/src/result.rs                                              9               9    0.00%          2                 2     0.00%      8             8    0.00%         0                0        -
op-revm-8.1.0/src/transaction/abstraction.rs                           209              98   53.11%         38                15    60.53%    190            85   55.26%        26               19   26.92%
op-revm-8.1.0/src/transaction/error.rs                                  17              11   35.29%          3                 1    66.67%     14             8   42.86%         0                0        -
op-revm-8.1.0/src/transaction/deposit.rs                                 4               4    0.00%          1                 1     0.00%      7             7    0.00%         0                0        -
op-revm-8.1.0/src/l1block.rs                                           264             105   60.23%         13                 4    69.23%    163            58   64.42%        28               17   39.29%
op-revm-8.1.0/src/handler.rs                                           442              46   89.59%         11                 1    90.91%    263            29   88.97%        67               22   67.16%
op-revm-8.1.0/src/transaction.rs                                         8               0  100.00%          1                 0   100.00%      7             0  100.00%         0                0        -
op-revm-8.1.0/src/precompiles.rs                                       127             104   18.11%         18                14    22.22%    109            91   16.51%         8                6   25.00%
op-revm-8.1.0/src/fast_lz.rs                                           112               1   99.11%          7                 0   100.00%     72             1   98.61%        22                1   95.45%
op-revm-8.1.0/src/evm.rs                                                63              63    0.00%         16                16     0.00%     89            89    0.00%         0                0        -
revm-context-8.0.4/src/block.rs                                         31               3   90.32%         10                 1    90.00%     49             3   93.88%         0                0        -
revm-context-8.0.4/src/journal.rs                                      192              36   81.25%         38                 8    78.95%    169            30   82.25%         0                0        -
revm-context-8.0.4/src/tx.rs                                           380             240   36.84%         49                25    48.98%    359           219   39.00%        50               41   18.00%
revm-context-8.0.4/src/journal/inner.rs                                737              90   87.79%         35                 6    82.86%    506            51   89.92%        70               16   77.14%
revm-context-8.0.4/src/journal/entry.rs                                155               6   96.13%         12                 0   100.00%    159             6   96.23%        12                5   58.33%
revm-context-8.0.4/src/cfg.rs                                          128              64   50.00%         28                12    57.14%    144            66   54.17%         2                1   50.00%
revm-context-8.0.4/src/context.rs                                      415             178   57.11%         74                33    55.41%    397           174   56.17%         2                2    0.00%
revm-context-8.0.4/src/local.rs                                         14               3   78.57%          4                 1    75.00%     14             3   78.57%         0                0        -
revm-context-8.0.4/src/evm.rs                                           33              24   27.27%          7                 5    28.57%     46            34   26.09%         0                0        -
revm-context-interface-9.0.0/src/block.rs                               10               5   50.00%          2                 1    50.00%      6             3   50.00%         0                0        -
revm-context-interface-9.0.0/src/result.rs                             183             131   28.42%         16                 8    50.00%    130            80   38.46%         0                0        -
revm-context-interface-9.0.0/src/transaction/transaction_type.rs        20              10   50.00%          4                 2    50.00%     18             8   55.56%         0                0        -
revm-context-interface-9.0.0/src/transaction/alloy_types.rs             57              32   43.86%         14                 6    57.14%     42            18   57.14%         0                0        -
revm-context-interface-9.0.0/src/block/blob.rs                          76              44   42.11%          7                 4    42.86%     83            54   34.94%         8                6   25.00%
revm-context-interface-9.0.0/src/transaction.rs                         89              33   62.92%          7                 2    71.43%     48            15   68.75%        10                3   70.00%
revm-context-interface-9.0.0/src/journaled_state.rs                     58               2   96.55%          7                 0   100.00%     39             0  100.00%         2                0  100.00%
revm-context-interface-9.0.0/src/context.rs                             27               6   77.78%          8                 2    75.00%     24             6   75.00%         0                0        -
revm-context-interface-9.0.0/src/local.rs                              109               3   97.25%         17                 0   100.00%     81             0  100.00%         8                0  100.00%
revm-handler-8.1.0/src/instructions.rs                                  19               9   52.63%          6                 3    50.00%     22            11   50.00%         0                0        -
revm-handler-8.1.0/src/execution.rs                                     23               0  100.00%          1                 0   100.00%     22             0  100.00%         0                0        -
revm-handler-8.1.0/src/handler.rs                                      289             179   38.06%         22                12    45.45%    192           109   43.23%         4                0  100.00%
revm-handler-8.1.0/src/frame_data.rs                                    66              18   72.73%         12                 4    66.67%     53            17   67.92%         0                0        -
revm-handler-8.1.0/src/mainnet_builder.rs                               17              12   29.41%          3                 2    33.33%     24            21   12.50%         0                0        -
revm-handler-8.1.0/src/validation.rs                                   217              81   62.67%          5                 1    80.00%    165            81   50.91%        74               46   37.84%
revm-handler-8.1.0/src/api.rs                                          117             104   11.11%         14                13     7.14%     72            63   12.50%         0                0        -
revm-handler-8.1.0/src/item_or_result.rs                                22              11   50.00%          4                 2    50.00%     16             8   50.00%         0                0        -
revm-handler-8.1.0/src/pre_execution.rs                                248             112   54.84%          5                 2    60.00%    163            70   57.06%        44               14   68.18%
revm-handler-8.1.0/src/post_execution.rs                                88               5   94.32%          4                 0   100.00%     66             5   92.42%         4                1   75.00%
revm-handler-8.1.0/src/mainnet_handler.rs                                3               0  100.00%          1                 0   100.00%      5             0  100.00%         0                0        -
revm-handler-8.1.0/src/frame.rs                                        462              34   92.64%         14                 1    92.86%    391            21   94.63%        52               11   78.85%
revm-handler-8.1.0/src/evm.rs                                           91              42   53.85%         11                 7    36.36%     68            29   57.35%         8                0  100.00%
revm-handler-8.1.0/src/system_call.rs                                   73              48   34.25%          9                 7    22.22%     76            52   31.58%         0                0        -
revm-handler-8.1.0/src/precompile_provider.rs                           70              70    0.00%          5                 5     0.00%     58            58    0.00%         0                0        -
revm-interpreter-24.0.0/src/gas.rs                                     102              16   84.31%         21                 2    90.48%     92            12   86.96%         6                2   66.67%
revm-interpreter-24.0.0/src/interpreter_types.rs                        58              37   36.21%         14                 9    35.71%     40            25   37.50%         0                0        -
revm-interpreter-24.0.0/src/instructions.rs                            157             155    1.27%          2                 1    50.00%    158           155    1.90%         0                0        -
revm-interpreter-24.0.0/src/instruction_result.rs                      118              81   31.36%         14                 8    42.86%    108            75   30.56%         0                0        -
revm-interpreter-24.0.0/src/interpreter/return_data.rs                   7               0  100.00%          2                 0   100.00%      6             0  100.00%         0                0        -
revm-interpreter-24.0.0/src/interpreter/input.rs                        16               4   75.00%          5                 1    80.00%     15             3   80.00%         0                0        -
revm-interpreter-24.0.0/src/interpreter/shared_memory.rs               396             123   68.94%         54                18    66.67%    275            78   71.64%        14                2   85.71%
revm-interpreter-24.0.0/src/interpreter/stack.rs                       299              71   76.25%         31                10    67.74%    190            53   72.11%        22                5   77.27%
revm-interpreter-24.0.0/src/interpreter/runtime_flags.rs                 6               0  100.00%          2                 0   100.00%      6             0  100.00%         0                0        -
revm-interpreter-24.0.0/src/interpreter/ext_bytecode.rs                101              27   73.27%         21                 6    71.43%     91            22   75.82%         2                1   50.00%
revm-interpreter-24.0.0/src/interpreter/ext_bytecode/serde.rs           32              32    0.00%          2                 2     0.00%     26            26    0.00%         0                0        -
revm-interpreter-24.0.0/src/interpreter.rs                             161              76   52.80%         22                13    40.91%    171            69   59.65%         4                0  100.00%
revm-interpreter-24.0.0/src/interpreter_action/call_inputs.rs           99              54   45.45%         22                14    36.36%     77            44   42.86%         0                0        -
revm-interpreter-24.0.0/src/interpreter_action/create_outcome.rs        12               0  100.00%          4                 0   100.00%     12             0  100.00%         0                0        -
revm-interpreter-24.0.0/src/interpreter_action/create_inputs.rs         14               2   85.71%          1                 0   100.00%      8             1   87.50%         0                0        -
revm-interpreter-24.0.0/src/interpreter_action/call_outcome.rs          19               3   84.21%          6                 1    83.33%     21             3   85.71%         0                0        -
revm-interpreter-24.0.0/src/gas/calc.rs                                378              60   84.13%         28                 2    92.86%    257            42   83.66%       112               39   65.18%
revm-interpreter-24.0.0/src/interpreter_action.rs                       45              29   35.56%         10                 7    30.00%     38            27   28.95%         0                0        -
revm-interpreter-24.0.0/src/instructions/contract/call_helpers.rs       74               8   89.19%          3                 0   100.00%     41             1   97.56%         6                1   83.33%
revm-interpreter-24.0.0/src/instructions/tx_info.rs                     32              18   43.75%          3                 1    66.67%     23            11   52.17%         0                0        -
revm-interpreter-24.0.0/src/instructions/block_info.rs                  81              35   56.79%          8                 2    75.00%     55            15   72.73%         2                1   50.00%
revm-interpreter-24.0.0/src/instructions/control.rs                    105              10   90.48%         11                 0   100.00%     64             1   98.44%         6                0  100.00%
revm-interpreter-24.0.0/src/instructions/stack.rs                       49               8   83.67%          5                 0   100.00%     37             2   94.59%         6                1   83.33%
revm-interpreter-24.0.0/src/instructions/arithmetic.rs                 113              13   88.50%         11                 0   100.00%     66             1   98.48%         8                2   75.00%
revm-interpreter-24.0.0/src/instructions/system.rs                     239              30   87.45%         13                 0   100.00%    155             8   94.84%        14                1   92.86%
revm-interpreter-24.0.0/src/instructions/memory.rs                      94              18   80.85%          5                 0   100.00%     44             1   97.73%         2                0  100.00%
revm-interpreter-24.0.0/src/instructions/utility.rs                     15               5   66.67%          3                 1    66.67%      9             3   66.67%         0                0        -
revm-interpreter-24.0.0/src/instructions/host.rs                       375             103   72.53%         12                 0   100.00%    240            59   75.42%        54               28   48.15%
revm-interpreter-24.0.0/src/instructions/contract.rs                   278              97   65.11%          5                 1    80.00%    215            66   69.30%        44               19   56.82%
revm-interpreter-24.0.0/src/instructions/bitwise.rs                    152              32   78.95%         15                 0   100.00%     94             7   92.55%        10                3   70.00%
revm-interpreter-24.0.0/src/instructions/i256.rs                        91               1   98.90%          8                 0   100.00%     61             1   98.36%        24                1   95.83%
revm-interpreter-24.0.0/src/instruction_context.rs                      17              17    0.00%          2                 2     0.00%     11            11    0.00%         0                0        -
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL                                                                19364            7057   63.56%       1743               682    60.87%  14653          5327   63.65%      2027              775   61.77%
The 84 selected blocks — sorted by block number: block number, items it newly covered when picked, items it covers on its own, pattern key, block hash
 1277159  gain=1      bits=7620   pattern=c0ec42bca957e460  0x22e587a927ba1bab836e891faceeddfc44751113fef8710462558cd82c7f8c78
 1708174  gain=17     bits=7299   pattern=37ff4b8166c560dc  0x3b4be7f397108d2230c8cf7349217db456213f3c77b821bbaac852dc7c5617cd
 1790368  gain=267    bits=7770   pattern=49e8aec0a25060a7  0xc100644c4616dc157455e38214176baec3aa8784e8a658286503df874b137ae1
 1883527  gain=5      bits=8921   pattern=314ac3ab7ccac1a7  0x814e9e196964f28d77f692a4dd8b01a8eaefbf31fe52c7a0622f9a155c5c2673
 2052680  gain=48     bits=5890   pattern=609dfa24d12294d6  0xf87252bff694d792bc67ae7d941614617c5b7bbd124c41414092c0b96f2c99e9
 2053779  gain=3      bits=7582   pattern=547fa5d531056577  0x51b1f50710c0e94342aa3c0ff672d5f4cb3b4a1aa2246e666145541c90c00734
 3182971  gain=8      bits=8664   pattern=a4418768b3e29599  0x3d38b649a20d924ed13950eb512f995dd2e0011e1160dc27b1af70bd209b8191
 3224158  gain=2      bits=7856   pattern=02c3486413131512  0x1adca0bcef3a06756304e6b43642c37ef693a34b10e4c9f6513497a741fdaa76
 3451849  gain=3      bits=8488   pattern=7742e7a5c4f6f6ee  0x6da3fe19e89817ff5280478947c63d1b846eb38431d2e08e6ebcc8c59429594f
 3644041  gain=2      bits=8566   pattern=eead976d5e8317bb  0xc8abfecc2b8884c19be68858cf62cda95ea63e49de836a43ecb10117badc9c3e
 7057359  gain=36     bits=8090   pattern=7704874926ac8c24  0xa06a037f8575ea54c744ea411e9e135a9400d6efbbb90d1abdcc4a68f0ddb21b
 7193432  gain=1      bits=8250   pattern=b174760ed0441315  0xffdd7bc8ce5e3d6e07788b19a850c1060511ed12e422f0e18aacfb85c7a47c4e
 7266754  gain=4      bits=8259   pattern=3e7f128c3a4a7f1e  0x92d3c9ac237ee3d3ed0fb4e566c063c2c6cd4270631840dd0668b51ec31bd6c7
 7448989  gain=1      bits=8294   pattern=0d791e64ea110c74  0x9ea4deaa6e0503972890af25135fa5f9348b229b3e0427a642a0a11a119d301e
 7487457  gain=907    bits=9494   pattern=9b7c8b925fa71a71  0x318177f902ba8c9b554d3f7a639a76ce4c3152f9dac1a107353808eb2313c47e
 7808664  gain=53     bits=9183   pattern=486bfb1641c4d509  0x2fd881051efe463fd264648cef4a54b7517433c774597e2cb43b960ee75e6e6d
 7921084  gain=427    bits=9632   pattern=a40847539190c566  0x3b6d08a7619c49864e952765f5601b9647cd55080390a9b588b9d536f699f199
 7942160  gain=6      bits=8962   pattern=f8a714ea5c942b14  0xe8584ad412fae71c26f776b70739faa4e7dc8d451e2a9c0ad4ed44d89a7e4bde
 8013460  gain=2      bits=8472   pattern=93bf49c9950798b0  0x588123464bf94845183870349e3b71b683e036bcea1319717332839fb3beec82
 8046913  gain=1      bits=8877   pattern=d6554799e50bffce  0x4172abe09fe6b8af1f1f8a8b0aca8883206672d12881a30cae48df9209599408
 8064067  gain=14     bits=8275   pattern=6855c837ab3025cd  0x7b5833e73147deab9690395cecacc78cff06c5a0cc40a5687c4cee602c7ec554
 8095833  gain=1      bits=9121   pattern=52cc3aa784235779  0xb31d088718f4b6ceb018a47702418865aca0b2fa0413f35cbbb87ec6a6e5bcf5
 8588594  gain=1      bits=8471   pattern=5b002c620fcd9b84  0x527806d5273d5ce0847b2950464cd681a377d31503c8fc1ea3f4cb60940b13c5
 8679271  gain=5      bits=8496   pattern=fb5728295e36a29c  0xeea5f2d3786e0d5556dd8fda4fd7584b339dab8b5e23ae5f0b7533aa9bb8babb
 8715390  gain=6      bits=9058   pattern=070a4e0c7802ec1f  0xfc4a2b95ae7d0b8d8a89c406b923a6e3f029b4307813c1a5602aaffe583510a1
 8990018  gain=1      bits=8416   pattern=714cc39f34b40ec2  0xf65190a2f9f36113c575d6a3f7213b529cacedcab1f7483548e4ea9167591e26
10108963  gain=10     bits=8432   pattern=09e3dcd6bf0eb536  0x9e21bb092456c3bd20c9cc788b0e0412d5480cf0d04eb7a5a254284385817fce
10206729  gain=21     bits=8993   pattern=2eb59c30347175dc  0x805b9057a1183627c9f74dca12110170a5507f9110370ef5fc56d2b02b2d12af
12747297  gain=2      bits=8998   pattern=b985929b4a15df59  0x9aa7579ec7a161ba36cbd9a0b8364a9c423d32ccd95072bf26b9a15aaf7200ca
12956090  gain=6      bits=8851   pattern=e0e6d34a779e6536  0x21c5d162de0e4deba3b2741036f6649ce84e1242bf9f8a86bd66d6ae6ebd5eaf
14104013  gain=7      bits=9161   pattern=8220fb81878133f4  0x4f700ab073cabf96dad67c130fa24b1f820ae38b9fe5a920edd179e1f7157ce1
14208515  gain=1      bits=9050   pattern=f3deba722cc162d6  0x714d6b6cedadba5d7cc29b6976bcb817a83ecae4ff7f657cf4f3b84d5adb28ee
14753409  gain=157    bits=9338   pattern=8b0ea5ab72cb809b  0x4314014aa470beb208e62cfcb9f9120e643b1abb394a09288d5af50df1fafabd
14924732  gain=30     bits=9228   pattern=494089776f7f68fb  0xa3e83b0de9e2a0dfe4d69fd2786fe019e6c72be0456cc260bbb0331b1ad28201
15052089  gain=5      bits=9323   pattern=a7c480e246add68a  0x9c9246753a6e6541b6902ccbb91ce5e5fe709896156dc5c646e1f8f7fe575d0f
15197778  gain=4      bits=8850   pattern=75c50319e81ac51c  0xdde8e89e4486cdff0dccda73ed469b468150a0288a927fee9d43e0ac25f09a22
15212274  gain=2      bits=8754   pattern=acc1efd039a0722e  0xc4f698612b00fac6000a3735dabee0c7ad3ec17fb2d246e774dac2de4c323494
15297491  gain=2      bits=8979   pattern=742247634fb6af35  0x4fd1411499ac5df9eee85f360aee060c9bd2c883c99e137211ef2ce17be4acf9
16095618  gain=20     bits=8730   pattern=51d251c313621d20  0x7d8644fa513c53a47d550c7789f443d83f1c0dc8c6cb160ecc15a1c546255047
16219408  gain=2      bits=8955   pattern=c2260ce146b83f9d  0x6d9d65cc98ac911d04727a918672342ee6dca920e61e263598504f1f9e20d639
16511216  gain=3      bits=9062   pattern=0175e55b79a267f8  0x9d7858fd033b3648da7db555eefbce4fe03574d2e9905ee6222b19867df5b539
16575691  gain=1      bits=9099   pattern=1033f283185a6864  0xf7178fa1ba369aa278ce07eb6bf31bb6a89c0fd26eed01a9c3fc1cae89351bd3
17256656  gain=1      bits=8533   pattern=570286283aea528e  0x0c3cff7dbe6522128615eef798e2aebaa9bd2b327d27083ba9a3d89bfe9de2cb
17399514  gain=1      bits=8479   pattern=a0a7ff072dc2d999  0x0ea0f76ed6bcaa70b2f108fc7a968cf1c861d9c2af10549ece9407b3dc4cad97
17444065  gain=1      bits=8529   pattern=994757af61894a28  0x5ca349360b7a6e08dc8879f02bb02ed804976dbae5ad4cbfcd287d7897f30133
17537999  gain=1      bits=8845   pattern=870113619e2dabb3  0xc544d7c50147b1f7444a14d62a7212ddbe41c1dd37677b2ed47ed85d096905b1
17545017  gain=1      bits=8787   pattern=fd8e76dc004c5bd7  0x916d7127294d4198bca4688df731b5fcb8e238306492270d865d354307f0a1f1
17822091  gain=1      bits=8929   pattern=244d3765ff76db0f  0x347689511ab2bd092f04e0d42ffb800268b85efc9f0a332097bd835a71df2ab8
17834989  gain=51     bits=8938   pattern=95894ab71f29a791  0xcb5b5b86dc4d894a8db2a8c5be6041f3673fbb16c75d54a1916dfec86f9f4c4c
18062966  gain=1      bits=9268   pattern=b0a9849a52a29829  0x61439ad6a8d049abcc1723f26fc87f1024549f3fedf2857014aa66823f880082
18076959  gain=11     bits=8905   pattern=3a9d0c29e817cc88  0x7ba490bb4706b2fed17adea9a8d105c755273748dee13927fbe8041d021ce54f
18303634  gain=95     bits=9662   pattern=8619308643658008  0xe7ed6889a1139099964ad668c8bec7958c1b72938420ab1d103c0bf45fb46a04
18751390  gain=10     bits=9617   pattern=b8a6c8988dbdf87b  0x67b9ef1795a72ceef5ca199144575490982f554e08e7032b4c63b5b269adc5d6
18922221  gain=3      bits=9539   pattern=58900ded7056b57c  0xddc2ba02086d32056505ca1284ec32d2629cf01721169bcf9c46fcaa1cd7d7cd
19004244  gain=7      bits=8949   pattern=0b2ad42dd6377b67  0xd34da24fdc8036204de3bf6684767a23a72c8dcb4f0ce6c63ab738a172f6c291
19673098  gain=34     bits=9308   pattern=9a5a53e808ce6853  0x9b3f50f9778d4052fc813b2459b8dd735f6ff0d80788dda84ae5bebb7e79ded7
20492161  gain=1      bits=9293   pattern=dbf298bfeeed009c  0xa61561d619b7b328449b3da7ccb148b0a846504013faa5f6f3ee633c53c891e5
21478836  gain=7      bits=9341   pattern=1ec704a26abaf5eb  0x77c02ecf87049f3c27ae46874afe3f65afa054bc785c84d354f49d38fcc90f71
22016777  gain=24     bits=8737   pattern=ba8fb7eea4cb3e2c  0x7423505d551a44ecdc128e1a1e0db64938d858daba93740a1332910f4a6cc2a0
22018358  gain=5      bits=9242   pattern=dcb0760ea94f3a54  0x88a4fd35c09950bb0af97f3f43c0ff882e5389bb0e28ad4cc64ba0d62e69e4db
22037073  gain=10342  bits=10342  pattern=955ab5002bd397c5  0x39a840d375fc7862c16dfdf2c2d0d309c8aa1d5006d93000cb16f45515ab0f26
22770953  gain=1      bits=9022   pattern=8bfd5698ef02afa0  0xfb63279436ad994242bfe959960439075ef850dfb041b071023e0146a6a33324
22815990  gain=3      bits=9364   pattern=56aecd687f230b8a  0x7252b43ec0b0e398a22bc03ac4934fa79d8f3e3d13509856fc92068b1c34089a
23001113  gain=16     bits=8582   pattern=7092cdce12ae4fff  0x5ba6babeffba7f9dd918ca527f75f7caa57b6619fd711c5d86fb1de72639d334
23661359  gain=14     bits=9208   pattern=407dfe6c43fd1bc9  0x11046f733cdd86241e47e59ba84e2094c05f18014a7b716d06cfa76d6054fd4d
24061835  gain=10     bits=8785   pattern=6aac1c923e410fb4  0x2927975c7d3bc5812125c48c1b8f3d79833e997a875a81df43f7ffa471d30035
24328495  gain=1      bits=9359   pattern=a708dd321db78917  0x969d096bee84c07331c72c87fd88f327d4689394d03d61888195520b9aed5991
24376466  gain=2      bits=9353   pattern=3d1c10a39e0b215b  0x8cd1ea24555c08f1855afdebef26449dde00dfcc679dda7e167f4f13d19a0ccd
24405273  gain=2      bits=9100   pattern=5e1aa170942000b9  0x0e938ab051650a5083820314280bc4e2d8636c0c7f9491d778841d1503613f0c
24410814  gain=2      bits=9086   pattern=9bbdee823538f7a9  0x67c83508fcda9e9f59603874def0f37690b2e75d9d695e42f480464158fe1796
24829789  gain=25     bits=8915   pattern=356c9225125adcb6  0x9dfbc5514180137eef514447b3a511d13858efe3c8516bef657abf90d1881582
24985478  gain=3      bits=9414   pattern=d11da897ca8dc0d6  0xe93a63eaf9713d0cb08d9d459395840a3b2d8cba44a31ee21e873a0ec944f251
25086401  gain=14     bits=9818   pattern=f976dc12671954a5  0x71671137e5908df92ac5f4a4dbd2b31fad5ccbdf8e5bc961fe39e4b2701f227c
25447376  gain=579    bits=9957   pattern=90572c586698449a  0x4699a0a3b212db6c6da0965118c4f2f3822b37131ad735429850376cee4f0f7c
25650673  gain=1      bits=9274   pattern=306b462b5b7def59  0x71d3c5b357996964464acd41837c20e6ac73a3a3591e0cdae6f0c1d00f263716
25732615  gain=1      bits=9313   pattern=fdeb3cc21ce1ed3f  0xe139a581db2ea59c0ef08e4bac2eae059102e3550f6dedb64a6fa98b184b7120
25996947  gain=1      bits=8850   pattern=8b4f79052fc2e394  0xea6cada8facde58d790d8fe2558a956cf3f824e75472a07b7f6ed1c363d6a4be
26743363  gain=25     bits=9504   pattern=1e2b5d560b3c7d0c  0xd69d3c50fc476fb238960aaf38d3e072c4dcc657da78b0c8309b7448d282d9f0
27167408  gain=2      bits=9096   pattern=a4c32923cf97daf1  0xee91b40f4bdbc9bf8278ba402a004fcddd580a01cecbb254173ad6cff60f912a
27269572  gain=2      bits=9908   pattern=4fdae6fdf808944c  0xaf3a0e6ed4273cf70a319b45af9a409810be0e87bc9d38cf0930f3e23febf509
27670576  gain=4      bits=9183   pattern=c777401364f513b5  0xf96cd0b4d326faefe3074f186f1e568b92b5ebe5d8642fd79280afd401f5c786
27765585  gain=1      bits=9090   pattern=47678c427ea2c654  0x59ddfcd7485594252489495880bff603258abe798c4ab6213fbadb0e1fad1541
27768650  gain=16     bits=9026   pattern=3794055875856a0b  0x2b427f1c4ff5ac7081c84c75eba7ce4691d0b91abedd64cd906d7e06ed6c6b0c
27784007  gain=2      bits=9054   pattern=428223c1df99bb86  0xd6d649bb2a5990ab9d8d3b7646d7ecbb1c26216c0846a1a9db3128d082b84473

The full-history scan (mega-evm v1.6.1, physical-counter universe)

These numbers come from the scan that ran before the item definition above existed; its stores are legacy stores (recognized as such and never mixed with current ones). They stand as a scan of mainnet, with one known bias stated below.

Four shards, combined with a merge subcommand since removed (mega-evm v1.6.1, nightly-2026-02-03):

counters patterns blocks
merged 2,622 1,533,398 20,954,362
  • ok=20954362 divergent=0 error=0 — every block the four shards were assigned replayed cleanly against its header (gas / receipts root / logs bloom). Their ranges left one stretch, 6,624,362–6,649,999, to none of them; this branch's build replayed it instead (above).
  • 94 blocks cover 2,622/2,622 physical counters. Branches 59.35% (1,048 total, 426 missed) · Regions 61.05% · Functions 57.10% · Lines 61.26%. sandbox/* alone accounts for 168 of the 426 missed arms.
  • What that universe actually contained: its filter was a substring match on mangled symbol names, and a mangled name carries its generic arguments and its instantiating crate — so only 1,652 of the 2,622 counters (63%) were mega-evm's. revm-interpreter held 510, revm-handler 119, and 30 crates in all the remaining 37%, down to k256, generic-array and typenum. Scoping by source directory is what replaced that: revm's execution engine stays, deliberately; the cryptography and type-level crates go.
  • Known bias of that universe: the union of the 71,930 archived profiles plus those 94 reaches Branches ≥ 60.02% — at least 7 branch arms and 12 regions mainnet did exercise are missing from the 94-block cover (in evm/execution.rs, evm/precompiles.rs, evm/instructions.rs). That is a lower bound: the archive was filtered by the same domination. This is the finding that led to the item definition above.
  • antichain = 44,514 of 1,533,398 patterns (97.1% strictly dominated). That is the candidate pool a re-sweep replays: a 471× reduction against the full range. It inherits the bias above.
  • Only 31 counters (1.2% of the universe) are covered by exactly one pattern — the fragile tail.
  • The universe had not saturated: the last 2M blocks still contributed 5 new counters, and the 6.3M–8.4M stress-block region was the single largest contributor (+603).

Performance at full-history scale

One cost only shows at this scale — the antichain prune, which both commands below run; it was found on the consolidation run above and fixed here, then re-measured on the same store (old-pin build, same machine):

before after
select_cover inside set-cover 21:11 0:56 22.6×
inspect --dump-pool 21:30 1:45 12×

The antichain prune tested each pattern against every earlier one (~10¹² iterations for 1.5M patterns). split_antichain (setcover.rs) scans only kept patterns and finds candidates through an inverted index from counter to the kept patterns containing it: a superset must contain the candidate's rarest counter, and a counter no kept pattern has proves the candidate maximal outright.

Equivalence was checked three ways: the manifest from the new code is identical to the quadratic run's (same 94 blocks, order and gains), the exported pool is line-for-line identical, and a differential test keeps the quadratic scan as the oracle over randomized stores (verified to fail on a mutant).

Replay throughput. Two costs, neither of them the network, kept the stress-test range at a crawl (one block in nine minutes at worst):

296 mainnet blocks, avg 447 tx every crate instrumented only the measured code
wall clock 39.9 s 5.2 s
worker p50 per block 1,958 ms 231 ms
universe / selected blocks / per-file report identical
  • The checked block fetch recovered every transaction's signer: >90% of the dispatcher's CPU was secp256k1 on blocks of ~20k transactions. The fetch is now unchecked, like the trace server's; the worker's header comparison catches a wrong sender or transaction set, and the header hash is still verified.
  • RUSTFLAGS instrumented every crate, and tiny hot functions (k256, per-byte bincode, the interpreter loop) paid a shared-counter increment per basic block, with threads contending for the same cache lines. cov-rustc-wrapper.sh instruments the measured crates and the workspace only. The workspace is required — most measured code is generic and is compiled where it is instantiated — and nothing outside the workspace depends on mega-evm. Replaying a finished store's antichain both ways gives the same universe and a byte-identical report, in about a tenth of the time. The registry crates that depend on the measured revm crates (alloy-evm, alloy-op-evm, revm, revm-inspector, four reth crates) stay uninstrumented on evidence: a build instrumenting them too, replaying the same 401 blocks, gave the same 11,706-item universe and an identical per-file report for all 127 measured files, at ~7% more worker time per block.
  • With both, the 44,514-block pool replays in 8–20 minutes depending on scope (stress range ~8 blocks/s, elsewhere ~90), against an estimated ten hours before. --fetch-concurrency now defaults to 32: the dispatch queue bounds the spool backlog, so the only cost of a higher value was idle workers. The densest stretch, 6,624,362–6,649,999 at ~21,000 transactions a block (~540M in all), runs at ~4.8 blocks/s on 32 workers — CPU-bound on the replay itself, with fetched blocks queued ahead of every worker — so its 25,638 blocks take about 90 minutes.

inspect --no-cover-preview skips the set-cover pass, which after the above is about half of inspect on a full-history store; it stays on by default because it is the only non-destructive way to see the cover.

Testing

cargo test --workspace green. Unit tests cover the item extraction against committed llvm-cov export fixtures (the then-only / else-only pair that physical counters got wrong), the judge's dedup/promotion paths, the set-cover algorithm (domination strictness, gain tie-breaks, redundancy elimination), the report's re-derivation check, scope detection under several registry indexes, tool lookup order, the judge's domination check through the undominated set, the pattern-key probing walk, store namespacing, spool checksums, and the block-list/pool formats. bin/coverage-replayer/tests/replay_fixtures.rs replays every mainnet fixture through the exact path the worker uses and checks the header sanity triple — it runs uninstrumented, so it guards the replay glue in normal CI. tests/worker_protocol.rs drives the real binary as a worker and checks that a request gets exactly one frame on stdout. Two properties are verified only by instrumented runs, since an uninstrumented test sees no counters and no stray prints: the warm-up (the replays above) and the worker's exclusive stdout.

Notes

  • The tool needs the instrumented [profile.coverage] build, made through RUSTC_WRAPPER=bin/coverage-replayer/cov-rustc-wrapper.sh; Cargo.toml carries the build line and the reason -C link-dead-code must not be added. measured-crates.txt is the single list both the wrapper and the default --source-dir scope are derived from.
  • llvm-cov matches the absolute source paths baked in at build time, so the sources must sit where they sat for the build. The default scope is therefore looked up under the cargo home the build used (recorded by build.rs, so neither sudo nor CARGO_HOME moves it), a crate present under two registry indexes is refused rather than picked by listing order, and the LLVM tools default to the ones shipped with the building toolchain. A scope that matches nothing fails the block instead of recording an empty bitmap. An explicit --source-dir is made absolute (symlinks left unresolved) and one still containing .. is refused, since the paths llvm-cov lists are absolute and never contain ...
  • report scopes llvm-cov to the measured sources: reporting over the full covmap crashes llvm-cov on an instantiation-group bug.
  • --help fix, workspace-wide. clap was pinned with default-features = false and only derive/env/std, which drops its help, usage and error-context features — so --help answered error: unexpected argument found on all three binaries, including stateless-validator and debug-trace-server, and errors named neither the offending flag nor the usage. This PR opts those three features back in (Cargo.toml) and pins the behaviour with tests by error kind. With help on, clap renders an env-backed flag as [env: NAME=<current value>], so the three R2 secrets each binary already redacts in Debug (the Access client id and secret, the S3 secret access key) set hide_env_values: --help names the variable and not its value, which a test per binary checks with all three set.
  • Scope integrity. A root that does not match the coverage map is answered by llvm-cov with a warning and a success exit, so every configured root must contribute a file (checked per block, and in report against its table) — reproduced first: a stale revm root beside a valid mega-evm one dropped the file list from 126 to 47 while the store's stamp still claimed the full scope. binary_id hashes the measured crates' locked versions, since a revm bump with mega-evm unchanged rewrites part of the coverage map and report validates a manifest by that id alone. The universe stamp is built from root labels rather than absolute paths, so the same scope stamps identically whatever home it was resolved under. Duplicate and nested roots are refused when the scope is resolved, which is what makes a label a sound identity and lets ids take the first matching root. The manifest records the universe it was computed over, and report refuses any other scope: the archived profiles carry counters for every instrumented crate, so a wider scope would otherwise report cleanly and read the gap as uncovered code. report re-derives the covered items from the merged profiles through the same extraction the per-block path uses — which also checks every root — and refuses a count that differs from the manifest's. The measured generics are instantiated in the workspace, and their profile names carry the workspace crates' cargo metadata, so a version bump or a dependency change renames them. That was observed, not hypothetical: after this PR's own dependency changes, the previous build's cover evaluated to 4,124 of its 13,006 items under the new binary, and report refused it. binary_id therefore fingerprints the lockfile too, so such a build refuses the store up front instead of resuming it and mixing two builds' profiles in its archive; the re-derivation stays as the check for what the id cannot see (features, compiler flags).
  • Branches inside macros are not measured, by the tool or by llvm-cov: rustc's branch instrumentation emits no branch region for an if inside a macro_rules! body (verified on this toolchain — no Branch line at the call site, and no expansion branch records in a 400-profile export of the measured binary). revm's instruction macros are the main place this matters.
  • Robustness for long scans. Workers are spawned from, and their profiles evaluated against, the running image (/proc/<pid>/exe), so rebuilding the binary mid-scan can neither mix coverage maps nor wedge respawns on a deleted executable. A worker keeps stdout for its frames alone — library prints go to stderr — so frames are parsed strictly instead of salvaged. Item provenance travels in the frame, only for ids new to that worker, replacing a per-block, fsynced sidecar file. Contract files verified once are not re-read and re-hashed for every block, and that file work runs off the async runtime. A % in --data-dir (a filename pattern to the profile runtime) is refused up front, and report works in a private temp dir.
  • clap error-context elsewhere in the workspace. Enabling it made five comments and three AGENTS.md sentences in the validator, the trace server and stateless-common false ("clap names no argument"); they are corrected. The R2 rules still run after parsing, for the reasons that remain: one verdict in one wording for both binaries, and a blank env line diagnosed as blank rather than as a phantom conflict.
  • One definition each. The scope and LLVM tool flags are one LlvmArgs, shared by backfill, report and the worker, and a spool entry passes one check, SpoolEntry::open, for the worker and a resumed run alike.
  • Judge cost at full-history scale. A new pattern is checked for domination only against the patterns that arrived undominated — transitivity makes that complete, and all but a few percent arrive dominated — instead of against every pattern. The store is flushed every 64 judged blocks and on every exit path rather than on every commit; a kill loses at most that many, which are replayed (verified: killed mid-run with -9, the resumed run ended on the identical pattern table). Spool entries and contract codes skip the fsync, since both are re-checked before use.
  • Not in this PR: a finality margin below the tip for scans near the head (resume skips blocks, and reuses spools, by height); a per-worker cache of analyzed contract bytecode; and a cache keyed on the raw counter vector so identical blocks — empty ones, mostly — skip the export.

🤖 Generated with Claude Code

…ximizes mega-evm branch coverage

Adds an offline tool that answers "which mainnet blocks, replayed, exercise
every mega-evm branch we have ever seen taken?" — the fixture set a stateless
validator wants for regression coverage, without replaying 21M blocks each
time.

`backfill` replays blocks under LLVM branch instrumentation: resident worker
subprocesses reset counters, replay one block, and capture a profraw; a judge
dedups the resulting per-block bitmaps into "patterns" in a redb store, keeping
the lightest block of each pattern as its representative. `set-cover` runs a
greedy cover with antichain pruning and redundancy elimination over those
patterns, `report` renders an llvm-cov summary for the selected set, `inspect`
prints store statistics, and `merge` folds per-machine shard stores from a
distributed scan into one (remapping each shard's private dense indices through
machine-stable counter ids).

Counter ids belong to one instrumented build, so `binary_id` (mega-evm rev +
toolchain) namespaces every store and the write paths refuse a mismatch. That
would strand a scan at each mega-evm bump, since a full sweep costs weeks — so
`inspect --dump-pool` exports the antichain's representatives as a block list
and `backfill --blocks-file` replays one, carrying the block numbers (which
survive) rather than the bitmaps (which do not). `inspect` deliberately skips
the binary-id check, so a pool can still be extracted long after the bump.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mega-maxwell

mega-maxwell Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Claude review status

Living comment — rewritten in place. The review workflow keeps this single comment up to date instead of posting a new one each round, so it always describes the latest reviewed commit and the earlier text is intentionally gone. No reply is needed here; reply to a finding in its own review thread, and answer an open question in a reply on this PR. The next review round reconciles your answer.

🛠️ Review did not finish

Attempted f2b93c3e..06b57327 · updated 2026-09-28T14:14:54+00:00

This round did not publish: MODEL_ACTION_FAILED in phase review_retry. Anything listed below is from the last round that did. Re-run the workflow or push a new commit to try again.

@claude

claude Bot commented Sep 21, 2026

Copy link
Copy Markdown

Label check: this PR currently has no labels applied.

Given the title (feat: coverage-replayer — ...) and the summary (a wholly new offline binary/tool, 22 files added), this looks like a good fit for the enhancement label (new feature or request). None of the other available labels (bug, documentation, duplicate, good first issue, help wanted, invalid, question, wontfix, compat:seamless) apply here.

Suggest adding enhancement.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76c04a183c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/llvm.rs Outdated
Comment thread bin/coverage-replayer/src/report.rs Outdated
Comment thread bin/coverage-replayer/src/backfill.rs Outdated
Comment thread bin/coverage-replayer/src/store.rs
Comment thread bin/coverage-replayer/src/backfill.rs
Comment thread bin/coverage-replayer/src/llvm.rs Outdated
The workspace pins clap with `default-features = false` and only
`derive`/`env`/`std`, which drops clap's `help`, `usage` and `error-context`
features. `--help` is one of those features, not something the derive
provides, so every binary answered `coverage-replayer --help`,
`stateless-validator --help` and `debug-trace-server --help` with

    error: unexpected argument found

— and the error did not even name the argument, because without
`error-context` and `usage` clap's messages carry no offender and no usage
line. Anyone deploying these binaries had no way to discover a flag from the
binary itself.

Opting the three features back in restores `--help` on the root and on every
subcommand, and makes an unknown flag report itself with a usage line. Two
tests pin the behaviour by error kind (`DisplayHelp` / `UnknownArgument` plus
the rendered text), so trimming the features again fails the suite instead of
silently removing `--help`; both were verified to fail with the features
removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fda8347481

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/setcover.rs Outdated
Comment thread bin/coverage-replayer/src/backfill.rs Outdated
Comment thread bin/coverage-replayer/src/store.rs Outdated
Comment thread bin/coverage-replayer/src/backfill.rs
flyq and others added 2 commits September 21, 2026 09:45
clippy::items_after_test_module — the help regression tests landed above
`fn main`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… split

Two costs that only show at full-history scale (21M blocks, 1.5M patterns),
both measured on the consolidated mainnet scan:

`merge` wrote ~35 GB to produce a 3.6 GB store. `write_table` drained a
`HashMap` straight into 100k-row transactions, i.e. random-order insertion:
every batch dirtied pages all over the B-tree, so each commit rewrote a slice
of the whole tree. Sorting the rows by key first makes every batch land on
the right edge, and a commit costs what the batch holds.

`select_cover`'s antichain prune tested each pattern against every earlier
one — quadratic in the pattern count, ~10^12 iterations for 1.5M patterns,
and it runs inside `inspect` as well as `set-cover`. `split_antichain` scans
only kept patterns (the dominated 97% never dominate anything a kept pattern
does not) and looks candidates up through an inverted index from counter to
the kept patterns containing it: a superset must contain the candidate's
rarest counter, so the shortest posting list bounds the search, and a counter
no kept pattern has proves the candidate maximal outright. The quadratic scan
stays in the tests as the oracle for a differential test over randomized
stores shaped like real ones (hubs, derived subsets, equal-bitmap twins, an
empty pattern); the test was verified to fail on a mutant that checks only
the first posting-list entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b1c3a9cc7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/backfill.rs Outdated
Comment thread bin/coverage-replayer/src/inspect.rs Outdated
`inspect` always ran the real set-cover algorithm to print the antichain
count and the selection preview. With the indexed antichain split that is no
longer a twenty-minute step, but on a full-history store it is still about
half of the command — the rest is a single pass over the tables.

`--no-cover-preview` skips it. The preview stays on by default: it is the
only way to see "N blocks cover X/Y" without running `set-cover`, which
deletes dominated profiles, and on anything short of a full-history store it
is effectively free. The flag conflicts with `--dump-pool`, since the pool is
made of the antichain that pass computes — rejected by clap up front rather
than producing a run that silently writes nothing.

Also covers `inspect::run` end to end over a real redb store, which the
direct `write_pool` tests did not reach.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84604b1641

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread README.md Outdated
Comment thread bin/coverage-replayer/src/setcover.rs Outdated
Comment thread bin/coverage-replayer/src/store.rs Outdated
Comment thread bin/coverage-replayer/src/merge.rs Outdated
…eview

The tool took "which physical counters are non-zero" as a block's coverage
bitmap, on the assumption that with `-Z coverage-options=branch` both arms of
a branch are plain counters. They are not: rustc minimizes physical counters,
so an `if`/`else` gets two (entry, then-arm) and the else-arm exists only as
the expression `entry - then`. Reproduced on nightly-2026-02-03 — a then-only
run reads `[1,1]`, an else-only run `[1,0]` — and no `-Z coverage-options`
value changes it. Over physical counters the else-only block is `{entry}`, a
strict subset of `{entry, then}`: the judge never archived its profile,
set-cover pruned it as dominated, and the cover lost a branch arm the scan had
covered. On the consolidated v1.6.1 scan the 94-block cover reported Branches
59.35% where the union of the archived profiles reached at least 60.02%.

A counter is now an evaluated item: every block's profile goes through
`llvm-cov export --format=text --skip-functions`, scoped to the source dirs,
and the items are its region entries and branch arms with a non-zero count,
keyed by source span and OR-ed across instantiations — the arithmetic `report`
runs. ~0.6 s per block. The scope replaces the symbol-substring filter
(`--source-dir`, default the mega-evm checkout the binary was built against),
which also stops dependency generics instantiated with a mega-evm type from
entering the universe; an unscoped export of this binary segfaults llvm-cov,
and a scope that matches nothing fails the block instead of recording an empty
bitmap. Stores carry a universe stamp; a legacy store is recognized by its
symbol-filter key and labelled `physical-counters/v0`, so it is never mixed
with current ids yet still inspects and merges with its own kind. The
provenance fields of `CounterInfo` are renamed, not re-encoded: schema v1
stores still decode.

Validated on 296 blocks replayed under mega-evm v1.7.0: `report` over the 14
selected blocks is identical, file by file, to `report` over the union of all
174 archived profiles.

Review items (chatgpt-codex-connector on #222), each verified against the code:

- report: refuse a manifest from another `binary_id` — llvm-cov does not fail
  on a mismatch, it drops the functions and reports them uncovered.
- backfill: fail unless every selected block was judged; a panicked fetch task
  or a dead manager dropped its block and the run still exited 0.
- backfill: load the chain spec in the dispatcher first. A worker that cannot
  start looks like one that crashed mid-block, and blocks are retried forever,
  so a mistyped `--genesis-file` wedged the run in a respawn loop.
- set-cover: load patterns only, point-look-up the selected hashes; deleting
  dominated patterns' profiles is opt-in (`--prune-profiles`) and happens
  after the manifest is written.
- inspect: fold the BLOCKS table in a streaming pass instead of materializing
  it (7.2 GB RSS on a 21M-row store); siblings by a second bounded pass.
- merge: mark the output incomplete until the last batch, and refuse such a
  store on every open; fold a height two shards share when the replays agree
  (counting its pattern hit once), stop when they disagree, and let a clean
  record win over a quarantined one.
- docs: the cover is greedy and redundancy-eliminated, not minimum-cardinality;
  scans are for final blocks; the ValidatorDB caveat is scoped to the
  validator.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a109fc1d2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/llvm.rs Outdated
Comment thread bin/coverage-replayer/src/backfill.rs Outdated
Comment thread bin/coverage-replayer/src/report.rs Outdated
flyq and others added 3 commits September 21, 2026 11:34
Replay is fetch-bound: on the pool re-sweep the worker pool could take
roughly twenty times what eight concurrent fetches delivered, because a
deep-history block costs far longer to download than to execute. The spool
backlog stays bounded whatever the value — a full dispatch queue blocks the
fetch loop — so the higher default costs nothing but idle workers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The checked `get_block` recovers the signer of each transaction. On mainnet's
stress-test blocks — tens of thousands of transactions each — that, not the
download, was what the fetch stage spent its time on: a profile of the
dispatcher showed over 90% of its CPU in secp256k1 arithmetic, with the
concurrent fetches contending for the same coverage counters, and the sweep
advanced by one block in nine minutes.

Fetch unchecked, as the trace server does. Nothing is lost: a wrong sender or
transaction set cannot reproduce the header's gas, receipts root and logs
bloom, which the worker compares after replaying, and a divergence stops the
run. The header hash is still checked here — it is one keccak, and it is what
the manifest publishes and the witness is addressed by.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…trument only what is measured

Scope. Coverage was scoped to the mega-evm sources alone. But mega-evm shapes
execution *through* revm — its host and handler are type arguments of revm's
generic interpreter and handler — so which EVM paths mainnet exercises is a
fact about revm's source as much as mega-evm's. (The first, symbol-substring
universe had included revm by accident: a mangled name carries its generic
arguments and its instantiating crate, so 37% of that universe was dependency
code — revm's interpreter, but also k256, generic-array and typenum.) The
default scope is now the mega-evm checkout plus the crates listed in
`measured-crates.txt`: revm-interpreter, revm-handler, revm-context,
revm-context-interface and op-revm. build.rs resolves their locked versions
into the default `--source-dir`s, and the same file drives the rustc wrapper,
so what is instrumented and what is measured cannot drift. Item ids now start
with the source dir's own name — two scoped crates both have a `src/lib.rs` —
and the universe stamp moves to v2 so ids from the two derivations never share
a store.

Instrumentation. The build line used RUSTFLAGS, which instruments every crate.
Every basic block of an instrumented crate bumps a process-global counter; in
tight loops that dwarfs the work itself (k256 field arithmetic, per-byte
bincode encoding), and threads running the same code contend for the same
counter cache lines. `cov-rustc-wrapper.sh` instruments only the measured code
and this workspace — the workspace because most of the measured code is
generic and a generic function is compiled, counters included, in the crate
that instantiates it; nothing outside the workspace depends on mega-evm, so
nothing else can. Host artifacts are skipped (instrumented build scripts drop
default_*.profraw files into the source tree), which is what the explicit
`--target` in the build line is for.

Verified on mainnet blocks: replaying the antichain of a finished store under
full and under selective instrumentation gives the same universe and a
byte-identical per-file report, at roughly a tenth of the time.

`report` documents one llvm-cov convention that shows up with generic code in
scope: it summarizes a generic function by its best single instantiation, so
its region total can trail a report over the whole scan by a region in a
const-generic family even though every source region is covered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1ce000c91

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/store.rs
Comment thread bin/coverage-replayer/src/llvm.rs Outdated
Comment thread bin/coverage-replayer/src/llvm.rs Outdated
Comment thread bin/coverage-replayer/src/llvm.rs
Comment thread bin/coverage-replayer/src/r2.rs Outdated
Four holes, all of them opened or widened by taking revm's execution engine
into the scope. Review threads on #222.

`binary_id` fingerprinted mega-evm's revision and the toolchain. The measured
dependencies are now measured code, not merely linked code, so a revm bump
with mega-evm unchanged rewrote part of the coverage map while leaving the id
identical — and `report` validates a manifest by that id alone, so it would
have accepted profiles whose functions llvm-cov then drops, reporting the
difference as uncovered. The locked versions of the measured crates join the
hash (sorted in build.rs, so reordering measured-crates.txt is not a new
build).

The universe stamp embedded absolute source paths, so two shards of one
distributed scan stamped differently under different home directories and
`merge`, which compares stamps byte for byte, refused them — while their item
ids, which strip the prefix, were identical and mergeable. Stamps are now
built from root labels, like the ids.

A root that does not match the coverage map is answered by llvm-cov with a
warning on stderr and a success exit, and the check here was that the scope as
a whole matched something: a valid mega-evm root beside a stale revm one
measured no revm at all while the store's stamp went on claiming that scope,
which would invalidate a scan silently and late. Every root must now
contribute a file. That is well defined per block because llvm-cov lists the
files of the coverage map under the scope, not the files this block executed
— verified against real per-block profiles, where each of the six roots
appears on every block. `report` grew the same check against its table.

A root is identified by its final path component, in both the ids and the
stamp, so two roots sharing one would have had their coverage merged under a
stamp still claiming two; nested roots would have made "which root owns this
file" depend on the order the roots were listed in, which the sorted stamp
cannot see. Both are now refused when the scope is resolved, which is also
what lets the ids take the first matching root rather than the most specific.

Roots are matched as spelled, never canonicalized: llvm-cov matches the
absolute paths baked in at build time, so a canonical form could disagree with
what llvm-cov itself matches. A path spelled a way that does not prefix the
coverage map is caught by the per-root check instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0dafdb9c5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/report.rs Outdated
Comment thread bin/coverage-replayer/src/report.rs Outdated
Comment thread bin/coverage-replayer/src/merge.rs Outdated
Comment thread bin/coverage-replayer/src/llvm.rs
…ed for

Two ways `report` could run cleanly and still be wrong. Review threads on #222.

A cover only promises the scope it was computed over, but a manifest carried
just its `binary_id`. The archived profiles hold counters for every
instrumented crate, so reporting a cover over a wider scope than its scan's —
or over a root swapped for another — rendered a clean table that read whatever
the cover never had to reach as uncovered code. The manifest now records the
store's universe stamp, read from the store rather than re-derived from flags
so it cannot drift from the scan that produced the profiles, and `report`
refuses any other scope. Manifests written before the field existed still
report on the build check alone, with a warning that the scope went
unchecked.

`report` checked each root by looking for its label anywhere in the table,
because llvm-cov strips the common prefix from the paths it prints. That is a
substring test, and a stale root named `src` passed it on every other root's
`/src/` paths. It now reads a `--summary-only` export of the merged profile,
whose filenames are absolute, and checks roots through the same
prefix-matching function the per-block export uses — one definition for both.

Verified on the consolidated scan's store: re-running set-cover keeps the same
86 blocks and binary_id and records the universe; `report` over the scan's
scope reproduces the same totals; a narrower scope is refused; and a scan
scoped to `mega-evm + revm-handler/src`, reported with a stale `/…/src` in
place of the real one — same universe stamp, so only the root check stands in
the way — is refused by name where the substring test had let it through.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a32b7868a6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/src/r2.rs Outdated
Comment thread bin/coverage-replayer/src/r2.rs Outdated
Comment thread bin/coverage-replayer/src/llvm.rs
Comment thread Cargo.toml
…ling, and harden the scan

- Workers build the run-once precompile tables (mega-evm `rex`/`mini_rex`,
  op-revm `isthmus`/`granite`/`fjord`) before capturing anything. Left to the
  blocks they were credited to whichever block a worker replayed first, and
  to the first block of each later table, so ~2.5% of blocks recorded
  different coverage from run to run. The item universe moves to v3, so
  stores, shards and manifests from before are refused.
- `report` re-derives the covered items from the merged profiles and refuses
  a count that differs from the manifest's: `binary_id` does not see the
  workspace crates whose metadata the measured instances are named by.
- `set-cover --incumbent-manifest` matches patterns, not blocks: a pattern's
  representative moves to lighter blocks between runs.
- Workers are spawned from, and profiles evaluated against, the running image
  (`/proc/<pid>/exe`), so a rebuild mid-scan cannot mix coverage maps or wedge
  respawns; a `%` in `--data-dir` is refused up front.
- The default scope and the LLVM tools come from the cargo home and the
  sysroot the binary was built with; a crate found under two registry indexes
  is refused instead of picked by listing order.
- The worker keeps stdout for its frames (library prints go to stderr), so
  frames are parsed strictly; item provenance travels in the frame, only for
  ids new to the worker, instead of a fsynced per-block sidecar file.
- Contract files verified once are not re-read for every block, and the file
  work runs off the async runtime.
- `report` works in a private temp dir and no longer scaffolds a data dir.
- The wrapper reads measured-crates.txt the way build.rs does; docs corrected
  (R2 retention, report scope, clap `error-context` statements elsewhere in
  the workspace).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@mega-maxwell mega-maxwell Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Review needs attention — 1 finding(s)

1 blocking · 0 should-fix · 0 suggestion(s) · 0 open question(s)

Reviewed 76c04a18..101a964f.

Details are attached inline.

Comment thread Cargo.toml
…the store, judge and worker plumbing

Reuse:
- R2 witnesses go through the workspace's R2WitnessTransport, fetcher and
  light decoder (one attempt per round: the fetch loop's retry stays the
  policy); `r2.rs`, its RedactedSecret copy and the chrono/reqwest deps go.
- `LlvmArgs` is the one definition of the scope and tool flags, flattened into
  backfill, report and the worker; extraction is a method of the resolved value.
- `Manifest::read` / `ManifestBlock::pattern_key` replace three parsers.

Simplification:
- Store reads are counters / patterns / blocks(range) / block_records / load;
  one encode/decode helper; the universe stamp is required (the legacy
  physical-counter label could no longer be reached), and readers that
  interpret dense indices (set-cover, merge) open read-only through one
  build check.
- `PatternRecord::first_seen` / `absorb` are the one pattern fold the judge and
  merge share; merge keeps a single id map.
- Redundancy elimination is one forward pass; the judge's universe bitmap and
  the response's `ok` flag were derivable and are gone.
- The worker takes `--data-dir`; `SpoolEntry::open` is the one check a spool
  entry passes, for the worker and for a resumed run alike (the fetch now
  checks the block number itself); `DataDir` owns the per-block scratch name
  and clears what a killed run left, replacing an age-based sweep.
- `report` works in a system temp dir; comments that restated one policy in
  several places now state it once.

Efficiency:
- The judge checks domination only against patterns that arrived undominated
  (transitivity makes that complete), not against every pattern.
- Judged blocks are flushed to disk every 64 commits and on every exit path;
  spool entries and contract codes are written without fsync (both are
  re-checked before use); blocks are serialized off the async runtime; ids
  resolve in one Fx-hashed pass; a resumed run keeps only block statuses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.6%. Comparing base (727a34b) to head (06b5732).

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ded72fcc3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/coverage-replayer/build.rs
flyq and others added 3 commits September 24, 2026 14:38
A profile names each instance of the measured generics by a symbol that
carries the cargo metadata of the crate instantiating it, and that metadata
moves with any dependency or version change. binary_id did not see it: after
this branch's own dependency changes, a cover taken by the previous build
evaluated to 4,124 of its 13,006 items under the new binary. `report` refused
it, but backfill would still have resumed such a store and mixed the two
builds' profiles in its archive.

build.rs now fingerprints Cargo.lock (FNV-1a, stable across machines, so
shards of one scan still agree) into binary_id, so backfill, set-cover and
merge refuse a store whose profiles this build cannot read. `report`'s
re-derivation stays as the check for what the id cannot see (features,
compiler flags).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…arget is the switch

As in the validator and the trace server: the `--r2-*` flags go through the
shared `validate_r2_flags` rules (a half-configured target or a blank value is
refused by name), a configured target makes R2 the first witness path, and a
block R2 cannot serve within three attempts and a minute goes to the witness
RPC — a second path to the same bucket. `--witness-endpoint` is therefore
always required; the placeholder witness endpoints are gone. S3 target only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b546acfe66

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +263 to +264
} else {
explicit.to_vec()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize source-root paths before matching exports

When an operator passes a relative --source-dir such as . or ../mega-evm, this accepts the path, but llvm-cov exports the absolute filenames embedded in the coverage map. ensure_every_root_matched then compares those absolute filenames against this relative root with Path::starts_with, finds no match, and makes both backfill and report fail despite the root naming the correct directory. The same failure can affect the default scope if the build used a relative CARGO_HOME; convert roots to absolute paths at resolution time (without resolving symlinks) or reject relative roots explicitly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relative --source-dir never matches. resolve_source_dirs doesn't absolutize explicit roots, but llvm-cov exports absolute paths, so --source-dir . fails every block. Running the roots through std::path::absolute at resolution time should fix it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9578055. resolve_source_dirs now runs every root through std::path::absolute (bin/coverage-replayer/src/llvm.rs:243), so --source-dir . and --source-dir mega-evm match. It also fixes the label: . used to stamp the universe as ".".

absolute alone does not cover ../mega-evm. On POSIX it keeps .., because it does not resolve symlinks, and roots must match as spelled. A root that still contains .. is therefore refused by name (llvm.rs:245) instead of failing later as an unmatched root. Test: resolve_makes_relative_roots_absolute (llvm.rs:687).

A relative CARGO_HOME is not covered. Cargo resolves it against the directory it was invoked from, which build.rs cannot see.

…n its flushing, tidy the R2 path

- The worker protocol names a block by number alone: both ends derive its
  spool entry and profiles from the data dir (`DataDir::block_profdata`), and
  new items travel as `CoveredItem` itself instead of a string-typed copy.
- `DataDir` owns the archived-profile format (write and read); `llvm-cov
  report` is a method of `Llvm` like the other tool calls.
- The store decides which commits flush (every 64th, plus `flush` on exit);
  callers no longer pass a durability flag. Metadata keys are named once.
- Dense indices are the counter count; the judge's archive and "undominated"
  branches are one; merge's re-key warning lives in its insert branch.
- R2: each of the three attempts gets its share of the minute, so a stalled
  GET no longer uses up the rest; the no-op metrics are `impl R2Metrics for ()`
  in stateless-common; the command-line-secret warning only this binary had is
  gone (the flag's doc says to prefer the env var, as in the other binaries).
- `block_json` goes through serde's byte-array hooks: the same bytes, written
  and read in one call instead of one per byte.
- Removed a block-number check the RPC client already makes, clones the fetch
  task did not need, and a test asserting a deleted flag stays deleted; the
  block statuses are dropped once the todo list exists; stale text fixed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@flyq
flyq requested a review from Troublor September 25, 2026 12:52
@vincent-k2026

Copy link
Copy Markdown
Collaborator
  1. Help output leaks more than the R2 secrets. With clap's help on, --help also prints the values of *_RPC_ENDPOINT, *_WITNESS_ENDPOINT, *_WITNESS_GENERATOR_ENDPOINT and *_REPORT_VALIDATION_ENDPOINT, and these URLs often carry API keys. They need hide_env_values = true too.

  2. Could the --help fix be a separate PR? It changes CLI behaviour for all three binaries and has nothing to do with the coverage tool. On its own, with the hide_env_values changes and a test that the rendered help doesn't contain env values, it would be quick to review and wouldn't block this one.

  3. CI doesn't cover the instrumented path. CI only runs the uninstrumented tests, so the coverage build, the worker warm-up and exclusive stdout are only checked by hand. A follow-up manual or nightly job that builds with the wrapper and replays a few fixtures would catch regressions there.

  4. Resume assumes final history. Resume and spool reuse match by height only. That's fine as long as the tool only scans finalized blocks. The docs say so already; mainly flagging the finality-margin follow-up so it doesn't get lost.

flyq and others added 3 commits September 26, 2026 10:47
…onger needs

- `merge` and the store's bulk-write path: the workflow is a candidate-pool
  re-sweep on one machine; pools from several stores still union as block
  lists.
- The R2 witness route: witnesses come from `--witness-endpoint`, the gateway
  in front of the same bucket.
- `inspect`'s rarity and growth statistics and `--pool-siblings`.
- `set-cover --incumbent-manifest` and `--prune-profiles`; gain ties go to
  the higher block number.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment lines 1,097 → 568. Each comment now states what would break, once,
at the item that owns it; history, measurements and restatements of the code
are gone (the rationale lives in the PR description). No code line changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pruning flag is gone; dominated patterns are simply never cover candidates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@mega-maxwell mega-maxwell Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Review needs attention — 1 finding(s)

0 blocking · 0 should-fix · 1 suggestion(s) · 0 open question(s)

Reviewed a46fdebe..f2b93c3e.

Details are attached inline.

}

#[derive(Subcommand, Debug)]
enum Cmd {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] PR description promises features this delta removes (merge, --pool-siblings, coverage-replayer R2)

This repo uses squash-and-merge and AGENTS.md states the PR description becomes the squash commit message. git log will describe features coverage-replayer --help no longer offers, and readers grepping history for merge/--pool-siblings/R2 in this tool find promises the code does not keep. The 84 blocks cover 13,006/13,006 items claim was produced with --pool-siblings per the current text, so reproducing it after this delta needs an updated method note as well.

Suggested fix: Update the PR description before squash-merge: drop the merge bullet from ## How it works, drop the --pool-siblings N sentence from ## Carrying a scan across a mega-evm bump, and rewrite the ## Notes / Witness sources paragraph so it no longer describes a coverage-replayer R2 route (or notes that R2 was removed and this binary now uses --witness-endpoint alone). Also revisit the merge row of the Performance at full-history scale table or move it under a Prior scan heading so it reads as history.

flyq and others added 2 commits September 27, 2026 08:02
Restoring clap's `help` feature made `--help` render every env-backed flag as
`[env: NAME=<current value>]`, so on a host or CI job with the R2 credentials
in its environment, `stateless-validator --help` and `debug-trace-server
--help` printed them in the clear. `RedactedSecret` does not help there: clap
prints the raw env string, not the parsed value's `Debug`.

The three flags each binary already redacts (the Access client id and secret,
and the S3 secret access key) now set `hide_env_values`, so the help still
names the variable but not its value. A test per binary sets all three, renders
the long help, and checks that the names are there and the values are not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
llvm-cov lists the absolute source paths baked into the coverage map, and a
root is matched against them with `Path::starts_with`, so a relative
`--source-dir` such as `.` or `mega-evm` matched nothing and failed every block
in `backfill` and `report` although it named the right directory. It also gave
`.` the label `"."`, so the universe stamp depended on how the root was
spelled.

Roots now go through `std::path::absolute` when they are resolved. It does not
resolve symlinks, which keeps roots matching as spelled, and for the same
reason it keeps `..`; no listed path contains `..`, so a root that still does
is refused by name rather than failing later as an unmatched root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread bin/debug-trace-server/src/main.rs Fixed
Comment thread bin/debug-trace-server/src/main.rs Fixed
Comment thread bin/stateless-validator/tests/integration.rs Fixed
Comment thread bin/stateless-validator/tests/integration.rs Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9578055b61

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +35 to +39
.filter(|l| {
l.starts_with("release:") || l.starts_with("host:") || l.starts_with("LLVM version:")
})
.collect::<Vec<_>>()
.join(";");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the rustc commit in the coverage namespace

When scans are built with two nightly snapshots that share release:, host, and LLVM version, this filter emits the same COVERAGE_RUSTC_VERSION even though rustc -vV also exposes distinct commit-hash/commit-date values and compiler changes can alter MIR coverage mapping. Unlike the earlier optimization-profile concern, this is a compiler-version change. Resuming into the same data directory then retains old Ok bitmaps and profiles alongside counters from the new compiler; include the commit hash (or the complete -vV output) in the fingerprint so this starts a new coverage namespace.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a7fe2a1. The toolchain fingerprint now keeps commit-hash: alongside release:, host: and LLVM version: (bin/coverage-replayer/build.rs:37), so two nightlies of the same release cycle no longer share a namespace. A store written by an earlier build is refused by this one.

Comment on lines +475 to +478
let codes = client
.get_codes(&missing, true)
.await
.map_err(|e| eyre::eyre!("fetch {} bytecodes: {e}", missing.len()))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound bytecode fetch concurrency

With the default 32 fetch tasks, an uncached block that references N distinct contract hashes reaches this call with all N hashes at once. RpcClient::new leaves the data-request semaphore unlimited, and get_codes starts every hash through try_join_all, so this can burst to 32×N concurrent eth_getCodeByHash calls against one endpoint. Gate or batch these code fetches (and expose a data concurrency limit) so a high-fan-out block cannot trigger gateway rate limits and stall the scan in its infinite retry loop.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 42e49d4. backfill now builds its client with data_max_concurrent_requests, set by a new --data-max-concurrent-requests flag (env COVERAGE_REPLAYER_DATA_MAX_CONCURRENT_REQUESTS, default 64, at least 1): bin/coverage-replayer/src/backfill.rs:96, :259. The cap covers block and bytecode requests together, so the get_codes fan-out at backfill.rs:502 queues on the same semaphore instead of bursting. Test: data_requests_are_capped_by_default (backfill.rs:1035).

Comment thread bin/coverage-replayer/src/backfill.rs Outdated
Comment on lines +166 to +168
Self::Range(r) => store.blocks(r.clone(), |n, record| {
found.insert(n, record.status);
})?,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stream range statuses instead of materializing the block table

On a resumed full-history range, this path inserts every BLOCKS row into a HashMap solely to decide whether it is Ok; it then separately materializes the remaining block numbers in todo. With tens of millions of completed rows, the status map alone consumes hundreds of megabytes before any worker starts and can prevent the documented resumable scan from running. Stream the range records while deriving pending work, or use bounded/batched status lookups rather than retaining every status.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 06b5732. Selection::pending (bin/coverage-replayer/src/backfill.rs:166) walks a range against the store's rows in block order and yields the pending blocks and the retry count directly, so no per-row status map is built; only the pending list itself is held. Block lists are still looked up point by point, since they are pool-sized. Test: pending_skips_only_clean_replays (backfill.rs:1044), which fails on each of three mutants of the walk.

flyq and others added 4 commits September 27, 2026 13:29
CodeQL's cleartext-logging query reads a binding named `secrets` as sensitive
data and flagged the `assert!` messages that name each variable. The table
holds variable names and dummy values, and the messages print only the names;
calling it `vars` says that and keeps the query quiet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The toolchain part of `binary_id` kept only the `release:`, `host:` and
`LLVM version:` lines of `rustc -vV`. Every nightly of a release cycle shares
`release:` (1.95.0-nightly for all of them) and often the LLVM version, so two
different compilers produced the same namespace, and a store could be resumed
by a build whose compiler maps coverage differently. `commit-hash:` now joins
the fingerprint.

A store written by an earlier build of this branch is refused by this one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`backfill` built its RPC client with the default config, which leaves the
data-request semaphore unlimited, and a block fetches every bytecode it lacks
in one `try_join_all`. With 32 block fetches in flight, a stretch of blocks
that each reference many new contracts could put 32 x N `eth_getCodeByHash`
calls on one endpoint at once, enough to hit its rate limit and stall the scan
in its retry loop.

`--data-max-concurrent-requests` (env COVERAGE_REPLAYER_DATA_MAX_CONCURRENT_REQUESTS,
default 64, at least 1) now caps block and bytecode requests together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resuming a range inserted every stored row of it into a HashMap only to tell
clean replays from the rest, then built the pending list from the selection
in a second pass. On a resumed full-history range that is tens of millions of
rows, hundreds of megabytes before any worker starts.

`Selection::pending` walks the range against the store's rows in block order
and yields the pending blocks, in order, with the count that have a record to
retry; a block list is still looked up point by point, since lists are
pool-sized. `Selection::iter`, whose only caller was the old filter, is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants