Skip to content

feat(operator-rotation): add timelock and grace period to operator rotation - #27

Merged
Meshmulla merged 1 commit into
stellar-kracken:mainfrom
priscaenoch:feature/8-operator-rotation-timelock
Aug 18, 2026
Merged

feat(operator-rotation): add timelock and grace period to operator rotation#27
Meshmulla merged 1 commit into
stellar-kracken:mainfrom
priscaenoch:feature/8-operator-rotation-timelock

Conversation

@priscaenoch

Copy link
Copy Markdown
Contributor

Summary

Closes #8

Adds a two-step propose/execute rotation flow with a configurable timelock delay and grace period, plus a cancel path, to soroban/src/operator_rotation.rs. Operator rotation controls who can perform sensitive operations, and until now add_operator/remove_operator took effect instantly — this change gives observers a window to detect and react to a malicious or mistaken rotation before it takes effect, as requested in #8.

  • RotationConfig { delay_secs, grace_period_secs }, admin-configurable via set_rotation_config / get_rotation_config, with sane defaults (24h delay, 24h grace period) applied when the admin hasn't configured one.
  • propose_rotation (admin only) stores a PendingRotation (RotationAction::Add(Address, String) or Remove(Address)) with executes_at = now + delay_secs and expires_at = executes_at + grace_period_secs. Only one rotation may be pending at a time, and the action is validated up front (non-empty name for Add; operator must currently be active for Remove).
  • execute_rotation (admin only) reverts with "rotation delay has not elapsed" before executes_at and "rotation grace period has expired" after expires_at (a stale rotation must be cancelled and re-proposed); otherwise it applies the pending Add/Remove and clears the pending state.
  • cancel_rotation is callable by the admin or by any address holding the EmergencyPause permission via the existing acl module — so a pending malicious rotation can be aborted even if the admin key that proposed it is later found to be compromised.
  • get_pending_rotation read view.
  • add_operator/remove_operator are refactored into thin admin-check wrappers around new apply_add_operator/apply_remove_operator internals (identical bodies, unchanged behavior) so execute_rotation can reuse the mutation logic without re-invoking require_auth() on the same address within one call frame (Soroban rejects a repeated require_auth() in the same frame).
  • Events are published for propose (rot_prop), execute (rot_exec), and cancel (rot_cncl), each carrying the full pending-rotation record for observers to monitor.

Acceptance criteria

  • Rotation requires propose → wait → execute, with a configurable delay
  • A pending rotation can be cancelled by an authorized role
  • Executing before the delay elapses reverts
  • Events are emitted for propose/execute/cancel, with tests covering each path

Testing / validation performed

  • 18 new unit tests in operator_rotation.rs's own test module: default/custom config, invalid config rejection, propose validation (empty name, unknown/inactive operator for Remove, already-pending rejection), execute before delay / after grace period reverting, execute succeeding for both Add and Remove after the delay, cancel by admin and by an EmergencyPause-permitted address, cancel by an unauthorized caller reverting, cancel-then-repropose, and event emission across all three actions.
  • 9 new integration tests extending soroban/tests/operator_rotation.test.rs (per the issue's suggestion), exercising the same flows through the crate's public API.
  • Full existing suite passes unchanged (525 → 543 lib tests, 14 → 23 operator_rotation integration tests) — confirms add_operator/remove_operator's existing behavior and every other test are unaffected by the refactor.
  • cargo fmt --all -- --check — clean.
  • cargo clippy --all-targets --all-features -- -D warnings — clean, zero warnings.
  • cargo build --release --target wasm32-unknown-unknown — builds successfully.
  • cargo test (full workspace) — all green.

Issue: #8

…tation

Adds a two-step propose/execute rotation flow with a configurable timelock
delay and grace period, plus a cancel path, so that operator changes (which
control sensitive operations) can no longer take effect instantly. This
gives observers a window to detect and react to a malicious or mistaken
rotation before it takes effect.

What changed (soroban/src/operator_rotation.rs):
- Add `RotationConfig { delay_secs, grace_period_secs }`, admin-configurable
  via `set_rotation_config` / `get_rotation_config`, with sane defaults
  (24h delay, 24h grace period) applied when unset.
- Add `RotationAction::Add(Address, String)` / `Remove(Address)` describing
  the change a pending rotation will apply.
- Add `propose_rotation` (admin only) — stores a `PendingRotation` with
  `executes_at = now + delay` and `expires_at = executes_at + grace_period`.
  Only one rotation may be pending at a time. Validates the action up front
  (non-empty name for Add, operator must be currently active for Remove).
- Add `execute_rotation` (admin only) — reverts with "rotation delay has
  not elapsed" before `executes_at`, and "rotation grace period has
  expired" after `expires_at`; otherwise applies the pending Add/Remove
  and clears the pending state.
- Add `cancel_rotation` — callable by the admin or by any address holding
  the `EmergencyPause` permission (via the existing `acl` module), so a
  pending malicious rotation can be aborted even if the proposing admin
  key is later found to be compromised.
- Add `get_pending_rotation` read view.
- Refactor `add_operator`/`remove_operator` into thin admin-check wrappers
  around new `apply_add_operator`/`apply_remove_operator` internals (same
  bodies, unchanged behavior) so `execute_rotation` can reuse the mutation
  logic without re-invoking `require_auth()` on the same address within one
  call frame.
- Events are published for propose (`rot_prop`), execute (`rot_exec`), and
  cancel (`rot_cncl`), each carrying the full pending-rotation record.

Testing performed:
- 18 new unit tests in `operator_rotation.rs`'s own test module covering:
  default/custom config, invalid config rejection, propose validation
  (empty name, unknown/inactive operator for Remove, already-pending
  rejection), execute before delay / after grace period reverting, execute
  succeeding for both Add and Remove after the delay, cancel by admin and
  by an EmergencyPause-permitted address, cancel by an unauthorized caller
  reverting, cancel-then-repropose, and event emission across all three
  actions.
- 9 new integration tests extending `soroban/tests/operator_rotation.test.rs`
  exercising the same flows through the crate's public API.
- Full existing suite passes unchanged (525 -> 543 lib tests, 14 -> 23
  operator_rotation integration tests), confirming `add_operator`/
  `remove_operator`'s existing behavior and all other tests are unaffected.
- `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features
  -- -D warnings`, `cargo build --release --target wasm32-unknown-unknown`,
  and the full `cargo test` workspace run all pass.

Closes stellar-kracken#8
@Meshmulla
Meshmulla merged commit f379bbc into stellar-kracken:main Aug 18, 2026
3 checks passed
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.

Add a timelock and grace period to operator rotation

2 participants