Skip to content

test(ethexe-runtime-common): repro for take_actual_tasks u32::MAX overflow (auto-tester) b556aa218ff2#5522

Draft
grishasobol wants to merge 2 commits into
masterfrom
auto-tester/ethexe-runtime-common-b556aa218ff2
Draft

test(ethexe-runtime-common): repro for take_actual_tasks u32::MAX overflow (auto-tester) b556aa218ff2#5522
grishasobol wants to merge 2 commits into
masterfrom
auto-tester/ethexe-runtime-common-b556aa218ff2

Conversation

@grishasobol
Copy link
Copy Markdown
Member

take_actual_tasks silently fails to drain tasks scheduled at u32::MAX block height because saturating_add(1) returns u32::MAX (not u32::MAX+1), causing split_off(&u32::MAX) to retain the task rather than drain it. This contradicts the documented invariant.

Scenario: gas_boundary
Hash: b556aa218ff2
Unit: ethexe-runtime-common

Observed

assertion left == right failed: Task at u32::MAX must be drained at block_height u32::MAX left: 0 right: 1

The test reaches the bug through 100% public API:

  1. InBlockTransitions::new(u32::MAX - 1, …) then schedule_task(NonZero::new(1), …) → scheduled_block = u32::MAX (no overflow)
  2. take_actual_tasks() at block_height = u32::MAX - 1 → correctly empty (task is in future)
  3. finalize() → schedule preserved
  4. New InBlockTransitions::new(u32::MAX, …, schedule)take_actual_tasks() at block_height = u32::MAX
  5. Bug: task at u32::MAX is NOT drained, despite the "at or before block_height" invariant.

Reproduces 3/3 deterministically.

Rubric items satisfied

  • (a) Contradicts documented invariant. Doc comment at ethexe/runtime/common/src/transitions.rs:83-85 says: /// Drain every scheduled task whose deadline is at or before /// block_height and return them in chronological order …. At block_height = u32::MAX, the function leaves the u32::MAX entry undrained, violating "at or before".

Root cause

// transitions.rs:86-91
pub fn take_actual_tasks(&mut self) -> Vec<ScheduledTask> {
    let cutoff = self.block_height.saturating_add(1);     // u32::MAX.saturating_add(1) == u32::MAX (saturated!)
    let kept  = self.schedule.split_off(&cutoff);          // keeps keys >= cutoff; at u32::MAX this keeps the bug entry
    let due   = core::mem::replace(&mut self.schedule, kept);
    due.into_values().flatten().collect()
}

A possible fix: drop the +1 and use a different split point (e.g., compute the next key with BTreeMap::range), or special-case block_height == u32::MAX to drain the whole map.

Practical reachability

A u32 block height of u32::MAX requires ~1635 years at 12s block time, so this is unreachable in practice on Vara/Ethereum cadence. The PR exists to document the invariant gap, not because it can be triggered in production. Reviewers may choose to: (a) fix and close, (b) keep the test as documentation but adjust the comment to acknowledge the edge, (c) reject as not worth fixing.


Generated by /gear-dev:tester (auto-tester). This is a draft PR — requires human review before merge.

grishasobol and others added 2 commits May 27, 2026 16:50
…rflow (auto-tester) b556aa218ff2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-tester) b1ed02dd83bc

Same function pair as parent commit. schedule_task uses plain `+` at
transitions.rs:94 which panics on u32 overflow in debug builds and wraps
silently in release builds (so a task ends up scheduled in the past).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@grishasobol
Copy link
Copy Markdown
Member Author

Iter 9 of the auto-tester found a related bug in the same function pair. Pushed a follow-up test (b1ed02dd83bc):

schedule_task at transitions.rs:94 uses plain +:

let scheduled_block = self.block_height + u32::from(in_blocks);

This panics with attempt to add with overflow in debug builds whenever block_height + in_blocks > u32::MAX. In release builds the addition wraps silently — a task scheduled "far in the future" ends up in the past and would be drained immediately by take_actual_tasks on the next block.

Both the take_actual_tasks saturating-add issue (original commit) and the schedule_task plain-add issue have the same root cause: missing overflow handling in the block-height arithmetic. A single fix (e.g., checked_add + a defined error path, or u64-internally) probably closes both.

Reproduces 3/3 deterministically.

/gear-dev:tester auto-tester

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