diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index b851955943b..631ac550c0c 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -128,35 +128,37 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { } // initGenesis returns the app initialization genesis for modules -func (a *AppBuilder[T]) initGenesis(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) (store.WriterMap, error) { +func (a *AppBuilder[T]) initGenesis(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) (store.WriterMap, []appmodule.ValidatorUpdate, error) { // this implementation assumes that the state is a JSON object bz, err := io.ReadAll(src) if err != nil { - return nil, fmt.Errorf("failed to read import state: %w", err) + return nil, nil, fmt.Errorf("failed to read import state: %w", err) } var genesisJSON map[string]json.RawMessage if err = json.Unmarshal(bz, &genesisJSON); err != nil { - return nil, err + return nil, nil, err } v, zeroState, err := a.app.db.StateLatest() if err != nil { - return nil, fmt.Errorf("unable to get latest state: %w", err) + return nil, nil, fmt.Errorf("unable to get latest state: %w", err) } if v != 0 { // TODO: genesis state may be > 0, we need to set version on store - return nil, errors.New("cannot init genesis on non-zero state") + return nil, nil, errors.New("cannot init genesis on non-zero state") } genesisCtx := services.NewGenesisContext(a.branch(zeroState)) + var valUpdates []appmodulev2.ValidatorUpdate genesisState, err := genesisCtx.Mutate(ctx, func(ctx context.Context) error { - err = a.app.moduleManager.InitGenesisJSON(ctx, genesisJSON, txHandler) + valUpdates, err = a.app.moduleManager.InitGenesisJSON(ctx, genesisJSON, txHandler) if err != nil { return fmt.Errorf("failed to init genesis: %w", err) } + return nil }) - return genesisState, err + return genesisState, valUpdates, err } // exportGenesis returns the app export genesis logic for modules diff --git a/runtime/v2/manager.go b/runtime/v2/manager.go index 9e99a2b08c4..18b6587e86a 100644 --- a/runtime/v2/manager.go +++ b/runtime/v2/manager.go @@ -141,9 +141,10 @@ func (m *MM[T]) InitGenesisJSON( ctx context.Context, genesisData map[string]json.RawMessage, txHandler func(json.RawMessage) error, -) error { +) ([]appmodulev2.ValidatorUpdate, error) { m.logger.Info("initializing blockchain state from genesis.json", "order", m.config.InitGenesis) - var seenValUpdates bool + + var validatorUpdates []appmodulev2.ValidatorUpdate for _, moduleName := range m.config.InitGenesis { if genesisData[moduleName] == nil { continue @@ -158,38 +159,39 @@ func (m *MM[T]) InitGenesisJSON( case appmodulev2.GenesisDecoder: // GenesisDecoder needs to supersede HasGenesis and HasABCIGenesis. genTxs, err := module.DecodeGenesisJSON(genesisData[moduleName]) if err != nil { - return err + return nil, err } for _, jsonTx := range genTxs { if err := txHandler(jsonTx); err != nil { - return fmt.Errorf("failed to handle genesis transaction: %w", err) + return nil, fmt.Errorf("failed to handle genesis transaction: %w", err) } } case appmodulev2.HasGenesis: m.logger.Debug("running initialization for module", "module", moduleName) if err := module.InitGenesis(ctx, genesisData[moduleName]); err != nil { - return fmt.Errorf("init module %s: %w", moduleName, err) + return nil, fmt.Errorf("init module %s: %w", moduleName, err) } case appmodulev2.HasABCIGenesis: m.logger.Debug("running initialization for module", "module", moduleName) + var err error moduleValUpdates, err := module.InitGenesis(ctx, genesisData[moduleName]) if err != nil { - return err + return nil, err } // use these validator updates if provided, the module manager assumes // only one module will update the validator set if len(moduleValUpdates) > 0 { - if seenValUpdates { - return fmt.Errorf("validator InitGenesis updates already set by a previous module: current module %s", moduleName) - } else { - seenValUpdates = true + if len(validatorUpdates) > 0 { + return nil, fmt.Errorf("validator InitGenesis updates already set by a previous module: current module %s", moduleName) } + + validatorUpdates = append(validatorUpdates, moduleValUpdates...) } } - } - return nil + + return validatorUpdates, nil } // ExportGenesisForModules performs export genesis functionality for modules diff --git a/server/v2/appmanager/appmanager.go b/server/v2/appmanager/appmanager.go index 597d1c8f411..b31bd33db97 100644 --- a/server/v2/appmanager/appmanager.go +++ b/server/v2/appmanager/appmanager.go @@ -107,7 +107,7 @@ func (a appManager[T]) InitGenesis( txDecoder transaction.Codec[T], ) (*server.BlockResponse, corestore.WriterMap, error) { var genTxs []T - genesisState, err := a.initGenesis( + genesisState, valUpdates, err := a.initGenesis( ctx, bytes.NewBuffer(initGenesisJSON), func(jsonTx json.RawMessage) error { @@ -122,6 +122,7 @@ func (a appManager[T]) InitGenesis( if err != nil { return nil, nil, fmt.Errorf("failed to import genesis state: %w", err) } + // run block blockRequest.Txs = genTxs @@ -141,6 +142,17 @@ func (a appManager[T]) InitGenesis( return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err) } + // override validator updates with the ones from the init genesis state + // this triggers only when x/staking or another module that returns validator updates in InitGenesis + // otherwise, genutil validator updates takes precedence (returned from executing the genesis txs (as it implements appmodule.GenesisDecoder) in the end block) + if len(valUpdates) > 0 && len(blockResponse.ValidatorUpdates) > 0 { + return nil, nil, errors.New("validator updates returned from InitGenesis and genesis transactions, only one can be used") + } + + if len(valUpdates) > 0 { + blockResponse.ValidatorUpdates = valUpdates + } + return blockResponse, genesisState, err } diff --git a/server/v2/appmanager/genesis.go b/server/v2/appmanager/genesis.go index 347d0f30e07..40d95e094f4 100644 --- a/server/v2/appmanager/genesis.go +++ b/server/v2/appmanager/genesis.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io" + appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/store" ) @@ -21,7 +22,7 @@ type ( ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error, - ) (store.WriterMap, error) + ) (store.WriterMap, []appmodulev2.ValidatorUpdate, error) // ExportGenesis is a function type that represents the export of the genesis state. ExportGenesis func(ctx context.Context, version uint64) ([]byte, error) diff --git a/server/v2/cometbft/abci_test.go b/server/v2/cometbft/abci_test.go index c2bd81d65f2..b7bc427d13b 100644 --- a/server/v2/cometbft/abci_test.go +++ b/server/v2/cometbft/abci_test.go @@ -701,10 +701,10 @@ func setUpConsensus(t *testing.T, gasLimit uint64, mempool mempool.Mempool[mock. }, mockStore, s, - func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) (store.WriterMap, error) { + func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) (store.WriterMap, []appmodulev2.ValidatorUpdate, error) { _, st, err := mockStore.StateLatest() require.NoError(t, err) - return branch.DefaultNewWriterMap(st), nil + return branch.DefaultNewWriterMap(st), nil, nil }, nil, ) diff --git a/x/genutil/module.go b/x/genutil/module.go index ba33eab26b1..18ebc6a06c6 100644 --- a/x/genutil/module.go +++ b/x/genutil/module.go @@ -19,6 +19,7 @@ var ( _ appmodule.AppModule = AppModule{} _ appmodulev2.GenesisDecoder = AppModule{} + _ appmodulev2.HasABCIGenesis = AppModule{} ) // AppModule implements an application module for the genutil module.