CIP-1: Actor Message Scheduler
Status: Draft for Internal Review
Type: Standards Track
Category: Core
Created: 2025-10-01
Type: Standards Track
Category: Core
Created: 2025-10-01
Abstract
This document specifies the Autonomous Actor Scheduler: the protocol-level mechanism that implements chain-native timers. It combines a tiered Calendar Queue for scalable O(1) event scheduling with an EIP-1559 timer-lane pricing model (basefee + priority tip) and a per-actor fairness weightW(actor) ∈ [1, 2]. Per-actor Gas Bidding Agent (GBA) contracts may override the protocol-supplied default GBA to compute bids from real-time block context, enabling actors to dynamically respond to network congestion and weigh the urgency of their own scheduled tasks.
The scheduler operates on top of CIP-5’s per-fire basefee model: every fire is a paid execution (fee_payer pre-charge plus refund). The EIP-1559 priority tip is additional to the per-fire max_cost pre-charge, not a replacement. Fees and metering align with CIP-3.
1. Motivation
The Cowboy actor model’s reliance on timers requires a scheduler that is both scalable and economically intelligent. Network conditions are dynamic, and a scheduled task’s importance can change based on external events. A fixed pre-paid fee for a future transaction is insufficient. This design lets actors make real-time, economically rational decisions about the cost of their own execution, so high-priority tasks can aggressively compete for block space when it matters most. EIP-1559 pricing is preferred over a first-price-per-cycle auction for three structural reasons:- First-price-per-cycle is unstable in repeated knapsack settings. Programmatic bidders best-respond by underbidding the prior round’s clearing price, leading to oscillation and revenue collapse. Autonomous actors cannot easily run a complex bidding strategy or converge a Nash equilibrium against other agents. EIP-1559 eliminates the strategic-bidding component: the basefee is deterministic from the prior block’s utilisation, and the priority tip is a simple price-discovery channel for ordering within the lane.
- The default GBA collapses to ~2 lines. Under EIP-1559 the default bidding strategy reduces to
max_fee = 2 × basefee,max_priority_fee = previous_block_p50_tip— analogous to MetaMask’s default estimator on EVM. This removes the centralisation pressure that conservative ad-hoc defaults would otherwise impose on unsophisticated actors. - The invalid-bid attack class disappears structurally. There is no
bidfield;max_priority_fee_per_cycleis bounded bymax_fee_per_cycle − basefee(lossy clamping is structural), and the per-firemax_costpre-charge (CIP-5 §6.3) already caps the worst-case debit per fire. No drain attack survives.
2. Tiered Calendar Queue
The Actor Scheduler state is part of the global consensus state (σ) and is organized into a three-tier structure to manage timers across different time horizons.2.1 Tier 1 — Block Ring Buffer
Imminent timers.- Structure: A fixed-size ring buffer of
RING_BUFFER_SIZEbuckets, one per upcoming block height. - Function: A timer scheduled for block
His placed in bucketH % RING_BUFFER_SIZE. - Performance: O(1) enqueue and dequeue; the block producer accesses only the single bucket for the current height.
2.2 Tier 2 — Epoch Queue
Medium-term timers.- Structure: An array of buckets, one per future epoch (≈ one hour of blocks).
- Function: A timer scheduled for a block in epoch
Eis placed in bucketE. At each epoch rollover, the protocol redistributes that bucket’s timers into the appropriate Ring Buffer slots — an amortized maintenance cost.
2.3 Tier 3 — Overflow Sorted Set
Long-horizon timers.- Structure: A Merkleized balanced binary search tree ordered by block height.
- Function: Far-future timers are inserted here. Epoch maintenance also walks this tree and migrates any timers now within the Epoch Queue’s range.
3. Integration with the State Transition Function
Let σ be the global state, B a block, andH = height(B). The per-block sequence (canonical, matching CIP-5 §5.1 and the validator code path):
- Header / proposer. Determined by Simplex consensus and the previous QC.
- Epoch maintenance (if applicable). Redistribute timers from higher tiers into the Block Ring Buffer.
- Execute transactions (TX phase). Process the ordered transaction set Tᵢ. Calls to
schedule_timerMAY supply(max_fee_per_cycle, max_priority_fee_per_cycle); if omitted, the runtime supplies the default-GBA values (§7.1). - Collect due timers (end-of-block). Read the timer bucket for
Hand apply lifecycle classification per CIP-5 §5.4:- TTL expired or
balance(fee_payer) < max_cost→ self-destruct under the GC lane (§9). - Otherwise → enqueue for priority sort.
- TTL expired or
- Compute lane basefee. The Timer-lane basefee adjusts per block using EIP-1559 dynamics over the prior block’s timer-lane utilisation (§5).
- Compute effective priority for each enqueued timer:
where
W(actor) ∈ [1, 2]is the per-actor fairness weight (§8). - Sort and select. Order due timers by
effective_prioritydescending; tie-break by(timer_id, schedule_block). Greedily fillLANE_TIMER_CYCLES. Each selected timer is gated bycycles_consumed_so_far + gas_limit_per_fire ≤ LANE_TIMER_CYCLES − cycles_already_usedandgas_limit_per_fire ≤ MAX_CYCLES_PER_FIRE_AUCTION_PHASE(§10). Timers exceeding the per-timer cap are deferred without an attempt. - Settle. For each selected timer:
- Pre-charge
fee_payerper CIP-5 §6.3 with the priority-tip term added: - Execute the handler. On normal return, refund unused cycles ×
(basefee_cycle + priority_per_cycle). The tip portion goes to the block proposer (consistent with CIP-3 §2.4 tips routing); the basefee portion is burned. - On insufficient funds at any step → CIP-5 §5.4 path 2 (self-destruct without firing).
- Pre-charge
- Defer. Timers not selected remain in the bucket; on the next block they fall through the same flow. The 1,000-block fairness window naturally raises
W(actor)for actors whose timers are repeatedly deferred (§8). - Update fairness counters. Increment per-actor
recent_executions[actor] += 1for every timer fired; the 1,000-block rolling decay is applied at the start of the next end-of-block step (§8). - Resolve jobs, adjust basefees, mint rewards. As elsewhere defined.
4. Same-Block Prohibition
Timers created within the current block’s transactions MUST NOT execute in the same block. This avoids reentrancy via timer scheduling and contextual ambiguity around the basefees, congestion signals, and balances passed to GBAs. Consensus-critical.5. Timer-Lane EIP-1559 Pricing
The Timer-lane basefee adjusts per block over the prior block’s timer-lane utilisation:- Target utilisation: 0.5 (50%) of the 2,000,000-cycle Timer lane.
- Max basefee adjustment per block: ±12.5%.
- 100% of the basefee is burned (consistent with CIP-3 §2.4 and WP §6 “100% basefee burn”). The lane basefee is additional to the cycle basefee charged under CIP-3 — implementations track the two separately to preserve per-lane burn telemetry.
- Per-lane fee multiplier is pinned at
1.0×at launch (CIP-3 §2.2.3 + WP §6 / §17.9; no subsidy). Tier-0 governance-tunable.
max_priority_fee_per_cycle = 0 still fires when the lane is uncongested, paying only the lane basefee. Under congestion, only timers whose effective_priority (§3 step 6) clears the marginal kicked-out timer make the cut; losers carry forward.
6. schedule_timer API
The PVM host API (node/execution/src/pvm_host.rs):
max_fee_per_cycle ≥ basefee_lane_timer→ elseTimerRejectedBelowBasefee(immediate failure; nothing escrowed).max_priority_fee_per_cycle ≤ max_fee_per_cycle − basefee_lane_timer→ else clamp at schedule time and emitTimerPriorityClampedAtSchedule(stated, clamped)for observability.- No escrow at scheduling. The per-fire
max_cost(CIP-5 §6.3) is pre-charged at execution time only, not at scheduling. - Scheduling cost remains a small fixed cycle charge paid upfront by
schedule_timerto occupy the queue slot — independent of execution pricing.
bid: int parameter is withdrawn. During a one-release deprecation window, callers passing bid are silently accepted (the value is ignored); thereafter the call is rejected with TimerArgDeprecated.
7. Gas Bidding Agents (GBAs)
A GBA is an actor-owned contract responsible for pricing one or more of the actor’s timers. The protocol callsgetGasBid(context) read-only at fire time. The runtime supplies a default GBA when an actor doesn’t provide one; custom GBAs remain useful for actors that need dynamic, context-sensitive bidding (DeFi liquidation actors, oracle-pushers, MEV-aware schedulers).
7.1 Default GBA (normative)
2 × basefeeheadroom absorbs up to ~5 blocks of basefee growth at the ±12.5% per-block clamp (1.125^5 ≈ 1.80) before hitting the cap — sufficient for normal congestion swings.p50 priority tipis the prior-block median priority fee paid by fired timers; falls back to0if no timers fired in the prior block (avoids an undefined estimator on cold start).
7.2 GBA interface and context
Custom GBAs implement:context carries the data needed for real-time pricing decisions:
trigger_block_height(u64) — the height the timer was originally scheduled for.current_block_height(u64) — current height; lets the GBA compute lateness.basefee_cycle(u128) — current compute-cycle basefee (CIP-3).basefee_cell(u128) — current storage/byte basefee (CIP-3).basefee_lane_timer(u128) — current Timer-lane basefee (§5).last_block_cycle_usage(u64) — total cycles used in the previous block (congestion signal).previous_block_p50_priority_tip_per_cycle(u128) — prior-block median fired-timer tip.owner_actor_balance(u128) — current CBY balance of the owning actor.
7.3 SDK convenience: priority_tier_hint
The cowboy-py SDK MAY expose a high-level enum for callers who don’t want to construct a GBA:
0x09 under system:cip1:priority_tier_multipliers.{economy,standard,fast,urgent} and are Tier-0 governance-tunable (consistent with the lane fee multipliers in CIP-3 §2.2.3 — both are multiplicative scalars on fee components, neither redirects revenue across recipient classes). CIP-12 §5.1 Tier-0 scope already references “any governance-tunable parameter (see genesis defaults and CIP-1/3/5/9/10)”; the priority-tier multipliers are picked up automatically by that clause.
8. Per-Actor Fairness Weight W(actor)
Formula:
- An actor with no recent fires (
ratio = 0) getsW = 2(maximum boost); an actor at or above the network median (ratio ≥ 1) getsW = 1(no boost). Linear interpolation forratioin[0, 1]. - Window length
FAIRNESS_WINDOW_BLOCKS = 1_000(~16.7 min at 1s blocks), Tier-2 governance-tunable. Stored at0x09undersystem:cip1:fairness_window_blocks. ratiois clipped to[0, 2]before subtraction, so a brand-new actor with zero recent fires getsW = 2rather than infinity.
actor carries a 1,000-element ring buffer of fire-counts per block in the Actor Scheduler state. Memory cost: ~8 KiB per actively-firing actor. Eviction: actors with zero fires in the entire 1,000-block window are pruned from the fairness map (next fire re-creates a fresh entry).
Mutability of formula structure. Tier-2 (governance changes the inclusion ordering, which affects fee revenue distribution). The numeric FAIRNESS_WINDOW_BLOCKS and the [1, 2] clip bounds are Tier-0.
Known limitations (Phase-5 simulation deferred — §16):
- Fragmentation attack. A sophisticated developer can deploy 100 actor instances and treat each as a separate “quiet” actor to bypass per-actor weight. Mitigation candidate: per-deployer weight (using actor-creation trace) instead of per-actor — requires deeper actor metadata than CIP-2 currently exposes. Phase-5 simulation MUST size the attack magnitude before this spec ships; if material, per-deployer weight is added in a follow-up revision.
- Median moves under shock. When a large actor enters/exits and shifts the network median sharply, incumbents are briefly disadvantaged for ~1,000 blocks. EMA-smoothing the median (analogous to the HHI smoothing in CIP-2 §5 amend) is a candidate follow-up addition.
9. Lane Budgets and the Three-Path Timer Lifecycle
Per CIP-5 §6.5, the timer subsystem has two independent per-block budgets:| Constant | Purpose | Default |
|---|---|---|
LANE_TIMER_CYCLES | Execution lane — natural-fire timers (path 1) | 2,000,000; governance-tunable via TimerConfig |
TIMER_GC_CYCLES | Cleanup lane — TTL expiry and insufficient-funds destruction (paths 2 / 3) | TimerConfig.gc_cycles_per_block = 5,000,000 |
LANE_TIMER_CYCLES. GC draws from TIMER_GC_CYCLES, so a resume-after-outage storm of expired timers cannot starve live timer execution.
The three-path lifecycle for any due timer (CIP-5 §5.4), plus explicit cancellation:
- Natural fire.
fee_payeris solvent and TTL has not expired. Pre-charge, execute, refund unused. - TTL expiry.
expires_athas passed. Self-destruct under the GC lane. - Insufficient funds.
balance(fee_payer) < max_cost. Self-destruct under the GC lane and emitTimerCancelledInsufficientFunds. - Explicit cancellation. Actor-self cancel (host syscall) or validator-set emergency cancel via
SYS_CANCEL_TIMER(§12).
10. Per-Timer Cycle Cap
LANE_TIMER_CYCLES. A single timer cannot consume more than 1/8th of the lane. CIP-5 §6.4’s max_cycles_per_fire = 550_000 remains in force during the legacy FIFO phase (when there is no per-block scarcity competition); the tighter 250k cap takes effect with this spec’s activation, preventing single-timer monopolisation in the auction phase.
Actors with handlers that legitimately need >250k cycles can split the work across multiple timer fires; the same-block prohibition (§4) prevents tail-end re-fires from compounding in a single block.
Mutability. Tier-0, key system:cip1:max_cycles_per_fire_auction_phase.
11. DoS Mitigation and Congestion Handling
The combination of a bounded lane budget, deterministic basefee growth, and per-actor fairness weight defends against scheduler-targeted DoS:- Bounded execution.
LANE_TIMER_CYCLEScaps timer-driven work per block, so timers cannot crowd out user transactions. - Economic prioritisation. Instead of plain FIFO, the lane is sorted by
effective_priority: timers whose owners bid higher tips run first. Low-priority spam timers are priced out during congestion as the basefee ratchets up. - Best-effort delivery with fairness. Losers carry over to the next bucket; the per-actor fairness weight
W(actor)raises priority for actors who have fired less than the network median in the last 1,000 blocks, preserving liveness without amplifying timer-spam. - GC isolation. TTL-expiry storms and insufficient-funds destruction are routed through
TIMER_GC_CYCLES, an independent budget. - Structural invalid-bid resistance. There is no
bidfield to game;max_priority_fee_per_cycleis bounded bymax_fee_per_cycle − basefee_lane_timer, and the per-firemax_costpre-charge (CIP-5 §6.3) caps the worst-case debit per fire.
12. System Instructions
This spec introduces no new system-instruction opcodes. Theschedule_timer extension is a PVM host-API change to existing syscalls (schedule_timer / schedule_timer_ex / cancel_timer / extend_timer at node/execution/src/pvm_host.rs); it adds optional parameters but allocates no opcodes.
The supporting timer-management system instructions already live in code at the following opcodes (node/types/src/execution.rs):
| Symbolic name | Opcode | Sender | Purpose |
|---|---|---|---|
SYS_CANCEL_TIMER { timer_id } | 48 | system_deployers | Validator-set emergency cancel of a misbehaving / abandoned timer |
SYS_UPDATE_TIMER_CONFIG { config } | 49 | system_deployers | Governance-tune TimerConfig (TTL / per-fire caps / GC budget) |
SYS_EXTEND_TIMER { timer_id, new_expires_at } | 50 | system_deployers | Emergency TTL extension when an actor can no longer self-extend |
13. Migration
13.1 From “free timers” to the per-fire fee model
CIP-5’s per-firefee_payer model is binding today. Callers that assumed timers were free will receive TimerCancelledInsufficientFunds on first fire. Each scheduling actor MUST fund either itself or a designated fee_payer with at least one max_cost reserve per pending timer. Long-running heartbeat actors SHOULD use schedule_timer_ex with explicit fee_payer (default actor_self) and monitor balance. Actors and their watchtowers SHOULD subscribe to TimerCancelledInsufficientFunds. TTL-protected timers auto-clean if abandoned — no manual cleanup required.
13.2 From FIFO to the EIP-1559 hybrid
Activation is a single governance proposal (Tier-3 by analogy with CIP-3 basefee curve changes; bicameral). At activation blockH_activation:
| Subsystem | Pre-activation (CIP-5 FIFO) | Post-activation |
|---|---|---|
| Inclusion order within bucket | FIFO by insertion | Sort by effective_priority (§3 step 7) |
bid field | Accepted, ignored (one-release deprecation window) | Rejected: TimerArgDeprecated |
| Timer-lane basefee | Equal to global cycle basefee | EIP-1559 dynamics over prior-block lane utilisation (§5) |
| Default GBA | Underspecified | Normative two-line estimator (§7.1) |
| Per-timer cap | max_cycles_per_fire = 550_000 (CIP-5 §6.4) | MAX_CYCLES_PER_FIRE_AUCTION_PHASE = 250_000 (§10) |
| Fairness | None | W(actor) ∈ [1, 2], 1,000-block window (§8) |
W(actor) = 2 (max boost) by default (zero recent fires in the freshly-initialised window); compete on effective_priority from the next block onward.
Caller migration: existing callers (schedule_timer(height, payload) with no fee fields) continue to work — they receive the default-GBA estimator under the hood. Callers that previously passed bid get a one-release deprecation warning, then a hard error. Other specs that schedule CIP-5 timers and currently reference bid (notably CIP-9 PoR challenge timer, CIP-16 external-domain reverify) MUST drop the bid field in the same activation batch.
14. Backwards Compatibility
- CIP-5 §§1–8 unchanged for both FIFO and post-activation phases. CIP-5 §9 is removed in the activation batch — its functionality is replaced by this spec plus the per-actor fairness weight.
- System instruction opcodes 48 / 49 / 50 unchanged.
- Per-fire
fee_payermodel (CIP-5 §6.3) unchanged. The EIP-1559 priority tip extends themax_costformula (§3 step 8) but does not change the pre-charge / refund mechanics. - Block ordering Tx-then-Timer (CIP-5 §5.1) unchanged.
- Same-block prohibition (§4 / CIP-5 §5.3) unchanged.
- Existing callers of
schedule_timer(height, payload)work without source change — they get the default GBA estimator implicitly. - This spec is otherwise strictly additive over CIP-5: no syscall, opcode, or constant changes are introduced beyond the new optional
schedule_timerparameters.
15. Decision-Register Dependencies
This spec has no Decision-Register gates — every parameter and design choice is either a Tier-0 governance-tunable default or a structural recommendation. The activation blockH_activation is itself a Tier-3 governance proposal; that is the only policy lever required.
(Cross-ref: WP §5.1 is split into 5.1a “Currently Implemented (CIP-5 FIFO)” and 5.1b “Target Design (CIP-1 EIP-1559 hybrid)” in the same activation batch. The priority_tier_multipliers are Tier-0 and auto-covered by CIP-12 §5.1’s governance-tunable clause (§7.3) — no CIP-12 list edit required.)
16. Open Questions Deferred to Phase-5 Simulation
FAIRNESS_WINDOW_BLOCKScalibration. 1,000 blocks (~16.7 min) is the launch default; longer windows smooth more but lag more on actor identity changes.- Per-deployer vs per-actor weight (fragmentation-attack threat magnitude, §8 limitation 1).
- Median EMA-smoothing (§8 limitation 2) — whether to ship in the first follow-up revision or wait for empirical shock evidence.
- Priority-tier multipliers
{0.8, 1.0, 1.5, 2.5}— empirical tuning against actual congestion patterns. - Lane fee multiplier — pinned at 1.0× at launch (CIP-3 §2.2.3); Phase-5 simulation MAY recommend a 0.8× Timer-lane subsidy if the lane is structurally under-utilised post-mainnet.

