Skip to content

Commit 9094794

Browse files
authored
feat!: Ensure InitGenesis returns with non-empty validator set (#9697)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #8961 SDK allows InitGenesis to return with an empty validator set. In practice, the error for an empty validator set gets thrown in tendermint. To fix this, * Add non-empty validator set check to the `mm.InitGenesis` function. This will break `simapp.Setup` because it relies on an empty validator set [#comment](#8909 (comment)). * Update `simapp.Setup` to use a single validator. * Fix failing tests (Most of them are keeper tests). <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
1 parent 0b6f3b9 commit 9094794

37 files changed

+422
-207
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
122122
* (types) [\#10021](https://github.com/cosmos/cosmos-sdk/pull/10021) Speedup coins.AmountOf(), by removing many intermittent regex calls.
123123
* (rosetta) [\#10001](https://github.com/cosmos/cosmos-sdk/issues/10001) Add documentation for rosetta-cli dockerfile and rename folder for the rosetta-ci dockerfile
124124
* [\#9699](https://github.com/cosmos/cosmos-sdk/pull/9699) Add `:`, `.`, `-`, and `_` as allowed characters in the default denom regular expression.
125+
* (genesis) [\#9697](https://github.com/cosmos/cosmos-sdk/pull/9697) Ensure `InitGenesis` returns with non-empty validator set.
125126
* [\#10262](https://github.com/cosmos/cosmos-sdk/pull/10262) Remove unnecessary logging in `x/feegrant` simulation.
126127

127128
### Bug Fixes

server/export_test.go

+10-19
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package server_test
33
import (
44
"bytes"
55
"context"
6-
"encoding/json"
76
"fmt"
87
"io"
98
"os"
@@ -22,7 +21,6 @@ import (
2221

2322
"github.com/cosmos/cosmos-sdk/client"
2423
"github.com/cosmos/cosmos-sdk/client/flags"
25-
"github.com/cosmos/cosmos-sdk/codec"
2624
"github.com/cosmos/cosmos-sdk/server"
2725
"github.com/cosmos/cosmos-sdk/server/types"
2826
"github.com/cosmos/cosmos-sdk/simapp"
@@ -123,6 +121,8 @@ func TestExportCmd_Height(t *testing.T) {
123121
}
124122

125123
func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, *tmtypes.GenesisDoc, *cobra.Command) {
124+
t.Helper()
125+
126126
if err := createConfigFolder(tempDir); err != nil {
127127
t.Fatalf("error creating config folder: %s", err)
128128
}
@@ -132,11 +132,18 @@ func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, *t
132132
encCfg := simapp.MakeTestEncodingConfig()
133133
app := simapp.NewSimApp(logger, db, nil, true, map[int64]bool{}, tempDir, 0, encCfg, simapp.EmptyAppOptions{})
134134

135+
genesisState := simapp.GenesisStateWithSingleValidator(t, app)
136+
stateBytes, err := tmjson.MarshalIndent(genesisState, "", " ")
137+
require.NoError(t, err)
138+
135139
serverCtx := server.NewDefaultContext()
136140
serverCtx.Config.RootDir = tempDir
137141

138142
clientCtx := client.Context{}.WithCodec(app.AppCodec())
139-
genDoc := newDefaultGenesisDoc(encCfg.Codec)
143+
genDoc := &tmtypes.GenesisDoc{}
144+
genDoc.ChainID = "theChainId"
145+
genDoc.Validators = nil
146+
genDoc.AppState = stateBytes
140147

141148
require.NoError(t, saveGenesisFile(genDoc, serverCtx.Config.GenesisFile()))
142149
app.InitChain(
@@ -177,22 +184,6 @@ func createConfigFolder(dir string) error {
177184
return os.Mkdir(path.Join(dir, "config"), 0700)
178185
}
179186

180-
func newDefaultGenesisDoc(cdc codec.Codec) *tmtypes.GenesisDoc {
181-
genesisState := simapp.NewDefaultGenesisState(cdc)
182-
183-
stateBytes, err := json.MarshalIndent(genesisState, "", " ")
184-
if err != nil {
185-
panic(err)
186-
}
187-
188-
genDoc := &tmtypes.GenesisDoc{}
189-
genDoc.ChainID = "theChainId"
190-
genDoc.Validators = nil
191-
genDoc.AppState = stateBytes
192-
193-
return genDoc
194-
}
195-
196187
func saveGenesisFile(genDoc *tmtypes.GenesisDoc, dir string) error {
197188
err := genutil.ExportGenesisFile(genDoc, dir)
198189
if err != nil {

simapp/sim_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"math/rand"
77
"os"
8+
"runtime/debug"
9+
"strings"
810
"testing"
911

1012
storetypes "github.com/cosmos/cosmos-sdk/store/types"
@@ -153,6 +155,17 @@ func TestAppImportExport(t *testing.T) {
153155
err = json.Unmarshal(exported.AppState, &genesisState)
154156
require.NoError(t, err)
155157

158+
defer func() {
159+
if r := recover(); r != nil {
160+
err := fmt.Sprintf("%v", r)
161+
if !strings.Contains(err, "validator set is empty after InitGenesis") {
162+
panic(r)
163+
}
164+
logger.Info("Skipping simulation as all validators have been unbonded")
165+
logger.Info("err", err, "stacktrace", string(debug.Stack()))
166+
}
167+
}()
168+
156169
ctxA := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()})
157170
ctxB := newApp.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()})
158171
newApp.mm.InitGenesis(ctxB, app.AppCodec(), genesisState)

simapp/test_helpers.go

+52-34
Original file line numberDiff line numberDiff line change
@@ -121,22 +121,24 @@ func NewSimappWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptio
121121
func Setup(t *testing.T, isCheckTx bool) *SimApp {
122122
t.Helper()
123123

124-
app, genesisState := setup(!isCheckTx, 5)
125-
if !isCheckTx {
126-
// init chain must be called to stop deliverState from being nil
127-
stateBytes, err := json.MarshalIndent(genesisState, "", " ")
128-
require.NoError(t, err)
124+
privVal := mock.NewPV()
125+
pubKey, err := privVal.GetPubKey()
126+
require.NoError(t, err)
129127

130-
// Initialize the chain
131-
app.InitChain(
132-
abci.RequestInitChain{
133-
Validators: []abci.ValidatorUpdate{},
134-
ConsensusParams: DefaultConsensusParams,
135-
AppStateBytes: stateBytes,
136-
},
137-
)
128+
// create validator set with single validator
129+
validator := tmtypes.NewValidator(pubKey, 1)
130+
valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
131+
132+
// generate genesis account
133+
senderPrivKey := secp256k1.GenPrivKey()
134+
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
135+
balance := banktypes.Balance{
136+
Address: acc.GetAddress().String(),
137+
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))),
138138
}
139139

140+
app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance)
141+
140142
return app
141143
}
142144

@@ -181,8 +183,13 @@ func genesisStateWithValSet(t *testing.T,
181183

182184
totalSupply := sdk.NewCoins()
183185
for _, b := range balances {
184-
// add genesis acc tokens and delegated tokens to total supply
185-
totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))...)
186+
// add genesis acc tokens to total supply
187+
totalSupply = totalSupply.Add(b.Coins...)
188+
}
189+
190+
for range delegations {
191+
// add delegated tokens to total supply
192+
totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))
186193
}
187194

188195
// add bonded amount to bonded pool module account
@@ -237,33 +244,44 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs
237244
func SetupWithGenesisAccounts(t *testing.T, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *SimApp {
238245
t.Helper()
239246

240-
app, genesisState := setup(true, 0)
241-
authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs)
242-
genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis)
247+
privVal := mock.NewPV()
248+
pubKey, err := privVal.GetPubKey()
249+
require.NoError(t, err)
243250

244-
totalSupply := sdk.NewCoins()
245-
for _, b := range balances {
246-
totalSupply = totalSupply.Add(b.Coins...)
247-
}
251+
// create validator set with single validator
252+
validator := tmtypes.NewValidator(pubKey, 1)
253+
valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
248254

249-
bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{})
250-
genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis)
255+
return SetupWithGenesisValSet(t, valSet, genAccs, balances...)
256+
}
251257

252-
stateBytes, err := json.MarshalIndent(genesisState, "", " ")
258+
// SetupWithGenesisValSet initializes GenesisState with a single validator and genesis accounts
259+
// that also act as delegators.
260+
func GenesisStateWithSingleValidator(t *testing.T, app *SimApp) GenesisState {
261+
t.Helper()
262+
263+
privVal := mock.NewPV()
264+
pubKey, err := privVal.GetPubKey()
253265
require.NoError(t, err)
254266

255-
app.InitChain(
256-
abci.RequestInitChain{
257-
Validators: []abci.ValidatorUpdate{},
258-
ConsensusParams: DefaultConsensusParams,
259-
AppStateBytes: stateBytes,
267+
// create validator set with single validator
268+
validator := tmtypes.NewValidator(pubKey, 1)
269+
valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
270+
271+
// generate genesis account
272+
senderPrivKey := secp256k1.GenPrivKey()
273+
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
274+
balances := []banktypes.Balance{
275+
{
276+
Address: acc.GetAddress().String(),
277+
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))),
260278
},
261-
)
279+
}
262280

263-
app.Commit()
264-
app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1}})
281+
genesisState := NewDefaultGenesisState(app.appCodec)
282+
genesisState = genesisStateWithValSet(t, app, genesisState, valSet, []authtypes.GenesisAccount{acc}, balances...)
265283

266-
return app
284+
return genesisState
267285
}
268286

269287
type GenerateAccountStrategy func(int) []sdk.AccAddress

types/module/module.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ package module
3030

3131
import (
3232
"encoding/json"
33+
"fmt"
3334
"sort"
3435

3536
"github.com/gorilla/mux"
@@ -296,7 +297,9 @@ func (m *Manager) RegisterServices(cfg Configurator) {
296297
}
297298
}
298299

299-
// InitGenesis performs init genesis functionality for modules
300+
// InitGenesis performs init genesis functionality for modules. Exactly one
301+
// module must return a non-empty validator set update to correctly initialize
302+
// the chain.
300303
func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) abci.ResponseInitChain {
301304
var validatorUpdates []abci.ValidatorUpdate
302305
ctx.Logger().Info("initializing blockchain state from genesis.json")
@@ -318,6 +321,11 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData
318321
}
319322
}
320323

324+
// a chain must initialize with a non-empty validator set
325+
if len(validatorUpdates) == 0 {
326+
panic(fmt.Sprintf("validator set is empty after InitGenesis, please ensure at least one validator is initialized with a delegation greater than or equal to the DefaultPowerReduction (%d)", sdk.DefaultPowerReduction))
327+
}
328+
321329
return abci.ResponseInitChain{
322330
Validators: validatorUpdates,
323331
}

types/module/module_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,9 @@ func TestManager_InitGenesis(t *testing.T) {
206206
cdc := codec.NewProtoCodec(interfaceRegistry)
207207
genesisData := map[string]json.RawMessage{"module1": json.RawMessage(`{"key": "value"}`)}
208208

209+
// this should panic since the validator set is empty even after init genesis
209210
mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module1"])).Times(1).Return(nil)
210-
require.Equal(t, abci.ResponseInitChain{Validators: []abci.ValidatorUpdate(nil)}, mm.InitGenesis(ctx, cdc, genesisData))
211+
require.Panics(t, func() { mm.InitGenesis(ctx, cdc, genesisData) })
211212

212213
// test panic
213214
genesisData = map[string]json.RawMessage{

x/auth/keeper/grpc_query_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ func (suite *KeeperTestSuite) TestGRPCQueryAccounts() {
3737
},
3838
true,
3939
func(res *types.QueryAccountsResponse) {
40-
for _, acc := range res.Accounts {
40+
addresses := make([]sdk.AccAddress, len(res.Accounts))
41+
for i, acc := range res.Accounts {
4142
var account types.AccountI
4243
err := suite.app.InterfaceRegistry().UnpackAny(acc, &account)
4344
suite.Require().NoError(err)
44-
45-
suite.Require().True(
46-
first.Equals(account.GetAddress()) || second.Equals(account.GetAddress()))
45+
addresses[i] = account.GetAddress()
4746
}
47+
suite.Subset(addresses, []sdk.AccAddress{first, second})
4848
},
4949
},
5050
}

x/auth/module_test.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,32 @@ import (
55

66
"github.com/stretchr/testify/require"
77
abcitypes "github.com/tendermint/tendermint/abci/types"
8+
tmjson "github.com/tendermint/tendermint/libs/json"
9+
"github.com/tendermint/tendermint/libs/log"
810
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
11+
dbm "github.com/tendermint/tm-db"
912

1013
"github.com/cosmos/cosmos-sdk/simapp"
1114
"github.com/cosmos/cosmos-sdk/x/auth/types"
1215
)
1316

1417
func TestItCreatesModuleAccountOnInitBlock(t *testing.T) {
15-
app := simapp.Setup(t, false)
16-
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
18+
db := dbm.NewMemDB()
19+
encCdc := simapp.MakeTestEncodingConfig()
20+
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, encCdc, simapp.EmptyAppOptions{})
21+
22+
genesisState := simapp.GenesisStateWithSingleValidator(t, app)
23+
stateBytes, err := tmjson.Marshal(genesisState)
24+
require.NoError(t, err)
1725

1826
app.InitChain(
1927
abcitypes.RequestInitChain{
20-
AppStateBytes: []byte("{}"),
28+
AppStateBytes: stateBytes,
2129
ChainId: "test-chain-id",
2230
},
2331
)
2432

33+
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
2534
acc := app.AccountKeeper.GetAccount(ctx, types.NewModuleAddress(types.FeeCollectorName))
2635
require.NotNil(t, acc)
2736
}

x/bank/keeper/genesis_test.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ func (suite *IntegrationTestSuite) TestExportGenesis() {
1111
app, ctx := suite.app, suite.ctx
1212

1313
expectedMetadata := suite.getTestMetadata()
14-
expectedBalances, totalSupply := suite.getTestBalancesAndSupply()
14+
expectedBalances, expTotalSupply := suite.getTestBalancesAndSupply()
15+
16+
// Adding genesis supply to the expTotalSupply
17+
genesisSupply, _, err := suite.app.BankKeeper.GetPaginatedTotalSupply(suite.ctx, &query.PageRequest{Limit: query.MaxLimit})
18+
suite.Require().NoError(err)
19+
expTotalSupply = expTotalSupply.Add(genesisSupply...)
20+
1521
for i := range []int{1, 2} {
1622
app.BankKeeper.SetDenomMetaData(ctx, expectedMetadata[i])
1723
accAddr, err1 := sdk.AccAddressFromBech32(expectedBalances[i].Address)
@@ -32,8 +38,8 @@ func (suite *IntegrationTestSuite) TestExportGenesis() {
3238

3339
suite.Require().Len(exportGenesis.Params.SendEnabled, 0)
3440
suite.Require().Equal(types.DefaultParams().DefaultSendEnabled, exportGenesis.Params.DefaultSendEnabled)
35-
suite.Require().Equal(totalSupply, exportGenesis.Supply)
36-
suite.Require().Equal(expectedBalances, exportGenesis.Balances)
41+
suite.Require().Equal(expTotalSupply, exportGenesis.Supply)
42+
suite.Require().Subset(exportGenesis.Balances, expectedBalances)
3743
suite.Require().Equal(expectedMetadata, exportGenesis.DenomMetadata)
3844
}
3945

@@ -74,6 +80,9 @@ func (suite *IntegrationTestSuite) TestTotalSupply() {
7480
}
7581
totalSupply := sdk.NewCoins(sdk.NewCoin("foocoin", sdk.NewInt(11)), sdk.NewCoin("barcoin", sdk.NewInt(21)))
7682

83+
genesisSupply, _, err := suite.app.BankKeeper.GetPaginatedTotalSupply(suite.ctx, &query.PageRequest{Limit: query.MaxLimit})
84+
suite.Require().NoError(err)
85+
7786
testcases := []struct {
7887
name string
7988
genesis *types.GenesisState
@@ -107,7 +116,10 @@ func (suite *IntegrationTestSuite) TestTotalSupply() {
107116
suite.app.BankKeeper.InitGenesis(suite.ctx, tc.genesis)
108117
totalSupply, _, err := suite.app.BankKeeper.GetPaginatedTotalSupply(suite.ctx, &query.PageRequest{Limit: query.MaxLimit})
109118
suite.Require().NoError(err)
110-
suite.Require().Equal(tc.expSupply, totalSupply)
119+
120+
// adding genesis supply to expected supply
121+
expected := tc.expSupply.Add(genesisSupply...)
122+
suite.Require().Equal(expected, totalSupply)
111123
}
112124
})
113125
}

0 commit comments

Comments
 (0)