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

fix(runtime/v2): bring back concurrent export genesis in v2 #21554

Merged
merged 14 commits into from
Sep 24, 2024
16 changes: 8 additions & 8 deletions runtime/v2/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,17 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) {
return genesisState, err
},
ExportGenesis: func(ctx context.Context, version uint64) ([]byte, error) {
_, state, err := a.app.db.StateLatest()
state, err := a.app.db.StateAt(version)
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("unable to get latest state: %w", err)
return nil, fmt.Errorf("unable to get state at given version: %w", err)
}
genesisCtx := services.NewGenesisContext(a.branch(state))

var genesisJson map[string]json.RawMessage
_, err = genesisCtx.Run(ctx, func(ctx context.Context) error {
genesisJson, err = a.app.moduleManager.ExportGenesisForModules(ctx)
return err
})
genesisJson, err := a.app.moduleManager.ExportGenesisForModules(
ctx,
func() store.WriterMap {
return a.branch(state)
},
)
if err != nil {
return nil, fmt.Errorf("failed to export genesis: %w", err)
}
Expand Down
38 changes: 30 additions & 8 deletions runtime/v2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
"cosmossdk.io/core/appmodule"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/registry"
"cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2/services"
"cosmossdk.io/server/v2/stf"
)

Expand Down Expand Up @@ -193,6 +195,7 @@
// ExportGenesisForModules performs export genesis functionality for modules
func (m *MM[T]) ExportGenesisForModules(
ctx context.Context,
stateFactory func() store.WriterMap,
modulesToExport ...string,
) (map[string]json.RawMessage, error) {
if len(modulesToExport) == 0 {
Expand All @@ -203,17 +206,19 @@
return nil, err
}

type genesisResult struct {
bz json.RawMessage
err error
}

type ModuleI interface {
ExportGenesis(ctx context.Context) (json.RawMessage, error)
}

genesisData := make(map[string]json.RawMessage)

// TODO: make async export genesis https://github.com/cosmos/cosmos-sdk/issues/21303
channels := make(map[string]chan genesisResult)
for _, moduleName := range modulesToExport {
mod := m.modules[moduleName]
var moduleI ModuleI

if module, hasGenesis := mod.(appmodulev2.HasGenesis); hasGenesis {
moduleI = module.(ModuleI)
} else if module, hasABCIGenesis := mod.(appmodulev2.HasABCIGenesis); hasABCIGenesis {
Expand All @@ -222,12 +227,29 @@
continue
}

res, err := moduleI.ExportGenesis(ctx)
if err != nil {
return nil, err
channels[moduleName] = make(chan genesisResult)
go func(moduleI ModuleI, ch chan genesisResult) {
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
genesisCtx := services.NewGenesisContext(stateFactory())
_, _ = genesisCtx.Run(ctx, func(ctx context.Context) error {
jm, err := moduleI.ExportGenesis(ctx)
if err != nil {
ch <- genesisResult{nil, err}
return err
}
ch <- genesisResult{jm, nil}
return nil
})
}(moduleI, channels[moduleName])
Comment on lines +231 to +242

Check notice

Code scanning / CodeQL

Spawning a Go routine Note

Spawning a Go routine may be a possible source of non-determinism
}

genesisData := make(map[string]json.RawMessage)
for moduleName := range channels {
res := <-channels[moduleName]
if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
}

genesisData[moduleName] = res
genesisData[moduleName] = res.bz
Comment on lines +245 to +252
Copy link
Contributor

Choose a reason for hiding this comment

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

Use a deterministic order when collecting genesis data.

Collecting the exported genesis data from the channels using a range loop over the channels map may result in non-deterministic ordering. This is because the order of iteration over a map is not guaranteed to be the same across different runs.

To ensure deterministic ordering, consider the following:

  • Create a sorted slice of module names based on the keys of the channels map.
  • Iterate over the sorted slice of module names to collect the exported genesis data from the channels in a deterministic order.

Here's an example of how you can modify the code to achieve deterministic ordering:

 genesisData := make(map[string]json.RawMessage)
-for moduleName := range channels {
+moduleNames := make([]string, 0, len(channels))
+for moduleName := range channels {
+    moduleNames = append(moduleNames, moduleName)
+}
+sort.Strings(moduleNames)
+for _, moduleName := range moduleNames {
     res := <-channels[moduleName]
     if res.err != nil {
         return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
     }
     genesisData[moduleName] = res.bz
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
genesisData := make(map[string]json.RawMessage)
for moduleName := range channels {
res := <-channels[moduleName]
if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
}
genesisData[moduleName] = res
genesisData[moduleName] = res.bz
genesisData := make(map[string]json.RawMessage)
moduleNames := make([]string, 0, len(channels))
for moduleName := range channels {
moduleNames = append(moduleNames, moduleName)
}
sort.Strings(moduleNames)
for _, moduleName := range moduleNames {
res := <-channels[moduleName]
if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
}
genesisData[moduleName] = res.bz

}

return genesisData, nil
Expand Down
Loading