Skip to content

fix(bots): restructure keeper drawing phase gate to allow mid-phase entry - #2575

Open
salgozino wants to merge 4 commits into
devfrom
fix(contracts)/keeper-drawing-phase-gate
Open

fix(bots): restructure keeper drawing phase gate to allow mid-phase entry#2575
salgozino wants to merge 4 commits into
devfrom
fix(contracts)/keeper-drawing-phase-gate

Conversation

@salgozino

@salgozino salgozino commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

The keeper bot's drawing-phase entry gate was phase-agnostic: hasMinStakingTimePassed() and hasMaxDrawingTimePassed() both read sortition.lastPhaseChange() regardless of the court's current phase, and the outer gate applied the minStakingTime predicate to the entire drawing workflow instead of only the staking -> generating transition. If the court was already in generating or drawing (advanced externally, or after a keeper crash mid-cycle) when a fresh keeper run started, the run could skip the entire drawing/RNG-wait block — and with minStakingTime == maxDrawingTime on non-devnet deployments, the unconditional back-to-staking cleanup could even push the phase back to staking with zero draw() calls, because the keeper's own predicates used strict > while SortitionModule.sol uses >=.

Closes #2574

Changes

contracts/scripts/keeperBot.ts only — this PR is scoped to the production fix. Three rounds, incorporating code review feedback:

Change Why
hasMinStakingTimePassed() / hasMaxDrawingTimePassed(): >>= Matches SortitionModule.sol's own boundary semantics
Drawing-entry gate restructured to be phase-aware: enters the block directly when the court is already generating or drawing and disputes still need jurors, without waiting on minStakingTime minStakingTime only gates the staking -> generating transition per the contract; generating -> drawing only requires RNG readiness
Zero-draw observability warning now counts actual jurors drawn (via getMissingJurors() deltas) instead of requested iterations drawJurors() returns true once its transaction confirms, not based on how many jurors it actually drew, so the warning could be silently suppressed by a fully-stalled run

Test Plan

  • yarn check-types — clean
  • yarn check-style — clean

Note on scope

This PR previously included a new regression test file (contracts/test/arbitration/keeperBot-phase-gate.ts). Per review feedback, those tests reimplemented local copies of the keeper's phase predicates rather than exercising keeperBot.ts itself (it has no exports and self-executes main() on import), so they could pass even if the production fix regressed. Rather than ship tests that don't actually protect this code, the tests move to a follow-up PR that first makes the keeper's phase-entry logic importable/testable, then wires proper regression coverage against the real implementation. That follow-up targets this PR's branch as its base.


PR-Codex overview

This PR enhances the keeperBot.ts functionality by refining phase transitions for juror drawing and improving logging for disputes. It adjusts conditions for entering drawing phases and adds checks for actual jurors drawn, aiming to prevent stalls and improve clarity in dispute processing.

Detailed summary

  • Changed comparison from > to >= for minStakingTime and maxDrawingTime.
  • Introduced enterDrawingBlock logic to handle phase transitions more effectively.
  • Added checks for actual jurors drawn to prevent misleading logs.
  • Implemented warnings for stalls when no jurors are drawn.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • Bug Fixes
    • Updated drawing-time checks to include the exact cutoff time.
    • Allowed drawing to begin during active generating or drawing phases.
    • Preserved the minimum staking-time requirement for staking operations.
    • Improved juror-count tracking and prevented unnecessary empty draw attempts.
    • Added warnings when unresolved disputes remain without newly drawn jurors.

@netlify

netlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploy Preview for kleros-v2-testnet-devtools failed. Why did it fail? →

Name Link
🔨 Latest commit 6995bad
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet-devtools/deploys/6a7b8968216ba300081b32f8

@netlify

netlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit 6995bad
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/6a7b8968181b4600080ae5ea
😎 Deploy Preview https://deploy-preview-2575--kleros-v2-testnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploy Preview for kleros-v2-neo failed. Why did it fail? →

Name Link
🔨 Latest commit 6995bad
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/6a7b89682ab22b00071b6f11

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The keeper bot now uses inclusive timing checks, enters drawing during eligible phases, skips empty draw calls, counts actual newly drawn jurors, and warns when unresolved disputes receive no jurors.

Changes

Keeper drawing workflow

Layer / File(s) Summary
Phase-aware drawing control
contracts/scripts/keeperBot.ts
The keeper uses inclusive timeout checks, supports drawing during generating or drawing phases, skips zero-iteration calls, tracks actual juror additions, and warns when disputes remain unresolved.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: jaybuidl

Poem

A rabbit checks the clock at dawn,
And skips empty draws before moving on.
New jurors count with every run,
Pending disputes meet the sun.
“Hop onward, keeper—your work is done!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the keeper drawing-phase gate change that enables mid-phase entry.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix(contracts)/keeper-drawing-phase-gate

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/scripts/keeperBot.ts`:
- Around line 717-719: Update the enterDrawingBlock condition in
contracts/scripts/keeperBot.ts (lines 717-719) so drawing dispatch is allowed in
generating or drawing, while hasMinStakingTimePassed() is evaluated only for
staking; update contracts/test/arbitration/keeperBot-phase-gate.ts (lines 82-93)
to mirror this phase rule and add coverage for a generating-phase restart with
elapsed time below minStakingTime.
- Around line 760-773: Update the drawing loop around drawJurors() and
drawIterationsTotal so the total counts only jurors actually drawn, by comparing
getMissingJurors(dispute) before and after each confirmed transaction rather
than adding requested iterations. Before the stall warning, re-query unresolved
disputes and use that refreshed result when deciding whether to warn and
building pending IDs.

In `@contracts/test/arbitration/keeperBot-phase-gate.ts`:
- Around line 4-7: Extract the keeper phase-entry policy from keeperBot.ts into
an importable production module, preserving the intended phase-aware dispatch
and >= boundary behavior. Update the regression tests in keeperBot-phase-gate.ts
to invoke that production predicate or dispatcher instead of local copies, while
retaining coverage for the affected phase transitions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ef9139e-087a-4afe-808b-2187bbddd00d

📥 Commits

Reviewing files that changed from the base of the PR and between 5171a80 and 85eaf9e.

📒 Files selected for processing (2)
  • contracts/scripts/keeperBot.ts
  • contracts/test/arbitration/keeperBot-phase-gate.ts

Comment thread contracts/scripts/keeperBot.ts
Comment thread contracts/scripts/keeperBot.ts Outdated
Comment thread contracts/test/arbitration/keeperBot-phase-gate.ts Outdated
salgozino added a commit that referenced this pull request Aug 11, 2026
minStakingTime only gates the staking -> generating transition per
SortitionModule.sol. The generating -> drawing transition only requires
RNG readiness. A keeper that restarts (or resumes after an external actor
advanced the phase) while phase is already generating was wrongly held
behind the minStakingTime check, skipping the RNG-readiness check entirely.

Addresses code review feedback on PR #2575.
salgozino added a commit that referenced this pull request Aug 11, 2026
…ions

drawJurors() returns true once its transaction confirms, not based on how many
jurors it actually drew — its pre-flight probe checks a much larger simulated
horizon (iterations * MAX_DRAW_CALLS_WITHOUT_JURORS) than the real batch it
submits, so a confirmed tx can still draw zero new jurors. Track actual draws
via getMissingJurors() deltas instead of accumulating requested drawIterations,
so the zero-draw stall warning can no longer be silently suppressed by
transactions that succeed without drawing anyone. Also re-query which disputes
are still unresolved before building the warning message.

Verified against KlerosCore.sol: nbVotes is immutable during the draw loop
(draw() requires Period.evidence, the only nbVotes-changing path via appeal
requires Period.appeal — mutually exclusive) and drawnJurors is push-only
within draw(), so the getMissingJurors() delta is always >= 0 and always
equals actual new jurors drawn in that window.

Addresses code review feedback on PR #2575.
minStakingTime only gates the staking -> generating transition per
SortitionModule.sol. The generating -> drawing transition only requires
RNG readiness. A keeper that restarts (or resumes after an external actor
advanced the phase) while phase is already generating was wrongly held
behind the minStakingTime check, skipping the RNG-readiness check entirely.

Addresses code review feedback on PR #2575.
…ions

drawJurors() returns true once its transaction confirms, not based on how many
jurors it actually drew — its pre-flight probe checks a much larger simulated
horizon (iterations * MAX_DRAW_CALLS_WITHOUT_JURORS) than the real batch it
submits, so a confirmed tx can still draw zero new jurors. Track actual draws
via getMissingJurors() deltas instead of accumulating requested drawIterations,
so the zero-draw stall warning can no longer be silently suppressed by
transactions that succeed without drawing anyone. Also re-query which disputes
are still unresolved before building the warning message.

Verified against KlerosCore.sol: nbVotes is immutable during the draw loop
(draw() requires Period.evidence, the only nbVotes-changing path via appeal
requires Period.appeal — mutually exclusive) and drawnJurors is push-only
within draw(), so the getMissingJurors() delta is always >= 0 and always
equals actual new jurors drawn in that window.

Addresses code review feedback on PR #2575.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 11, 2026
@salgozino salgozino self-assigned this Aug 11, 2026
The drawing do-while loop could enter with numberOfMissingJurors already
at 0 (dispute fully drawn externally, e.g. by another keeper instance,
between the pre-loop snapshot and this iteration), calling
drawJurors(dispute, 0) and logging a misleading 'Failed to draw jurors'
error for a non-error condition. Mirrors the existing zero-iterations
guard already used in the executeRepartitions loop.

Found by Judgment Day dual review of the keeper phase-gate fix.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(bots): keeper skips drawing when phase already advanced to drawing before minStakingTime elapses

1 participant