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
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion runtime/v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
cosmossdk.io/store v1.1.0
cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000
cosmossdk.io/x/tx v0.13.1
github.com/cometbft/cometbft v0.38.6
github.com/cosmos/cosmos-sdk v0.51.0
github.com/cosmos/gogoproto v1.4.12
github.com/golang/protobuf v1.5.4
Expand Down Expand Up @@ -74,7 +75,6 @@ require (
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft v0.38.6 // indirect
github.com/cometbft/cometbft-db v0.9.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
Expand Down
167 changes: 162 additions & 5 deletions runtime/v2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@
cosmosmsg "cosmossdk.io/api/cosmos/msg/v1"
"cosmossdk.io/core/appmodule"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/genesis"
"cosmossdk.io/core/registry"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2/protocompat"
"cosmossdk.io/server/v2/stf"
cmtcryptoproto "github.com/cometbft/cometbft/proto/tendermint/crypto"

storetypes "cosmossdk.io/store/types"
abci "github.com/cometbft/cometbft/abci/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 @@ -139,14 +144,166 @@
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) (*abci.ResponseInitChain, error) {
hieuvubk marked this conversation as resolved.
Show resolved Hide resolved
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]
// Check if module has auto genesis, if so return err
if _, ok := mod.(appmodule.HasGenesisAuto); ok {
hieuvubk marked this conversation as resolved.
Show resolved Hide resolved
m.logger.Debug("running initialization for module", "module", moduleName)
return &abci.ResponseInitChain{}, fmt.Errorf("Not support auto genesis, module: %v", moduleName)
} else 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 &abci.ResponseInitChain{}, 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 &abci.ResponseInitChain{}, 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 &abci.ResponseInitChain{}, 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 &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: cometValidatorUpdates,
}, 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.(appmodule.HasGenesisAuto); ok {
// core API genesis
channels[moduleName] = make(chan genesisResult)
go func(module appmodule.HasGenesisAuto, ch chan genesisResult) {
hieuvubk marked this conversation as resolved.
Show resolved Hide resolved
ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions
target := genesis.RawJSONTarget{}
err := module.ExportGenesis(ctx, target.Target())
if err != nil {
ch <- genesisResult{nil, err}
return
}

rawJSON, err := target.JSON()
if err != nil {
ch <- genesisResult{nil, err}
return
}

ch <- genesisResult{rawJSON, nil}
}(module, channels[moduleName])

Check notice

Code scanning / CodeQL

Spawning a Go routine Note

} else if module, ok := mod.(appmodulev2.HasGenesis); ok {
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
Loading