feat(aggregation): cap aggregation at one job before our proposal - #544
Conversation
At interval 2 the aggregation worker runs up to MAX_AGGREGATION_JOBS leanVM proofs. When interval 4 of the same slot builds the next slot's block, that build runs its own proofs, so the two contend for the prover — and the build is the one with a hard slot-boundary deadline. Drop the session to a single job whenever one of our validators proposes the next slot, mirroring the propose path's condition (proposer + duties allowed) so a sync-suppressed slot keeps the full budget. The retained job is the best-scoring candidate, so the highest-value coverage survives the cap. Applies to both entry points into start_aggregation_session: the interval-2 tick and the early 2/3-threshold trigger.
🤖 Kimi Code ReviewOverall Assessment: Solid PR with good defensive resource management. The change prevents leanVM prover contention when the node is about to propose, which is critical for block production latency. Issues & Suggestions: 1. Defensive validation for Suggestion: Add an early return or assert: if max_jobs == 0 {
return None;
}Or document the precondition in the function docstring if callers are strictly controlled. 2. Documentation consistency ( 3. Information leak consideration ( 4. Test robustness ( Positive Notes:
Nitpick: // Current:
let next_proposer = self
.get_our_proposer(slot + 1)
.filter(|_| self.sync_status.duties_allowed());
// Alternative (clearer to some readers):
let next_proposer = if self.sync_status.duties_allowed() {
self.get_our_proposer(slot + 1)
} else {
None
};Conclusion: Address Item 1 (defensive check for Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code Review
No other material correctness, security, or memory-safety issues stood out in the touched code. I did not run tests. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
| .get_our_proposer(slot + 1) | ||
| .filter(|_| self.sync_status.duties_allowed()); |
There was a problem hiding this comment.
When the node is syncing at the interval-2 tick but becomes synced before interval 4, aggregation starts with three jobs while the later duty check permits the proposal, causing the original prover contention and potentially delaying block publication.
Knowledge Base Used: Blockchain core: fork choice, state transition, block building, sync
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/src/lib.rs
Line: 499-500
Comment:
**Duty transition bypasses cap**
When the node is syncing at the interval-2 tick but becomes synced before interval 4, aggregation starts with three jobs while the later duty check permits the proposal, causing the original prover contention and potentially delaying block publication.
**Knowledge Base Used:** [Blockchain core: fork choice, state transition, block building, sync](https://app.greptile.com/lambdaclass/-/custom-context/knowledge-base/lambdaclass/ethlambda/-/docs/blockchain-core.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Greptile SummaryThis PR introduces a proposer-aware aggregation budget.
Confidence Score: 3/5The sync-status timing gap should be fixed before merging because it can leave three aggregation jobs competing with a newly enabled proposal. Aggregation and proposal eligibility read mutable sync status at different ticks, so a node that catches up between intervals can start the uncapped worker and still build the next block. Files Needing Attention: crates/blockchain/src/lib.rs
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/lib.rs | Selects the proposer-aware aggregation cap, but checking mutable duty status at session start leaves a syncing-to-synced contention window. |
| crates/blockchain/src/aggregation.rs | Parameterizes the greedy job limit and verifies that both configured caps retain score ordering. |
Sequence Diagram
sequenceDiagram
participant Tick2 as Interval-2 tick
participant Sync as Sync status
participant Agg as Aggregation worker
participant Tick4 as Interval-4 tick
participant Prover as leanVM prover
Tick2->>Sync: "duties_allowed() = false"
Tick2->>Agg: Start with 3 jobs
Sync-->>Tick4: Node becomes synced
Tick4->>Sync: "duties_allowed() = true"
Tick4->>Prover: Build next-slot proposal
Agg->>Prover: Aggregation proofs contend with build
Prompt To Fix All With AI
### Issue 1
crates/blockchain/src/lib.rs:499-500
**Duty transition bypasses cap**
When the node is syncing at the interval-2 tick but becomes synced before interval 4, aggregation starts with three jobs while the later duty check permits the proposal, causing the original prover contention and potentially delaying block publication.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(aggregation): cap aggregation at on..." | Re-trigger Greptile
…r cap Two proofs per session instead of three, and the proposer cap is now a literal `1` at its only use site rather than a named constant, so the rationale lives in the comment next to the decision.
🤖 Claude Code ReviewReview: PR #544 — cap aggregation at one job before our proposalOverall: Sound, well-scoped change. The core fix — threading A few things worth considering:
Nothing here blocks merging; items 1–3 are the most actionable if you want to harden this before it ships. Automated review by Claude (Anthropic) · sonnet · custom prompt |
Collapse the match to an if and drop the throttle log: the decision is a one-liner and the comment carries the reasoning.
Motivation
At interval 2 the aggregation worker runs up to
MAX_AGGREGATION_JOBSleanVM proofs. When interval 4 of the same slot builds the next slot's block, that build runs its own proofs — so the two compete for the prover, and the build is the one with a hard deadline (it has to publish at the next slot's interval-0 tick).Change
start_aggregation_sessiondrops the session to a single job whenever one of our validators proposes the next slot:SlotInterval::EndOfSlot): proposer andduties_allowed(). A slot where duties are sync-suppressed keeps the full job budget, since no build will happen.start_aggregation_session, so it applies to the interval-2 tick and the early 2/3-threshold trigger. The early session is the slot's session, so exempting it would defeat the change.snapshot_aggregation_inputsgained amax_jobsparameter that bounds the greedy selection loop; the pool is unchanged (groups_consideredstill counts every candidate), so the one job we run is the same one the uncapped selection picks first.MAX_AGGREGATION_JOBSlowered 3 → 2, trimming baseline prover work per session too.Unchanged:
AGGREGATION_DEADLINE, the early-trigger threshold, and the worker loop.Tests
snapshot_caps_jobs_at_max_aggregation_jobsrefactored to share a store fixture with a newsnapshot_caps_jobs_at_one_for_proposer, which asserts the proposer cap yields exactly one job and that it is the top-scoring candidate (not an arbitrary one).make lintclean,cargo test --workspace --releasegreen (122 fork-choice spec, 119 STF, all unit tests).