From 016b1d6e13300f417ddb51b3cb73f31fb8044078 Mon Sep 17 00:00:00 2001 From: Narangde Date: Sat, 14 Sep 2024 15:30:10 +0900 Subject: [PATCH] feat(epochs): port epochs module from cosmos-sdk (#101) --- client/app/app.go | 1 + client/app/app_config.go | 9 + client/app/keepers/types.go | 2 + client/x/epochs/README.md | 195 +++++ client/x/epochs/keeper/abci.go | 85 ++ client/x/epochs/keeper/abci_test.go | 190 +++++ client/x/epochs/keeper/epoch.go | 72 ++ client/x/epochs/keeper/epoch_test.go | 105 +++ client/x/epochs/keeper/genesis.go | 31 + client/x/epochs/keeper/genesis_test.go | 99 +++ client/x/epochs/keeper/grpc_query.go | 51 ++ client/x/epochs/keeper/grpc_query_test.go | 22 + client/x/epochs/keeper/hooks.go | 27 + client/x/epochs/keeper/keeper.go | 53 ++ client/x/epochs/keeper/keeper_test.go | 91 +++ client/x/epochs/module/depinject.go | 83 ++ client/x/epochs/module/module.go | 137 ++++ client/x/epochs/module/module.proto | 12 + client/x/epochs/module/module.pulsar.go | 502 ++++++++++++ client/x/epochs/types/codec.go | 10 + client/x/epochs/types/events.pb.go | 493 ++++++++++++ client/x/epochs/types/events.proto | 17 + client/x/epochs/types/genesis.go | 72 ++ client/x/epochs/types/genesis.pb.go | 820 +++++++++++++++++++ client/x/epochs/types/genesis.proto | 60 ++ client/x/epochs/types/hooks.go | 65 ++ client/x/epochs/types/hooks_test.go | 121 +++ client/x/epochs/types/identifier.go | 22 + client/x/epochs/types/keys.go | 16 + client/x/epochs/types/query.pb.go | 908 ++++++++++++++++++++++ client/x/epochs/types/query.proto | 32 + go.mod | 2 +- lib/netconf/local/genesis.json | 13 + 33 files changed, 4417 insertions(+), 1 deletion(-) create mode 100644 client/x/epochs/README.md create mode 100644 client/x/epochs/keeper/abci.go create mode 100644 client/x/epochs/keeper/abci_test.go create mode 100644 client/x/epochs/keeper/epoch.go create mode 100644 client/x/epochs/keeper/epoch_test.go create mode 100644 client/x/epochs/keeper/genesis.go create mode 100644 client/x/epochs/keeper/genesis_test.go create mode 100644 client/x/epochs/keeper/grpc_query.go create mode 100644 client/x/epochs/keeper/grpc_query_test.go create mode 100644 client/x/epochs/keeper/hooks.go create mode 100644 client/x/epochs/keeper/keeper.go create mode 100644 client/x/epochs/keeper/keeper_test.go create mode 100644 client/x/epochs/module/depinject.go create mode 100644 client/x/epochs/module/module.go create mode 100644 client/x/epochs/module/module.proto create mode 100644 client/x/epochs/module/module.pulsar.go create mode 100644 client/x/epochs/types/codec.go create mode 100644 client/x/epochs/types/events.pb.go create mode 100644 client/x/epochs/types/events.proto create mode 100644 client/x/epochs/types/genesis.go create mode 100644 client/x/epochs/types/genesis.pb.go create mode 100644 client/x/epochs/types/genesis.proto create mode 100644 client/x/epochs/types/hooks.go create mode 100644 client/x/epochs/types/hooks_test.go create mode 100644 client/x/epochs/types/identifier.go create mode 100644 client/x/epochs/types/keys.go create mode 100644 client/x/epochs/types/query.pb.go create mode 100644 client/x/epochs/types/query.proto diff --git a/client/app/app.go b/client/app/app.go index a59d2577..094aafd6 100644 --- a/client/app/app.go +++ b/client/app/app.go @@ -92,6 +92,7 @@ func newApp( &app.Keepers.ConsensusParamsKeeper, &app.Keepers.GovKeeper, &app.Keepers.UpgradeKeeper, + &app.Keepers.EpochsKeeper, &app.Keepers.EvmStakingKeeper, &app.Keepers.EVMEngKeeper, ); err != nil { diff --git a/client/app/app_config.go b/client/app/app_config.go index f23b65a1..faee4920 100644 --- a/client/app/app_config.go +++ b/client/app/app_config.go @@ -30,6 +30,8 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + epochsmodule "github.com/piplabs/story/client/x/epochs/module" + epochstypes "github.com/piplabs/story/client/x/epochs/types" evmenginemodule "github.com/piplabs/story/client/x/evmengine/module" evmenginetypes "github.com/piplabs/story/client/x/evmengine/types" evmstakingmodule "github.com/piplabs/story/client/x/evmstaking/module" @@ -84,6 +86,7 @@ var ( genutiltypes.ModuleName, upgradetypes.ModuleName, // Story modules + epochstypes.ModuleName, evmenginetypes.ModuleName, evmstakingtypes.ModuleName, } @@ -98,6 +101,7 @@ var ( // CanWithdrawInvariant invariant. // NOTE: staking module is required if HistoricalEntries param > 0. beginBlockers = []string{ + epochstypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, // Note: slashing happens after distr.BeginBlocker slashingtypes.ModuleName, @@ -117,6 +121,7 @@ var ( stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, evmstakingtypes.ModuleName, + epochstypes.ModuleName, } moduleAccPerms = []*authmodulev1.ModuleAccountPermission{ @@ -196,6 +201,10 @@ var ( Name: upgradetypes.ModuleName, Config: appconfig.WrapAny(&upgrademodulev1.Module{}), }, + { + Name: epochstypes.ModuleName, + Config: appconfig.WrapAny(&epochsmodule.Module{}), + }, { Name: evmstakingtypes.ModuleName, Config: appconfig.WrapAny(&evmstakingmodule.Module{}), diff --git a/client/app/keepers/types.go b/client/app/keepers/types.go index b404cc6f..7b21abdc 100644 --- a/client/app/keepers/types.go +++ b/client/app/keepers/types.go @@ -15,6 +15,7 @@ import ( slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + epochskeeper "github.com/piplabs/story/client/x/epochs/keeper" evmengkeeper "github.com/piplabs/story/client/x/evmengine/keeper" evmstakingkeeper "github.com/piplabs/story/client/x/evmstaking/keeper" ) @@ -34,4 +35,5 @@ type Keepers struct { // Story EvmStakingKeeper *evmstakingkeeper.Keeper EVMEngKeeper *evmengkeeper.Keeper + EpochsKeeper *epochskeeper.Keeper } diff --git a/client/x/epochs/README.md b/client/x/epochs/README.md new file mode 100644 index 00000000..612a12de --- /dev/null +++ b/client/x/epochs/README.md @@ -0,0 +1,195 @@ +> [!NOTE] +> This `epochs` module is copied from cosmos-sdk +> [v0.52.0-alpha.1](https://github.com/cosmos/cosmos-sdk/tree/v0.52.0-alpha.1/x/epochs) + +--- +sidebar_position: 1 +--- + +# `x/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 + +1. **[Concept](#concepts)** +2. **[State](#state)** +3. **[Events](#events)** +4. **[Keeper](#keepers)** +5. **[Hooks](#hooks)** +6. **[Queries](#queries)** + +## 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. + +``` go +// 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 + +```go + // 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: + +```golang +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. + +```protobuf +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 + +```sh + query epochs epoch-infos +``` + +::: details Example + +An example output: + +```sh +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 + +```sh + query epochs current-epoch [identifier] +``` + +::: details Example + +Query the current `day` epoch: + +```sh + query epochs current-epoch day +``` + +Which in this example outputs: + +```sh +current_epoch: "183" +``` diff --git a/client/x/epochs/keeper/abci.go b/client/x/epochs/keeper/abci.go new file mode 100644 index 00000000..02ee7b53 --- /dev/null +++ b/client/x/epochs/keeper/abci.go @@ -0,0 +1,85 @@ +package keeper + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/piplabs/story/client/x/epochs/types" + "github.com/piplabs/story/lib/errors" + "github.com/piplabs/story/lib/log" +) + +// BeginBlocker of epochs module. +func (k Keeper) BeginBlocker(ctx context.Context) error { + defer telemetry.ModuleMeasureSince(types.ModuleName, telemetry.Now(), telemetry.MetricKeyBeginBlocker) + + // NOTE(Narangde): Use UnwrapSDKContext instead of Environment's HeaderService + headerInfo := sdk.UnwrapSDKContext(ctx).HeaderInfo() + err := k.EpochInfo.Walk( + ctx, + nil, + func(_ string, epochInfo types.EpochInfo) (stop bool, err error) { + // If blocktime < initial epoch start time, return + if headerInfo.Time.Before(epochInfo.StartTime) { + return false, nil + } + // if epoch counting hasn't started, signal we need to start. + shouldInitialEpochStart := !epochInfo.EpochCountingStarted + + epochEndTime := epochInfo.CurrentEpochStartTime.Add(epochInfo.Duration) + shouldEpochStart := (headerInfo.Time.After(epochEndTime)) || shouldInitialEpochStart + + if !shouldEpochStart { + return false, nil + } + epochInfo.CurrentEpochStartHeight = headerInfo.Height + + if shouldInitialEpochStart { + epochInfo.EpochCountingStarted = true + epochInfo.CurrentEpoch = 1 + epochInfo.CurrentEpochStartTime = epochInfo.StartTime + log.Debug(ctx, "Starting new epoch", "epoch_identifier", epochInfo.Identifier, "current_epoch", epochInfo.CurrentEpoch) + } else { + err := k.EventService.EventManager(ctx).Emit(ctx, &types.EventEpochEnd{ + EpochNumber: epochInfo.CurrentEpoch, + }) + if err != nil { + return false, errors.Wrap(err, "emit epoch end event") + } + + if err := k.AfterEpochEnd(ctx, epochInfo.Identifier, epochInfo.CurrentEpoch); err != nil { + // purposely ignoring the error here not to halt the chain if the hook fails + log.Error(ctx, "Error after epoch end", err, "epoch_identifier", epochInfo.Identifier, "current_epoch", epochInfo.CurrentEpoch) + } + + epochInfo.CurrentEpoch++ + epochInfo.CurrentEpochStartTime = epochInfo.CurrentEpochStartTime.Add(epochInfo.Duration) + log.Debug(ctx, "Starting epoch with", "epoch_identifier", epochInfo.Identifier, "current_epoch", epochInfo.CurrentEpoch) + } + + // emit new epoch start event, set epoch info, and run BeforeEpochStart hook + err = k.EventService.EventManager(ctx).Emit(ctx, &types.EventEpochStart{ + EpochNumber: epochInfo.CurrentEpoch, + EpochStartTime: epochInfo.CurrentEpochStartTime.Unix(), + }) + if err != nil { + return false, errors.Wrap(err, "emit epoch start event") + } + err = k.EpochInfo.Set(ctx, epochInfo.Identifier, epochInfo) + if err != nil { + log.Error(ctx, "Error set epoch info", err, "epoch_identifier", epochInfo.Identifier, "current_epoch", epochInfo.CurrentEpoch) + return false, nil + } + if err := k.BeforeEpochStart(ctx, epochInfo.Identifier, epochInfo.CurrentEpoch); err != nil { + // purposely ignoring the error here not to halt the chain if the hook fails + log.Error(ctx, "Error before epoch start", err, "epoch_identifier", epochInfo.Identifier, "current_epoch", epochInfo.CurrentEpoch) + } + + return false, nil + }, + ) + + return err +} diff --git a/client/x/epochs/keeper/abci_test.go b/client/x/epochs/keeper/abci_test.go new file mode 100644 index 00000000..f1ab8f6d --- /dev/null +++ b/client/x/epochs/keeper/abci_test.go @@ -0,0 +1,190 @@ +package keeper_test + +import ( + "sort" + "testing" + "time" + + "golang.org/x/exp/maps" + + "cosmossdk.io/core/header" + + "github.com/stretchr/testify/require" + + "github.com/piplabs/story/client/x/epochs/types" +) + +// This test is responsible for testing how epochs increment based off +// of their initial conditions, and subsequent block height / times. +func (s *KeeperTestSuite) TestEpochInfoBeginBlockChanges() { + block1Time := time.Unix(1656907200, 0).UTC() + const defaultIdentifier = "hourly" + const defaultDuration = time.Hour + // eps is short for epsilon - in this case a negligible amount of time. + const eps = time.Nanosecond + + tests := map[string]struct { + // if identifier, duration is not set, we make it defaultIdentifier and defaultDuration. + // EpochCountingStarted, if unspecified, is inferred by CurrentEpoch == 0 + // StartTime is inferred to be block1Time if left blank. + initialEpochInfo types.EpochInfo + blockHeightTimePairs map[int]time.Time + expEpochInfo types.EpochInfo + }{ + "First block running at exactly start time sets epoch tick": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 1, CurrentEpochStartTime: block1Time, CurrentEpochStartHeight: 1}, + }, + "First block run sets start time, subsequent blocks within timer interval do not cause timer tick": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(time.Second), 3: block1Time.Add(time.Minute), 4: block1Time.Add(30 * time.Minute)}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 1, CurrentEpochStartTime: block1Time, CurrentEpochStartHeight: 1}, + }, + "Second block at exactly timer interval later does not tick": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(defaultDuration)}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 1, CurrentEpochStartTime: block1Time, CurrentEpochStartHeight: 1}, + }, + "Second block at timer interval + epsilon later does tick": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(defaultDuration).Add(eps)}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 2, CurrentEpochStartTime: block1Time.Add(time.Hour), CurrentEpochStartHeight: 2}, + }, + "Downtime recovery (many intervals), first block causes 1 tick and sets current start time 1 interval ahead": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(24 * time.Hour)}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 2, CurrentEpochStartTime: block1Time.Add(time.Hour), CurrentEpochStartHeight: 2}, + }, + "Downtime recovery (many intervals), second block is at tick 2, w/ start time 2 intervals ahead": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(24 * time.Hour), 3: block1Time.Add(24 * time.Hour).Add(eps)}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 3, CurrentEpochStartTime: block1Time.Add(2 * time.Hour), CurrentEpochStartHeight: 3}, + }, + "Many blocks between first and second tick": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 1, CurrentEpochStartTime: block1Time}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(time.Second), 3: block1Time.Add(2 * time.Second), 4: block1Time.Add(time.Hour).Add(eps)}, + expEpochInfo: types.EpochInfo{StartTime: block1Time, CurrentEpoch: 2, CurrentEpochStartTime: block1Time.Add(time.Hour), CurrentEpochStartHeight: 4}, + }, + "Distinct identifier and duration still works": { + initialEpochInfo: types.EpochInfo{Identifier: "hello", Duration: time.Minute, StartTime: block1Time, CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + blockHeightTimePairs: map[int]time.Time{2: block1Time.Add(time.Second), 3: block1Time.Add(time.Minute).Add(eps)}, + expEpochInfo: types.EpochInfo{Identifier: "hello", Duration: time.Minute, StartTime: block1Time, CurrentEpoch: 2, CurrentEpochStartTime: block1Time.Add(time.Minute), CurrentEpochStartHeight: 3}, + }, + "StartTime in future won't get ticked on first block": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time.Add(time.Second), CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + // currentEpochStartHeight is 1 because that's when the timer was created on-chain + expEpochInfo: types.EpochInfo{StartTime: block1Time.Add(time.Second), CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}, CurrentEpochStartHeight: 1}, + }, + "StartTime in past will get ticked on first block": { + initialEpochInfo: types.EpochInfo{StartTime: block1Time.Add(-time.Second), CurrentEpoch: 0, CurrentEpochStartTime: time.Time{}}, + expEpochInfo: types.EpochInfo{StartTime: block1Time.Add(-time.Second), CurrentEpoch: 1, CurrentEpochStartTime: block1Time.Add(-time.Second), CurrentEpochStartHeight: 1}, + }, + } + for name, test := range tests { + s.Run(name, func() { + s.SetupTest() + s.Ctx = s.Ctx.WithHeaderInfo(header.Info{Height: 1, Time: block1Time}) + initialEpoch := initializeBlankEpochInfoFields(test.initialEpochInfo, defaultIdentifier, defaultDuration) + err := s.EpochsKeeper.AddEpochInfo(s.Ctx, initialEpoch) + s.Require().NoError(err) + err = s.EpochsKeeper.BeginBlocker(s.Ctx) + s.Require().NoError(err) + + // get sorted heights + heights := maps.Keys(test.blockHeightTimePairs) + sort.Slice(heights, func(i, j int) bool { return heights[i] < heights[j] }) + + for _, h := range heights { + // for each height in order, run begin block + s.Ctx = s.Ctx.WithHeaderInfo(header.Info{Height: int64(h), Time: test.blockHeightTimePairs[h]}) + err := s.EpochsKeeper.BeginBlocker(s.Ctx) + s.Require().NoError(err) + } + expEpoch := initializeBlankEpochInfoFields(test.expEpochInfo, initialEpoch.Identifier, initialEpoch.Duration) + actEpoch, err := s.EpochsKeeper.EpochInfo.Get(s.Ctx, initialEpoch.Identifier) + s.Require().NoError(err) + s.Require().Equal(expEpoch, actEpoch) + }) + } +} + +// initializeBlankEpochInfoFields set identifier, duration and epochCountingStarted if blank in epoch. +func initializeBlankEpochInfoFields(epoch types.EpochInfo, identifier string, duration time.Duration) types.EpochInfo { + if epoch.Identifier == "" { + epoch.Identifier = identifier + } + if epoch.Duration == time.Duration(0) { + epoch.Duration = duration + } + epoch.EpochCountingStarted = (epoch.CurrentEpoch != 0) + + return epoch +} + +func TestEpochStartingOneMonthAfterInitGenesis(t *testing.T) { + t.Parallel() + ctx, epochsKeeper := Setup(t) + // On init genesis, default epochs information is set + // To check init genesis again, should make it fresh status + epochInfos, err := epochsKeeper.AllEpochInfos(ctx) + require.NoError(t, err) + for _, epochInfo := range epochInfos { + err := epochsKeeper.EpochInfo.Remove(ctx, epochInfo.Identifier) + require.NoError(t, err) + } + + now := time.Now() + week := time.Hour * 24 * 7 + month := time.Hour * 24 * 30 + initialBlockHeight := int64(1) + ctx = ctx.WithHeaderInfo(header.Info{Height: initialBlockHeight, Time: now}) + + err = epochsKeeper.InitGenesis(ctx, types.GenesisState{ + Epochs: []types.EpochInfo{ + { + Identifier: "monthly", + StartTime: now.Add(month), + Duration: time.Hour * 24 * 30, + CurrentEpoch: 0, + CurrentEpochStartHeight: ctx.HeaderInfo().Height, + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: false, + }, + }, + }) + require.NoError(t, err) + + // epoch not started yet + epochInfo, err := epochsKeeper.EpochInfo.Get(ctx, "monthly") + require.NoError(t, err) + require.Equal(t, int64(0), epochInfo.CurrentEpoch) + require.Equal(t, epochInfo.CurrentEpochStartHeight, initialBlockHeight) + require.Equal(t, time.Time{}, epochInfo.CurrentEpochStartTime) + require.False(t, epochInfo.EpochCountingStarted) + + // after 1 week + ctx = ctx.WithHeaderInfo(header.Info{Height: 2, Time: now.Add(week)}) + err = epochsKeeper.BeginBlocker(ctx) + require.NoError(t, err) + + // epoch not started yet + epochInfo, err = epochsKeeper.EpochInfo.Get(ctx, "monthly") + require.NoError(t, err) + require.Equal(t, int64(0), epochInfo.CurrentEpoch) + require.Equal(t, epochInfo.CurrentEpochStartHeight, initialBlockHeight) + require.Equal(t, time.Time{}, epochInfo.CurrentEpochStartTime) + require.False(t, epochInfo.EpochCountingStarted) + + // after 1 month + ctx = ctx.WithHeaderInfo(header.Info{Height: 3, Time: now.Add(month)}) + err = epochsKeeper.BeginBlocker(ctx) + require.NoError(t, err) + + // epoch started + epochInfo, err = epochsKeeper.EpochInfo.Get(ctx, "monthly") + require.NoError(t, err) + require.Equal(t, int64(1), epochInfo.CurrentEpoch) + require.Equal(t, epochInfo.CurrentEpochStartHeight, ctx.HeaderInfo().Height) + require.Equal(t, epochInfo.CurrentEpochStartTime.UTC().String(), now.Add(month).UTC().String()) + require.True(t, epochInfo.EpochCountingStarted) +} diff --git a/client/x/epochs/keeper/epoch.go b/client/x/epochs/keeper/epoch.go new file mode 100644 index 00000000..188a6399 --- /dev/null +++ b/client/x/epochs/keeper/epoch.go @@ -0,0 +1,72 @@ +package keeper + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/piplabs/story/client/x/epochs/types" +) + +// AddEpochInfo adds a new epoch info. Will return an error if the epoch fails validation, +// or re-uses an existing identifier. +// This method also sets the start time if left unset, and sets the epoch start height. +func (k Keeper) AddEpochInfo(ctx context.Context, epoch types.EpochInfo) error { + err := epoch.Validate() + if err != nil { + return err + } + // Check if identifier already exists + isExist, err := k.EpochInfo.Has(ctx, epoch.Identifier) + if err != nil { + return err + } + if isExist { + return fmt.Errorf("epoch with identifier %s already exists", epoch.Identifier) + } + + // Initialize empty and default epoch values + if epoch.StartTime.IsZero() { + epoch.StartTime = sdk.UnwrapSDKContext(ctx).HeaderInfo().Time + } + if epoch.CurrentEpochStartHeight == 0 { + epoch.CurrentEpochStartHeight = sdk.UnwrapSDKContext(ctx).HeaderInfo().Height + } + + return k.EpochInfo.Set(ctx, epoch.Identifier, epoch) +} + +// AllEpochInfos iterate through epochs to return all epochs info. +func (k Keeper) AllEpochInfos(ctx context.Context) ([]types.EpochInfo, error) { + epochs := []types.EpochInfo{} + err := k.EpochInfo.Walk( + ctx, + nil, + func(_ string, value types.EpochInfo) (stop bool, err error) { + epochs = append(epochs, value) + return false, nil + }, + ) + + return epochs, err +} + +// NumBlocksSinceEpochStart returns the number of blocks since the epoch started. +// if the epoch started on block N, then calling this during block N (after BeforeEpochStart) +// would return 0. +// Calling it any point in block N+1 (assuming the epoch doesn't increment) would return 1. +func (k Keeper) NumBlocksSinceEpochStart(ctx context.Context, identifier string) (int64, error) { + epoch, err := k.EpochInfo.Get(ctx, identifier) + if err != nil { + return 0, fmt.Errorf("epoch with identifier %s not found", identifier) + } + + return sdk.UnwrapSDKContext(ctx).HeaderInfo().Height - epoch.CurrentEpochStartHeight, nil +} + +// GetEpochInfo gets current epoch info by identifier. +// NOTE(Narangde): add this func which can be a diff from cosmos-sdk. +func (k Keeper) GetEpochInfo(ctx context.Context, identifier string) (types.EpochInfo, error) { + return k.EpochInfo.Get(ctx, identifier) +} diff --git a/client/x/epochs/keeper/epoch_test.go b/client/x/epochs/keeper/epoch_test.go new file mode 100644 index 00000000..888e81e4 --- /dev/null +++ b/client/x/epochs/keeper/epoch_test.go @@ -0,0 +1,105 @@ +package keeper_test + +import ( + "time" + + "cosmossdk.io/core/header" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/piplabs/story/client/x/epochs/types" +) + +func (s *KeeperTestSuite) TestAddEpochInfo() { + defaultIdentifier := "default_add_epoch_info_id" + defaultDuration := time.Hour + startBlockHeight := int64(100) + startBlockTime := time.Unix(1656907200, 0).UTC() + tests := map[string]struct { + addedEpochInfo types.EpochInfo + expErr bool + expEpochInfo types.EpochInfo + }{ + "simple_add": { + addedEpochInfo: types.EpochInfo{ + Identifier: defaultIdentifier, + StartTime: time.Time{}, + Duration: defaultDuration, + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: false, + }, + expErr: false, + expEpochInfo: types.EpochInfo{ + Identifier: defaultIdentifier, + StartTime: startBlockTime, + Duration: defaultDuration, + CurrentEpoch: 0, + CurrentEpochStartHeight: startBlockHeight, + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: false, + }, + }, + "zero_duration": { + addedEpochInfo: types.EpochInfo{ + Identifier: defaultIdentifier, + StartTime: time.Time{}, + Duration: time.Duration(0), + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: false, + }, + expErr: true, + }, + } + for name, test := range tests { + s.Run(name, func() { + s.SetupTest() + s.Ctx = s.Ctx.WithHeaderInfo(header.Info{Height: startBlockHeight, Time: startBlockTime}) + err := s.EpochsKeeper.AddEpochInfo(s.Ctx, test.addedEpochInfo) + if !test.expErr { + s.Require().NoError(err) + actualEpochInfo, err := s.EpochsKeeper.EpochInfo.Get(s.Ctx, test.addedEpochInfo.Identifier) + s.Require().NoError(err) + s.Require().Equal(test.expEpochInfo, actualEpochInfo) + } else { + s.Require().Error(err) + } + }) + } +} + +func (s *KeeperTestSuite) TestDuplicateAddEpochInfo() { + identifier := "duplicate_add_epoch_info" + epochInfo := types.NewGenesisEpochInfo(identifier, time.Hour*24*30) + err := s.EpochsKeeper.AddEpochInfo(s.Ctx, epochInfo) + s.Require().NoError(err) + err = s.EpochsKeeper.AddEpochInfo(s.Ctx, epochInfo) + s.Require().Error(err) +} + +func (s *KeeperTestSuite) TestEpochLifeCycle() { + s.SetupTest() + + epochInfo := types.NewGenesisEpochInfo("monthly", time.Hour*24*30) + err := s.EpochsKeeper.AddEpochInfo(s.Ctx, epochInfo) + s.Require().NoError(err) + epochInfoSaved, err := s.EpochsKeeper.EpochInfo.Get(s.Ctx, "monthly") + s.Require().NoError(err) + // setup expected epoch info + expectedEpochInfo := epochInfo + expectedEpochInfo.StartTime = sdk.UnwrapSDKContext(s.Ctx).HeaderInfo().Time + expectedEpochInfo.CurrentEpochStartHeight = s.Ctx.BlockHeight() + s.Require().Equal(expectedEpochInfo, epochInfoSaved) + + allEpochs, err := s.EpochsKeeper.AllEpochInfos(s.Ctx) + s.Require().NoError(err) + s.Require().Len(allEpochs, 5) + s.Require().Equal(allEpochs[0].Identifier, "day") // alphabetical order + s.Require().Equal(allEpochs[1].Identifier, "hour") + s.Require().Equal(allEpochs[2].Identifier, "minute") + s.Require().Equal(allEpochs[3].Identifier, "monthly") + s.Require().Equal(allEpochs[4].Identifier, "week") +} diff --git a/client/x/epochs/keeper/genesis.go b/client/x/epochs/keeper/genesis.go new file mode 100644 index 00000000..ba881a66 --- /dev/null +++ b/client/x/epochs/keeper/genesis.go @@ -0,0 +1,31 @@ +package keeper + +import ( + "context" + + "github.com/piplabs/story/client/x/epochs/types" +) + +// InitGenesis sets epoch info from genesis. +func (k Keeper) InitGenesis(ctx context.Context, genState types.GenesisState) error { + for _, epoch := range genState.Epochs { + err := k.AddEpochInfo(ctx, epoch) + if err != nil { + return err + } + } + + return nil +} + +// ExportGenesis returns the capability module's exported genesis. +func (k Keeper) ExportGenesis(ctx context.Context) (*types.GenesisState, error) { + genesis := types.DefaultGenesis() + epochs, err := k.AllEpochInfos(ctx) + if err != nil { + return nil, err + } + genesis.Epochs = epochs + + return genesis, nil +} diff --git a/client/x/epochs/keeper/genesis_test.go b/client/x/epochs/keeper/genesis_test.go new file mode 100644 index 00000000..8ebe388e --- /dev/null +++ b/client/x/epochs/keeper/genesis_test.go @@ -0,0 +1,99 @@ +package keeper_test + +import ( + "testing" + "time" + + "cosmossdk.io/core/header" + + "github.com/stretchr/testify/require" + + "github.com/piplabs/story/client/x/epochs/types" +) + +//nolint:paralleltest // no parallel +func TestEpochsExportGenesis(t *testing.T) { + ctx, epochsKeeper := Setup(t) + + chainStartTime := ctx.BlockTime() + chainStartHeight := ctx.HeaderInfo().Height + + genesis, err := epochsKeeper.ExportGenesis(ctx) + require.NoError(t, err) + require.Len(t, genesis.Epochs, 4) + + expectedEpochs := types.DefaultGenesis().Epochs + for i := range expectedEpochs { + expectedEpochs[i].CurrentEpochStartHeight = chainStartHeight + expectedEpochs[i].StartTime = chainStartTime + } + require.Equal(t, expectedEpochs, genesis.Epochs) +} + +func TestEpochsInitGenesis(t *testing.T) { + t.Parallel() + ctx, epochsKeeper := Setup(t) + + // On init genesis, default epochs information is set + // To check init genesis again, should make it fresh status + epochInfos, err := epochsKeeper.AllEpochInfos(ctx) + require.NoError(t, err) + for _, epochInfo := range epochInfos { + err := epochsKeeper.EpochInfo.Remove(ctx, epochInfo.Identifier) + require.NoError(t, err) + } + + // now := time.Now() + ctx.WithHeaderInfo(header.Info{Height: 1, Time: time.Now().UTC()}) + + // test genesisState validation + genesisState := types.GenesisState{ + Epochs: []types.EpochInfo{ + { + Identifier: "monthly", + StartTime: time.Time{}, + Duration: time.Hour * 24, + CurrentEpoch: 0, + CurrentEpochStartHeight: ctx.BlockHeight(), + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: true, + }, + { + Identifier: "monthly", + StartTime: time.Time{}, + Duration: time.Hour * 24, + CurrentEpoch: 0, + CurrentEpochStartHeight: ctx.BlockHeight(), + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: true, + }, + }, + } + require.EqualError(t, genesisState.Validate(), "epoch identifier should be unique") + + genesisState = types.GenesisState{ + Epochs: []types.EpochInfo{ + { + Identifier: "monthly", + StartTime: time.Time{}, + Duration: time.Hour * 24, + CurrentEpoch: 0, + CurrentEpochStartHeight: ctx.BlockHeight(), + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: true, + }, + }, + } + + err = epochsKeeper.InitGenesis(ctx, genesisState) + require.NoError(t, err) + epochInfo, err := epochsKeeper.EpochInfo.Get(ctx, "monthly") + require.NoError(t, err) + require.Equal(t, "monthly", epochInfo.Identifier) + require.Equal(t, epochInfo.StartTime.UTC().String(), ctx.HeaderInfo().Time.UTC().String()) + require.Equal(t, time.Hour*24, epochInfo.Duration) + require.Equal(t, int64(0), epochInfo.CurrentEpoch) + require.Equal(t, epochInfo.CurrentEpochStartHeight, ctx.BlockHeight()) + require.Equal(t, epochInfo.CurrentEpochStartTime.UTC().String(), time.Time{}.String()) + require.True(t, epochInfo.EpochCountingStarted) +} diff --git a/client/x/epochs/keeper/grpc_query.go b/client/x/epochs/keeper/grpc_query.go new file mode 100644 index 00000000..cd26d019 --- /dev/null +++ b/client/x/epochs/keeper/grpc_query.go @@ -0,0 +1,51 @@ +package keeper + +import ( + "context" + "errors" + + "github.com/piplabs/story/client/x/epochs/types" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ types.QueryServer = Querier{} + +// Querier defines a wrapper around the x/epochs keeper providing gRPC method +// handlers. +type Querier struct { + Keeper +} + +// NewQuerier initializes new querier. +func NewQuerier(k Keeper) Querier { + return Querier{Keeper: k} +} + +// EpochInfos provide running epochInfos. +func (q Querier) EpochInfos(ctx context.Context, _ *types.QueryEpochsInfoRequest) (*types.QueryEpochsInfoResponse, error) { + epochs, err := q.Keeper.AllEpochInfos(ctx) + return &types.QueryEpochsInfoResponse{ + Epochs: epochs, + }, err +} + +// CurrentEpoch provides current epoch of specified identifier. +func (q Querier) CurrentEpoch(ctx context.Context, req *types.QueryCurrentEpochRequest) (*types.QueryCurrentEpochResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + if req.Identifier == "" { + return nil, status.Error(codes.InvalidArgument, "identifier is empty") + } + + info, err := q.Keeper.EpochInfo.Get(ctx, req.Identifier) + if err != nil { + return nil, errors.New("not available identifier") + } + + return &types.QueryCurrentEpochResponse{ + CurrentEpoch: info.CurrentEpoch, + }, nil +} diff --git a/client/x/epochs/keeper/grpc_query_test.go b/client/x/epochs/keeper/grpc_query_test.go new file mode 100644 index 00000000..30fc6dba --- /dev/null +++ b/client/x/epochs/keeper/grpc_query_test.go @@ -0,0 +1,22 @@ +package keeper_test + +import ( + "github.com/piplabs/story/client/x/epochs/types" +) + +func (s *KeeperTestSuite) TestQueryEpochInfos() { + s.SetupTest() + queryClient := s.queryClient + + // Check that querying epoch infos on default genesis returns the default genesis epoch infos + epochInfosResponse, err := queryClient.EpochInfos(s.Ctx, &types.QueryEpochsInfoRequest{}) + s.Require().NoError(err) + s.Require().Len(epochInfosResponse.Epochs, 4) + expectedEpochs := types.DefaultGenesis().Epochs + for id := range expectedEpochs { + expectedEpochs[id].StartTime = s.Ctx.BlockTime() + expectedEpochs[id].CurrentEpochStartHeight = s.Ctx.BlockHeight() + } + + s.Require().Equal(expectedEpochs, epochInfosResponse.Epochs) +} diff --git a/client/x/epochs/keeper/hooks.go b/client/x/epochs/keeper/hooks.go new file mode 100644 index 00000000..28d2d939 --- /dev/null +++ b/client/x/epochs/keeper/hooks.go @@ -0,0 +1,27 @@ +package keeper + +import ( + "context" + + "github.com/piplabs/story/client/x/epochs/types" +) + +// Hooks gets the hooks for governance Keeper. +func (k Keeper) Hooks() types.EpochHooks { + if k.hooks == nil { + // return a no-op implementation if no hooks are set + return types.MultiEpochHooks{} + } + + return k.hooks +} + +// AfterEpochEnd gets called at the end of the epoch, end of epoch is the timestamp of first block produced after epoch duration. +func (k Keeper) AfterEpochEnd(ctx context.Context, identifier string, epochNumber int64) error { + return k.Hooks().AfterEpochEnd(ctx, identifier, epochNumber) +} + +// BeforeEpochStart new epoch is next block of epoch end block. +func (k Keeper) BeforeEpochStart(ctx context.Context, identifier string, epochNumber int64) error { + return k.Hooks().BeforeEpochStart(ctx, identifier, epochNumber) +} diff --git a/client/x/epochs/keeper/keeper.go b/client/x/epochs/keeper/keeper.go new file mode 100644 index 00000000..183bfae6 --- /dev/null +++ b/client/x/epochs/keeper/keeper.go @@ -0,0 +1,53 @@ +package keeper + +import ( + "cosmossdk.io/collections" + "cosmossdk.io/core/event" + "cosmossdk.io/core/store" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/piplabs/story/client/x/epochs/types" +) + +type Keeper struct { + cdc codec.BinaryCodec + hooks types.EpochHooks + + // NOTE(Narangde): Add storeService and EventService manually instead of Environment + storeService store.KVStoreService + EventService event.Service + + Schema collections.Schema + EpochInfo collections.Map[string, types.EpochInfo] +} + +// NewKeeper returns a new keeper by codec and storeKey inputs. +func NewKeeper(storeService store.KVStoreService, eventService event.Service, cdc codec.BinaryCodec) *Keeper { + sb := collections.NewSchemaBuilder(storeService) + k := Keeper{ + cdc: cdc, + storeService: storeService, + EventService: eventService, + EpochInfo: collections.NewMap(sb, types.KeyPrefixEpoch, "epoch_info", collections.StringKey, codec.CollValue[types.EpochInfo](cdc)), + } + + schema, err := sb.Build() + if err != nil { + panic(err) + } + k.Schema = schema + + return &k +} + +// Set hooks. +func (k *Keeper) SetHooks(eh types.EpochHooks) *Keeper { + if k.hooks != nil { + panic("cannot set epochs hooks twice") + } + + k.hooks = eh + + return k +} diff --git a/client/x/epochs/keeper/keeper_test.go b/client/x/epochs/keeper/keeper_test.go new file mode 100644 index 00000000..163ad219 --- /dev/null +++ b/client/x/epochs/keeper/keeper_test.go @@ -0,0 +1,91 @@ +package keeper_test + +import ( + "testing" + "time" + + "cosmossdk.io/core/header" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + cosmosmodule "github.com/cosmos/cosmos-sdk/types/module" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + epochskeeper "github.com/piplabs/story/client/x/epochs/keeper" + "github.com/piplabs/story/client/x/epochs/module" + "github.com/piplabs/story/client/x/epochs/types" +) + +type KeeperTestSuite struct { + suite.Suite + Ctx sdk.Context + EpochsKeeper *epochskeeper.Keeper + queryClient types.QueryClient +} + +func (s *KeeperTestSuite) SetupTest() { + s.Ctx, s.EpochsKeeper = Setup(s.T()) + + queryRouter := baseapp.NewGRPCQueryRouter() + cfg := cosmosmodule.NewConfigurator(nil, nil, queryRouter) + types.RegisterQueryServer(cfg.QueryServer(), epochskeeper.NewQuerier(*s.EpochsKeeper)) + grpcQueryService := &baseapp.QueryServiceTestHelper{ + GRPCQueryRouter: queryRouter, + Ctx: s.Ctx, + } + encCfg := moduletestutil.MakeTestEncodingConfig(module.AppModuleBasic{}) + grpcQueryService.SetInterfaceRegistry(encCfg.InterfaceRegistry) + s.queryClient = types.NewQueryClient(grpcQueryService) +} + +func Setup(t *testing.T) (sdk.Context, *epochskeeper.Keeper) { + t.Helper() + + key := storetypes.NewKVStoreKey(types.StoreKey) + storeService := runtime.NewKVStoreService(key) + eventService := runtime.EventService{} + testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) + ctx := testCtx.Ctx.WithHeaderInfo(header.Info{Time: time.Now()}) + encCfg := moduletestutil.MakeTestEncodingConfig(module.AppModuleBasic{}) + + epochsKeeper := epochskeeper.NewKeeper( + storeService, + eventService, + encCfg.Codec, + ) + epochsKeeper = epochsKeeper.SetHooks(types.NewMultiEpochHooks()) + ctx.WithHeaderInfo(header.Info{Height: 1, Time: time.Now().UTC(), ChainID: "epochs"}) + err := epochsKeeper.InitGenesis(ctx, *types.DefaultGenesis()) + require.NoError(t, err) + SetEpochStartTime(ctx, *epochsKeeper) + + return ctx, epochsKeeper +} + +func TestKeeperTestSuite(t *testing.T) { + t.Parallel() + suite.Run(t, new(KeeperTestSuite)) +} + +func SetEpochStartTime(ctx sdk.Context, epochsKeeper epochskeeper.Keeper) { + epochs, err := epochsKeeper.AllEpochInfos(ctx) + if err != nil { + panic(err) + } + for _, epoch := range epochs { + epoch.StartTime = ctx.BlockTime() + err := epochsKeeper.EpochInfo.Remove(ctx, epoch.Identifier) + if err != nil { + panic(err) + } + err = epochsKeeper.AddEpochInfo(ctx, epoch) + if err != nil { + panic(err) + } + } +} diff --git a/client/x/epochs/module/depinject.go b/client/x/epochs/module/depinject.go new file mode 100644 index 00000000..9b3fa336 --- /dev/null +++ b/client/x/epochs/module/depinject.go @@ -0,0 +1,83 @@ +package module + +import ( + "fmt" + "sort" + + "golang.org/x/exp/maps" + + "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/event" + "cosmossdk.io/core/store" + "cosmossdk.io/depinject" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/piplabs/story/client/x/epochs/keeper" + "github.com/piplabs/story/client/x/epochs/types" +) + +var _ depinject.OnePerModuleType = AppModule{} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +//nolint:gochecknoinits // Cosmos-style +func init() { + appmodule.Register( + &Module{}, + appmodule.Provide(ProvideModule), + appmodule.Invoke(InvokeSetHooks), + ) +} + +type ModuleInputs struct { + depinject.In + + StoreService store.KVStoreService + Cdc codec.Codec + Config *Module + TXConfig client.TxConfig + + EventService event.Service +} + +type ModuleOutputs struct { + depinject.Out + + EpochKeeper *keeper.Keeper + Module appmodule.AppModule +} + +func ProvideModule(in ModuleInputs) ModuleOutputs { + k := keeper.NewKeeper(in.StoreService, in.EventService, in.Cdc) + m := NewAppModule(in.Cdc, k) + + return ModuleOutputs{EpochKeeper: k, Module: m} +} + +func InvokeSetHooks(keeper *keeper.Keeper, hooks map[string]types.EpochHooksWrapper) error { + if hooks == nil { + return nil + } + + // Default ordering is lexical by module name. + // Explicit ordering can be added to the module config if required. + modNames := maps.Keys(hooks) + order := modNames + sort.Strings(order) + + var multiHooks types.MultiEpochHooks + for _, modName := range order { + hook, ok := hooks[modName] + if !ok { + return fmt.Errorf("can't find epoch hooks for module %s", modName) + } + multiHooks = append(multiHooks, hook) + } + + keeper.SetHooks(multiHooks) + + return nil +} diff --git a/client/x/epochs/module/module.go b/client/x/epochs/module/module.go new file mode 100644 index 00000000..eeeef0c8 --- /dev/null +++ b/client/x/epochs/module/module.go @@ -0,0 +1,137 @@ +package module + +import ( + "context" + "encoding/json" + "fmt" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + + "github.com/piplabs/story/client/x/epochs/keeper" + "github.com/piplabs/story/client/x/epochs/types" + "github.com/piplabs/story/lib/errors" +) + +var ( + _ module.HasGenesis = AppModule{} + + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} +) + +const ConsensusVersion = 1 + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface that defines the +// independent methods a Cosmos SDK module needs to implement. +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +// Name returns the name of the module as a string. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the amino codec for the module, which is used +// to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message. +func (AppModuleBasic) RegisterInterfaces(reg codectypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(client.Context, *runtime.ServeMux) {} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface for the epochs module. +type AppModule struct { + AppModuleBasic + + keeper *keeper.Keeper +} + +// NewAppModule creates a new AppModule object. +func NewAppModule(cdc codec.Codec, keeper *keeper.Keeper) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + } +} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// Name returns the epochs module's name. +// Deprecated: kept for legacy reasons. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the epochs module's types for the given codec. +func (AppModule) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the epochs module. +func (AppModule) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {} + +// DefaultGenesis returns the epochs module's default genesis state. +func (AppModule) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis performs genesis state validation for the epochs module. +func (AppModule) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return errors.Wrap(err, "failed to unmarshal %s genesis state", types.ModuleName) + } + + return gs.Validate() +} + +// InitGenesis performs the epochs module's genesis initialization. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, bz json.RawMessage) { + var gs types.GenesisState + cdc.MustUnmarshalJSON(bz, &gs) + + if err := am.keeper.InitGenesis(ctx, gs); err != nil { + panic(fmt.Sprintf("failed to initialize %s genesis state: %v", types.ModuleName, err)) + } +} + +// ExportGenesis returns the epochs module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs, err := am.keeper.ExportGenesis(ctx) + if err != nil { + panic(err) + } + + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements HasConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +// BeginBlock executes all ABCI BeginBlock logic respective to the epochs module. +func (am AppModule) BeginBlock(ctx context.Context) error { + return am.keeper.BeginBlocker(ctx) +} diff --git a/client/x/epochs/module/module.proto b/client/x/epochs/module/module.proto new file mode 100644 index 00000000..df4c1cf8 --- /dev/null +++ b/client/x/epochs/module/module.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package client.x.epochs.module; + +import "cosmos/app/v1alpha1/module.proto"; + +// Module is the config object of the epochs module. +message Module { + option (cosmos.app.v1alpha1.module) = { + go_import: "github.com/piplabs/story/client/x/epochs" + }; +} \ No newline at end of file diff --git a/client/x/epochs/module/module.pulsar.go b/client/x/epochs/module/module.pulsar.go new file mode 100644 index 00000000..5ad34eea --- /dev/null +++ b/client/x/epochs/module/module.pulsar.go @@ -0,0 +1,502 @@ +// Code generated by protoc-gen-go-pulsar. DO NOT EDIT. +package module + +import ( + _ "cosmossdk.io/api/cosmos/app/v1alpha1" + fmt "fmt" + runtime "github.com/cosmos/cosmos-proto/runtime" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoiface "google.golang.org/protobuf/runtime/protoiface" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + io "io" + reflect "reflect" + sync "sync" +) + +var ( + md_Module protoreflect.MessageDescriptor +) + +func init() { + file_client_x_epochs_module_module_proto_init() + md_Module = File_client_x_epochs_module_module_proto.Messages().ByName("Module") +} + +var _ protoreflect.Message = (*fastReflection_Module)(nil) + +type fastReflection_Module Module + +func (x *Module) ProtoReflect() protoreflect.Message { + return (*fastReflection_Module)(x) +} + +func (x *Module) slowProtoReflect() protoreflect.Message { + mi := &file_client_x_epochs_module_module_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_Module_messageType fastReflection_Module_messageType +var _ protoreflect.MessageType = fastReflection_Module_messageType{} + +type fastReflection_Module_messageType struct{} + +func (x fastReflection_Module_messageType) Zero() protoreflect.Message { + return (*fastReflection_Module)(nil) +} +func (x fastReflection_Module_messageType) New() protoreflect.Message { + return new(fastReflection_Module) +} +func (x fastReflection_Module_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_Module +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_Module) Descriptor() protoreflect.MessageDescriptor { + return md_Module +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_Module) Type() protoreflect.MessageType { + return _fastReflection_Module_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_Module) New() protoreflect.Message { + return new(fastReflection_Module) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_Module) Interface() protoreflect.ProtoMessage { + return (*Module)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_Module) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_Module) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: client.x.epochs.module.Module")) + } + panic(fmt.Errorf("message client.x.epochs.module.Module does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_Module) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: client.x.epochs.module.Module")) + } + panic(fmt.Errorf("message client.x.epochs.module.Module does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_Module) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: client.x.epochs.module.Module")) + } + panic(fmt.Errorf("message client.x.epochs.module.Module does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_Module) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: client.x.epochs.module.Module")) + } + panic(fmt.Errorf("message client.x.epochs.module.Module does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_Module) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: client.x.epochs.module.Module")) + } + panic(fmt.Errorf("message client.x.epochs.module.Module does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_Module) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: client.x.epochs.module.Module")) + } + panic(fmt.Errorf("message client.x.epochs.module.Module does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_Module) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in client.x.epochs.module.Module", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_Module) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_Module) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_Module) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*Module) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*Module) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*Module) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: Module: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: Module: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.0 +// protoc (unknown) +// source: client/x/epochs/module/module.proto + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Module is the config object of the epochs module. +type Module struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Module) Reset() { + *x = Module{} + if protoimpl.UnsafeEnabled { + mi := &file_client_x_epochs_module_module_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Module) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Module) ProtoMessage() {} + +// Deprecated: Use Module.ProtoReflect.Descriptor instead. +func (*Module) Descriptor() ([]byte, []int) { + return file_client_x_epochs_module_module_proto_rawDescGZIP(), []int{0} +} + +var File_client_x_epochs_module_module_proto protoreflect.FileDescriptor + +var file_client_x_epochs_module_module_proto_rawDesc = []byte{ + 0x0a, 0x23, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x78, 0x2f, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x78, 0x2e, + 0x65, 0x70, 0x6f, 0x63, 0x68, 0x73, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x1a, 0x20, 0x63, + 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x3a, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x3a, 0x30, 0xba, 0xc0, 0x96, 0xda, 0x01, + 0x2a, 0x0a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x69, + 0x70, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x2f, 0x78, 0x2f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x73, 0x42, 0xce, 0x01, 0x0a, 0x1a, + 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x78, 0x2e, 0x65, 0x70, 0x6f, + 0x63, 0x68, 0x73, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x63, 0x6f, 0x73, 0x6d, 0x6f, + 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x2f, 0x78, 0x2f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0xa2, 0x02, 0x04, 0x43, 0x58, 0x45, 0x4d, 0xaa, 0x02, 0x16, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x2e, 0x58, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x73, 0x2e, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0xca, 0x02, 0x16, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5c, 0x58, 0x5c, 0x45, 0x70, + 0x6f, 0x63, 0x68, 0x73, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xe2, 0x02, 0x22, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5c, 0x58, 0x5c, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x73, 0x5c, 0x4d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0xea, 0x02, 0x19, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x3a, 0x58, 0x3a, 0x3a, 0x45, 0x70, + 0x6f, 0x63, 0x68, 0x73, 0x3a, 0x3a, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_client_x_epochs_module_module_proto_rawDescOnce sync.Once + file_client_x_epochs_module_module_proto_rawDescData = file_client_x_epochs_module_module_proto_rawDesc +) + +func file_client_x_epochs_module_module_proto_rawDescGZIP() []byte { + file_client_x_epochs_module_module_proto_rawDescOnce.Do(func() { + file_client_x_epochs_module_module_proto_rawDescData = protoimpl.X.CompressGZIP(file_client_x_epochs_module_module_proto_rawDescData) + }) + return file_client_x_epochs_module_module_proto_rawDescData +} + +var file_client_x_epochs_module_module_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_client_x_epochs_module_module_proto_goTypes = []interface{}{ + (*Module)(nil), // 0: client.x.epochs.module.Module +} +var file_client_x_epochs_module_module_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_client_x_epochs_module_module_proto_init() } +func file_client_x_epochs_module_module_proto_init() { + if File_client_x_epochs_module_module_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_client_x_epochs_module_module_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Module); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_client_x_epochs_module_module_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_client_x_epochs_module_module_proto_goTypes, + DependencyIndexes: file_client_x_epochs_module_module_proto_depIdxs, + MessageInfos: file_client_x_epochs_module_module_proto_msgTypes, + }.Build() + File_client_x_epochs_module_module_proto = out.File + file_client_x_epochs_module_module_proto_rawDesc = nil + file_client_x_epochs_module_module_proto_goTypes = nil + file_client_x_epochs_module_module_proto_depIdxs = nil +} diff --git a/client/x/epochs/types/codec.go b/client/x/epochs/types/codec.go new file mode 100644 index 00000000..b03261a0 --- /dev/null +++ b/client/x/epochs/types/codec.go @@ -0,0 +1,10 @@ +package types + +import ( + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func RegisterInterfaces(registrar cdctypes.InterfaceRegistry) { + registrar.RegisterImplementations((*sdk.Msg)(nil)) +} diff --git a/client/x/epochs/types/events.pb.go b/client/x/epochs/types/events.pb.go new file mode 100644 index 00000000..e149b3c2 --- /dev/null +++ b/client/x/epochs/types/events.pb.go @@ -0,0 +1,493 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: client/x/epochs/types/events.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// EventEpochEnd is an event emitted when an epoch end. +type EventEpochEnd struct { + EpochNumber int64 `protobuf:"varint,1,opt,name=epoch_number,json=epochNumber,proto3" json:"epoch_number,omitempty"` +} + +func (m *EventEpochEnd) Reset() { *m = EventEpochEnd{} } +func (m *EventEpochEnd) String() string { return proto.CompactTextString(m) } +func (*EventEpochEnd) ProtoMessage() {} +func (*EventEpochEnd) Descriptor() ([]byte, []int) { + return fileDescriptor_ee2d70eeda0e3075, []int{0} +} +func (m *EventEpochEnd) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventEpochEnd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventEpochEnd.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventEpochEnd) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventEpochEnd.Merge(m, src) +} +func (m *EventEpochEnd) XXX_Size() int { + return m.Size() +} +func (m *EventEpochEnd) XXX_DiscardUnknown() { + xxx_messageInfo_EventEpochEnd.DiscardUnknown(m) +} + +var xxx_messageInfo_EventEpochEnd proto.InternalMessageInfo + +func (m *EventEpochEnd) GetEpochNumber() int64 { + if m != nil { + return m.EpochNumber + } + return 0 +} + +// EventEpochStart is an event emitted when an epoch start. +type EventEpochStart struct { + EpochNumber int64 `protobuf:"varint,1,opt,name=epoch_number,json=epochNumber,proto3" json:"epoch_number,omitempty"` + EpochStartTime int64 `protobuf:"varint,2,opt,name=epoch_start_time,json=epochStartTime,proto3" json:"epoch_start_time,omitempty"` +} + +func (m *EventEpochStart) Reset() { *m = EventEpochStart{} } +func (m *EventEpochStart) String() string { return proto.CompactTextString(m) } +func (*EventEpochStart) ProtoMessage() {} +func (*EventEpochStart) Descriptor() ([]byte, []int) { + return fileDescriptor_ee2d70eeda0e3075, []int{1} +} +func (m *EventEpochStart) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventEpochStart) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventEpochStart.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventEpochStart) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventEpochStart.Merge(m, src) +} +func (m *EventEpochStart) XXX_Size() int { + return m.Size() +} +func (m *EventEpochStart) XXX_DiscardUnknown() { + xxx_messageInfo_EventEpochStart.DiscardUnknown(m) +} + +var xxx_messageInfo_EventEpochStart proto.InternalMessageInfo + +func (m *EventEpochStart) GetEpochNumber() int64 { + if m != nil { + return m.EpochNumber + } + return 0 +} + +func (m *EventEpochStart) GetEpochStartTime() int64 { + if m != nil { + return m.EpochStartTime + } + return 0 +} + +func init() { + proto.RegisterType((*EventEpochEnd)(nil), "client.x.epochs.types.EventEpochEnd") + proto.RegisterType((*EventEpochStart)(nil), "client.x.epochs.types.EventEpochStart") +} + +func init() { + proto.RegisterFile("client/x/epochs/types/events.proto", fileDescriptor_ee2d70eeda0e3075) +} + +var fileDescriptor_ee2d70eeda0e3075 = []byte{ + // 187 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0xce, 0xc9, 0x4c, + 0xcd, 0x2b, 0xd1, 0xaf, 0xd0, 0x4f, 0x2d, 0xc8, 0x4f, 0xce, 0x28, 0xd6, 0x2f, 0xa9, 0x2c, 0x48, + 0x2d, 0xd6, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, + 0x85, 0xa8, 0xd1, 0xab, 0xd0, 0x83, 0xa8, 0xd1, 0x03, 0xab, 0x51, 0x32, 0xe2, 0xe2, 0x75, 0x05, + 0x29, 0x73, 0x05, 0x09, 0xba, 0xe6, 0xa5, 0x08, 0x29, 0x72, 0xf1, 0x80, 0x15, 0xc4, 0xe7, 0x95, + 0xe6, 0x26, 0xa5, 0x16, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0x71, 0x83, 0xc5, 0xfc, 0xc0, + 0x42, 0x4a, 0x71, 0x5c, 0xfc, 0x08, 0x3d, 0xc1, 0x25, 0x89, 0x45, 0x25, 0x44, 0xe8, 0x12, 0xd2, + 0xe0, 0x12, 0x80, 0x28, 0x29, 0x06, 0xe9, 0x88, 0x2f, 0xc9, 0xcc, 0x4d, 0x95, 0x60, 0x02, 0x2b, + 0xe3, 0x4b, 0x85, 0x1b, 0x14, 0x92, 0x99, 0x9b, 0xea, 0xa4, 0x7f, 0xe2, 0x91, 0x1c, 0xe3, 0x85, + 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, + 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0xa2, 0x58, 0x3d, 0x9a, 0xc4, 0x06, 0xf6, 0xa2, 0x31, 0x20, 0x00, + 0x00, 0xff, 0xff, 0xa9, 0x19, 0xc8, 0xb7, 0x08, 0x01, 0x00, 0x00, +} + +func (m *EventEpochEnd) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventEpochEnd) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventEpochEnd) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.EpochNumber != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.EpochNumber)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *EventEpochStart) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventEpochStart) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventEpochStart) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.EpochStartTime != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.EpochStartTime)) + i-- + dAtA[i] = 0x10 + } + if m.EpochNumber != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.EpochNumber)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { + offset -= sovEvents(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *EventEpochEnd) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.EpochNumber != 0 { + n += 1 + sovEvents(uint64(m.EpochNumber)) + } + return n +} + +func (m *EventEpochStart) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.EpochNumber != 0 { + n += 1 + sovEvents(uint64(m.EpochNumber)) + } + if m.EpochStartTime != 0 { + n += 1 + sovEvents(uint64(m.EpochStartTime)) + } + return n +} + +func sovEvents(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozEvents(x uint64) (n int) { + return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EventEpochEnd) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventEpochEnd: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventEpochEnd: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochNumber", wireType) + } + m.EpochNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochNumber |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventEpochStart) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventEpochStart: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventEpochStart: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochNumber", wireType) + } + m.EpochNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochNumber |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochStartTime", wireType) + } + m.EpochStartTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochStartTime |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipEvents(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthEvents + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupEvents + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthEvents + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthEvents = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowEvents = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupEvents = fmt.Errorf("proto: unexpected end of group") +) diff --git a/client/x/epochs/types/events.proto b/client/x/epochs/types/events.proto new file mode 100644 index 00000000..0df97db1 --- /dev/null +++ b/client/x/epochs/types/events.proto @@ -0,0 +1,17 @@ +// Since: x/epochs 0.1.0 +syntax = "proto3"; + +package client.x.epochs.types; + +option go_package = "client/x/epochs/types"; + +// EventEpochEnd is an event emitted when an epoch end. +message EventEpochEnd { + int64 epoch_number = 1; +} + +// EventEpochStart is an event emitted when an epoch start. +message EventEpochStart { + int64 epoch_number = 1; + int64 epoch_start_time = 2; +} diff --git a/client/x/epochs/types/genesis.go b/client/x/epochs/types/genesis.go new file mode 100644 index 00000000..00110b1c --- /dev/null +++ b/client/x/epochs/types/genesis.go @@ -0,0 +1,72 @@ +package types + +import ( + "errors" + "time" +) + +// DefaultIndex is the default capability global index. +const DefaultIndex uint64 = 1 + +func NewGenesisState(epochs []EpochInfo) *GenesisState { + return &GenesisState{Epochs: epochs} +} + +// DefaultGenesis returns the default Capability genesis state. +func DefaultGenesis() *GenesisState { + epochs := []EpochInfo{ + NewGenesisEpochInfo("day", time.Hour*24), // alphabetical order + NewGenesisEpochInfo("hour", time.Hour), + NewGenesisEpochInfo("minute", time.Minute), + NewGenesisEpochInfo("week", time.Hour*24*7), + } + + return NewGenesisState(epochs) +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + epochIdentifiers := map[string]bool{} + for _, epoch := range gs.Epochs { + if err := epoch.Validate(); err != nil { + return err + } + if epochIdentifiers[epoch.Identifier] { + return errors.New("epoch identifier should be unique") + } + epochIdentifiers[epoch.Identifier] = true + } + + return nil +} + +// Validate also validates epoch info. +func (epoch EpochInfo) Validate() error { + if epoch.Identifier == "" { + return errors.New("epoch identifier should NOT be empty") + } + if epoch.Duration == 0 { + return errors.New("epoch duration should NOT be 0") + } + if epoch.CurrentEpoch < 0 { + return errors.New("epoch CurrentEpoch must be non-negative") + } + if epoch.CurrentEpochStartHeight < 0 { + return errors.New("epoch CurrentEpochStartHeight must be non-negative") + } + + return nil +} + +func NewGenesisEpochInfo(identifier string, duration time.Duration) EpochInfo { + return EpochInfo{ + Identifier: identifier, + StartTime: time.Time{}, + Duration: duration, + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: false, + } +} diff --git a/client/x/epochs/types/genesis.pb.go b/client/x/epochs/types/genesis.pb.go new file mode 100644 index 00000000..158e4abb --- /dev/null +++ b/client/x/epochs/types/genesis.pb.go @@ -0,0 +1,820 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: client/x/epochs/types/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/durationpb" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// EpochInfo is a struct that describes the data going into +// a timer defined by the x/epochs module. +type EpochInfo struct { + // identifier is a unique reference to this particular timer. + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + // start_time is the time at which the timer first ever ticks. + // If start_time is in the future, the epoch will not begin until the start + // time. + StartTime time.Time `protobuf:"bytes,2,opt,name=start_time,json=startTime,proto3,stdtime" json:"start_time"` + // duration is the time in between epoch ticks. + // In order for intended behavior to be met, duration should + // be greater than the chains expected block time. + // Duration must be non-zero. + Duration time.Duration `protobuf:"bytes,3,opt,name=duration,proto3,stdduration" json:"duration,omitempty"` + // current_epoch is the current epoch number, or in other words, + // how many times has the timer 'ticked'. + // The first tick (current_epoch=1) is defined as + // the first block whose blocktime is greater than the EpochInfo start_time. + CurrentEpoch int64 `protobuf:"varint,4,opt,name=current_epoch,json=currentEpoch,proto3" json:"current_epoch,omitempty"` + // current_epoch_start_time describes the start time of the current timer + // interval. The interval is (current_epoch_start_time, + // current_epoch_start_time + duration] When the timer ticks, this is set to + // current_epoch_start_time = last_epoch_start_time + duration only one timer + // tick for a given identifier can occur per block. + // + // NOTE! The current_epoch_start_time may diverge significantly from the + // wall-clock time the epoch began at. Wall-clock time of epoch start may be + // >> current_epoch_start_time. Suppose current_epoch_start_time = 10, + // duration = 5. Suppose the chain goes offline at t=14, and comes back online + // at t=30, and produces blocks at every successive time. (t=31, 32, etc.) + // * The t=30 block will start the epoch for (10, 15] + // * The t=31 block will start the epoch for (15, 20] + // * The t=32 block will start the epoch for (20, 25] + // * The t=33 block will start the epoch for (25, 30] + // * The t=34 block will start the epoch for (30, 35] + // * The **t=36** block will start the epoch for (35, 40] + CurrentEpochStartTime time.Time `protobuf:"bytes,5,opt,name=current_epoch_start_time,json=currentEpochStartTime,proto3,stdtime" json:"current_epoch_start_time"` + // epoch_counting_started is a boolean, that indicates whether this + // epoch timer has began yet. + EpochCountingStarted bool `protobuf:"varint,6,opt,name=epoch_counting_started,json=epochCountingStarted,proto3" json:"epoch_counting_started,omitempty"` + // current_epoch_start_height is the block height at which the current epoch + // started. (The block height at which the timer last ticked) + CurrentEpochStartHeight int64 `protobuf:"varint,8,opt,name=current_epoch_start_height,json=currentEpochStartHeight,proto3" json:"current_epoch_start_height,omitempty"` +} + +func (m *EpochInfo) Reset() { *m = EpochInfo{} } +func (m *EpochInfo) String() string { return proto.CompactTextString(m) } +func (*EpochInfo) ProtoMessage() {} +func (*EpochInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_98958ed62d3b01f7, []int{0} +} +func (m *EpochInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EpochInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EpochInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EpochInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_EpochInfo.Merge(m, src) +} +func (m *EpochInfo) XXX_Size() int { + return m.Size() +} +func (m *EpochInfo) XXX_DiscardUnknown() { + xxx_messageInfo_EpochInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_EpochInfo proto.InternalMessageInfo + +func (m *EpochInfo) GetIdentifier() string { + if m != nil { + return m.Identifier + } + return "" +} + +func (m *EpochInfo) GetStartTime() time.Time { + if m != nil { + return m.StartTime + } + return time.Time{} +} + +func (m *EpochInfo) GetDuration() time.Duration { + if m != nil { + return m.Duration + } + return 0 +} + +func (m *EpochInfo) GetCurrentEpoch() int64 { + if m != nil { + return m.CurrentEpoch + } + return 0 +} + +func (m *EpochInfo) GetCurrentEpochStartTime() time.Time { + if m != nil { + return m.CurrentEpochStartTime + } + return time.Time{} +} + +func (m *EpochInfo) GetEpochCountingStarted() bool { + if m != nil { + return m.EpochCountingStarted + } + return false +} + +func (m *EpochInfo) GetCurrentEpochStartHeight() int64 { + if m != nil { + return m.CurrentEpochStartHeight + } + return 0 +} + +// GenesisState defines the epochs module's genesis state. +type GenesisState struct { + Epochs []EpochInfo `protobuf:"bytes,1,rep,name=epochs,proto3" json:"epochs"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_98958ed62d3b01f7, []int{1} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetEpochs() []EpochInfo { + if m != nil { + return m.Epochs + } + return nil +} + +func init() { + proto.RegisterType((*EpochInfo)(nil), "client.x.epochs.types.EpochInfo") + proto.RegisterType((*GenesisState)(nil), "client.x.epochs.types.GenesisState") +} + +func init() { + proto.RegisterFile("client/x/epochs/types/genesis.proto", fileDescriptor_98958ed62d3b01f7) +} + +var fileDescriptor_98958ed62d3b01f7 = []byte{ + // 425 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x3f, 0x8f, 0xd3, 0x30, + 0x18, 0xc6, 0x6b, 0x5a, 0x4a, 0xea, 0x3b, 0x24, 0x64, 0x5d, 0xc1, 0x74, 0x70, 0xa2, 0xbb, 0x25, + 0x03, 0xb2, 0xa5, 0x83, 0x0d, 0x89, 0x21, 0x07, 0xe2, 0xcf, 0xc0, 0x90, 0xc2, 0x82, 0x84, 0xaa, + 0x5c, 0xea, 0xa6, 0x96, 0x2e, 0x76, 0x94, 0xbc, 0x91, 0xee, 0xbe, 0xc5, 0x8d, 0x7c, 0x20, 0x86, + 0x1b, 0x6f, 0x64, 0x3a, 0x50, 0xbb, 0xf1, 0x29, 0x50, 0xec, 0xa4, 0x2a, 0xb4, 0x0b, 0x5b, 0xeb, + 0xe7, 0x79, 0x7f, 0x8f, 0x1f, 0xe7, 0xc5, 0x27, 0xe9, 0x85, 0x92, 0x1a, 0xc4, 0xa5, 0x90, 0x85, + 0x49, 0x97, 0x95, 0x80, 0xab, 0x42, 0x56, 0x22, 0x93, 0x5a, 0x56, 0xaa, 0xe2, 0x45, 0x69, 0xc0, + 0x90, 0xb1, 0x33, 0xf1, 0x4b, 0xee, 0x4c, 0xdc, 0x9a, 0x26, 0x47, 0x99, 0xc9, 0x8c, 0x75, 0x88, + 0xe6, 0x97, 0x33, 0x4f, 0x58, 0x66, 0x4c, 0x76, 0x21, 0x85, 0xfd, 0x77, 0x5e, 0x2f, 0xc4, 0xbc, + 0x2e, 0x13, 0x50, 0x46, 0xb7, 0xba, 0xff, 0xaf, 0x0e, 0x2a, 0x97, 0x15, 0x24, 0x79, 0xe1, 0x0c, + 0xc7, 0xdf, 0xfb, 0x78, 0xf4, 0xa6, 0xc9, 0x79, 0xaf, 0x17, 0x86, 0x30, 0x8c, 0xd5, 0x5c, 0x6a, + 0x50, 0x0b, 0x25, 0x4b, 0x8a, 0x02, 0x14, 0x8e, 0xe2, 0xad, 0x13, 0x72, 0x86, 0x71, 0x05, 0x49, + 0x09, 0xb3, 0x06, 0x43, 0xef, 0x05, 0x28, 0x3c, 0x38, 0x9d, 0x70, 0x97, 0xc1, 0xbb, 0x0c, 0xfe, + 0xa9, 0xcb, 0x88, 0xbc, 0x9b, 0x3b, 0xbf, 0x77, 0xfd, 0xd3, 0x47, 0xf1, 0xc8, 0xce, 0x35, 0x0a, + 0xf9, 0x8c, 0xbd, 0xee, 0x96, 0xb4, 0x6f, 0x11, 0x4f, 0x77, 0x10, 0xaf, 0x5b, 0x43, 0xc4, 0x1a, + 0xc2, 0xef, 0x3b, 0x9f, 0x74, 0x23, 0xcf, 0x4c, 0xae, 0x40, 0xe6, 0x05, 0x5c, 0x7d, 0x6b, 0xb8, + 0x1b, 0x14, 0x39, 0xc1, 0x0f, 0xd3, 0xba, 0x2c, 0xa5, 0x86, 0x99, 0x7d, 0x38, 0x3a, 0x08, 0x50, + 0xd8, 0x8f, 0x0f, 0xdb, 0x43, 0x5b, 0x92, 0x7c, 0xc5, 0xf4, 0x2f, 0xd3, 0x6c, 0xab, 0xce, 0xfd, + 0xff, 0xa8, 0x33, 0xde, 0xa6, 0x4e, 0x37, 0xd5, 0x5e, 0xe0, 0xc7, 0x0e, 0x9b, 0x9a, 0x5a, 0x83, + 0xd2, 0x99, 0xe3, 0xcb, 0x39, 0x1d, 0x06, 0x28, 0xf4, 0xe2, 0x23, 0xab, 0x9e, 0xb5, 0xe2, 0xd4, + 0x69, 0xe4, 0x25, 0x9e, 0xec, 0xbb, 0xd4, 0x52, 0xaa, 0x6c, 0x09, 0xd4, 0xb3, 0x35, 0x9e, 0xec, + 0x04, 0xbe, 0xb3, 0xf2, 0x87, 0x81, 0xf7, 0xe0, 0x91, 0x77, 0xfc, 0x11, 0x1f, 0xbe, 0x75, 0x5b, + 0x34, 0x85, 0x04, 0x24, 0x79, 0x85, 0x87, 0x6e, 0x7b, 0x28, 0x0a, 0xfa, 0xe1, 0xc1, 0x69, 0xc0, + 0xf7, 0x6e, 0x15, 0xdf, 0x7c, 0xfa, 0x68, 0xd0, 0x74, 0x8b, 0xdb, 0xa9, 0x48, 0xdc, 0xac, 0x18, + 0xba, 0x5d, 0x31, 0xf4, 0x6b, 0xc5, 0xd0, 0xf5, 0x9a, 0xf5, 0x6e, 0xd7, 0xac, 0xf7, 0x63, 0xcd, + 0x7a, 0x5f, 0xc6, 0x7b, 0x77, 0xf8, 0x7c, 0x68, 0x9f, 0xeb, 0xf9, 0x9f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xb1, 0xd4, 0xe2, 0x83, 0xe3, 0x02, 0x00, 0x00, +} + +func (m *EpochInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EpochInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EpochInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.CurrentEpochStartHeight != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.CurrentEpochStartHeight)) + i-- + dAtA[i] = 0x40 + } + if m.EpochCountingStarted { + i-- + if m.EpochCountingStarted { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.CurrentEpochStartTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.CurrentEpochStartTime):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintGenesis(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x2a + if m.CurrentEpoch != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.CurrentEpoch)) + i-- + dAtA[i] = 0x20 + } + n2, err2 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.Duration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.Duration):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintGenesis(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0x1a + n3, err3 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.StartTime):]) + if err3 != nil { + return 0, err3 + } + i -= n3 + i = encodeVarintGenesis(dAtA, i, uint64(n3)) + i-- + dAtA[i] = 0x12 + if len(m.Identifier) > 0 { + i -= len(m.Identifier) + copy(dAtA[i:], m.Identifier) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Identifier))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Epochs) > 0 { + for iNdEx := len(m.Epochs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Epochs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *EpochInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Identifier) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.StartTime) + n += 1 + l + sovGenesis(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.Duration) + n += 1 + l + sovGenesis(uint64(l)) + if m.CurrentEpoch != 0 { + n += 1 + sovGenesis(uint64(m.CurrentEpoch)) + } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.CurrentEpochStartTime) + n += 1 + l + sovGenesis(uint64(l)) + if m.EpochCountingStarted { + n += 2 + } + if m.CurrentEpochStartHeight != 0 { + n += 1 + sovGenesis(uint64(m.CurrentEpochStartHeight)) + } + return n +} + +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Epochs) > 0 { + for _, e := range m.Epochs { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EpochInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EpochInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EpochInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.Duration, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentEpoch", wireType) + } + m.CurrentEpoch = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CurrentEpoch |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentEpochStartTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.CurrentEpochStartTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochCountingStarted", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.EpochCountingStarted = bool(v != 0) + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentEpochStartHeight", wireType) + } + m.CurrentEpochStartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CurrentEpochStartHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Epochs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Epochs = append(m.Epochs, EpochInfo{}) + if err := m.Epochs[len(m.Epochs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/client/x/epochs/types/genesis.proto b/client/x/epochs/types/genesis.proto new file mode 100644 index 00000000..9420542d --- /dev/null +++ b/client/x/epochs/types/genesis.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; +package client.x.epochs.types; + +import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "client/x/epochs/types"; + +// EpochInfo is a struct that describes the data going into +// a timer defined by the x/epochs module. +message EpochInfo { + // identifier is a unique reference to this particular timer. + string identifier = 1; + // start_time is the time at which the timer first ever ticks. + // If start_time is in the future, the epoch will not begin until the start + // time. + google.protobuf.Timestamp start_time = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; + // duration is the time in between epoch ticks. + // In order for intended behavior to be met, duration should + // be greater than the chains expected block time. + // Duration must be non-zero. + google.protobuf.Duration duration = 3 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.jsontag) = "duration,omitempty"]; + // current_epoch is the current epoch number, or in other words, + // how many times has the timer 'ticked'. + // The first tick (current_epoch=1) is defined as + // the first block whose blocktime is greater than the EpochInfo start_time. + int64 current_epoch = 4; + // current_epoch_start_time describes the start time of the current timer + // interval. The interval is (current_epoch_start_time, + // current_epoch_start_time + duration] When the timer ticks, this is set to + // current_epoch_start_time = last_epoch_start_time + duration only one timer + // tick for a given identifier can occur per block. + // + // NOTE! The current_epoch_start_time may diverge significantly from the + // wall-clock time the epoch began at. Wall-clock time of epoch start may be + // >> current_epoch_start_time. Suppose current_epoch_start_time = 10, + // duration = 5. Suppose the chain goes offline at t=14, and comes back online + // at t=30, and produces blocks at every successive time. (t=31, 32, etc.) + // * The t=30 block will start the epoch for (10, 15] + // * The t=31 block will start the epoch for (15, 20] + // * The t=32 block will start the epoch for (20, 25] + // * The t=33 block will start the epoch for (25, 30] + // * The t=34 block will start the epoch for (30, 35] + // * The **t=36** block will start the epoch for (35, 40] + google.protobuf.Timestamp current_epoch_start_time = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; + // epoch_counting_started is a boolean, that indicates whether this + // epoch timer has began yet. + bool epoch_counting_started = 6; + reserved 7; + // current_epoch_start_height is the block height at which the current epoch + // started. (The block height at which the timer last ticked) + int64 current_epoch_start_height = 8; +} + +// GenesisState defines the epochs module's genesis state. +message GenesisState { + repeated EpochInfo epochs = 1 [(gogoproto.nullable) = false]; +} diff --git a/client/x/epochs/types/hooks.go b/client/x/epochs/types/hooks.go new file mode 100644 index 00000000..a18f4621 --- /dev/null +++ b/client/x/epochs/types/hooks.go @@ -0,0 +1,65 @@ +package types + +import ( + "context" + "errors" + + storyerr "github.com/piplabs/story/lib/errors" +) + +type EpochHooks interface { + // the first block whose timestamp is after the duration is counted as the end of the epoch + AfterEpochEnd(ctx context.Context, epochIdentifier string, epochNumber int64) error + // new epoch is next block of epoch end block + BeforeEpochStart(ctx context.Context, epochIdentifier string, epochNumber int64) error + // Returns the name of the module implementing epoch hook. + GetModuleName() string +} + +var _ EpochHooks = MultiEpochHooks{} + +// combine multiple hooks, all hook functions are run in array sequence. +type MultiEpochHooks []EpochHooks + +// GetModuleName implements EpochHooks. +func (MultiEpochHooks) GetModuleName() string { + return ModuleName +} + +func NewMultiEpochHooks(hooks ...EpochHooks) MultiEpochHooks { + return hooks +} + +// AfterEpochEnd is called when epoch is going to be ended, epochNumber is the number of epoch that is ending. +func (h MultiEpochHooks) AfterEpochEnd(ctx context.Context, epochIdentifier string, epochNumber int64) error { + var errs error + for i := range h { + errs = errors.Join(errs, h[i].AfterEpochEnd(ctx, epochIdentifier, epochNumber)) + } + + if errs != nil { + return storyerr.Wrap(errs, "after epoch end") + } + + return nil +} + +// BeforeEpochStart is called when epoch is going to be started, epochNumber is the number of epoch that is starting. +func (h MultiEpochHooks) BeforeEpochStart(ctx context.Context, epochIdentifier string, epochNumber int64) error { + var errs error + for i := range h { + errs = errors.Join(errs, h[i].BeforeEpochStart(ctx, epochIdentifier, epochNumber)) + } + + if errs != nil { + return storyerr.Wrap(errs, "before epoch start") + } + + return nil +} + +// EpochHooksWrapper is a wrapper for modules to inject EpochHooks using depinject. +type EpochHooksWrapper struct{ EpochHooks } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (EpochHooksWrapper) IsOnePerModuleType() {} diff --git a/client/x/epochs/types/hooks_test.go b/client/x/epochs/types/hooks_test.go new file mode 100644 index 00000000..04804acc --- /dev/null +++ b/client/x/epochs/types/hooks_test.go @@ -0,0 +1,121 @@ +package types_test + +import ( + "context" + "testing" + + "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/piplabs/story/client/x/epochs/types" +) + +type KeeperTestSuite struct { + suite.Suite + Ctx sdk.Context +} + +func TestKeeperTestSuite(t *testing.T) { + t.Parallel() + suite.Run(t, new(KeeperTestSuite)) +} + +func (s *KeeperTestSuite) SetupTest() { + s.Ctx = testutil.DefaultContext(storetypes.NewKVStoreKey(types.StoreKey), storetypes.NewTransientStoreKey("transient_test")) +} + +var errDummy = errors.New("9", 9, "dummyError") + +// dummyEpochHook is a struct satisfying the epoch hook interface, +// that maintains a counter for how many times its been successfully called, +// and a boolean for whether it should panic during its execution. +type dummyEpochHook struct { + successCounter int + shouldError bool +} + +// GetModuleName implements types.EpochHooks. +func (*dummyEpochHook) GetModuleName() string { + return "dummy" +} + +func (hook *dummyEpochHook) AfterEpochEnd(ctx context.Context, epochIdentifier string, epochNumber int64) error { + if hook.shouldError { + return errDummy + } + hook.successCounter += 1 + + return nil +} + +func (hook *dummyEpochHook) BeforeEpochStart(ctx context.Context, epochIdentifier string, epochNumber int64) error { + if hook.shouldError { + return errDummy + } + hook.successCounter += 1 + + return nil +} + +func (hook *dummyEpochHook) Clone() *dummyEpochHook { + newHook := dummyEpochHook{successCounter: hook.successCounter, shouldError: hook.shouldError} + return &newHook +} + +var _ types.EpochHooks = &dummyEpochHook{} + +func (s *KeeperTestSuite) TestHooksPanicRecovery() { + errorHook := dummyEpochHook{shouldError: true} + noErrorHook := dummyEpochHook{shouldError: false} + simpleHooks := []dummyEpochHook{errorHook, noErrorHook} + + tests := []struct { + hooks []dummyEpochHook + expectedCounterValues []int + lenEvents int + expErr bool + }{ + {[]dummyEpochHook{errorHook}, []int{0}, 0, true}, + {simpleHooks, []int{0, 1, 0, 1}, 2, true}, + } + + for tcIndex, tc := range tests { + for epochActionSelector := range 2 { + s.SetupTest() + hookRefs := []types.EpochHooks{} + + for _, hook := range tc.hooks { + hookRefs = append(hookRefs, hook.Clone()) + } + + hooks := types.NewMultiEpochHooks(hookRefs...) + + //nolint:nestif // Cosmos style + if epochActionSelector == 0 { + err := hooks.BeforeEpochStart(s.Ctx, "id", 0) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + } + } else if epochActionSelector == 1 { + err := hooks.AfterEpochEnd(s.Ctx, "id", 0) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + } + } + + for i := range hooks { + epochHook, ok := hookRefs[i].(*dummyEpochHook) + s.Require().True(ok) + s.Require().Equal(tc.expectedCounterValues[i], epochHook.successCounter, "test case index %d", tcIndex) + } + } + } +} diff --git a/client/x/epochs/types/identifier.go b/client/x/epochs/types/identifier.go new file mode 100644 index 00000000..2e7ebd31 --- /dev/null +++ b/client/x/epochs/types/identifier.go @@ -0,0 +1,22 @@ +package types + +import ( + "fmt" +) + +func ValidateEpochIdentifierInterface(i any) error { + v, ok := i.(string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return ValidateEpochIdentifierString(v) +} + +func ValidateEpochIdentifierString(s string) error { + if s == "" { + return fmt.Errorf("empty distribution epoch identifier: %+v", s) + } + + return nil +} diff --git a/client/x/epochs/types/keys.go b/client/x/epochs/types/keys.go new file mode 100644 index 00000000..27d7a724 --- /dev/null +++ b/client/x/epochs/types/keys.go @@ -0,0 +1,16 @@ +package types + +import ( + "cosmossdk.io/collections" +) + +const ( + // ModuleName defines the module name. + ModuleName = "epochs" + + // StoreKey defines the primary module store key. + StoreKey = ModuleName +) + +// KeyPrefixEpoch defines prefix key for storing epochs. +var KeyPrefixEpoch = collections.NewPrefix(1) diff --git a/client/x/epochs/types/query.pb.go b/client/x/epochs/types/query.pb.go new file mode 100644 index 00000000..29e534b2 --- /dev/null +++ b/client/x/epochs/types/query.pb.go @@ -0,0 +1,908 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: client/x/epochs/types/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type QueryEpochsInfoRequest struct { +} + +func (m *QueryEpochsInfoRequest) Reset() { *m = QueryEpochsInfoRequest{} } +func (m *QueryEpochsInfoRequest) String() string { return proto.CompactTextString(m) } +func (*QueryEpochsInfoRequest) ProtoMessage() {} +func (*QueryEpochsInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_78cd7c4fa831b33b, []int{0} +} +func (m *QueryEpochsInfoRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryEpochsInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryEpochsInfoRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryEpochsInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryEpochsInfoRequest.Merge(m, src) +} +func (m *QueryEpochsInfoRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryEpochsInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryEpochsInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryEpochsInfoRequest proto.InternalMessageInfo + +type QueryEpochsInfoResponse struct { + Epochs []EpochInfo `protobuf:"bytes,1,rep,name=epochs,proto3" json:"epochs"` +} + +func (m *QueryEpochsInfoResponse) Reset() { *m = QueryEpochsInfoResponse{} } +func (m *QueryEpochsInfoResponse) String() string { return proto.CompactTextString(m) } +func (*QueryEpochsInfoResponse) ProtoMessage() {} +func (*QueryEpochsInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_78cd7c4fa831b33b, []int{1} +} +func (m *QueryEpochsInfoResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryEpochsInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryEpochsInfoResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryEpochsInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryEpochsInfoResponse.Merge(m, src) +} +func (m *QueryEpochsInfoResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryEpochsInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryEpochsInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryEpochsInfoResponse proto.InternalMessageInfo + +func (m *QueryEpochsInfoResponse) GetEpochs() []EpochInfo { + if m != nil { + return m.Epochs + } + return nil +} + +type QueryCurrentEpochRequest struct { + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` +} + +func (m *QueryCurrentEpochRequest) Reset() { *m = QueryCurrentEpochRequest{} } +func (m *QueryCurrentEpochRequest) String() string { return proto.CompactTextString(m) } +func (*QueryCurrentEpochRequest) ProtoMessage() {} +func (*QueryCurrentEpochRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_78cd7c4fa831b33b, []int{2} +} +func (m *QueryCurrentEpochRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCurrentEpochRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCurrentEpochRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCurrentEpochRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCurrentEpochRequest.Merge(m, src) +} +func (m *QueryCurrentEpochRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryCurrentEpochRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCurrentEpochRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCurrentEpochRequest proto.InternalMessageInfo + +func (m *QueryCurrentEpochRequest) GetIdentifier() string { + if m != nil { + return m.Identifier + } + return "" +} + +type QueryCurrentEpochResponse struct { + CurrentEpoch int64 `protobuf:"varint,1,opt,name=current_epoch,json=currentEpoch,proto3" json:"current_epoch,omitempty"` +} + +func (m *QueryCurrentEpochResponse) Reset() { *m = QueryCurrentEpochResponse{} } +func (m *QueryCurrentEpochResponse) String() string { return proto.CompactTextString(m) } +func (*QueryCurrentEpochResponse) ProtoMessage() {} +func (*QueryCurrentEpochResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_78cd7c4fa831b33b, []int{3} +} +func (m *QueryCurrentEpochResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCurrentEpochResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCurrentEpochResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCurrentEpochResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCurrentEpochResponse.Merge(m, src) +} +func (m *QueryCurrentEpochResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryCurrentEpochResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCurrentEpochResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCurrentEpochResponse proto.InternalMessageInfo + +func (m *QueryCurrentEpochResponse) GetCurrentEpoch() int64 { + if m != nil { + return m.CurrentEpoch + } + return 0 +} + +func init() { + proto.RegisterType((*QueryEpochsInfoRequest)(nil), "client.x.epochs.types.QueryEpochsInfoRequest") + proto.RegisterType((*QueryEpochsInfoResponse)(nil), "client.x.epochs.types.QueryEpochsInfoResponse") + proto.RegisterType((*QueryCurrentEpochRequest)(nil), "client.x.epochs.types.QueryCurrentEpochRequest") + proto.RegisterType((*QueryCurrentEpochResponse)(nil), "client.x.epochs.types.QueryCurrentEpochResponse") +} + +func init() { proto.RegisterFile("client/x/epochs/types/query.proto", fileDescriptor_78cd7c4fa831b33b) } + +var fileDescriptor_78cd7c4fa831b33b = []byte{ + // 367 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x31, 0x4b, 0x3b, 0x31, + 0x18, 0xc6, 0x2f, 0xed, 0xff, 0x5f, 0x30, 0xd6, 0x25, 0x58, 0x3d, 0x0f, 0x49, 0xaf, 0xd7, 0xc1, + 0x2e, 0x26, 0x5a, 0x37, 0x07, 0x91, 0x8a, 0x83, 0xa3, 0xb7, 0xe9, 0x22, 0xf5, 0x4c, 0xcf, 0x40, + 0x49, 0xae, 0x97, 0x54, 0xda, 0xd5, 0x5d, 0x10, 0xdc, 0xfc, 0x1c, 0x7e, 0x88, 0x8e, 0x05, 0x17, + 0x27, 0x91, 0xd6, 0x0f, 0x22, 0xcd, 0x05, 0xa9, 0xf4, 0x94, 0x6e, 0x21, 0xf9, 0x3d, 0xef, 0xf3, + 0xe4, 0x7d, 0x5f, 0x58, 0x8b, 0xba, 0x9c, 0x09, 0x4d, 0x07, 0x94, 0x25, 0x32, 0xba, 0x55, 0x54, + 0x0f, 0x13, 0xa6, 0x68, 0xaf, 0xcf, 0xd2, 0x21, 0x49, 0x52, 0xa9, 0x25, 0xaa, 0x64, 0x08, 0x19, + 0x90, 0x0c, 0x21, 0x06, 0xf1, 0xd6, 0x63, 0x19, 0x4b, 0x43, 0xd0, 0xd9, 0x29, 0x83, 0xbd, 0xed, + 0x58, 0xca, 0xb8, 0xcb, 0x68, 0x3b, 0xe1, 0xb4, 0x2d, 0x84, 0xd4, 0x6d, 0xcd, 0xa5, 0x50, 0xf6, + 0xb5, 0x9e, 0xef, 0x16, 0x33, 0xc1, 0x14, 0xb7, 0x50, 0xe0, 0xc2, 0x8d, 0xf3, 0x99, 0xfd, 0xa9, + 0x41, 0xce, 0x44, 0x47, 0x86, 0xac, 0xd7, 0x67, 0x4a, 0x07, 0x17, 0x70, 0x73, 0xe1, 0x45, 0x25, + 0x52, 0x28, 0x86, 0x8e, 0x60, 0x29, 0x2b, 0xe9, 0x02, 0xbf, 0xd8, 0x58, 0x6d, 0xfa, 0x24, 0x37, + 0x35, 0x31, 0xd2, 0x99, 0xb2, 0xf5, 0x6f, 0xf4, 0x5e, 0x75, 0x42, 0xab, 0x0a, 0x0e, 0xa1, 0x6b, + 0x4a, 0x9f, 0xf4, 0xd3, 0x94, 0x09, 0x6d, 0x30, 0x6b, 0x8b, 0x30, 0x84, 0xfc, 0x86, 0x09, 0xcd, + 0x3b, 0x9c, 0xa5, 0x2e, 0xf0, 0x41, 0x63, 0x25, 0x9c, 0xbb, 0x09, 0x8e, 0xe1, 0x56, 0x8e, 0xd6, + 0x06, 0xab, 0xc3, 0xb5, 0x28, 0xbb, 0xbf, 0x32, 0x56, 0x46, 0x5f, 0x0c, 0xcb, 0xd1, 0x1c, 0xdc, + 0x7c, 0x29, 0xc0, 0xff, 0xa6, 0x04, 0x7a, 0x00, 0x10, 0x7e, 0x67, 0x54, 0x68, 0xf7, 0x97, 0x6f, + 0xe4, 0x37, 0xc8, 0x23, 0xcb, 0xe2, 0x59, 0xb8, 0xc0, 0xbf, 0x7f, 0xfd, 0x7c, 0x2a, 0x78, 0xc8, + 0xa5, 0x76, 0x30, 0x76, 0x2c, 0x77, 0xfb, 0xf6, 0x84, 0x9e, 0x01, 0x2c, 0xcf, 0xff, 0x0b, 0xd1, + 0xbf, 0x2c, 0x72, 0xba, 0xe7, 0xed, 0x2d, 0x2f, 0xb0, 0xa9, 0x76, 0x4c, 0xaa, 0x1a, 0xaa, 0x2e, + 0xa6, 0xfa, 0xd1, 0xca, 0x16, 0x1d, 0x4d, 0x30, 0x18, 0x4f, 0x30, 0xf8, 0x98, 0x60, 0xf0, 0x38, + 0xc5, 0xce, 0x78, 0x8a, 0x9d, 0xb7, 0x29, 0x76, 0x2e, 0x2b, 0xb9, 0x8b, 0x76, 0x5d, 0x32, 0x1b, + 0x76, 0xf0, 0x15, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x87, 0x4b, 0x27, 0xf6, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // EpochInfos provide running epochInfos + EpochInfos(ctx context.Context, in *QueryEpochsInfoRequest, opts ...grpc.CallOption) (*QueryEpochsInfoResponse, error) + // CurrentEpoch provide current epoch of specified identifier + CurrentEpoch(ctx context.Context, in *QueryCurrentEpochRequest, opts ...grpc.CallOption) (*QueryCurrentEpochResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) EpochInfos(ctx context.Context, in *QueryEpochsInfoRequest, opts ...grpc.CallOption) (*QueryEpochsInfoResponse, error) { + out := new(QueryEpochsInfoResponse) + err := c.cc.Invoke(ctx, "/client.x.epochs.types.Query/EpochInfos", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) CurrentEpoch(ctx context.Context, in *QueryCurrentEpochRequest, opts ...grpc.CallOption) (*QueryCurrentEpochResponse, error) { + out := new(QueryCurrentEpochResponse) + err := c.cc.Invoke(ctx, "/client.x.epochs.types.Query/CurrentEpoch", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // EpochInfos provide running epochInfos + EpochInfos(context.Context, *QueryEpochsInfoRequest) (*QueryEpochsInfoResponse, error) + // CurrentEpoch provide current epoch of specified identifier + CurrentEpoch(context.Context, *QueryCurrentEpochRequest) (*QueryCurrentEpochResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) EpochInfos(ctx context.Context, req *QueryEpochsInfoRequest) (*QueryEpochsInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EpochInfos not implemented") +} +func (*UnimplementedQueryServer) CurrentEpoch(ctx context.Context, req *QueryCurrentEpochRequest) (*QueryCurrentEpochResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CurrentEpoch not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_EpochInfos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryEpochsInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).EpochInfos(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/client.x.epochs.types.Query/EpochInfos", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).EpochInfos(ctx, req.(*QueryEpochsInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_CurrentEpoch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryCurrentEpochRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).CurrentEpoch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/client.x.epochs.types.Query/CurrentEpoch", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).CurrentEpoch(ctx, req.(*QueryCurrentEpochRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "client.x.epochs.types.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "EpochInfos", + Handler: _Query_EpochInfos_Handler, + }, + { + MethodName: "CurrentEpoch", + Handler: _Query_CurrentEpoch_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "client/x/epochs/types/query.proto", +} + +func (m *QueryEpochsInfoRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryEpochsInfoRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryEpochsInfoRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryEpochsInfoResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryEpochsInfoResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryEpochsInfoResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Epochs) > 0 { + for iNdEx := len(m.Epochs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Epochs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryCurrentEpochRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCurrentEpochRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCurrentEpochRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Identifier) > 0 { + i -= len(m.Identifier) + copy(dAtA[i:], m.Identifier) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Identifier))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryCurrentEpochResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCurrentEpochResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCurrentEpochResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.CurrentEpoch != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.CurrentEpoch)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryEpochsInfoRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryEpochsInfoResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Epochs) > 0 { + for _, e := range m.Epochs { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryCurrentEpochRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Identifier) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryCurrentEpochResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CurrentEpoch != 0 { + n += 1 + sovQuery(uint64(m.CurrentEpoch)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryEpochsInfoRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryEpochsInfoRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryEpochsInfoRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryEpochsInfoResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryEpochsInfoResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryEpochsInfoResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Epochs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Epochs = append(m.Epochs, EpochInfo{}) + if err := m.Epochs[len(m.Epochs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCurrentEpochRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCurrentEpochRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCurrentEpochRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCurrentEpochResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCurrentEpochResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCurrentEpochResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentEpoch", wireType) + } + m.CurrentEpoch = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CurrentEpoch |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/client/x/epochs/types/query.proto b/client/x/epochs/types/query.proto new file mode 100644 index 00000000..fd4f5916 --- /dev/null +++ b/client/x/epochs/types/query.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +package client.x.epochs.types; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "client/x/epochs/types/genesis.proto"; + +option go_package = "client/x/epochs/types"; + +// Query defines the gRPC querier service. +service Query { + // EpochInfos provide running epochInfos + rpc EpochInfos(QueryEpochsInfoRequest) returns (QueryEpochsInfoResponse) { + option (google.api.http).get = "/client/epochs/v1/epochs"; + } + // CurrentEpoch provide current epoch of specified identifier + rpc CurrentEpoch(QueryCurrentEpochRequest) returns (QueryCurrentEpochResponse) { + option (google.api.http).get = "/client/epochs/v1/current_epoch"; + } +} + +message QueryEpochsInfoRequest {} +message QueryEpochsInfoResponse { + repeated EpochInfo epochs = 1 [(gogoproto.nullable) = false]; +} + +message QueryCurrentEpochRequest { + string identifier = 1; +} +message QueryCurrentEpochResponse { + int64 current_epoch = 1; +} \ No newline at end of file diff --git a/go.mod b/go.mod index 14816d3b..3c36fe7a 100644 --- a/go.mod +++ b/go.mod @@ -233,7 +233,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.23.0 // indirect diff --git a/lib/netconf/local/genesis.json b/lib/netconf/local/genesis.json index 17e9ef01..22c1e0a0 100644 --- a/lib/netconf/local/genesis.json +++ b/lib/netconf/local/genesis.json @@ -191,6 +191,19 @@ "goal_bonded": "0.670000000000000000", "blocks_per_year": "6311520" } + }, + "epochs": { + "epochs": [ + { + "identifier": "minute", + "start_time": "1970-01-01T00:00:00Z", + "duration": "60s", + "current_epoch": "0", + "current_epoch_start_time": "1970-01-01T00:00:00Z", + "epoch_counting_started": false, + "current_epoch_start_height": "10" + } + ] } } }