feat(proof-gen): request admission limits, per-chain fill budget, single-flight block fills - #1354
DylanVerstraete wants to merge 2 commits into
Conversation
PR SummaryMedium Risk Overview Admission wraps Per-chain cache fills: new Observability: Reviewed by Cursor Bugbot for commit 91725d0. Bugbot is set up for automated code reviews on this repo. Configure here. |
2cb2d0a to
dd09a48
Compare
…gle-flight block fills
Prover liveness audit, finding 6.
max_batch_size bounded the work inside one batch, not how many requests
ran at once, and nothing ever timed a request out. 32 identical cold
proof-by-tx requests produced 32 concurrent tip reads and 32 duplicate
block fetch+builds.
- Admission middleware around the proof endpoints: a process-wide and a
per-chain cap on requests in flight (503 + Retry-After on refusal, code
Overloaded / ChainOverloaded) and an end-to-end deadline per request
(504, code RequestTimeout). /livez, /readyz, /api/v1/health and /metrics
are never limited so probes and recovery keep working under load.
Config: admission.{max_in_flight_requests (64), max_in_flight_per_chain
(32), request_timeout_secs (120)} in YAML, MAX_IN_FLIGHT_REQUESTS /
MAX_IN_FLIGHT_PER_CHAIN / REQUEST_TIMEOUT_SECS in the environment.
- Per-chain block-fill budget: cache.max_concurrent_block_fills (16)
bounds simultaneous source-block fetches for the merkle cache across
requests and the backfill worker together.
- Single-flight fills: concurrent misses for one height share one
fetch+build (leader/followers on a Notify; a failed leader wakes the
followers, which retry as leader themselves). Cached blocks and
processed-empty blocks are served without a fetch.
- Metrics: proof_gen_requests_in_flight gauge and
proof_gen_requests_rejected{reason} counter.
Tests (liveness_admission.rs): 32 simultaneous identical cold requests
with an in-flight budget of 8 → exactly 8 admitted, 24 refused with
Overloaded, peak tip reads ≤ 8, one block fetch; sequential retries all
succeed with no new fetch. A 2 s upstream under a 200 ms deadline → 504.
With a single admission slot occupied, health/livez/readyz/metrics still
answer immediately.
ba297eb to
bb72d48
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bb72d48. Configure here.
Bugbot follow-up on #1354: the leader removed its in_flight_fills entry and woke followers only after its future completed. The admission deadline and try_join! cancel handlers by dropping the future, so a leader cut off mid-fetch left the height parked: every later caller (requests and the backfill worker) waited on a Notify that never fired and proof-by-tx for that block returned 504 forever. Cleanup now lives in a drop guard, so it runs on completion and on cancellation alike; the next caller becomes leader and retries. Test: a 2 s first fetch under a 300 ms deadline → 504, then the next request for the same block succeeds with a second fetch.

Fifth and last PR of the prover liveness audit, finding 6. Stacked on #1353 → #1352 → #1351 → #1350; retarget to
usc-devas those merge.Problem
max_batch_sizebounded the work inside one batch, not how many requests ran at once, and nothing ever timed a request out. The audit fixture sent 32 identical coldproof-by-txrequests and observed 32 concurrent tip reads and 32 duplicate block fetch+builds.Changes
/api/v1/proof*:503+Retry-After: 1withcode: Overloaded | ChainOverloaded,retriable: true504,code: RequestTimeout/livez,/readyz,/api/v1/health,/metricsare never limited, so probes and recovery keep working under loadadmission.{max_in_flight_requests (64), max_in_flight_per_chain (32), request_timeout_secs (120)}in YAML;MAX_IN_FLIGHT_REQUESTS,MAX_IN_FLIGHT_PER_CHAIN,REQUEST_TIMEOUT_SECSenv overrides (both config paths)cache.max_concurrent_block_fills(default 16) bounds simultaneous source-block fetches for the merkle cache across all requests and the backfill worker together.Notify; a failed leader wakes the followers, which retry as leader on their own, so one bad fetch is not broadcast as everyone's error. Cached blocks and processed-empty blocks are served without a fetch (previously every coldproof-by-txon an empty block refetched it).proof_gen_requests_in_flightgauge,proof_gen_requests_rejected{reason}counter.config.example.yamldocuments both knobs.Tests
proof-gen-api-server/tests/liveness_admission.rs:Overloaded), peak concurrent tip reads ≤ 8, one block fetch; 24 sequential retries all 200 with no new fetchRequestTimeoutin < 1 s/api/v1/health,/livez,/readyz,/metricsall answer immediatelycargo test -p proof-gen-api-servergreen (81 unit, 27 route, 3+3+4+2 liveness), clippy-D warnings, fmt.