Skip to content

Commit

Permalink
feat!: remove genesis time from genesis state (celestiaorg#1851)
Browse files Browse the repository at this point in the history
Closes celestiaorg#1757

## Description

Prior to this PR, the mint module contained a placeholder `genesis_time`
in genesis state. It ignored the placeholder and used the block time for
the block height `1` as the "genesis time". This PR removes the
placeholder and that mechanism.

```diff
      "minter": {
        "inflation_rate": "0.080000000000000000",
        "annual_provisions": "0.000000000000000000",
-       "genesis_time": "2023-06-22T18:46:11.873883Z",
        "previous_block_time": null,
        "bond_denom": "utia"
      }
```

After this PR, `genesis_time` in the mint module is set to the chain's
genesis time which is the root level `genesis_time` specified in
genesis.json. Genesis.json is created on chain initialization (i.e.
`celestia-appd init chain-id`).

This is breaking b/c it modifies a state transition for the mint module
state and removes a field from the `Minter` state.

## Testing

Now there is only one `genesis_time` in genesis.json:

```shell
$ ./scripts/build-run-single-node.sh

$ cat /var/folders/y0/dd92_x8x4tlf397xstgwfz_c0000gn/T/celestia_app_XXXXXXXXXXXXX.wYBu5mwm/config/genesis.json | grep genesis_time -A 3 -B 3
{
  "genesis_time": "2023-06-23T16:13:24.719488Z",
  "chain_id": "private",
  "initial_height": "1",
  "consensus_params": {
```

Additionally it gets set correctly in mint module state

```shell
$ ./build/celestia-appd query mint genesis-time
2023-06-23 16:13:24.719488 +0000 UTC
```

---------

Co-authored-by: Evan Forbes <[email protected]>
  • Loading branch information
rootulp and evan-forbes authored Jun 23, 2023
1 parent 4248170 commit 7b52005
Show file tree
Hide file tree
Showing 18 changed files with 367 additions and 188 deletions.
10 changes: 7 additions & 3 deletions proto/celestia/mint/v1/mint.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ message Minter {
(gogoproto.nullable) = false
];

// GenesisTime is the timestamp of the genesis block.
google.protobuf.Timestamp genesis_time = 3 [ (gogoproto.stdtime) = true ];

// PreviousBlockTime is the timestamp of the previous block.
google.protobuf.Timestamp previous_block_time = 4
[ (gogoproto.stdtime) = true ];

// BondDenom is the denomination of the token that should be minted.
string bond_denom = 5;
}

// GenesisTime contains the timestamp of the genesis block.
message GenesisTime {
// GenesisTime is the timestamp of the genesis block.
google.protobuf.Timestamp genesis_time = 1
[ (gogoproto.stdtime) = true ];
}
15 changes: 9 additions & 6 deletions test/util/test_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ func (ao emptyAppOptions) Get(_ string) interface{} {
return nil
}

// SetupTestAppWithGenesisValSet initializes a new app with a validator set and genesis accounts
// that also act as delegators. For simplicity, each validator is bonded with a delegation
// of one consensus engine unit in the default token of the app from first genesis
// account. A Nop logger is set in app.
// SetupTestAppWithGenesisValSet initializes a new app with a validator set and
// genesis accounts that also act as delegators. For simplicity, each validator
// is bonded with a delegation of one consensus engine unit in the default token
// of the app from first genesis account. A no-op logger is set in app.
func SetupTestAppWithGenesisValSet(cparams *tmproto.ConsensusParams, genAccounts ...string) (*app.App, keyring.Keyring) {
// var cache sdk.MultiStorePersistentCache
// EmptyAppOptions is a stub implementing AppOptions
Expand Down Expand Up @@ -88,9 +88,12 @@ func SetupTestAppWithGenesisValSet(cparams *tmproto.ConsensusParams, genAccounts
Validator: &cparams.Validator,
}

genesisTime := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()

// init chain will set the validator set and initialize the genesis accounts
testApp.InitChain(
abci.RequestInitChain{
Time: genesisTime,
Validators: []abci.ValidatorUpdate{},
ConsensusParams: abciParams,
AppStateBytes: stateBytes,
Expand Down Expand Up @@ -173,8 +176,8 @@ func AddGenesisAccount(addr sdk.AccAddress, appState app.GenesisState, cdc codec
return appState, nil
}

// GenesisStateWithSingleValidator initializes GenesisState with a single validator and genesis accounts
// that also act as delegators.
// GenesisStateWithSingleValidator initializes GenesisState with a single
// validator and genesis accounts that also act as delegators.
func GenesisStateWithSingleValidator(testApp *app.App, genAccounts ...string) (app.GenesisState, *tmtypes.ValidatorSet, keyring.Keyring) {
privVal := mock.NewPV()
pubKey, err := privVal.GetPubKey()
Expand Down
4 changes: 4 additions & 0 deletions x/mint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ $ celestia-appd query mint inflation
0.080000000000000000
```

## Genesis State

The genesis state is defined in [./types/genesis.go](./types/genesis.go).

## Params

All params have been removed from this module because they should not be modifiable via governance.
Expand Down
21 changes: 7 additions & 14 deletions x/mint/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,26 @@ import (
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

maybeSetGenesisTime(ctx, k)
maybeUpdateMinter(ctx, k)
mintBlockProvision(ctx, k)
setPreviousBlockTime(ctx, k)
}

// maybeSetGenesisTime sets the genesis time if the current block height is 1.
func maybeSetGenesisTime(ctx sdk.Context, k keeper.Keeper) {
if ctx.BlockHeight() == 1 {
genesisTime := ctx.BlockTime()
minter := k.GetMinter(ctx)
minter.GenesisTime = &genesisTime
k.SetMinter(ctx, minter)
}
}

// maybeUpdateMinter updates the inflation rate and annual provisions if the
// inflation rate has changed.
// inflation rate has changed. The inflation rate is expected to change once per
// year at the genesis time anniversary until the TargetInflationRate is
// reached.
func maybeUpdateMinter(ctx sdk.Context, k keeper.Keeper) {
minter := k.GetMinter(ctx)
newInflationRate := minter.CalculateInflationRate(ctx)
genesisTime := k.GetGenesisTime(ctx).GenesisTime
newInflationRate := minter.CalculateInflationRate(ctx, *genesisTime)

isNonZeroAnnualProvisions := !minter.AnnualProvisions.IsZero()
if newInflationRate.Equal(minter.InflationRate) && isNonZeroAnnualProvisions {
// The minter's InflationRate and AnnualProvisions already reflect the
// values for this year. Exit early because we don't need to update
// them.
// them. AnnualProvisions must be updated if it is zero (expected at
// genesis).
return
}

Expand Down
86 changes: 23 additions & 63 deletions x/mint/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,17 @@ import (
"github.com/tendermint/tendermint/proto/tendermint/types"
)

func TestGenesisTime(t *testing.T) {
app, _ := util.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams())
ctx := sdk.NewContext(app.CommitMultiStore(), types.Header{}, false, tmlog.NewNopLogger())
unixEpoch := time.Unix(0, 0).UTC()
fixedTime := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()

type testCase struct {
name string
ctx sdk.Context
want time.Time
}

testCases := []testCase{
{
name: "initially genesis time is unix epoch",
ctx: ctx.WithBlockHeight(0).WithBlockTime(unixEpoch),
want: unixEpoch,
},
{
name: "genesis time is set to time of first block",
ctx: ctx.WithBlockHeight(1).WithBlockTime(fixedTime),
want: fixedTime,
},
{
name: "genesis time remains set to time of first block",
ctx: ctx.WithBlockHeight(2).WithBlockTime(fixedTime.Add(time.Hour)),
want: fixedTime,
},
}

for _, tc := range testCases {
mint.BeginBlocker(tc.ctx, app.MintKeeper)
got, err := app.MintKeeper.GenesisTime(ctx, &minttypes.QueryGenesisTimeRequest{})
assert.NoError(t, err)
assert.Equal(t, &tc.want, got.GenesisTime)
}
}
var oneYear = time.Duration(minttypes.NanosecondsPerYear)

func TestInflationRate(t *testing.T) {
app, _ := util.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams())
ctx := sdk.NewContext(app.CommitMultiStore(), types.Header{}, false, tmlog.NewNopLogger())
unixEpoch := time.Unix(0, 0).UTC()
yearZero := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()
oneYear := time.Duration(minttypes.NanosecondsPerYear)
yearOne := yearZero.Add(oneYear)
yearTwo := yearZero.Add(2 * oneYear)
yearTwenty := yearZero.Add(20 * oneYear)
genesisTime := app.MintKeeper.GetGenesisTime(ctx).GenesisTime

yearOneMinusOneSecond := genesisTime.Add(oneYear).Add(-time.Second)
yearOne := genesisTime.Add(oneYear)
yearTwo := genesisTime.Add(2 * oneYear)
yearTwenty := genesisTime.Add(20 * oneYear)

type testCase struct {
name string
Expand All @@ -71,19 +35,14 @@ func TestInflationRate(t *testing.T) {
}

testCases := []testCase{
{
name: "inflation rate is 0.08 initially",
ctx: ctx.WithBlockHeight(0).WithBlockTime(unixEpoch),
want: sdk.NewDecWithPrec(8, 2), // 0.08
},
{
name: "inflation rate is 0.08 for year zero",
ctx: ctx.WithBlockHeight(1).WithBlockTime(yearZero),
ctx: ctx.WithBlockHeight(1).WithBlockTime(*genesisTime),
want: sdk.NewDecWithPrec(8, 2), // 0.08
},
{
name: "inflation rate is 0.08 for year one minus one minute",
ctx: ctx.WithBlockTime(yearOne.Add(-time.Minute)),
name: "inflation rate is 0.08 for year one minus one second",
ctx: ctx.WithBlockTime(yearOneMinusOneSecond),
want: sdk.NewDecWithPrec(8, 2), // 0.08
},
{
Expand All @@ -104,10 +63,12 @@ func TestInflationRate(t *testing.T) {
}

for _, tc := range testCases {
mint.BeginBlocker(tc.ctx, app.MintKeeper)
got, err := app.MintKeeper.InflationRate(ctx, &minttypes.QueryInflationRateRequest{})
assert.NoError(t, err)
assert.Equal(t, tc.want, got.InflationRate)
t.Run(tc.name, func(t *testing.T) {
mint.BeginBlocker(tc.ctx, app.MintKeeper)
got, err := app.MintKeeper.InflationRate(ctx, &minttypes.QueryInflationRateRequest{})
assert.NoError(t, err)
assert.Equal(t, tc.want, got.InflationRate)
})
}
}

Expand All @@ -124,16 +85,15 @@ func TestAnnualProvisions(t *testing.T) {
t.Run("annual provisions are not updated more than once per year", func(t *testing.T) {
a, _ := util.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams())
ctx := sdk.NewContext(a.CommitMultiStore(), types.Header{}, false, tmlog.NewNopLogger())
genesisTime := a.MintKeeper.GetGenesisTime(ctx).GenesisTime
yearOneMinusOneSecond := genesisTime.Add(oneYear).Add(-time.Second)

initialSupply := sdk.NewInt(100_000_001_000_000)
require.Equal(t, initialSupply, a.MintKeeper.StakingTokenSupply(ctx))
require.Equal(t, a.MintKeeper.GetMinter(ctx).BondDenom, a.StakingKeeper.BondDenom(ctx))
require.True(t, a.MintKeeper.GetMinter(ctx).AnnualProvisions.IsZero())

blockInterval := time.Second * 15
firstBlockTime := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()
oneYear := time.Duration(minttypes.NanosecondsPerYear)
lastBlockInYear := firstBlockTime.Add(oneYear).Add(-time.Second)

want := minttypes.InitialInflationRateAsDec().MulInt(initialSupply)

Expand All @@ -142,10 +102,9 @@ func TestAnnualProvisions(t *testing.T) {
time time.Time
}
testCases := []testCase{
{1, firstBlockTime},
{2, firstBlockTime.Add(blockInterval)},
{3, firstBlockTime.Add(blockInterval * 2)},
{4, lastBlockInYear},
{1, genesisTime.Add(blockInterval)},
{2, genesisTime.Add(blockInterval * 2)},
{3, yearOneMinusOneSecond},
// testing annual provisions for years after year zero depends on the
// total supply which increased due to inflation in year zero.
}
Expand All @@ -159,7 +118,8 @@ func TestAnnualProvisions(t *testing.T) {
}

t.Run("one year later", func(t *testing.T) {
ctx = ctx.WithBlockHeight(5).WithBlockTime(lastBlockInYear.Add(time.Second))
yearOne := genesisTime.Add(oneYear)
ctx = ctx.WithBlockHeight(5).WithBlockTime(yearOne)
mint.BeginBlocker(ctx, a.MintKeeper)
assert.False(t, a.MintKeeper.GetMinter(ctx).AnnualProvisions.Equal(want))
})
Expand Down
58 changes: 57 additions & 1 deletion x/mint/client/testutil/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"testing"
"time"

"github.com/stretchr/testify/suite"
tmcli "github.com/tendermint/tendermint/libs/cli"
Expand Down Expand Up @@ -56,6 +57,9 @@ func (s *IntegrationTestSuite) textArgs() []string {
return []string{fmt.Sprintf("--%s=1", flags.FlagHeight), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}
}

// TestGetCmdQueryInflationRate tests that the CLI query command for inflation
// rate returns the correct value. This test assumes that the initial inflation
// rate is 0.08.
func (s *IntegrationTestSuite) TestGetCmdQueryInflationRate() {
val := s.network.Validators[0]

Expand Down Expand Up @@ -90,6 +94,11 @@ func (s *IntegrationTestSuite) TestGetCmdQueryInflationRate() {
}
}

// TestGetCmdQueryAnnualProvisions tests that the CLI query command for annual-provisions
// returns the correct value. This test assumes that the initial inflation
// rate is 0.08 and the initial total supply is 500_000_000 utia.
//
// TODO assert that total supply is 500_000_000 utia.
func (s *IntegrationTestSuite) TestGetCmdQueryAnnualProvisions() {
val := s.network.Validators[0]

Expand Down Expand Up @@ -124,8 +133,55 @@ func (s *IntegrationTestSuite) TestGetCmdQueryAnnualProvisions() {
}
}

// TestGetCmdQueryGenesisTime tests that the CLI command for genesis time
// returns the same time that is set in the genesis state. The CLI command to
// query genesis time looks like: `celestia-appd query mint genesis-time`
func (s *IntegrationTestSuite) TestGetCmdQueryGenesisTime() {
val := s.network.Validators[0]

testCases := []struct {
name string
args []string
}{
{
name: "json output",
args: s.jsonArgs(),
},
{
name: "text output",
args: s.textArgs(),
},
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
cmd := cli.GetCmdQueryGenesisTime()
clientCtx := val.ClientCtx

out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
s.Require().NoError(err)

trimmed := strings.TrimSpace(out.String())
layout := "2006-01-02 15:04:05.999999 -0700 UTC"

got, err := time.Parse(layout, trimmed)
s.Require().NoError(err)

now := time.Now()
oneMinForward := now.Add(time.Minute)
oneMinBackward := now.Add(-time.Minute)

s.Assert().True(got.Before(oneMinForward), "genesis time is too far in the future")
s.Assert().True(got.After(oneMinBackward), "genesis time is too far in the past")
})
}
}

// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestIntegrationTestSuite(t *testing.T) {
cfg := appnetwork.DefaultConfig()
cfg.NumValidators = 1
suite.Run(t, NewIntegrationTestSuite(cfg))
}
10 changes: 8 additions & 2 deletions x/mint/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// InitGenesis new mint genesis
// InitGenesis initializes the x/mint store with data from the genesis state.
func (keeper Keeper) InitGenesis(ctx sdk.Context, ak types.AccountKeeper, data *types.GenesisState) {
keeper.SetMinter(ctx, data.Minter)
// override the genesis time with the actual genesis time supplied in `InitChain`
blockTime := ctx.BlockTime()
gt := types.GenesisTime{
GenesisTime: &blockTime,
}
keeper.SetGenesisTime(ctx, gt)
ak.GetModuleAccount(ctx, types.ModuleName)
}

// ExportGenesis returns a GenesisState for a given context and keeper.
// ExportGenesis returns a x/mint GenesisState for the given context.
func (keeper Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState {
minter := keeper.GetMinter(ctx)
return types.NewGenesisState(minter)
Expand Down
6 changes: 3 additions & 3 deletions x/mint/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ func (k Keeper) AnnualProvisions(c context.Context, _ *types.QueryAnnualProvisio
return &types.QueryAnnualProvisionsResponse{AnnualProvisions: minter.AnnualProvisions}, nil
}

// GenesisTime returns minter.GensisTime of the mint module.
// GenesisTime returns the GensisTime of the mint module.
func (k Keeper) GenesisTime(c context.Context, _ *types.QueryGenesisTimeRequest) (*types.QueryGenesisTimeResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
minter := k.GetMinter(ctx)
genesisTime := k.GetGenesisTime(ctx).GenesisTime

return &types.QueryGenesisTimeResponse{GenesisTime: minter.GenesisTime}, nil
return &types.QueryGenesisTimeResponse{GenesisTime: genesisTime}, nil
}
2 changes: 1 addition & 1 deletion x/mint/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (suite *MintTestSuite) TestGRPC() {

genesisTime, err := queryClient.GenesisTime(gocontext.Background(), &types.QueryGenesisTimeRequest{})
suite.Require().NoError(err)
suite.Require().Equal(genesisTime.GenesisTime, app.MintKeeper.GetMinter(ctx).GenesisTime)
suite.Require().Equal(genesisTime.GenesisTime, app.MintKeeper.GetGenesisTime(ctx).GenesisTime)
}

func TestMintTestSuite(t *testing.T) {
Expand Down
Loading

0 comments on commit 7b52005

Please sign in to comment.