Skip to content

chore: aggregate nitro fixes for alphanet deploy - #976

Closed
0xOsiris wants to merge 17 commits into
mainfrom
nitro/alphanet-bundle
Closed

chore: aggregate nitro fixes for alphanet deploy#976
0xOsiris wants to merge 17 commits into
mainfrom
nitro/alphanet-bundle

Conversation

@0xOsiris

@0xOsiris 0xOsiris commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Deploy vehicle: main + #972 + #973 + #974, merged with no conflicts. This is the branch sha-6ed502b was built from and pinned in crypto-apps#751.

This overlaps the three PRs below — review those, not this one. Opened for traceability of what shipped to alphanet. Close it if you'd rather land the parts individually.

PR What
#972 nitro-worker pre-warms its own CertManager cert chain before registerKey
#973 registration retries on a backon backoff instead of exiting the process
#974 just proof-verify-pcrs — asserts the running enclave's PCR set is approved

Also carries one commit of its own: the approved PCR set recorded into pkg/contracts/deployments/alphanet-nitro.json by just proof-approve-pcrs.

Why these three together

They're the three stacked blockers behind alphanet producing zero proofs — 33 games, all with proofBitmap = 0. Fixing any one alone leaves it broken:

  1. Cert cache goes cold every ~3h (AWS leaf rotation) → inverse hint underflow. fix(nitro): pre-warm the CertManager cert chain before registerKey #972.
  2. That failure exits the worker → CrashLoopBackOff → no exec path to fix it. fix(nitro-worker): retry registration instead of crashlooping #973.
  3. Each EIF rollout changes PCR0/PCR2 and nothing re-approves them. feat: add proof-verify-pcrs target #974 detects it.

Verification

  • cargo check + clippy -D warnings clean for x86_64-unknown-linux-gnu --features enclave
  • 51 nitro tests pass
  • All seven images built green

🤖 Generated with Claude Code


Note

Medium Risk
Changes L1 registration behavior (extra cert-verify txs and long-lived retry loops) and proof deploy gates; mistakes could delay job leasing or block setup until PCRs are approved, but scope is Nitro worker/ops tooling rather than core chain consensus.

Overview
Unblocks alphanet Nitro proof workers by fixing three operational failure modes that left games with no TEE proofs.

CertManager self-prewarm before registerKey: Adds a prewarm module that builds an ordered verify plan from the enclave attestation’s AWS cert chain (cache keys, P-384 hints). register_enclave_key discovers CertManager via the registry’s verifier, submits missing verifyCACertWithHints / verifyClientCertWithHints txs, and handles peer races and expired cached certs—so leaf rotation (~3h) no longer requires a manual proof-certmanager-prewarm for registration.

Resilient --auto-register: The nitro-worker retries registration with jittered exponential backoff (unbounded, up to 5 min) instead of exiting on failure; it stays up without leasing jobs until registration succeeds or Ctrl-C. New metrics expose enclave_key.registered (initialized to 0) and registration attempt outcomes.

PCR drift detection in deploy: just proof-verify-pcrs measures the running enclave via proof-get-pcrs and asserts isPCRSetApproved on L1; proof-setup runs it as phase 3c after approve. Alphanet deployment JSON records an additional approved PCR set from the latest approve step.

Reviewed by Cursor Bugbot for commit 54d28a9. Configure here.

0xOsiris and others added 17 commits August 4, 2026 11:38
`registerKey` reaches `NitroValidator.validateAttestationWithHints`, which re-walks the
attestation's certificate bundle via `verifyCachedCertBundle` passing **empty** hint
streams. That only succeeds on certificates already in the `CertManager` cache; an
uncached cert falls through to signature verification against an empty hint stream and
reverts with "inverse hint underflow" — regardless of how good `attestationSigHints` is.

AWS rotates the enclave leaf certificate roughly every three hours, so the cache goes cold
on its own and the pre-warm had to be re-run by hand inside that window. In practice it
never was: the alphanet registry has emitted zero `SignerRegistered` events, and the worker
crashlooped on this revert with a leaf cached ~19h earlier and one intermediate CA that had
expired 11 minutes before the pod started.

The worker now pre-warms its own chain. `prewarm::build_prewarm_plan` turns an attestation
into the ordered `verifyCACertWithHints` / `verifyClientCertWithHints` calls needed, and
`register_enclave_key` submits the uncached ones before `registerKey`. The CertManager
address is discovered via `registry.verifier().certManager()`, so there is no second
address to configure and drift.

Details:
- The pinned AWS root is written into the cache by the CertManager constructor, so it never
  needs a transaction — it only seeds the parent hash for the next cert in the chain.
- Cache keys mirror `CertManager._certCacheKey`: keccak over the TBSCertificate element for
  every cert, and the pinned constant for the root.
- A cert that is cached but expired cannot be re-verified (`_verifyCert` short-circuits on
  the cache and reverts "cert expired"), so that case fails with an explicit message
  pointing at the real fix rather than an opaque revert later.
- A peer replica caching the same cert between the check and the submit is treated as
  success, not an error.

Verified: 47 nitro tests pass (7 new); `cargo check` and `clippy -D warnings` clean for
x86_64-unknown-linux-gnu with `--features enclave`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--auto-register` treated on-chain registration as a startup gate: any failure propagated
out of `run()` and exited the process. Every way registration can fail — PCR set not yet
approved, registration key unfunded, L1 unreachable, certificate chain not yet verifiable —
is a condition an operator resolves while the worker is running, so exiting turns all of
them into CrashLoopBackOff.

Worse, it destroys the path needed to fix them: `just proof-get-attestation`,
`proof-certmanager-prewarm` and `proof-register-key` all exec into the container and
require it to be in `Running` state. The failure blocked its own remedy — observed on
alphanet as 34 restarts with no way to inspect the enclave.

Registration now retries on a `backon` exponential backoff (5s → 5m, jittered, unbounded),
raced against ctrl-c so a pod being rolled does not wait out a full interval. The worker
stays up, keeps serving metrics, and simply does not lease proof jobs until the key is
registered — proofs signed by an unregistered key would not verify, so an unregistered
worker must be inert, not absent.

Jitter is load-bearing: replicas share one funding key, so un-jittered retries collide on
the same nonce every interval and fail as a group.

Adds two metrics so this is visible rather than inferred:
- `enclave_key.registered` gauge, published as 0 before the first attempt so "never
  registered" is a zero rather than an absent series a threshold monitor would ignore.
- `enclave_key.registration_attempts{outcome}` counter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The enclave image is built from a mutable tag, so every EIF rebuild changes PCR0 (enclave
image) and PCR2 (application) while PCR1 (kernel) stays put. Nothing re-approves the new
measurements, so the running enclave silently drops off the on-chain allowlist and every
registerKey / TEE proof reverts — with no signal until someone reads a revert reason.

This is not hypothetical: on alphanet the running enclave measures
PCR0=4f1adb…/PCR2=e89ada… while the verifier only approves b94235…/f0e0ed…, and
`isPCRSetApproved` returns false.

`just proof-verify-pcrs` measures the enclave that is actually running and asserts its PCR
set is approved on-chain, printing both sets side by side and the remediation command when
it is not. It deliberately re-measures rather than trusting PCR0/1/2 from the shell —
trusting the shell would reproduce exactly the drift this is meant to catch.

Wired into `proof-setup` as Step 3c so setup verifies rather than assumes (skipped under
dry_run, which approves nothing). Also documents the mutable-tag hazard where
PROOF_NITRO_IMAGE is defined — note that variable is currently declarative only, so
pinning a digest there does not by itself prevent drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@0xOsiris 0xOsiris closed this Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Base and PR measured on the same runner in the same workflow run.

Benchmark Base PR Change
flashblock_validation_launch_flashblock_sequence_eth_transfers/stream/4fb_x_125tx 29.1±0.10ms 29.5±1.12ms 🟡 +2.00%
flashblock_validation_launch_flashblock_sequence_eth_transfers/stream/4fb_x_250tx 60.1±0.43ms 59.3±0.97ms 🟢 -0.99%
flashblock_validation_launch_flashblock_sequence_eth_transfers/stream/4fb_x_50tx 12.5±0.19ms 12.3±0.10ms 🟢 -1.96%
flashblock_validation_launch_flashblock_sequence_eth_transfers_with_bal/stream/4fb_x_125tx 35.5±0.72ms 36.9±2.20ms 🟡 +4.00%
flashblock_validation_launch_flashblock_sequence_eth_transfers_with_bal/stream/4fb_x_250tx 76.7±1.43ms 94.7±7.50ms 🔴 +24.00%
flashblock_validation_launch_flashblock_sequence_eth_transfers_with_bal/stream/4fb_x_50tx 14.8±0.18ms 14.8±0.62ms 🟢 +0.00%
flashblock_validation_launch_flashblock_sequence_fib/stream/4fb_x_125tx 27.6±0.16ms 30.5±3.39ms 🔴 +11.00%
flashblock_validation_launch_flashblock_sequence_fib/stream/4fb_x_250tx 54.1±0.14ms 54.0±0.46ms 🟢 +0.00%
flashblock_validation_launch_flashblock_sequence_fib/stream/4fb_x_50tx 11.6±0.04ms 13.5±1.99ms 🔴 +16.00%
flashblock_validation_launch_flashblock_sequence_fib_with_bal/stream/4fb_x_125tx 29.6±0.15ms 30.5±0.22ms 🟡 +3.00%
flashblock_validation_launch_flashblock_sequence_fib_with_bal/stream/4fb_x_250tx 56.0±0.21ms 57.8±1.16ms 🟡 +3.00%
flashblock_validation_launch_flashblock_sequence_fib_with_bal/stream/4fb_x_50tx 13.6±0.21ms 12.9±0.07ms 🟢 -4.76%
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254/stream/4fb_x_10tx 136.8±0.36ms 142.2±1.65ms 🟡 +4.00%
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254/stream/4fb_x_12tx 163.5±0.31ms 165.8±4.44ms 🟡 +1.00%
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254/stream/4fb_x_5tx 69.0±0.09ms 69.1±0.44ms 🟢 +0.00%
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254_with_bal/stream/4fb_x_10tx 23.9±1.39ms 27.8±1.29ms 🔴 +17.00%
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254_with_bal/stream/4fb_x_12tx 25.3±1.63ms 26.8±1.02ms 🟡 +6.00%
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254_with_bal/stream/4fb_x_5tx 19.1±0.70ms 21.4±2.76ms 🔴 +12.00%
flashblock_validation_process_flashblock_eth_transfers/txs/1000 49.4±0.29ms 54.6±3.25ms 🔴 +11.00%
flashblock_validation_process_flashblock_eth_transfers/txs/50 2.7±0.03ms 2.7±0.02ms 🟢 -1.96%
flashblock_validation_process_flashblock_eth_transfers/txs/500 25.0±0.11ms 27.5±3.73ms 🔴 +10.00%
flashblock_validation_process_flashblock_eth_transfers_with_bal/txs/1000 50.4±0.12ms 51.8±1.49ms 🟡 +3.00%
flashblock_validation_process_flashblock_eth_transfers_with_bal/txs/50 3.1±0.09ms 4.6±0.59ms 🔴 +48.00%
flashblock_validation_process_flashblock_eth_transfers_with_bal/txs/500 25.6±0.07ms 26.3±0.81ms 🟡 +3.00%
flashblock_validation_process_flashblock_fib/txs/1000 48.5±0.26ms 49.0±0.71ms 🟡 +1.00%
flashblock_validation_process_flashblock_fib/txs/50 2.6±0.00ms 3.4±0.56ms 🔴 +32.00%
flashblock_validation_process_flashblock_fib/txs/500 24.3±0.06ms 26.7±2.01ms 🔴 +10.00%
flashblock_validation_process_flashblock_fib_with_bal/txs/1000 49.0±0.04ms 52.0±2.52ms 🟡 +6.00%
flashblock_validation_process_flashblock_fib_with_bal/txs/50 3.1±0.04ms 3.5±0.43ms 🔴 +14.00%
flashblock_validation_process_flashblock_fib_with_bal/txs/500 25.0±0.06ms 26.4±1.50ms 🟡 +6.00%
flashblock_validation_process_flashblock_world_id_like_bn254/txs/10 34.0±0.03ms 39.3±5.27ms 🔴 +16.00%
flashblock_validation_process_flashblock_world_id_like_bn254/txs/25 84.4±0.19ms 87.5±2.93ms 🟡 +4.00%
flashblock_validation_process_flashblock_world_id_like_bn254/txs/50 168.8±0.58ms 171.4±3.39ms 🟡 +2.00%
flashblock_validation_process_flashblock_world_id_like_bn254_with_bal/txs/10 6.4±0.45ms 5.6±0.39ms 🟢 -12.28%
flashblock_validation_process_flashblock_world_id_like_bn254_with_bal/txs/25 7.8±0.10ms 7.8±0.14ms 🟡 +1.00%
flashblock_validation_process_flashblock_world_id_like_bn254_with_bal/txs/50 12.6±2.02ms 14.1±0.39ms 🔴 +12.00%

🔴 regression > 10%  ·  🟡 slower  ·  🟢 faster. A regression > 10% fails the check.

Raw critcmp output
group                                                                                              base                                   pr
-----                                                                                              ----                                   --
flashblock_validation_launch_flashblock_sequence_eth_transfers/stream/4fb_x_125tx                  1.00     29.1±0.10ms        ? ?/sec    1.02     29.5±1.12ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_eth_transfers/stream/4fb_x_250tx                  1.01     60.1±0.43ms        ? ?/sec    1.00     59.3±0.97ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_eth_transfers/stream/4fb_x_50tx                   1.02     12.5±0.19ms        ? ?/sec    1.00     12.3±0.10ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_eth_transfers_with_bal/stream/4fb_x_125tx         1.00     35.5±0.72ms        ? ?/sec    1.04     36.9±2.20ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_eth_transfers_with_bal/stream/4fb_x_250tx         1.00     76.7±1.43ms        ? ?/sec    1.24     94.7±7.50ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_eth_transfers_with_bal/stream/4fb_x_50tx          1.00     14.8±0.18ms        ? ?/sec    1.00     14.8±0.62ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_fib/stream/4fb_x_125tx                            1.00     27.6±0.16ms        ? ?/sec    1.11     30.5±3.39ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_fib/stream/4fb_x_250tx                            1.00     54.1±0.14ms        ? ?/sec    1.00     54.0±0.46ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_fib/stream/4fb_x_50tx                             1.00     11.6±0.04ms        ? ?/sec    1.16     13.5±1.99ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_fib_with_bal/stream/4fb_x_125tx                   1.00     29.6±0.15ms        ? ?/sec    1.03     30.5±0.22ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_fib_with_bal/stream/4fb_x_250tx                   1.00     56.0±0.21ms        ? ?/sec    1.03     57.8±1.16ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_fib_with_bal/stream/4fb_x_50tx                    1.05     13.6±0.21ms        ? ?/sec    1.00     12.9±0.07ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254/stream/4fb_x_10tx             1.00    136.8±0.36ms        ? ?/sec    1.04    142.2±1.65ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254/stream/4fb_x_12tx             1.00    163.5±0.31ms        ? ?/sec    1.01    165.8±4.44ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254/stream/4fb_x_5tx              1.00     69.0±0.09ms        ? ?/sec    1.00     69.1±0.44ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254_with_bal/stream/4fb_x_10tx    1.00     23.9±1.39ms        ? ?/sec    1.17     27.8±1.29ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254_with_bal/stream/4fb_x_12tx    1.00     25.3±1.63ms        ? ?/sec    1.06     26.8±1.02ms        ? ?/sec
flashblock_validation_launch_flashblock_sequence_world_id_like_bn254_with_bal/stream/4fb_x_5tx     1.00     19.1±0.70ms        ? ?/sec    1.12     21.4±2.76ms        ? ?/sec
flashblock_validation_process_flashblock_eth_transfers/txs/1000                                    1.00     49.4±0.29ms        ? ?/sec    1.11     54.6±3.25ms        ? ?/sec
flashblock_validation_process_flashblock_eth_transfers/txs/50                                      1.02      2.7±0.03ms        ? ?/sec    1.00      2.7±0.02ms        ? ?/sec
flashblock_validation_process_flashblock_eth_transfers/txs/500                                     1.00     25.0±0.11ms        ? ?/sec    1.10     27.5±3.73ms        ? ?/sec
flashblock_validation_process_flashblock_eth_transfers_with_bal/txs/1000                           1.00     50.4±0.12ms        ? ?/sec    1.03     51.8±1.49ms        ? ?/sec
flashblock_validation_process_flashblock_eth_transfers_with_bal/txs/50                             1.00      3.1±0.09ms        ? ?/sec    1.48      4.6±0.59ms        ? ?/sec
flashblock_validation_process_flashblock_eth_transfers_with_bal/txs/500                            1.00     25.6±0.07ms        ? ?/sec    1.03     26.3±0.81ms        ? ?/sec
flashblock_validation_process_flashblock_fib/txs/1000                                              1.00     48.5±0.26ms        ? ?/sec    1.01     49.0±0.71ms        ? ?/sec
flashblock_validation_process_flashblock_fib/txs/50                                                1.00      2.6±0.00ms        ? ?/sec    1.32      3.4±0.56ms        ? ?/sec
flashblock_validation_process_flashblock_fib/txs/500                                               1.00     24.3±0.06ms        ? ?/sec    1.10     26.7±2.01ms        ? ?/sec
flashblock_validation_process_flashblock_fib_with_bal/txs/1000                                     1.00     49.0±0.04ms        ? ?/sec    1.06     52.0±2.52ms        ? ?/sec
flashblock_validation_process_flashblock_fib_with_bal/txs/50                                       1.00      3.1±0.04ms        ? ?/sec    1.14      3.5±0.43ms        ? ?/sec
flashblock_validation_process_flashblock_fib_with_bal/txs/500                                      1.00     25.0±0.06ms        ? ?/sec    1.06     26.4±1.50ms        ? ?/sec
flashblock_validation_process_flashblock_world_id_like_bn254/txs/10                                1.00     34.0±0.03ms        ? ?/sec    1.16     39.3±5.27ms        ? ?/sec
flashblock_validation_process_flashblock_world_id_like_bn254/txs/25                                1.00     84.4±0.19ms        ? ?/sec    1.04     87.5±2.93ms        ? ?/sec
flashblock_validation_process_flashblock_world_id_like_bn254/txs/50                                1.00    168.8±0.58ms        ? ?/sec    1.02    171.4±3.39ms        ? ?/sec
flashblock_validation_process_flashblock_world_id_like_bn254_with_bal/txs/10                       1.14      6.4±0.45ms        ? ?/sec    1.00      5.6±0.39ms        ? ?/sec
flashblock_validation_process_flashblock_world_id_like_bn254_with_bal/txs/25                       1.00      7.8±0.10ms        ? ?/sec    1.01      7.8±0.14ms        ? ?/sec
flashblock_validation_process_flashblock_world_id_like_bn254_with_bal/txs/50                       1.00     12.6±2.02ms        ? ?/sec    1.12     14.1±0.39ms        ? ?/sec

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.

1 participant