-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
Co-authored-by: marbar3778 <[email protected]> (cherry picked from commit aeeaca6) # Conflicts: # runtime/v2/app.go # runtime/v2/builder.go # runtime/v2/manager.go # server/v2/appmanager/appmanager.go # server/v2/cometbft/go.mod # simapp/app.go # simapp/v2/go.mod
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package runtime | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
|
||
gogoproto "github.com/cosmos/gogoproto/proto" | ||
"golang.org/x/exp/slices" | ||
|
||
runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" | ||
"cosmossdk.io/core/legacy" | ||
"cosmossdk.io/core/registry" | ||
"cosmossdk.io/core/transaction" | ||
"cosmossdk.io/log" | ||
"cosmossdk.io/server/v2/appmanager" | ||
Check failure on line 15 in runtime/v2/app.go GitHub Actions / split-test-files
Check failure on line 15 in runtime/v2/app.go GitHub Actions / dependency-review
Check failure on line 15 in runtime/v2/app.go GitHub Actions / dependency-review
|
||
"cosmossdk.io/server/v2/stf" | ||
Check failure on line 16 in runtime/v2/app.go GitHub Actions / split-test-files
Check failure on line 16 in runtime/v2/app.go GitHub Actions / dependency-review
Check failure on line 16 in runtime/v2/app.go GitHub Actions / dependency-review
|
||
) | ||
|
||
// App is a wrapper around AppManager and ModuleManager that can be used in hybrid | ||
// app.go/app config scenarios or directly as a servertypes.Application instance. | ||
// To get an instance of *App, *AppBuilder must be requested as a dependency | ||
// in a container which declares the runtime module and the AppBuilder.Build() | ||
// method must be called. | ||
// | ||
// App can be used to create a hybrid app.go setup where some configuration is | ||
// done declaratively with an app config and the rest of it is done the old way. | ||
// See simapp/app_v2.go for an example of this setup. | ||
type App[T transaction.Tx] struct { | ||
*appmanager.AppManager[T] | ||
|
||
// app manager dependencies | ||
stf *stf.STF[T] | ||
msgRouterBuilder *stf.MsgRouterBuilder | ||
queryRouterBuilder *stf.MsgRouterBuilder | ||
db Store | ||
|
||
// app configuration | ||
logger log.Logger | ||
config *runtimev2.Module | ||
|
||
// modules configuration | ||
storeKeys []string | ||
interfaceRegistrar registry.InterfaceRegistrar | ||
amino legacy.Amino | ||
moduleManager *MM[T] | ||
|
||
// GRPCMethodsToMessageMap maps gRPC method name to a function that decodes the request | ||
// bytes into a gogoproto.Message, which then can be passed to appmanager. | ||
GRPCMethodsToMessageMap map[string]func() gogoproto.Message | ||
} | ||
|
||
// Name returns the app name. | ||
func (a *App[T]) Name() string { | ||
return a.config.AppName | ||
} | ||
|
||
// Logger returns the app logger. | ||
func (a *App[T]) Logger() log.Logger { | ||
return a.logger | ||
} | ||
|
||
// ModuleManager returns the module manager. | ||
func (a *App[T]) ModuleManager() *MM[T] { | ||
return a.moduleManager | ||
} | ||
|
||
// DefaultGenesis returns a default genesis from the registered modules. | ||
func (a *App[T]) DefaultGenesis() map[string]json.RawMessage { | ||
return a.moduleManager.DefaultGenesis() | ||
} | ||
|
||
// LoadLatest loads the latest version. | ||
func (a *App[T]) LoadLatest() error { | ||
return a.db.LoadLatestVersion() | ||
} | ||
|
||
// LoadHeight loads a particular height | ||
func (a *App[T]) LoadHeight(height uint64) error { | ||
return a.db.LoadVersion(height) | ||
} | ||
|
||
// LoadLatestHeight loads the latest height. | ||
func (a *App[T]) LoadLatestHeight() (uint64, error) { | ||
return a.db.GetLatestVersion() | ||
} | ||
|
||
// Close is called in start cmd to gracefully cleanup resources. | ||
func (a *App[T]) Close() error { | ||
return nil | ||
} | ||
|
||
// GetStoreKeys returns all the app store keys. | ||
func (a *App[T]) GetStoreKeys() []string { | ||
return a.storeKeys | ||
} | ||
|
||
// UnsafeFindStoreKey fetches a registered StoreKey from the App in linear time. | ||
// NOTE: This should only be used in testing. | ||
func (a *App[T]) UnsafeFindStoreKey(storeKey string) (string, error) { | ||
i := slices.IndexFunc(a.storeKeys, func(s string) bool { return s == storeKey }) | ||
if i == -1 { | ||
return "", errors.New("store key not found") | ||
} | ||
|
||
return a.storeKeys[i], nil | ||
} | ||
|
||
// GetStore returns the app store. | ||
func (a *App[T]) GetStore() Store { | ||
return a.db | ||
} | ||
|
||
// GetLogger returns the app logger. | ||
func (a *App[T]) GetLogger() log.Logger { | ||
return a.logger | ||
} | ||
|
||
func (a *App[T]) GetAppManager() *appmanager.AppManager[T] { | ||
return a.AppManager | ||
} | ||
|
||
func (a *App[T]) GetGPRCMethodsToMessageMap() map[string]func() gogoproto.Message { | ||
return a.GRPCMethodsToMessageMap | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
package runtime | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"path/filepath" | ||
|
||
"github.com/spf13/viper" | ||
|
||
"cosmossdk.io/core/appmodule" | ||
appmodulev2 "cosmossdk.io/core/appmodule/v2" | ||
"cosmossdk.io/core/store" | ||
"cosmossdk.io/core/transaction" | ||
"cosmossdk.io/server/v2/appmanager" | ||
"cosmossdk.io/server/v2/stf" | ||
"cosmossdk.io/server/v2/stf/branch" | ||
Check failure on line 18 in runtime/v2/builder.go GitHub Actions / split-test-files
Check failure on line 18 in runtime/v2/builder.go GitHub Actions / dependency-review
Check failure on line 18 in runtime/v2/builder.go GitHub Actions / dependency-review
|
||
"cosmossdk.io/store/v2/db" | ||
Check failure on line 19 in runtime/v2/builder.go GitHub Actions / split-test-files
Check failure on line 19 in runtime/v2/builder.go GitHub Actions / dependency-review
Check failure on line 19 in runtime/v2/builder.go GitHub Actions / dependency-review
|
||
rootstore "cosmossdk.io/store/v2/root" | ||
Check failure on line 20 in runtime/v2/builder.go GitHub Actions / split-test-files
Check failure on line 20 in runtime/v2/builder.go GitHub Actions / dependency-review
Check failure on line 20 in runtime/v2/builder.go GitHub Actions / dependency-review
|
||
) | ||
|
||
// AppBuilder is a type that is injected into a container by the runtime/v2 module | ||
// (as *AppBuilder) which can be used to create an app which is compatible with | ||
// the existing app.go initialization conventions. | ||
type AppBuilder[T transaction.Tx] struct { | ||
app *App[T] | ||
storeOptions *rootstore.FactoryOptions | ||
viper *viper.Viper | ||
|
||
// the following fields are used to overwrite the default | ||
branch func(state store.ReaderMap) store.WriterMap | ||
txValidator func(ctx context.Context, tx T) error | ||
postTxExec func(ctx context.Context, tx T, success bool) error | ||
} | ||
|
||
// DefaultGenesis returns a default genesis from the registered AppModule's. | ||
func (a *AppBuilder[T]) DefaultGenesis() map[string]json.RawMessage { | ||
return a.app.moduleManager.DefaultGenesis() | ||
} | ||
|
||
// RegisterModules registers the provided modules with the module manager. | ||
// This is the primary hook for integrating with modules which are not registered using the app config. | ||
func (a *AppBuilder[T]) RegisterModules(modules map[string]appmodulev2.AppModule) error { | ||
for name, appModule := range modules { | ||
// if a (legacy) module implements the HasName interface, check that the name matches | ||
if mod, ok := appModule.(interface{ Name() string }); ok { | ||
if name != mod.Name() { | ||
a.app.logger.Warn(fmt.Sprintf("module name %q does not match name returned by HasName: %q", name, mod.Name())) | ||
} | ||
} | ||
|
||
if _, ok := a.app.moduleManager.modules[name]; ok { | ||
return fmt.Errorf("module named %q already exists", name) | ||
} | ||
a.app.moduleManager.modules[name] = appModule | ||
|
||
if mod, ok := appModule.(appmodulev2.HasRegisterInterfaces); ok { | ||
mod.RegisterInterfaces(a.app.interfaceRegistrar) | ||
} | ||
|
||
if mod, ok := appModule.(appmodule.HasAminoCodec); ok { | ||
mod.RegisterLegacyAminoCodec(a.app.amino) | ||
} | ||
} | ||
Check warning Code scanning / CodeQL Iteration over map Warning
Iteration over map may be a possible source of non-determinism
|
||
|
||
return nil | ||
} | ||
|
||
// RegisterStores registers the provided store keys. | ||
// This method should only be used for registering extra stores | ||
// which is necessary for modules that not registered using the app config. | ||
// To be used in combination of RegisterModules. | ||
func (a *AppBuilder[T]) RegisterStores(keys ...string) { | ||
a.app.storeKeys = append(a.app.storeKeys, keys...) | ||
if a.storeOptions != nil { | ||
a.storeOptions.StoreKeys = append(a.storeOptions.StoreKeys, keys...) | ||
} | ||
} | ||
|
||
// Build builds an *App instance. | ||
func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { | ||
for _, opt := range opts { | ||
opt(a) | ||
} | ||
|
||
// default branch | ||
if a.branch == nil { | ||
a.branch = branch.DefaultNewWriterMap | ||
} | ||
|
||
// default tx validator | ||
if a.txValidator == nil { | ||
a.txValidator = a.app.moduleManager.TxValidators() | ||
} | ||
|
||
// default post tx exec | ||
if a.postTxExec == nil { | ||
a.postTxExec = func(ctx context.Context, tx T, success bool) error { | ||
return nil | ||
} | ||
} | ||
|
||
if err := a.app.moduleManager.RegisterServices(a.app); err != nil { | ||
return nil, err | ||
} | ||
|
||
endBlocker, valUpdate := a.app.moduleManager.EndBlock() | ||
|
||
stf, err := stf.NewSTF[T]( | ||
a.app.logger.With("module", "stf"), | ||
a.app.msgRouterBuilder, | ||
a.app.queryRouterBuilder, | ||
a.app.moduleManager.PreBlocker(), | ||
a.app.moduleManager.BeginBlock(), | ||
endBlocker, | ||
a.txValidator, | ||
valUpdate, | ||
a.postTxExec, | ||
a.branch, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create STF: %w", err) | ||
} | ||
a.app.stf = stf | ||
|
||
v := a.viper | ||
home := v.GetString(FlagHome) | ||
|
||
storeOpts := rootstore.DefaultStoreOptions() | ||
if s := v.Sub("store.options"); s != nil { | ||
if err := s.Unmarshal(&storeOpts); err != nil { | ||
return nil, fmt.Errorf("failed to store options: %w", err) | ||
} | ||
} | ||
|
||
scRawDb, err := db.NewDB(db.DBType(v.GetString("store.app-db-backend")), "application", filepath.Join(home, "data"), nil) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
storeOptions := &rootstore.FactoryOptions{ | ||
Logger: a.app.logger, | ||
RootDir: home, | ||
Options: storeOpts, | ||
StoreKeys: append(a.app.storeKeys, "stf"), | ||
SCRawDB: scRawDb, | ||
} | ||
a.storeOptions = storeOptions | ||
|
||
rs, err := rootstore.CreateRootStore(a.storeOptions) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create root store: %w", err) | ||
} | ||
a.app.db = rs | ||
|
||
appManagerBuilder := appmanager.Builder[T]{ | ||
STF: a.app.stf, | ||
DB: a.app.db, | ||
ValidateTxGasLimit: a.app.config.GasConfig.ValidateTxGasLimit, | ||
QueryGasLimit: a.app.config.GasConfig.QueryGasLimit, | ||
SimulationGasLimit: a.app.config.GasConfig.SimulationGasLimit, | ||
InitGenesis: func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) error { | ||
// this implementation assumes that the state is a JSON object | ||
bz, err := io.ReadAll(src) | ||
if err != nil { | ||
return fmt.Errorf("failed to read import state: %w", err) | ||
} | ||
var genesisState map[string]json.RawMessage | ||
if err = json.Unmarshal(bz, &genesisState); err != nil { | ||
return err | ||
} | ||
if err = a.app.moduleManager.InitGenesisJSON(ctx, genesisState, txHandler); err != nil { | ||
return fmt.Errorf("failed to init genesis: %w", err) | ||
} | ||
return nil | ||
}, | ||
ExportGenesis: func(ctx context.Context, version uint64) ([]byte, error) { | ||
genesisJson, err := a.app.moduleManager.ExportGenesisForModules(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to export genesis: %w", err) | ||
} | ||
|
||
bz, err := json.Marshal(genesisJson) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to marshal genesis: %w", err) | ||
} | ||
|
||
return bz, nil | ||
}, | ||
} | ||
|
||
appManager, err := appManagerBuilder.Build() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to build app manager: %w", err) | ||
} | ||
a.app.AppManager = appManager | ||
|
||
return a.app, nil | ||
} | ||
|
||
// AppBuilderOption is a function that can be passed to AppBuilder.Build to customize the resulting app. | ||
type AppBuilderOption[T transaction.Tx] func(*AppBuilder[T]) | ||
|
||
// AppBuilderWithBranch sets a custom branch implementation for the app. | ||
func AppBuilderWithBranch[T transaction.Tx](branch func(state store.ReaderMap) store.WriterMap) AppBuilderOption[T] { | ||
return func(a *AppBuilder[T]) { | ||
a.branch = branch | ||
} | ||
} | ||
|
||
// AppBuilderWithTxValidator sets the tx validator for the app. | ||
// It overrides all default tx validators defined by modules. | ||
func AppBuilderWithTxValidator[T transaction.Tx](txValidators func(ctx context.Context, tx T) error) AppBuilderOption[T] { | ||
return func(a *AppBuilder[T]) { | ||
a.txValidator = txValidators | ||
} | ||
} | ||
|
||
// AppBuilderWithPostTxExec sets logic that will be executed after each transaction. | ||
// When not provided, a no-op function will be used. | ||
func AppBuilderWithPostTxExec[T transaction.Tx]( | ||
postTxExec func( | ||
ctx context.Context, | ||
tx T, | ||
success bool, | ||
) error, | ||
) AppBuilderOption[T] { | ||
return func(a *AppBuilder[T]) { | ||
a.postTxExec = postTxExec | ||
} | ||
} |