Skip to content

wire StartNewEpoch + periodic NewEpochHandler (ENGN-8911) - #971

Open
zale144 wants to merge 8 commits into
alek/engn-8910-epoch-nonce-format-per-topic-sequence-persistencefrom
alek/engn-8911-wire-startnewepoch-periodic-newepochhandler
Open

wire StartNewEpoch + periodic NewEpochHandler (ENGN-8911)#971
zale144 wants to merge 8 commits into
alek/engn-8910-epoch-nonce-format-per-topic-sequence-persistencefrom
alek/engn-8911-wire-startnewepoch-periodic-newepochhandler

Conversation

@zale144

@zale144 zale144 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add StartNewEpoch that allocates a NonceV2, opens the worker window immediately, and schedules later lifecycle transitions.
  • Apply computed TopicExtraLag to reputer window timing; register a per-topic periodic StartNewEpochTask on activate and cancel it on inactivate.
  • Wire a concrete x/scheduler keeper 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'
  • CI green on this PR

Stacked on: #970 (ENGN-8910)
Linear: ENGN-8911

Made with Cursor


Summary by cubic

Starts new epochs on schedule for active topics by wiring StartNewEpoch and 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.

  • Introduces StartNewEpoch (keeps StartEpoch as an alias): applies open-worker now; schedules close/open-reputer/close-reputer/complete; no longer schedules open-worker.
  • Registers periodic StartNewEpoch task via @scheduler: activation creates the first epoch and schedules the periodic task to start after one EpochLength; inactivation cancels it; reactivation is idempotent (drops leftover task before scheduling).
  • Applies types.TopicExtraLag in NewEpoch: reputer window opens at GroundTruthLag; ExtraLag extends close to land on an epoch boundary.
  • Adds StartNewEpochTaskArgs protobuf and handler; Keeper.TaskHandlers includes start and transition handlers; tests wire a real @scheduler keeper.
  • Binds topic lifecycle hooks to the emissions keeper (TopicKeeper.SetLifecycleHooks): OnTopicActivated starts the first epoch and schedules periodic; OnTopicInactivated cancels periodic.
  • Keeps EndBlocker nonce closes working in parallel with scheduler epochs: closes worker windows before rewards, allows overdue closes (only blocks too-early), and closes oldest unfulfilled nonces first to avoid skipping reputer opens.
  • ENGN-8911: Integrates @scheduler with 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.

Review in cubic

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>
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf Linter / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed⏩ skippedAug 26, 2026, 12:30 PM

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread x/emissions/keeper/tasks.go
zale144 and others added 4 commits August 20, 2026 19:50
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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread x/emissions/keeper/tasks.go Outdated
Comment thread x/emissions/keeper/actor_utils/worker.go Outdated
Comment thread x/emissions/keeper/actor_utils/worker.go Outdated
Comment thread x/emissions/keeper/tasks.go Outdated
Comment thread x/emissions/keeper/epoch_schedule_test.go Outdated
zale144 and others added 3 commits August 26, 2026 13:09
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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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