Skip to content

fix(types): do not clamp the delayed public mutable time horizon on a scheduled change to the same value - #25586

Merged
iAmMichaelConnor merged 1 commit into
nextfrom
cb/dpm-noop-change-horizon
Oct 4, 2026
Merged

iAmMichaelConnor merged 1 commit into
nextfrom
cb/dpm-noop-change-horizon

Conversation

@AztecBot

@AztecBot AztecBot commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

Closes AztecProtocol/aztec-claude#2425

Problem

ScheduledValueChange::get_time_horizon treats any pending scheduled change as a bound on how long the value read at the anchor block stays current, clamping the horizon to timestamp_of_change - 1. It does so even when the scheduled post equals pre, i.e. the change does not move the value. That state is produced by the documented cancellation mechanism (scheduling the current value) and by any idempotent setter such as ContractInstanceRegistry::update re-applying the class a contract already runs.

Because the private kernel folds the registry entry's horizon into expiration_timestamp for every private call to that contract, a cancelled or idempotent update pins every caller's deadline to the phantom instant for a full delay period. As the anchor approaches it, the inclusion window shrinks to zero: at anchor == timestamp_of_change - 1 the kernel emits expiration_timestamp == anchor_block_timestamp, which the rollup rejects for every later block.

Fix

In get_time_horizon, take the "no change ahead" branch (anchor_block_timestamp + minimum_delay) when self.pre == self.post. Such a change cannot alter the current value, so the earliest a different value can take effect is bounded by minimum_delay, exactly as when no change is pending. The method gains a T: Eq bound; its caller compute_delayed_public_mutable_time_horizon already requires T: Eq.

The clamp for a genuine pending change (pre != post) is unchanged. Which of pre or post a read returns (get_current_at) is unchanged.

Not covered

A contract that has never been updated has the stored class id 0, which the kernel reads as "use the original class". Cancelling that contract's first update needs update(original class), which stores (0, original, toc). The two values mean the same class but are not equal, so the clamp still applies. This is safe (the horizon is too early, never too late). Covering it needs a kernel-level comparison that maps 0 to the original class id.

Scope and cost

  • Private kernel VKs change for private_kernel_init, private_kernel_inner and their batched variants: +5 ACIR opcodes per private call. private_kernel_reset_tail is unchanged.
  • Genesis does not move. The protocol contracts compile to identical bytecode with and without the change.

Tests (red → green)

  • types: test_get_time_horizon_change_to_same_value_in_near_future asserts the horizon is anchor + minimum_delay and checks the existing horizon invariants. Fails without the fix (returns timestamp_of_change - 1).
  • private_kernel_lib (private_kernel_init), driven through the real registry hint with a rebuilt public-data tree:
    • expiration_timestamp_not_constrained_by_pending_contract_update_to_same_class: a change to the class the contract already runs at anchor + 1 yields expiration_timestamp == anchor + DEFAULT_UPDATE_DELAY - 1. Fails without the fix (emits anchor).
    • expiration_timestamp_constrained_by_pending_contract_update: control pinning that a genuine pending update at anchor + 1 still collapses the deadline to anchor.

The patch applies to next @ 551aa413aec with no changes beyond a 3-line hunk offset. Full types / private_kernel_lib suites and nargo fmt --check passed on the same change against the private mirror of next; CI covers this branch.

Labeled port-to-v6.


Created by claudebox · group: slackbot · requested by Mike (@iAmMichaelConnor) · Slack thread

@AztecBot AztecBot added ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure claudebox Owned by claudebox. it can push to this PR. port-to-v6 labels Oct 4, 2026
@iAmMichaelConnor

Copy link
Copy Markdown
Contributor

I'll nurse the required ports to the various branches

@iAmMichaelConnor
iAmMichaelConnor marked this pull request as ready for review October 4, 2026 16:16
@iAmMichaelConnor
iAmMichaelConnor enabled auto-merge (squash) October 4, 2026 16:16
@iAmMichaelConnor
iAmMichaelConnor merged commit d363a40 into next Oct 4, 2026
31 of 35 checks passed
@iAmMichaelConnor
iAmMichaelConnor deleted the cb/dpm-noop-change-horizon branch October 4, 2026 16:33
@AztecBot AztecBot mentioned this pull request Oct 4, 2026
1 task done
iAmMichaelConnor pushed a commit that referenced this pull request Oct 4, 2026
Rolling integration PR for changes labelled `port-to-v6` on `next`. Each
merged source PR is cherry-picked onto `cb/port-to-v6` as its own
commit, keeping `(#N)` in the title.

## Ported

- [x]
[#25586](#25586)
fix(types): do not clamp the delayed public mutable time horizon on a
scheduled change to the same value (clean pick; testnet re-pin pending)
- All three files end up identical to `next` at d363a40, as do the
`types` and `private-kernel-lib` crates.

## Protocol impact

- `ScheduledValueChange::get_time_horizon` no longer clamps the horizon
when the scheduled value equals the current one, so a cancelled or
idempotent contract update no longer shortens callers'
`expiration_timestamp`. A genuine pending change is clamped as before.
- This changes the `private_kernel_init` and `private_kernel_inner`
circuits (and their batched variants), so the VK tree root that `v6`
builds moves.
- Labs patch
`0001-test-pin-testnet-compatibility-to-the-v6-kernel-VK-t.patch`
currently pins
`0x05b95d9ef57c61d1c6626545101db55ae2d1f98ecb618d1c220610c049ee522b`, so
`testnet_compatibility.test.ts` is expected to fail on this head.
- Following the decision recorded on #25549 (a breaking kernel change
ported to `v6` re-pins testnet in the same PR), the patch is updated
here once CI reports the new root.
- Per the source PR, the protocol contracts compile to identical
bytecode, so genesis does not move.

## Testing

- `./labs-patches/bootstrap.sh check`: the series (0001, 0010) applies
cleanly to the labs pin `a0d73c0aed`.
- Not built or tested locally (no `nargo` in the port environment); the
`types` and kernel unit tests run on this PR's CI.

---
*Created by
[claudebox](https://claudebox.work/v2/sessions/134efca9a11ee14f/jobs/26)
· group: `slackbot` · [Slack
thread](https://aztecprotocol.slack.com/archives/C0AGN2WT3CP/p1790609320989739?thread_ts=1790609320.989739&cid=C0AGN2WT3CP)*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure claudebox Owned by claudebox. it can push to this PR. port-to-v6

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants