refactor(types): align genesis api (#19735)

This commit is contained in:
Julien Robert
2024-03-13 05:43:24 +00:00
committed by GitHub
parent a569ba6d29
commit cdc329189b
36 changed files with 417 additions and 360 deletions
+16 -18
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
@@ -95,21 +94,21 @@ func (c coreAppModuleAdaptor) ValidateGenesis(bz json.RawMessage) error {
}
// ExportGenesis implements HasGenesis
func (c coreAppModuleAdaptor) ExportGenesis(ctx context.Context) json.RawMessage {
func (c coreAppModuleAdaptor) ExportGenesis(ctx context.Context) (json.RawMessage, error) {
if module, ok := c.module.(appmodule.HasGenesisAuto); ok {
ctx := sdk.UnwrapSDKContext(ctx).WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions
target := genesis.RawJSONTarget{}
err := module.ExportGenesis(ctx, target.Target())
if err != nil {
panic(err)
return nil, err
}
rawJSON, err := target.JSON()
if err != nil {
panic(err)
return nil, err
}
return rawJSON
return rawJSON, nil
}
if mod, ok := c.module.(HasABCIGenesis); ok {
@@ -119,26 +118,26 @@ func (c coreAppModuleAdaptor) ExportGenesis(ctx context.Context) json.RawMessage
if mod, ok := c.module.(HasGenesis); ok {
eg, err := mod.ExportGenesis(ctx)
if err != nil {
panic(err)
return nil, err
}
return eg
return eg, nil
}
return nil
return nil, nil
}
// InitGenesis implements HasGenesis
func (c coreAppModuleAdaptor) InitGenesis(ctx context.Context, bz json.RawMessage) []abci.ValidatorUpdate {
func (c coreAppModuleAdaptor) InitGenesis(ctx context.Context, bz json.RawMessage) ([]ValidatorUpdate, error) {
if module, ok := c.module.(appmodule.HasGenesisAuto); ok {
// core API genesis
source, err := genesis.SourceFromRawJSON(bz)
if err != nil {
panic(err)
return nil, err
}
err = module.InitGenesis(ctx, source)
if err != nil {
panic(err)
if err = module.InitGenesis(ctx, source); err != nil {
return nil, err
}
}
@@ -147,13 +146,12 @@ func (c coreAppModuleAdaptor) InitGenesis(ctx context.Context, bz json.RawMessag
}
if mod, ok := c.module.(HasGenesis); ok {
err := mod.InitGenesis(ctx, bz)
if err != nil {
panic(err)
if err := mod.InitGenesis(ctx, bz); err != nil {
return nil, err
}
}
return nil
return nil, nil
}
// Name implements HasName
+3 -3
View File
@@ -14,9 +14,9 @@ import (
type AppModuleWithAllExtensions interface {
module.AppModule
module.HasServices
appmodulev2.HasGenesis
module.HasInvariants
module.HasConsensusVersion
appmodulev2.HasConsensusVersion
appmodulev2.HasGenesis
module.HasABCIEndBlock
module.HasName
}
@@ -27,7 +27,7 @@ type AppModuleWithAllExtensionsABCI interface {
module.HasServices
module.HasABCIGenesis
module.HasInvariants
module.HasConsensusVersion
appmodulev2.HasConsensusVersion
module.HasABCIEndBlock
module.HasName
}
+94 -37
View File
@@ -27,6 +27,7 @@ import (
"sort"
abci "github.com/cometbft/cometbft/abci/types"
cmtcryptoproto "github.com/cometbft/cometbft/proto/tendermint/crypto"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
"golang.org/x/exp/maps"
@@ -46,11 +47,11 @@ import (
// Deprecated: use the embed extension interfaces instead, when needed.
type AppModuleBasic interface {
appmodulev2.HasRegisterInterfaces
HasName
HasGRPCGateway
HasAminoCodec
appmodulev2.HasRegisterInterfaces
}
// AppModule is the form for an application module. Most of
@@ -84,22 +85,20 @@ type HasAminoCodec interface {
RegisterLegacyAminoCodec(*codec.LegacyAmino)
}
// HasRegisterInterfaces is the interface for modules to register their msg types.
type HasRegisterInterfaces appmodulev2.HasRegisterInterfaces
// HasGRPCGateway is the interface for modules to register their gRPC gateway routes.
type HasGRPCGateway interface {
RegisterGRPCGatewayRoutes(client.Context, *runtime.ServeMux)
}
// HasGenesis is the extension interface for stateful genesis methods.
// Prefer directly importing appmodulev2 or appmodule instead of using this alias.
type HasGenesis = appmodulev2.HasGenesis
// HasABCIGenesis is the extension interface for stateful genesis methods which returns validator updates.
type HasABCIGenesis interface {
HasGenesisBasics
InitGenesis(context.Context, json.RawMessage) []abci.ValidatorUpdate
ExportGenesis(context.Context) json.RawMessage
InitGenesis(context.Context, json.RawMessage) ([]ValidatorUpdate, error)
ExportGenesis(context.Context) (json.RawMessage, error)
}
// HasInvariants is the interface for registering invariants.
@@ -114,19 +113,19 @@ type HasServices interface {
RegisterServices(Configurator)
}
// HasConsensusVersion is the interface for declaring a module consensus version.
type HasConsensusVersion interface {
// ConsensusVersion is a sequence number for state-breaking change of the
// module. It should be incremented on each consensus-breaking change
// introduced by the module. To avoid wrong/empty versions, the initial version
// should be set to 1.
ConsensusVersion() uint64
}
// MigrationHandler is the migration function that each module registers.
type MigrationHandler func(sdk.Context) error
// VersionMap is a map of moduleName -> version
type VersionMap appmodule.VersionMap
// ValidatorUpdate is the type for validator updates.
type ValidatorUpdate = appmodulev2.ValidatorUpdate
// HasABCIEndBlock is the interface for modules that need to run code at the end of the block.
type HasABCIEndBlock interface {
AppModule
EndBlock(context.Context) ([]abci.ValidatorUpdate, error)
EndBlock(context.Context) ([]ValidatorUpdate, error)
}
// Manager defines a module manager that provides the high level utility for managing and executing
@@ -324,11 +323,13 @@ func (m *Manager) RegisterInterfaces(registry registry.LegacyRegistry) {
// DefaultGenesis provides default genesis information for all modules
func (m *Manager) DefaultGenesis() map[string]json.RawMessage {
genesisData := make(map[string]json.RawMessage)
for _, b := range m.Modules {
for name, b := range m.Modules {
if mod, ok := b.(HasGenesisBasics); ok {
genesisData[mod.Name()] = mod.DefaultGenesis()
} else if mod, ok := b.(HasName); ok {
genesisData[mod.Name()] = []byte("{}")
} else if mod, ok := b.(appmodule.HasGenesis); ok {
genesisData[name] = mod.DefaultGenesis()
} else {
genesisData[name] = []byte("{}")
}
}
@@ -337,12 +338,15 @@ func (m *Manager) DefaultGenesis() map[string]json.RawMessage {
// ValidateGenesis performs genesis state validation for all modules
func (m *Manager) ValidateGenesis(genesisData map[string]json.RawMessage) error {
for _, b := range m.Modules {
// first check if the module is an adapted Core API Module
for name, b := range m.Modules {
if mod, ok := b.(HasGenesisBasics); ok {
if err := mod.ValidateGenesis(genesisData[mod.Name()]); err != nil {
return err
}
} else if mod, ok := b.(appmodule.HasGenesis); ok {
if err := mod.ValidateGenesis(genesisData[name]); err != nil {
return err
}
}
}
@@ -425,8 +429,8 @@ func (m *Manager) RegisterServices(cfg Configurator) error {
// InitGenesis performs init genesis functionality for modules. Exactly one
// module must return a non-empty validator set update to correctly initialize
// the chain.
func (m *Manager) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, genesisData map[string]json.RawMessage) (*abci.ResponseInitChain, error) {
var validatorUpdates []abci.ValidatorUpdate
func (m *Manager) InitGenesis(ctx sdk.Context, genesisData map[string]json.RawMessage) (*abci.ResponseInitChain, error) {
var validatorUpdates []ValidatorUpdate
ctx.Logger().Info("initializing blockchain state from genesis.json")
for _, moduleName := range m.OrderInitGenesis {
if genesisData[moduleName] == nil {
@@ -454,7 +458,10 @@ func (m *Manager) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, genesisData ma
}
} else if module, ok := mod.(HasABCIGenesis); ok {
ctx.Logger().Debug("running initialization for module", "module", moduleName)
moduleValUpdates := module.InitGenesis(ctx, genesisData[moduleName])
moduleValUpdates, err := module.InitGenesis(ctx, genesisData[moduleName])
if err != nil {
return &abci.ResponseInitChain{}, err
}
// use these validator updates if provided, the module manager assumes
// only one module will update the validator set
@@ -472,8 +479,32 @@ func (m *Manager) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, genesisData ma
return &abci.ResponseInitChain{}, fmt.Errorf("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)
}
cometValidatorUpdates := make([]abci.ValidatorUpdate, len(validatorUpdates))
for i, v := range validatorUpdates {
var pubkey cmtcryptoproto.PublicKey
switch v.PubKeyType {
case "ed25519":
pubkey = cmtcryptoproto.PublicKey{
Sum: &cmtcryptoproto.PublicKey_Ed25519{
Ed25519: v.PubKey,
},
}
case "secp256k1":
pubkey = cmtcryptoproto.PublicKey{
Sum: &cmtcryptoproto.PublicKey_Secp256K1{
Secp256K1: v.PubKey,
},
}
}
cometValidatorUpdates[i] = abci.ValidatorUpdate{
PubKey: pubkey,
Power: v.Power,
}
}
return &abci.ResponseInitChain{
Validators: validatorUpdates,
Validators: cometValidatorUpdates,
}, nil
}
@@ -535,7 +566,11 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, modulesToExport []str
channels[moduleName] = make(chan genesisResult)
go func(module HasABCIGenesis, ch chan genesisResult) {
ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions
ch <- genesisResult{module.ExportGenesis(ctx), nil}
jm, err := module.ExportGenesis(ctx)
if err != nil {
ch <- genesisResult{nil, err}
}
ch <- genesisResult{jm, nil}
}(module, channels[moduleName])
}
}
@@ -590,12 +625,6 @@ func (m *Manager) assertNoForgottenModules(setOrderFnName string, moduleNames []
}
}
// MigrationHandler is the migration function that each module registers.
type MigrationHandler func(sdk.Context) error
// VersionMap is a map of moduleName -> version
type VersionMap map[string]uint64
// RunMigrations performs in-place store migrations for all modules. This
// function MUST be called inside an x/upgrade UpgradeHandler.
//
@@ -663,7 +692,7 @@ func (m Manager) RunMigrations(ctx context.Context, cfg Configurator, fromVM Ver
module := m.Modules[moduleName]
fromVersion, exists := fromVM[moduleName]
toVersion := uint64(0)
if module, ok := module.(HasConsensusVersion); ok {
if module, ok := module.(appmodule.HasConsensusVersion); ok {
toVersion = module.ConsensusVersion()
}
@@ -688,7 +717,11 @@ func (m Manager) RunMigrations(ctx context.Context, cfg Configurator, fromVM Ver
}
}
if module, ok := m.Modules[moduleName].(HasABCIGenesis); ok {
moduleValUpdates := module.InitGenesis(sdkCtx, module.DefaultGenesis())
moduleValUpdates, err := module.InitGenesis(sdkCtx, module.DefaultGenesis())
if err != nil {
return nil, err
}
// The module manager assumes only one module will update the
// validator set, and it can't be a new module.
if len(moduleValUpdates) > 0 {
@@ -741,7 +774,7 @@ func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) {
// modules.
func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
validatorUpdates := []abci.ValidatorUpdate{}
validatorUpdates := []ValidatorUpdate{}
for _, moduleName := range m.OrderEndBlockers {
if module, ok := m.Modules[moduleName].(appmodule.HasEndBlocker); ok {
@@ -766,8 +799,32 @@ func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) {
}
}
cometValidatorUpdates := make([]abci.ValidatorUpdate, len(validatorUpdates))
for i, v := range validatorUpdates {
var pubkey cmtcryptoproto.PublicKey
switch v.PubKeyType {
case "ed25519":
pubkey = cmtcryptoproto.PublicKey{
Sum: &cmtcryptoproto.PublicKey_Ed25519{
Ed25519: v.PubKey,
},
}
case "secp256k1":
pubkey = cmtcryptoproto.PublicKey{
Sum: &cmtcryptoproto.PublicKey_Secp256K1{
Secp256K1: v.PubKey,
},
}
}
cometValidatorUpdates[i] = abci.ValidatorUpdate{
PubKey: pubkey,
Power: v.Power,
}
}
return sdk.EndBlock{
ValidatorUpdates: validatorUpdates,
ValidatorUpdates: cometValidatorUpdates,
Events: ctx.EventManager().ABCIEvents(),
}, nil
}
@@ -805,7 +862,7 @@ func (m *Manager) GetVersionMap() VersionMap {
vermap := make(VersionMap)
for name, v := range m.Modules {
version := uint64(0)
if v, ok := v.(HasConsensusVersion); ok {
if v, ok := v.(appmodule.HasConsensusVersion); ok {
version = v.ConsensusVersion()
}
name := name
+13 -17
View File
@@ -172,16 +172,14 @@ func TestManager_InitGenesis(t *testing.T) {
require.Equal(t, 3, len(mm.Modules))
ctx := sdk.NewContext(nil, false, log.NewNopLogger())
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
genesisData := map[string]json.RawMessage{"module1": json.RawMessage(`{"key": "value"}`)}
// this should panic since the validator set is empty even after init genesis
// this should error since the validator set is empty even after init genesis
mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module1"])).Times(1)
_, err := mm.InitGenesis(ctx, cdc, genesisData)
_, err := mm.InitGenesis(ctx, genesisData)
require.ErrorContains(t, err, "validator set is empty after InitGenesis")
// test panic
// test error
genesisData = map[string]json.RawMessage{
"module1": json.RawMessage(`{"key": "value"}`),
"module2": json.RawMessage(`{"key": "value"}`),
@@ -193,19 +191,19 @@ func TestManager_InitGenesis(t *testing.T) {
mockAppModuleABCI1.EXPECT().Name().Times(4).Return("module1")
mockAppModuleABCI2.EXPECT().Name().Times(2).Return("module2")
mmABCI := module.NewManager(mockAppModuleABCI1, mockAppModuleABCI2)
// panic because more than one module returns validator set updates
mockAppModuleABCI1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module1"])).Times(1).Return([]abci.ValidatorUpdate{{}})
mockAppModuleABCI2.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module2"])).Times(1).Return([]abci.ValidatorUpdate{{}})
_, err = mmABCI.InitGenesis(ctx, cdc, genesisData)
// errors because more than one module returns validator set updates
mockAppModuleABCI1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module1"])).Times(1).Return([]module.ValidatorUpdate{{}}, nil)
mockAppModuleABCI2.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module2"])).Times(1).Return([]module.ValidatorUpdate{{}}, nil)
_, err = mmABCI.InitGenesis(ctx, genesisData)
require.ErrorContains(t, err, "validator InitGenesis updates already set by a previous module")
// happy path
mm2 := module.NewManager(mockAppModuleABCI1, mockAppModule2, module.CoreAppModuleAdaptor("module3", mockAppModule3))
mockAppModuleABCI1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module1"])).Times(1).Return([]abci.ValidatorUpdate{{}})
mockAppModuleABCI1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module1"])).Times(1).Return([]module.ValidatorUpdate{{}}, nil)
mockAppModule2.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(genesisData["module2"])).Times(1)
mockAppModule3.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Any()).Times(1).Return(nil)
_, err = mm2.InitGenesis(ctx, cdc, genesisData)
_, err = mm2.InitGenesis(ctx, genesisData)
require.NoError(t, err)
}
@@ -268,15 +266,15 @@ func TestManager_EndBlock(t *testing.T) {
require.NotNil(t, mm)
require.Equal(t, 3, len(mm.Modules))
mockAppModule1.EXPECT().EndBlock(gomock.Any()).Times(1).Return([]abci.ValidatorUpdate{{}}, nil)
mockAppModule1.EXPECT().EndBlock(gomock.Any()).Times(1).Return([]module.ValidatorUpdate{{}}, nil)
mockAppModule2.EXPECT().EndBlock(gomock.Any()).Times(1)
ret, err := mm.EndBlock(sdk.Context{})
require.NoError(t, err)
require.Equal(t, []abci.ValidatorUpdate{{}}, ret.ValidatorUpdates)
// test panic
mockAppModule1.EXPECT().EndBlock(gomock.Any()).Times(1).Return([]abci.ValidatorUpdate{{}}, nil)
mockAppModule2.EXPECT().EndBlock(gomock.Any()).Times(1).Return([]abci.ValidatorUpdate{{}}, nil)
mockAppModule1.EXPECT().EndBlock(gomock.Any()).Times(1).Return([]module.ValidatorUpdate{{}}, nil)
mockAppModule2.EXPECT().EndBlock(gomock.Any()).Times(1).Return([]module.ValidatorUpdate{{}}, nil)
_, err = mm.EndBlock(sdk.Context{})
require.Error(t, err)
}
@@ -307,13 +305,11 @@ func TestCoreAPIManager_InitGenesis(t *testing.T) {
require.Equal(t, 1, len(mm.Modules))
ctx := sdk.NewContext(nil, false, log.NewNopLogger())
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
genesisData := map[string]json.RawMessage{"module1": json.RawMessage(`{"key": "value"}`)}
// this should panic since the validator set is empty even after init genesis
mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Any()).Times(1).Return(nil)
_, err := mm.InitGenesis(ctx, cdc, genesisData)
_, err := mm.InitGenesis(ctx, genesisData)
require.ErrorContains(t, err, "validator set is empty after InitGenesis, please ensure at least one validator is initialized with a delegation greater than or equal to the DefaultPowerReduction")
// TODO: add happy path test. We are not returning any validator updates, this will come with the services.