Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime/v2): Export Genesis #20009

Merged
merged 10 commits into from
Apr 25, 2024
142 changes: 123 additions & 19 deletions runtime/v2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"cosmossdk.io/runtime/v2/protocompat"
"cosmossdk.io/server/v2/stf"

storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmodule "github.com/cosmos/cosmos-sdk/types/module"
)

Expand Down Expand Up @@ -140,14 +142,118 @@
return nil
}

// InitGenesis performs init genesis functionality for modules.
func (m *MM) InitGenesis() {
panic("implement me")
func (m *MM) InitGenesis(ctx context.Context, genesisData map[string]json.RawMessage) ([]appmodulev2.ValidatorUpdate, error) {
var validatorUpdates []appmodulev2.ValidatorUpdate
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
m.logger.Info("initializing blockchain state from genesis.json")

for _, moduleName := range m.config.InitGenesis {
if genesisData[moduleName] == nil {
continue
}

mod := m.modules[moduleName]
if module, ok := mod.(appmodulev2.HasGenesis); ok {
m.logger.Debug("running initialization for module", "module", moduleName)
if err := module.InitGenesis(ctx, genesisData[moduleName]); err != nil {
return nil, err
}
} else if module, ok := mod.(sdkmodule.HasABCIGenesis); ok {
m.logger.Debug("running initialization for module", "module", moduleName)
moduleValUpdates, err := module.InitGenesis(ctx, genesisData[moduleName])
if err != nil {
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 len(validatorUpdates) > 0 {
return nil, errors.New("validator InitGenesis updates already set by a previous module")
}
validatorUpdates = moduleValUpdates
}
}
}

// a chain must initialize with a non-empty validator set
if len(validatorUpdates) == 0 {
return nil, 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)
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}

// Not convert to abci response
// Return appmodule.ValidatorUpdates instead
return validatorUpdates, nil
}

// ExportGenesis performs export genesis functionality for modules
func (m *MM) ExportGenesis() {
panic("implement me")
func (m *MM) ExportGenesis(ctx sdk.Context) (map[string]json.RawMessage, error) {
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
return m.ExportGenesisForModules(ctx, []string{})
}

// ExportGenesisForModules performs export genesis functionality for modules
func (m *MM) ExportGenesisForModules(ctx sdk.Context, modulesToExport []string) (map[string]json.RawMessage, error) {
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
if len(modulesToExport) == 0 {
modulesToExport = m.config.ExportGenesis
hieuvubk marked this conversation as resolved.
Show resolved Hide resolved
}
// verify modules exists in app, so that we don't panic in the middle of an export
if err := m.checkModulesExists(modulesToExport); err != nil {
return nil, err
}

type genesisResult struct {
bz json.RawMessage
err error
}

channels := make(map[string]chan genesisResult)
for _, moduleName := range modulesToExport {
mod := m.modules[moduleName]
if module, ok := mod.(appmodulev2.HasGenesis); ok {
hieuvubk marked this conversation as resolved.
Show resolved Hide resolved
channels[moduleName] = make(chan genesisResult)
go func(module appmodulev2.HasGenesis, ch chan genesisResult) {
ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
jm, err := module.ExportGenesis(ctx)
if err != nil {
ch <- genesisResult{nil, err}
return
}
ch <- genesisResult{jm, nil}
}(module, channels[moduleName])
} else if module, ok := mod.(sdkmodule.HasABCIGenesis); ok {
Fixed Show fixed Hide fixed
channels[moduleName] = make(chan genesisResult)
go func(module sdkmodule.HasABCIGenesis, ch chan genesisResult) {
ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions
jm, err := module.ExportGenesis(ctx)
if err != nil {
ch <- genesisResult{nil, err}
}
ch <- genesisResult{jm, nil}
}(module, channels[moduleName])
}
Fixed Show fixed Hide fixed
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modules that do not implement the export method are silently ignored. Would some log output make sense?

Copy link
Member

@julienrbrt julienrbrt Apr 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All mandatory modules are present and checked by assertNoForgottenModules.
I'd rather have assertNoForgottenModules panic when unnecessary modules are present than adding extra log personally.

We have relaxed the required modules in the previous sdk release, but you could still add modules to say OrderBeginBlock that don't implement BeginBlocker, and it will be silently skipped. Given this is a green field, imho best to have assertNoForgottenModules more pedantic in this new module manager.


genesisData := make(map[string]json.RawMessage)
for moduleName := range channels {
res := <-channels[moduleName]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is blocking on the first channel in the map. If you would move the module name into the genesisResult, you can have a channel just for the results instead that would be a fifo. Not sure if this gives some significant performance benefit or makes it harder to read. 🤷

if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 would it make sense to collect all error messages instead and return them together? It is also ok to fail fast on first error. You could cancel the context in this case (ctx, cancel := context.WithCancel(ctx)) (assuming the modules would abort) and exit the Go routines early, too.

}

genesisData[moduleName] = res.bz
}

Dismissed Show dismissed Hide dismissed
return genesisData, nil
}

// checkModulesExists verifies that all modules in the list exist in the app
func (m *MM) checkModulesExists(moduleName []string) error {
for _, name := range moduleName {
if _, ok := m.modules[name]; !ok {
return fmt.Errorf("module %s does not exist", name)
}
}

return nil
}

// BeginBlock runs the begin-block logic of all modules
Expand Down Expand Up @@ -365,34 +471,32 @@

if err := m.assertNoForgottenModules("InitGenesis", m.config.InitGenesis, func(moduleName string) bool {
module := m.modules[moduleName]
if _, hasGenesis := module.(appmodulev2.HasGenesis); hasGenesis {
return !hasGenesis
if _, hasGenesis := module.(appmodule.HasGenesisAuto); hasGenesis {
panic(fmt.Sprintf("module %s isn't server/v2 compatible", moduleName))
}

// TODO, if we actually don't support old genesis, let's panic here saying this module isn't server/v2 compatible
if _, hasABCIGenesis := module.(sdkmodule.HasABCIGenesis); hasABCIGenesis {
return !hasABCIGenesis
if _, hasGenesis := module.(appmodulev2.HasGenesis); hasGenesis {
return !hasGenesis
}

_, hasGenesis := module.(sdkmodule.HasGenesis)
return !hasGenesis
_, hasABCIGenesis := module.(sdkmodule.HasABCIGenesis)
return !hasABCIGenesis
}); err != nil {
return err
}

if err := m.assertNoForgottenModules("ExportGenesis", m.config.ExportGenesis, func(moduleName string) bool {
module := m.modules[moduleName]
if _, hasGenesis := module.(appmodulev2.HasGenesis); hasGenesis {
return !hasGenesis
if _, hasGenesis := module.(appmodule.HasGenesisAuto); hasGenesis {
panic(fmt.Sprintf("module %s isn't server/v2 compatible", moduleName))
}

// TODO, if we actually don't support old genesis, let's panic here saying this module isn't server/v2 compatible
if _, hasABCIGenesis := module.(sdkmodule.HasABCIGenesis); hasABCIGenesis {
return !hasABCIGenesis
if _, hasGenesis := module.(appmodulev2.HasGenesis); hasGenesis {
return !hasGenesis
}

_, hasGenesis := module.(sdkmodule.HasGenesis)
return !hasGenesis
_, hasABCIGenesis := module.(sdkmodule.HasABCIGenesis)
return !hasABCIGenesis
}); err != nil {
return err
}
Expand Down
Loading