fix: advance(batch_size=0) must not brick the queue (#199) - #234
Merged
k-deejah merged 1 commit intoSep 3, 2026
Merged
Conversation
advance() previously had no guard against batch_size == 0: the for-loop
body (`for _ in 0..batch_size`) never executed, so nothing advanced, but
the function still fell through to the idx storage write — pure wasted
Soroban fee budget for zero work done.
That specific instance is otherwise harmless in the current code, because
this contract already gates the AdvancementActive transition on
`!advanced.is_empty()` (line ~315) — so an all-zero-result advance() no
longer permanently bricks the queue the way the issue originally
described. But the contract still silently accepted a call that can never
do anything useful, and the acceptance criteria explicitly asks for the
operator-facing panic plus a status-preserving guarantee, so:
- Added `if batch_size == 0 { panic!("batch_size_must_be_positive") }` at
the very top of advance(), before any storage read or write — a caller
that mistypes 0 finds out immediately instead of paying to no-op, and
the panic happens before config/admin are even loaded, so there's
nothing left to accidentally mutate.
- Added `test_advance_zero_batch_size_panics`
(`#[should_panic(expected = "batch_size_must_be_positive")]`).
- Added `test_advance_zero_batch_size_does_not_change_queue_status`,
which catches the panic via `std::panic::catch_unwind` (the same
pattern already used elsewhere in this test file), asserts the queue is
still EnrollmentClosed afterward, and then proves the queue is still
advanceable with a real batch size — i.e. advance(0) can't brick it
even implicitly.
On the contributor note's open question — whether an all-cancelled batch
(loop runs, `advanced` stays empty, only `Skipped` events fire) should
transition to AdvancementActive: left as-is (it does not, per the
existing `!advanced.is_empty()` guard), since a queue where every
position in the sampled range was pre-cancelled hasn't actually
progressed and re-running with a different admin/idx state should still
be treated as "still closed, nothing done" rather than silently
committing to AdvancementActive on cancellations alone.
Verification: no local Rust toolchain available in this environment
(link.exe fails on proc-macro2/quote build scripts, unrelated to this
change — same limitation hit on other Rust work this session), so
`cargo test -p lineproof-queue` could not be run here. Verified instead
by close manual review: the added guard is a single early-return panic
with no interaction with any other code path, `test_advance_closed_queue_panics`
and the rest of the existing `advance()` test suite are untouched and
call `advance()` with batch sizes >= 1 throughout, and the new
catch_unwind test reuses an already-proven-working pattern from this
same file (see the pre-existing `catch_unwind` test elsewhere in
test.rs).
|
@Obiajulu-gif is attempting to deploy a commit to the Deejah Team on Vercel. A member of the Team first needs to authorize it. |
2 tasks
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
advance()had no guard againstbatch_size == 0. Withbatch_size = 0thefor _ in 0..batch_sizeloop body never executes — nothing advances — but the function still fell through to write theidxstorage key, wasting Soroban fee budget for zero work.The original issue also described this as bricking the queue permanently in
AdvancementActivewith no way to advance. That specific consequence is already avoided in the current code — this contract gates theAdvancementActivetransition on!advanced.is_empty()— so a zero-resultadvance()no longer commits the status change. But the contract still silently accepted a call that can never do anything useful, and the issue's acceptance criteria explicitly wants the operator-facing panic plus a status-preserving guarantee proven by test, so:Changes
if batch_size == 0 { panic!("batch_size_must_be_positive") }at the very top ofadvance(), before any storage read or write (beforeconfig/adminare even loaded) — a caller that passes0finds out immediately, and there's nothing left to accidentally mutate.test_advance_zero_batch_size_panics(#[should_panic(expected = "batch_size_must_be_positive")]).test_advance_zero_batch_size_does_not_change_queue_status, which catches the panic viastd::panic::catch_unwind(the same pattern already used elsewhere intest.rs), asserts the queue is stillEnrollmentClosedafterward, and then proves the queue is still advanceable with a real batch size — i.e.advance(0)can't brick it even implicitly.The contributor note's open question
Whether an all-cancelled batch (loop runs,
advancedstays empty, onlySkippedevents fire) should transition toAdvancementActive: left as-is — it does not, per the existing!advanced.is_empty()guard. A queue where every position in the sampled range was pre-cancelled hasn't actually progressed, so treating it as "still closed, nothing done" rather than silently committing toAdvancementActiveon cancellations alone seemed like the safer default; happy to discuss if the intended behavior is different.Acceptance Criteria
advance(env, admin, 0)panics with"batch_size_must_be_positive"AdvancementActivewhenadvance()panics on validation#[should_panic(expected = "batch_size_must_be_positive")]test addedAdvancementActiveonly occurs when loop produces ≥1 result (pre-existing, verified untouched)batch_size == 0path)cargo test -p lineproof-queuepasses with zero failures — could not run locally, see verification noteVerification
No local Rust toolchain is available in this environment (
link.exefails compilingproc-macro2/quotebuild scripts — unrelated to this change). Verified instead by close manual review: the added guard is a single early-return panic with no interaction with any other code path, the rest of the existingadvance()test suite is untouched and callsadvance()with batch sizes ≥ 1 throughout, and the newcatch_unwindtest reuses an already-proven-working pattern from elsewhere in the same file. Would appreciate CI/a reviewer confirmingcargo test -p lineproof-queuelocally.Closes #199