Epochs
Abstract
Often in the SDK, we would like to run certain code every-so often. The
purpose of epochs
module is to allow other modules to set that they
would like to be signaled once every period. So another module can
specify it wants to execute code once a week, starting at UTC-time = x.
epochs
creates a generalized epoch interface to other modules so that
they can easily be signaled upon such events.
Contents
Concepts
The epochs module defines on-chain timers that execute at fixed time intervals. Other SDK modules can then register logic to be executed at the timer ticks. We refer to the period in between two timer ticks as an "epoch".
Every timer has a unique identifier.
Every epoch will have a start time, and an end time, where end time = start time + timer interval
.
On mainnet, we only utilize one identifier, with a time interval of one day
.
The timer will tick at the first block whose block time is greater than the timer end time, and set the start as the prior timer end time. (Notably, it's not set to the block time!) This means that if the chain has been down for a while, you will get one timer tick per block, until the timer has caught up.
State
The Epochs module keeps a single EpochInfo
per identifier.
This contains the current state of the timer with the corresponding identifier.
Its fields are modified at every timer tick.
EpochInfos are initialized as part of genesis initialization or upgrade logic,
and are only modified on begin blockers.
Events
The epochs
module emits the following events:
BeginBlocker
Type | Attribute Key | Attribute Value |
---|---|---|
epoch_start | epoch_number | {epoch_number} |
epoch_start | start_time | {start_time} |
EndBlocker
Type | Attribute Key | Attribute Value |
---|---|---|
epoch_end | epoch_number | {epoch_number} |
Keepers
Keeper functions
Epochs keeper module provides utility functions to manage epochs.
// Keeper is the interface for epochs module keeper
type Keeper interface {
// GetEpochInfo returns epoch info by identifier
GetEpochInfo(ctx sdk.Context, identifier string) types.EpochInfo
// SetEpochInfo set epoch info
SetEpochInfo(ctx sdk.Context, epoch types.EpochInfo)
// DeleteEpochInfo delete epoch info
DeleteEpochInfo(ctx sdk.Context, identifier string)
// IterateEpochInfo iterate through epochs
IterateEpochInfo(ctx sdk.Context, fn func(index int64, epochInfo types.EpochInfo) (stop bool))
// Get all epoch infos
AllEpochInfos(ctx sdk.Context) []types.EpochInfo
}
Hooks
// the first block whose timestamp is after the duration is counted as the end of the epoch
AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64)
// new epoch is next block of epoch end block
BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64)
How modules receive hooks
On hook receiver function of other modules, they need to filter
epochIdentifier
and only do executions for only specific
epochIdentifier. Filtering epochIdentifier could be in Params
of other
modules so that they can be modified by governance.
This is the standard dev UX of this:
func (k MyModuleKeeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) {
params := k.GetParams(ctx)
if epochIdentifier == params.DistrEpochIdentifier {
// my logic
}
}
Panic isolation
If a given epoch hook panics, its state update is reverted, but we keep proceeding through the remaining hooks. This allows more advanced epoch logic to be used, without concern over state machine halting, or halting subsequent modules.
This does mean that if there is behavior you expect from a prior epoch hook, and that epoch hook reverted, your hook may also have an issue. So do keep in mind "what if a prior hook didn't get executed" in the safety checks you consider for a new epoch hook.
Queries
The Epochs module provides the following queries to check the module's state.
service Query {
// EpochInfos provide running epochInfos
rpc EpochInfos(QueryEpochsInfoRequest) returns (QueryEpochsInfoResponse) {}
// CurrentEpoch provide current epoch of specified identifier
rpc CurrentEpoch(QueryCurrentEpochRequest) returns (QueryCurrentEpochResponse) {}
}
Epoch Infos
Query the currently running epochInfos
<appd> query epochs epoch-infos
::: details Example
An example output:
epochs:
- current_epoch: "183"
current_epoch_start_height: "2438409"
current_epoch_start_time: "2021-12-18T17:16:09.898160996Z"
duration: 86400s
epoch_counting_started: true
identifier: day
start_time: "2021-06-18T17:00:00Z"
- current_epoch: "26"
current_epoch_start_height: "2424854"
current_epoch_start_time: "2021-12-17T17:02:07.229632445Z"
duration: 604800s
epoch_counting_started: true
identifier: week
start_time: "2021-06-18T17:00:00Z"
:::
Current Epoch
Query the current epoch by the specified identifier
<appd> query epochs current-epoch [identifier]
::: details Example
Query the current day
epoch:
<appd> query epochs current-epoch day
Which in this example outputs:
current_epoch: "183"