wire StartNewEpoch + periodic NewEpochHandler (ENGN-8911) - #971
Conversation
Open the worker window immediately, schedule later transitions, apply ExtraLag to reputer windows, and start/stop a per-topic periodic task when topics activate or inactivate. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest Buf updates on your PR. Results from workflow Buf Linter / buf (pull_request).
|
There was a problem hiding this comment.
cubic analysis
All reported issues were addressed across 14 files
Linked issue analysis
Linked issue: ENGN-8911: Wire StartNewEpoch + periodic NewEpochHandler
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | StartNewEpoch allocates an epoch, opens the worker window immediately, schedules later lifecycle transitions, and applies TopicExtraLag to reputer window timing. | StartNewEpoch creates the epoch, calls applyEpochTransition to open the worker window immediately, and then schedules the remaining lifecycle tasks. TopicExtraLag is implemented and used when computing reputer window times. |
| ✅ | Register a per-topic periodic StartNewEpoch task on topic activation and cancel it on inactivation (periodic creation + pause on inactive). | Lifecycle hooks are wired so OnTopicActivated schedules the periodic StartNewEpoch task and OnTopicInactivated cancels it. The StartNewEpoch task handler checks active status before creating an epoch. |
| ✅ | Wire a concrete x/scheduler keeper in emissions testutil and register task handlers so tests exercise real scheduler behavior. | Test setup now creates a scheduler keeper, passes it into NewKeeper, and registers the emissions module task handlers with the scheduler keeper so tests run against a concrete scheduler implementation. |
| ✅ | Unit tests cover scheduling of lifecycle tasks, periodic creation, and pause-on-inactive behavior. | New tests assert worker/reputer window timing (including ExtraLag), that activation starts an epoch and registers a periodic task that fires after one EpochLength, and that inactivation cancels the periodic task and stops further epoch creation. |
Architecture diagram
sequenceDiagram
participant TopicKeeper as TopicKeeper
participant EpochKeeper as Emissions Keeper (StartNewEpoch)
participant Scheduler as x/scheduler Keeper
participant EpochFSM as EpochFSM Engine
participant EpochStore as Epoch Store (cosmos/collections)
participant TopicStore as Topic Store
Note over TopicKeeper,TopicStore: NEW: Activation/Inactivation Lifecycle
TopicKeeper->>TopicKeeper: ActivateTopic(topicId)
alt lifecycleHooks != nil
TopicKeeper->>EpochKeeper: OnTopicActivated(topicId)
EpochKeeper->>EpochKeeper: StartNewEpoch(topicId)
EpochKeeper->>TopicStore: GetTopic(topicId)
TopicStore-->>EpochKeeper: topic
EpochKeeper->>EpochKeeper: AllocateNextEpochNonce(topicId)
EpochKeeper->>EpochKeeper: NewEpoch(nonce, topic, blockTime)
Note over EpochKeeper: Computes TopicExtraLag for reputer window alignment
EpochKeeper->>EpochFSM: Init(&epoch)
EpochKeeper->>EpochStore: epoch.Set(key, epoch)
EpochKeeper->>EpochKeeper: applyEpochTransition(OPEN_WORKER)
EpochKeeper->>EpochKeeper: scheduleEpochLifecycle(epoch)
EpochKeeper->>Scheduler: ScheduleTask(CloseEpochWorkerWindow, workerCloseAt)
EpochKeeper->>Scheduler: ScheduleTask(OpenEpochReputerWindow, reputerOpenAt)
EpochKeeper->>Scheduler: ScheduleTask(CloseEpochReputerWindow, reputerCloseAt)
EpochKeeper->>Scheduler: ScheduleTask(CompleteEpoch, reputerCloseAt)
EpochKeeper-->>TopicKeeper: ok
Note over EpochKeeper,Scheduler: NEW: Register periodic new-epoch task
EpochKeeper->>Scheduler: ScheduleTask(StartNewEpoch, interval=EpochLength, repeat)
Scheduler-->>EpochKeeper: taskID
TopicKeeper-->>TopicKeeper: done
end
Note over TopicKeeper,TopicStore: NEW: Inactivation Cancels Periodic Task
TopicKeeper->>TopicKeeper: InactivateTopic(topicId)
alt lifecycleHooks != nil
TopicKeeper->>EpochKeeper: OnTopicInactivated(topicId)
EpochKeeper->>Scheduler: CancelTask(StartNewEpoch:topicId)
alt task not found
Scheduler-->>EpochKeeper: collections.ErrNotFound (swallowed)
else
Scheduler-->>EpochKeeper: ok
end
EpochKeeper-->>TopicKeeper: ok
TopicKeeper-->>TopicKeeper: done
end
Note over Scheduler: NEW: Periodic BeginBlock Fires
Scheduler->>Scheduler: BeginBlock(blockTime)
loop each due task
alt task type == StartNewEpoch
Scheduler->>EpochKeeper: handleStartNewEpochTask(topicId)
EpochKeeper->>TopicStore: IsTopicActive(topicId)
alt topic active
EpochKeeper->>EpochKeeper: StartNewEpoch(topicId)
else topic inactive
EpochKeeper-->>Scheduler: skip (return nil)
end
else task type == CloseEpochWorkerWindow
Scheduler->>EpochKeeper: handleCloseEpochWorkerWindow task
EpochKeeper->>EpochFSM: applyEpochTransition(CLOSE_WORKER)
else task type == OpenEpochReputerWindow
Scheduler->>EpochKeeper: handleOpenEpochReputerWindow task
EpochKeeper->>EpochFSM: applyEpochTransition(OPEN_REPUTER)
else task type == CloseEpochReputerWindow
Scheduler->>EpochKeeper: handleCloseEpochReputerWindow task
EpochKeeper->>EpochFSM: applyEpochTransition(CLOSE_REPUTER)
else task type == CompleteEpoch
Scheduler->>EpochKeeper: handleCompleteEpoch task
EpochKeeper->>EpochFSM: applyEpochTransition(COMPLETE)
end
end
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Match the spec and legacy EndBlocker: reputers may submit as soon as GTL elapses; ExtraLag only extends CloseAt so the window still ends on an epoch boundary. Co-authored-by: Cursor <cursoragent@cursor.com>
ActivateTopic can run again after churn without InactivateTopic, which left the start-new-epoch task in place and failed tests. Also fill TopicKeeper.lifecycleHooks for exhaustruct and skip that check in tests. Co-authored-by: Cursor <cursoragent@cursor.com>
…rallel Empty leftover worker nonces and scheduler task errors could fulfill a worker window without opening the reputer nonce, which broke the integration reputer insert. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The integration workflow applies this patch before localnet; adding sort made the old hunk fail to apply. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep overdue recovery for a missed close cadence, but do not fulfill in-progress nonces. Schedule periodic StartNewEpoch on absolute EpochLength boundaries so late blocks do not drift the cadence. Co-authored-by: Cursor <cursoragent@cursor.com>
Local integration suites pass with the original CloseWorkerNonce window check and scheduler error propagation. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
1 issue found across 8 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="x/emissions/keeper/actor_utils/worker_test.go">
<violation number="1">
P2: Deleting TestCloseWorkerNonce_WindowEligibility removes coverage of the window-eligibility guard in CloseWorkerNonce (worker.go:31-34) that still exists and is still reachable: the "still open" case where calling before the window closes must return ErrWorkerNonceWindowNotAvailable and leave the nonce unfulfilled, plus the on-time boundary close. The "overdue" sub-case legitimately no longer applies once the periodic scheduler closes at window end, but the still-valid rejection path is now completely untested in this package. Keep the still-open and on-time cases rather than dropping the whole test.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
Summary
StartNewEpochthat allocates aNonceV2, opens the worker window immediately, and schedules later lifecycle transitions.TopicExtraLagto reputer window timing; register a per-topic periodicStartNewEpochTaskon activate and cancel it on inactivate.x/schedulerkeeper in emissions testutil and cover schedule / periodic / inactive paths.Test plan
go test ./x/emissions/keeper/ -run 'TestKeeperTestSuite/TestStartNewEpoch|TestKeeperTestSuite/TestActivateTopicStartsEpoch|TestKeeperTestSuite/TestInactivateTopicCancels|TestKeeperTestSuite/TestInactivateAndActivateTopic'go test ./x/emissions/types/ -run 'TestTopicExtraLag|TestNewEpochAppliesExtraLag'Stacked on: #970 (ENGN-8910)
Linear: ENGN-8911
Made with Cursor
Summary by cubic
Starts new epochs on schedule for active topics by wiring
StartNewEpochand a per-topic periodic task, while keeping EndBlocker worker-window closes correct alongside scheduler-driven epochs. Opens the worker window immediately; reputers now open at GroundTruthLag and close at GroundTruthLag + ExtraLag + EpochLength to align with epoch boundaries.StartNewEpoch(keepsStartEpochas an alias): applies open-worker now; schedules close/open-reputer/close-reputer/complete; no longer schedules open-worker.StartNewEpochtask via@scheduler: activation creates the first epoch and schedules the periodic task to start after oneEpochLength; inactivation cancels it; reactivation is idempotent (drops leftover task before scheduling).types.TopicExtraLaginNewEpoch: reputer window opens atGroundTruthLag; ExtraLag extends close to land on an epoch boundary.StartNewEpochTaskArgsprotobuf and handler;Keeper.TaskHandlersincludes start and transition handlers; tests wire a real@schedulerkeeper.TopicKeeper.SetLifecycleHooks):OnTopicActivatedstarts the first epoch and schedules periodic;OnTopicInactivatedcancels periodic.@schedulerwith the topic lifecycle; tests cover immediate open, scheduled transitions, periodic create/cancel, idempotent activation, reputer window math, and worker→reputer flow.Written for commit 2708d30. Summary will update on new commits.