From af0b28d2c8bb92458ef12bf33d668d9c85363b91 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 21 Oct 2024 15:54:18 +0200 Subject: [PATCH] removal --- runtime/v2/app.go | 103 ---- runtime/v2/builder.go | 216 -------- runtime/v2/manager.go | 861 ----------------------------- runtime/v2/module.go | 257 --------- server/v2/api/grpc/server.go | 221 -------- server/v2/api/rest/handler.go | 99 ---- server/v2/api/rest/server.go | 96 ---- server/v2/appmanager/appmanager.go | 221 -------- server/v2/appmanager/config.go | 8 - server/v2/appmanager/genesis.go | 28 - server/v2/appmanager/stf.go | 43 -- server/v2/server_test.go | 110 ---- server/v2/stf/stf.go | 635 --------------------- server/v2/store/server.go | 91 --- server/v2/types.go | 26 - 15 files changed, 3015 deletions(-) delete mode 100644 runtime/v2/app.go delete mode 100644 runtime/v2/builder.go delete mode 100644 runtime/v2/manager.go delete mode 100644 runtime/v2/module.go delete mode 100644 server/v2/api/grpc/server.go delete mode 100644 server/v2/api/rest/handler.go delete mode 100644 server/v2/api/rest/server.go delete mode 100644 server/v2/appmanager/appmanager.go delete mode 100644 server/v2/appmanager/config.go delete mode 100644 server/v2/appmanager/genesis.go delete mode 100644 server/v2/appmanager/stf.go delete mode 100644 server/v2/server_test.go delete mode 100644 server/v2/stf/stf.go delete mode 100644 server/v2/store/server.go delete mode 100644 server/v2/types.go diff --git a/runtime/v2/app.go b/runtime/v2/app.go deleted file mode 100644 index b7887ab77f54..000000000000 --- a/runtime/v2/app.go +++ /dev/null @@ -1,103 +0,0 @@ -package runtime - -import ( - "encoding/json" - - runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" - appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/registry" - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - "cosmossdk.io/schema/decoding" - "cosmossdk.io/server/v2/appmanager" - "cosmossdk.io/server/v2/stf" -) - -// 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 configuration - logger log.Logger - config *runtimev2.Module - - // state - stf *stf.STF[T] - msgRouterBuilder *stf.MsgRouterBuilder - queryRouterBuilder *stf.MsgRouterBuilder - db Store - storeLoader StoreLoader - - // modules - interfaceRegistrar registry.InterfaceRegistrar - amino registry.AminoRegistrar - moduleManager *MM[T] - queryHandlers map[string]appmodulev2.Handler // queryHandlers defines the query handlers -} - -// 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() -} - -// SetStoreLoader sets the store loader. -func (a *App[T]) SetStoreLoader(loader StoreLoader) { - a.storeLoader = loader -} - -// LoadLatest loads the latest version. -func (a *App[T]) LoadLatest() error { - return a.storeLoader(a.db) -} - -// 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() -} - -// GetQueryHandlers returns the query handlers. -func (a *App[T]) QueryHandlers() map[string]appmodulev2.Handler { - return a.queryHandlers -} - -// SchemaDecoderResolver returns the module schema resolver. -func (a *App[T]) SchemaDecoderResolver() decoding.DecoderResolver { - moduleSet := map[string]any{} - for moduleName, module := range a.moduleManager.Modules() { - moduleSet[moduleName] = module - } - return decoding.ModuleSetDecoderResolver(moduleSet) -} - -// Close is called in start cmd to gracefully cleanup resources. -func (a *App[T]) Close() error { - return nil -} diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go deleted file mode 100644 index 8556e35745a8..000000000000 --- a/runtime/v2/builder.go +++ /dev/null @@ -1,216 +0,0 @@ -package runtime - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - - "cosmossdk.io/core/appmodule" - appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" - "cosmossdk.io/runtime/v2/services" - "cosmossdk.io/server/v2/appmanager" - "cosmossdk.io/server/v2/stf" - "cosmossdk.io/server/v2/stf/branch" - "cosmossdk.io/store/v2/root" -) - -// 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] - storeBuilder root.Builder - - // 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 -} - -// 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) - } - } - - return nil -} - -// 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 - } - } - - a.app.db = a.storeBuilder.Get() - if a.app.db == nil { - return nil, fmt.Errorf("storeBuilder did not return a db") - } - - if err := a.app.moduleManager.RegisterServices(a.app); err != nil { - return nil, err - } - - endBlocker, valUpdate := a.app.moduleManager.EndBlock() - - stf, err := stf.New[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 - - a.app.AppManager = appmanager.New[T]( - appmanager.Config{ - ValidateTxGasLimit: a.app.config.GasConfig.ValidateTxGasLimit, - QueryGasLimit: a.app.config.GasConfig.QueryGasLimit, - SimulationGasLimit: a.app.config.GasConfig.SimulationGasLimit, - }, - a.app.db, - a.app.stf, - a.initGenesis, - a.exportGenesis, - ) - - return a.app, nil -} - -// initGenesis returns the app initialization genesis for modules -func (a *AppBuilder[T]) initGenesis(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) (store.WriterMap, error) { - // this implementation assumes that the state is a JSON object - bz, err := io.ReadAll(src) - if err != nil { - return nil, fmt.Errorf("failed to read import state: %w", err) - } - var genesisJSON map[string]json.RawMessage - if err = json.Unmarshal(bz, &genesisJSON); err != nil { - return nil, err - } - - v, zeroState, err := a.app.db.StateLatest() - if err != nil { - return nil, fmt.Errorf("unable to get latest state: %w", err) - } - if v != 0 { // TODO: genesis state may be > 0, we need to set version on store - return nil, errors.New("cannot init genesis on non-zero state") - } - genesisCtx := services.NewGenesisContext(a.branch(zeroState)) - genesisState, err := genesisCtx.Mutate(ctx, func(ctx context.Context) error { - err = a.app.moduleManager.InitGenesisJSON(ctx, genesisJSON, txHandler) - if err != nil { - return fmt.Errorf("failed to init genesis: %w", err) - } - return nil - }) - - return genesisState, err -} - -// exportGenesis returns the app export genesis logic for modules -func (a *AppBuilder[T]) exportGenesis(ctx context.Context, version uint64) ([]byte, error) { - state, err := a.app.db.StateAt(version) - if err != nil { - return nil, fmt.Errorf("unable to get state at given version: %w", 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) - } - - bz, err := json.Marshal(genesisJson) - if err != nil { - return nil, fmt.Errorf("failed to marshal genesis: %w", err) - } - - return bz, 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 - } -} diff --git a/runtime/v2/manager.go b/runtime/v2/manager.go deleted file mode 100644 index e2e90c27f808..000000000000 --- a/runtime/v2/manager.go +++ /dev/null @@ -1,861 +0,0 @@ -package runtime - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "maps" - "reflect" - "slices" - "sort" - - gogoproto "github.com/cosmos/gogoproto/proto" - "google.golang.org/grpc" - proto "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - - runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" - cosmosmsg "cosmossdk.io/api/cosmos/msg/v1" - "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" -) - -type MM[T transaction.Tx] struct { - logger log.Logger - config *runtimev2.Module - modules map[string]appmodulev2.AppModule - migrationRegistrar *migrationRegistrar -} - -// NewModuleManager is the constructor for the module manager -// It handles all the interactions between the modules and the application -func NewModuleManager[T transaction.Tx]( - logger log.Logger, - config *runtimev2.Module, - modules map[string]appmodulev2.AppModule, -) *MM[T] { - // good defaults for the module manager order - modulesName := slices.Sorted(maps.Keys(modules)) - if len(config.PreBlockers) == 0 { - config.PreBlockers = modulesName - } - if len(config.BeginBlockers) == 0 { - config.BeginBlockers = modulesName - } - if len(config.EndBlockers) == 0 { - config.EndBlockers = modulesName - } - if len(config.TxValidators) == 0 { - config.TxValidators = modulesName - } - if len(config.InitGenesis) == 0 { - config.InitGenesis = modulesName - } - if len(config.ExportGenesis) == 0 { - config.ExportGenesis = modulesName - } - if len(config.OrderMigrations) == 0 { - config.OrderMigrations = defaultMigrationsOrder(modulesName) - } - - mm := &MM[T]{ - logger: logger, - config: config, - modules: modules, - migrationRegistrar: newMigrationRegistrar(), - } - - if err := mm.validateConfig(); err != nil { - panic(err) - } - - return mm -} - -// Modules returns the modules registered in the module manager -func (m *MM[T]) Modules() map[string]appmodulev2.AppModule { - return m.modules -} - -// RegisterLegacyAminoCodec registers all module codecs -func (m *MM[T]) RegisterLegacyAminoCodec(registrar registry.AminoRegistrar) { - for _, b := range m.modules { - if mod, ok := b.(appmodule.HasAminoCodec); ok { - mod.RegisterLegacyAminoCodec(registrar) - } - } -} - -// RegisterInterfaces registers all module interface types -func (m *MM[T]) RegisterInterfaces(registry registry.InterfaceRegistrar) { - for _, b := range m.modules { - if mod, ok := b.(appmodulev2.HasRegisterInterfaces); ok { - mod.RegisterInterfaces(registry) - } - } -} - -// DefaultGenesis provides default genesis information for all modules -func (m *MM[T]) DefaultGenesis() map[string]json.RawMessage { - genesisData := make(map[string]json.RawMessage) - for name, b := range m.modules { - if mod, ok := b.(appmodule.HasGenesisBasics); ok { - genesisData[name] = mod.DefaultGenesis() - } else if mod, ok := b.(appmodulev2.HasGenesis); ok { - genesisData[name] = mod.DefaultGenesis() - } else { - genesisData[name] = []byte("{}") - } - } - - return genesisData -} - -// ValidateGenesis performs genesis state validation for all modules -func (m *MM[T]) ValidateGenesis(genesisData map[string]json.RawMessage) error { - for name, b := range m.modules { - if mod, ok := b.(appmodule.HasGenesisBasics); ok { - if err := mod.ValidateGenesis(genesisData[name]); err != nil { - return err - } - } else if mod, ok := b.(appmodulev2.HasGenesis); ok { - if err := mod.ValidateGenesis(genesisData[name]); err != nil { - return err - } - } - } - - return nil -} - -// InitGenesisJSON performs init genesis functionality for modules from genesis data in JSON format -func (m *MM[T]) InitGenesisJSON( - ctx context.Context, - genesisData map[string]json.RawMessage, - txHandler func(json.RawMessage) error, -) error { - m.logger.Info("initializing blockchain state from genesis.json", "order", m.config.InitGenesis) - var seenValUpdates bool - for _, moduleName := range m.config.InitGenesis { - if genesisData[moduleName] == nil { - continue - } - - mod := m.modules[moduleName] - - // we might get an adapted module, a native core API module or a legacy module - switch module := mod.(type) { - case appmodule.HasGenesisAuto: - panic(fmt.Sprintf("module %s isn't server/v2 compatible", moduleName)) - case appmodulev2.GenesisDecoder: // GenesisDecoder needs to supersede HasGenesis and HasABCIGenesis. - genTxs, err := module.DecodeGenesisJSON(genesisData[moduleName]) - if err != nil { - return err - } - for _, jsonTx := range genTxs { - if err := txHandler(jsonTx); err != nil { - return fmt.Errorf("failed to handle genesis transaction: %w", err) - } - } - case appmodulev2.HasGenesis: - m.logger.Debug("running initialization for module", "module", moduleName) - if err := module.InitGenesis(ctx, genesisData[moduleName]); err != nil { - return fmt.Errorf("init module %s: %w", moduleName, err) - } - case appmodulev2.HasABCIGenesis: - m.logger.Debug("running initialization for module", "module", moduleName) - moduleValUpdates, err := module.InitGenesis(ctx, genesisData[moduleName]) - if err != nil { - return err - } - - // use these validator updates if provided, the module manager assumes - // only one module will update the validator set - if len(moduleValUpdates) > 0 { - if seenValUpdates { - return fmt.Errorf("validator InitGenesis updates already set by a previous module: current module %s", moduleName) - } else { - seenValUpdates = true - } - } - } - - } - return nil -} - -// 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 { - modulesToExport = m.config.ExportGenesis - } - // 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 - } - - type ModuleI interface { - ExportGenesis(ctx context.Context) (json.RawMessage, error) - } - - 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 { - moduleI = module.(ModuleI) - } else { - continue - } - - channels[moduleName] = make(chan genesisResult) - go func(moduleI ModuleI, ch chan genesisResult) { - genesisCtx := services.NewGenesisContext(stateFactory()) - err := genesisCtx.Read(ctx, func(ctx context.Context) error { - jm, err := moduleI.ExportGenesis(ctx) - if err != nil { - return err - } - ch <- genesisResult{jm, nil} - return nil - }) - if err != nil { - ch <- genesisResult{nil, err} - } - }(moduleI, channels[moduleName]) - } - - 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.bz - } - - return genesisData, nil -} - -// checkModulesExists verifies that all modules in the list exist in the app -func (m *MM[T]) 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 -func (m *MM[T]) BeginBlock() func(ctx context.Context) error { - return func(ctx context.Context) error { - for _, moduleName := range m.config.BeginBlockers { - if module, ok := m.modules[moduleName].(appmodulev2.HasBeginBlocker); ok { - if err := module.BeginBlock(ctx); err != nil { - return fmt.Errorf("failed to run beginblocker for %s: %w", moduleName, err) - } - } - } - - return nil - } -} - -// hasABCIEndBlock is the legacy EndBlocker implemented by x/staking in the CosmosSDK -type hasABCIEndBlock interface { - EndBlock(context.Context) ([]appmodulev2.ValidatorUpdate, error) -} - -// EndBlock runs the end-block logic of all modules and tx validator updates -func (m *MM[T]) EndBlock() ( - endBlockFunc func(ctx context.Context) error, - valUpdateFunc func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error), -) { - var validatorUpdates []appmodulev2.ValidatorUpdate - endBlockFunc = func(ctx context.Context) error { - for _, moduleName := range m.config.EndBlockers { - if module, ok := m.modules[moduleName].(appmodulev2.HasEndBlocker); ok { - err := module.EndBlock(ctx) - if err != nil { - return fmt.Errorf("failed to run endblock for %s: %w", moduleName, err) - } - } else if module, ok := m.modules[moduleName].(hasABCIEndBlock); ok { // we need to keep this for our module compatibility promise - moduleValUpdates, err := module.EndBlock(ctx) - if err != nil { - return fmt.Errorf("failed to run enblock for %s: %w", moduleName, 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 errors.New("validator end block updates already set by a previous module") - } - - validatorUpdates = append(validatorUpdates, moduleValUpdates...) - } - } - } - - return nil - } - - valUpdateFunc = func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) { - // get validator updates of modules implementing directly the new HasUpdateValidators interface - for _, v := range m.modules { - if module, ok := v.(appmodulev2.HasUpdateValidators); ok { - moduleValUpdates, err := module.UpdateValidators(ctx) - if err != nil { - return nil, err - } - - if len(moduleValUpdates) > 0 { - if len(validatorUpdates) > 0 { - return nil, errors.New("validator end block updates already set by a previous module") - } - - validatorUpdates = append(validatorUpdates, moduleValUpdates...) - } - } - } - - // Reset validatorUpdates - res := validatorUpdates - validatorUpdates = []appmodulev2.ValidatorUpdate{} - - return res, nil - } - - return endBlockFunc, valUpdateFunc -} - -// PreBlocker runs the pre-block logic of all modules -func (m *MM[T]) PreBlocker() func(ctx context.Context, txs []T) error { - return func(ctx context.Context, txs []T) error { - for _, moduleName := range m.config.PreBlockers { - if module, ok := m.modules[moduleName].(appmodulev2.HasPreBlocker); ok { - if err := module.PreBlock(ctx); err != nil { - return fmt.Errorf("failed to run preblock for %s: %w", moduleName, err) - } - } - } - - return nil - } -} - -// TxValidators validates incoming transactions -func (m *MM[T]) TxValidators() func(ctx context.Context, tx T) error { - return func(ctx context.Context, tx T) error { - for _, moduleName := range m.config.TxValidators { - if module, ok := m.modules[moduleName].(appmodulev2.HasTxValidator[T]); ok { - if err := module.TxValidator(ctx, tx); err != nil { - return fmt.Errorf("failed to run tx validator for %s: %w", moduleName, err) - } - } - } - - return nil - } -} - -// RunMigrations performs in-place store migrations for all modules. This -// function MUST be called inside an x/upgrade UpgradeHandler. -// -// Recall that in an upgrade handler, the `fromVM` VersionMap is retrieved from -// x/upgrade's store, and the function needs to return the target VersionMap -// that will in turn be persisted to the x/upgrade's store. In general, -// returning RunMigrations should be enough: -// -// Example: -// -// app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx context.Context, plan upgradetypes.Plan, fromVM appmodule.VersionMap) (appmodule.VersionMap, error) { -// return app.ModuleManager().RunMigrations(ctx, fromVM) -// }) -// -// Internally, RunMigrations will perform the following steps: -// - create an `updatedVM` VersionMap of module with their latest ConsensusVersion -// - if module implements `HasConsensusVersion` interface get the consensus version as `toVersion`, -// if not `toVersion` is set to 0. -// - get `fromVersion` from `fromVM` with module's name. -// - if the module's name exists in `fromVM` map, then run in-place store migrations -// for that module between `fromVersion` and `toVersion`. -// - if the module does not exist in the `fromVM` (which means that it's a new module, -// because it was not in the previous x/upgrade's store), then run -// `InitGenesis` on that module. -// -// - return the `updatedVM` to be persisted in the x/upgrade's store. -// -// Migrations are run in an order defined by `mm.config.OrderMigrations`. -// -// As an app developer, if you wish to skip running InitGenesis for your new -// module "foo", you need to manually pass a `fromVM` argument to this function -// foo's module version set to its latest ConsensusVersion. That way, the diff -// between the function's `fromVM` and `udpatedVM` will be empty, hence not -// running anything for foo. -// -// Example: -// -// app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { -// // Assume "foo" is a new module. -// // `fromVM` is fetched from existing x/upgrade store. Since foo didn't exist -// // before this upgrade, `v, exists := fromVM["foo"]; exists == false`, and RunMigration will by default -// // run InitGenesis on foo. -// // To skip running foo's InitGenesis, you need set `fromVM`'s foo to its latest -// // consensus version: -// fromVM["foo"] = foo.AppModule{}.ConsensusVersion() -// -// return app.ModuleManager().RunMigrations(ctx, fromVM) -// }) -// -// Please also refer to https://docs.cosmos.network/main/core/upgrade for more information. -func (m *MM[T]) RunMigrations(ctx context.Context, fromVM appmodulev2.VersionMap) (appmodulev2.VersionMap, error) { - updatedVM := appmodulev2.VersionMap{} - for _, moduleName := range m.config.OrderMigrations { - module := m.modules[moduleName] - fromVersion, exists := fromVM[moduleName] - toVersion := uint64(0) - if module, ok := module.(appmodulev2.HasConsensusVersion); ok { - toVersion = module.ConsensusVersion() - } - - // We run migration if the module is specified in `fromVM`. - // Otherwise we run InitGenesis. - // - // The module won't exist in the fromVM in two cases: - // 1. A new module is added. In this case we run InitGenesis with an - // empty genesis state. - // 2. An existing chain is upgrading from version < 0.43 to v0.43+ for the first time. - // In this case, all modules have yet to be added to x/upgrade's VersionMap store. - if exists { - m.logger.Info(fmt.Sprintf("migrating module %s from version %d to version %d", moduleName, fromVersion, toVersion)) - if err := m.migrationRegistrar.RunModuleMigrations(ctx, moduleName, fromVersion, toVersion); err != nil { - return nil, err - } - } else { - m.logger.Info(fmt.Sprintf("adding a new module: %s", moduleName)) - if mod, ok := m.modules[moduleName].(appmodule.HasGenesis); ok { - if err := mod.InitGenesis(ctx, mod.DefaultGenesis()); err != nil { - return nil, fmt.Errorf("failed to run InitGenesis for %s: %w", moduleName, err) - } - } - if mod, ok := m.modules[moduleName].(appmodulev2.HasABCIGenesis); ok { - moduleValUpdates, err := mod.InitGenesis(ctx, mod.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 { - return nil, errors.New("validator InitGenesis update is already set by another module") - } - } - } - - updatedVM[moduleName] = toVersion - } - - return updatedVM, nil -} - -// RegisterServices registers all module services. -func (m *MM[T]) RegisterServices(app *App[T]) error { - for _, module := range m.modules { - // register msg + query - if err := registerServices(module, app, protoregistry.GlobalFiles); err != nil { - return err - } - - // register migrations - if module, ok := module.(appmodulev2.HasMigrations); ok { - if err := module.RegisterMigrations(m.migrationRegistrar); err != nil { - return err - } - } - - // register pre and post msg - if module, ok := module.(appmodulev2.HasPreMsgHandlers); ok { - module.RegisterPreMsgHandlers(app.msgRouterBuilder) - } - - if module, ok := module.(appmodulev2.HasPostMsgHandlers); ok { - module.RegisterPostMsgHandlers(app.msgRouterBuilder) - } - } - - return nil -} - -// validateConfig validates the module manager configuration -// it asserts that all modules are defined in the configuration and that no modules are forgotten -func (m *MM[T]) validateConfig() error { - if err := m.assertNoForgottenModules("PreBlockers", m.config.PreBlockers, func(moduleName string) bool { - module := m.modules[moduleName] - _, hasPreBlock := module.(appmodulev2.HasPreBlocker) - return !hasPreBlock - }); err != nil { - return err - } - - if err := m.assertNoForgottenModules("BeginBlockers", m.config.BeginBlockers, func(moduleName string) bool { - module := m.modules[moduleName] - _, hasBeginBlock := module.(appmodulev2.HasBeginBlocker) - return !hasBeginBlock - }); err != nil { - return err - } - - if err := m.assertNoForgottenModules("EndBlockers", m.config.EndBlockers, func(moduleName string) bool { - module := m.modules[moduleName] - if _, hasEndBlock := module.(appmodulev2.HasEndBlocker); hasEndBlock { - return !hasEndBlock - } - - _, hasABCIEndBlock := module.(hasABCIEndBlock) - return !hasABCIEndBlock - }); err != nil { - return err - } - - if err := m.assertNoForgottenModules("TxValidators", m.config.TxValidators, func(moduleName string) bool { - module := m.modules[moduleName] - _, hasTxValidator := module.(appmodulev2.HasTxValidator[T]) - return !hasTxValidator - }); err != nil { - return err - } - - if err := m.assertNoForgottenModules("InitGenesis", m.config.InitGenesis, func(moduleName string) bool { - module := m.modules[moduleName] - if _, hasGenesis := module.(appmodule.HasGenesisAuto); hasGenesis { - panic(fmt.Sprintf("module %s isn't server/v2 compatible", moduleName)) - } - - if _, hasGenesis := module.(appmodulev2.HasGenesis); hasGenesis { - return !hasGenesis - } - - _, hasABCIGenesis := module.(appmodulev2.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.(appmodule.HasGenesisAuto); hasGenesis { - panic(fmt.Sprintf("module %s isn't server/v2 compatible", moduleName)) - } - - if _, hasGenesis := module.(appmodulev2.HasGenesis); hasGenesis { - return !hasGenesis - } - - _, hasABCIGenesis := module.(appmodulev2.HasABCIGenesis) - return !hasABCIGenesis - }); err != nil { - return err - } - - if err := m.assertNoForgottenModules("OrderMigrations", m.config.OrderMigrations, nil); err != nil { - return err - } - - return nil -} - -// assertNoForgottenModules checks that we didn't forget any modules in the *runtimev2.Module config. -// `pass` is a closure which allows one to omit modules from `moduleNames`. -// If you provide non-nil `pass` and it returns true, the module would not be subject of the assertion. -func (m *MM[T]) assertNoForgottenModules( - setOrderFnName string, - moduleNames []string, - pass func(moduleName string) bool, -) error { - ms := make(map[string]bool) - for _, m := range moduleNames { - ms[m] = true - } - var missing []string - for m := range m.modules { - if pass != nil && pass(m) { - continue - } - - if !ms[m] { - missing = append(missing, m) - } - } - - if len(missing) != 0 { - sort.Strings(missing) - return fmt.Errorf("all modules must be defined when setting %s, missing: %v", setOrderFnName, missing) - } - - return nil -} - -func registerServices[T transaction.Tx](s appmodulev2.AppModule, app *App[T], registry *protoregistry.Files) error { - // case module with services - if services, ok := s.(hasServicesV1); ok { - c := &configurator{ - queryHandlers: map[string]appmodulev2.Handler{}, - stfQueryRouter: app.queryRouterBuilder, - stfMsgRouter: app.msgRouterBuilder, - registry: registry, - err: nil, - } - if err := services.RegisterServices(c); err != nil { - return fmt.Errorf("unable to register services: %w", err) - } - - if c.err != nil { - app.logger.Warn("error registering services", "error", c.err) - } - - // merge maps - for path, decoder := range c.queryHandlers { - app.queryHandlers[path] = decoder - } - } - - // if module implements register msg handlers - if module, ok := s.(appmodulev2.HasMsgHandlers); ok { - wrapper := stfRouterWrapper{stfRouter: app.msgRouterBuilder} - module.RegisterMsgHandlers(&wrapper) - if wrapper.error != nil { - return fmt.Errorf("unable to register handlers: %w", wrapper.error) - } - } - - // if module implements register query handlers - if module, ok := s.(appmodulev2.HasQueryHandlers); ok { - wrapper := stfRouterWrapper{stfRouter: app.queryRouterBuilder} - module.RegisterQueryHandlers(&wrapper) - - for path, handler := range wrapper.handlers { - app.queryHandlers[path] = handler - } - } - - return nil -} - -var _ grpc.ServiceRegistrar = (*configurator)(nil) - -type configurator struct { - queryHandlers map[string]appmodulev2.Handler - - stfQueryRouter *stf.MsgRouterBuilder - stfMsgRouter *stf.MsgRouterBuilder - registry *protoregistry.Files - err error -} - -func (c *configurator) RegisterService(sd *grpc.ServiceDesc, ss interface{}) { - // first we check if it's a msg server - prefSd, err := c.registry.FindDescriptorByName(protoreflect.FullName(sd.ServiceName)) - if err != nil { - c.err = fmt.Errorf("register service: unable to find protov2 service descriptor: please make sure protov2 API counterparty is imported: %s", sd.ServiceName) - return - } - - if !proto.HasExtension(prefSd.(protoreflect.ServiceDescriptor).Options(), cosmosmsg.E_Service) { - err = c.registerQueryHandlers(sd, ss) - if err != nil { - c.err = err - } - } else { - err = c.registerMsgHandlers(sd, ss) - if err != nil { - c.err = err - } - } -} - -func (c *configurator) registerQueryHandlers(sd *grpc.ServiceDesc, ss interface{}) error { - for _, md := range sd.Methods { - // TODO(tip): what if a query is not deterministic? - - handler, err := grpcHandlerToAppModuleHandler(sd, md, ss) - if err != nil { - return fmt.Errorf("unable to make a appmodulev2.HandlerFunc from gRPC handler (%s, %s): %w", sd.ServiceName, md.MethodName, err) - } - - // register to stf query router. - err = c.stfQueryRouter.RegisterHandler(gogoproto.MessageName(handler.MakeMsg()), handler.Func) - if err != nil { - return fmt.Errorf("unable to register handler to stf router (%s, %s): %w", sd.ServiceName, md.MethodName, err) - } - - // register query handler using the same mapping used in stf - c.queryHandlers[gogoproto.MessageName(handler.MakeMsg())] = handler - } - return nil -} - -func (c *configurator) registerMsgHandlers(sd *grpc.ServiceDesc, ss interface{}) error { - for _, md := range sd.Methods { - handler, err := grpcHandlerToAppModuleHandler(sd, md, ss) - if err != nil { - return err - } - err = c.stfMsgRouter.RegisterHandler(gogoproto.MessageName(handler.MakeMsg()), handler.Func) - if err != nil { - return fmt.Errorf("unable to register msg handler %s.%s: %w", sd.ServiceName, md.MethodName, err) - } - } - return nil -} - -// grpcHandlerToAppModuleHandler converts a gRPC handler into an appmodulev2.HandlerFunc. -func grpcHandlerToAppModuleHandler( - sd *grpc.ServiceDesc, - md grpc.MethodDesc, - ss interface{}, -) (appmodulev2.Handler, error) { - requestName, responseName, err := requestFullNameFromMethodDesc(sd, md) - if err != nil { - return appmodulev2.Handler{}, err - } - - requestTyp := gogoproto.MessageType(string(requestName)) - if requestTyp == nil { - return appmodulev2.Handler{}, fmt.Errorf("no proto message found for %s", requestName) - } - responseTyp := gogoproto.MessageType(string(responseName)) - if responseTyp == nil { - return appmodulev2.Handler{}, fmt.Errorf("no proto message found for %s", responseName) - } - - handlerFunc := func( - ctx context.Context, - msg transaction.Msg, - ) (resp transaction.Msg, err error) { - res, err := md.Handler(ss, ctx, noopDecoder, messagePassingInterceptor(msg)) - if err != nil { - return nil, err - } - return res.(transaction.Msg), nil - } - - return appmodulev2.Handler{ - Func: handlerFunc, - MakeMsg: func() transaction.Msg { - return reflect.New(requestTyp.Elem()).Interface().(transaction.Msg) - }, - MakeMsgResp: func() transaction.Msg { - return reflect.New(responseTyp.Elem()).Interface().(transaction.Msg) - }, - }, nil -} - -func noopDecoder(_ interface{}) error { return nil } - -func messagePassingInterceptor(msg transaction.Msg) grpc.UnaryServerInterceptor { - return func( - ctx context.Context, - req interface{}, - _ *grpc.UnaryServerInfo, - handler grpc.UnaryHandler, - ) (interface{}, error) { - return handler(ctx, msg) - } -} - -// requestFullNameFromMethodDesc returns the fully-qualified name of the request message and response of the provided service's method. -func requestFullNameFromMethodDesc(sd *grpc.ServiceDesc, method grpc.MethodDesc) ( - protoreflect.FullName, protoreflect.FullName, error, -) { - methodFullName := protoreflect.FullName(fmt.Sprintf("%s.%s", sd.ServiceName, method.MethodName)) - desc, err := gogoproto.HybridResolver.FindDescriptorByName(methodFullName) - if err != nil { - return "", "", fmt.Errorf("cannot find method descriptor %s", methodFullName) - } - methodDesc, ok := desc.(protoreflect.MethodDescriptor) - if !ok { - return "", "", fmt.Errorf("invalid method descriptor %s", methodFullName) - } - return methodDesc.Input().FullName(), methodDesc.Output().FullName(), nil -} - -// defaultMigrationsOrder returns a default migrations order: ascending alphabetical by module name, -// except x/auth which will run last, see: -// https://github.com/cosmos/cosmos-sdk/issues/10591 -func defaultMigrationsOrder(modules []string) []string { - const authName = "auth" - out := make([]string, 0, len(modules)) - hasAuth := false - for _, m := range modules { - if m == authName { - hasAuth = true - } else { - out = append(out, m) - } - } - sort.Strings(out) - if hasAuth { - out = append(out, authName) - } - return out -} - -// hasServicesV1 is the interface for registering service in baseapp Cosmos SDK. -// This API is part of core/appmodule but commented out for dependencies. -type hasServicesV1 interface { - RegisterServices(grpc.ServiceRegistrar) error -} - -var _ appmodulev2.MsgRouter = (*stfRouterWrapper)(nil) - -// stfRouterWrapper wraps the stf router and implements the core appmodulev2.MsgRouter -// interface. -// The difference between this type and stf router is that the stf router expects -// us to provide it the msg name, but the core router interface does not have -// such requirement. -type stfRouterWrapper struct { - stfRouter *stf.MsgRouterBuilder - - error error - - handlers map[string]appmodulev2.Handler -} - -func (s *stfRouterWrapper) RegisterHandler(handler appmodulev2.Handler) { - req := handler.MakeMsg() - requestName := gogoproto.MessageName(req) - if requestName == "" { - s.error = errors.Join(s.error, fmt.Errorf("unable to extract request name for type: %T", req)) - } - - // register handler to stf router - err := s.stfRouter.RegisterHandler(requestName, handler.Func) - s.error = errors.Join(s.error, err) - - // also make the decoder - if s.error == nil { - s.handlers = map[string]appmodulev2.Handler{} - } - s.handlers[requestName] = handler -} diff --git a/runtime/v2/module.go b/runtime/v2/module.go deleted file mode 100644 index 54d77dc2742f..000000000000 --- a/runtime/v2/module.go +++ /dev/null @@ -1,257 +0,0 @@ -package runtime - -import ( - "fmt" - "os" - "slices" - - "github.com/cosmos/gogoproto/proto" - "google.golang.org/grpc" - "google.golang.org/protobuf/reflect/protodesc" - "google.golang.org/protobuf/reflect/protoregistry" - - runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" - appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" - autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" - reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" - appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/comet" - "cosmossdk.io/core/event" - "cosmossdk.io/core/header" - "cosmossdk.io/core/registry" - "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" - "cosmossdk.io/depinject" - "cosmossdk.io/depinject/appconfig" - "cosmossdk.io/log" - "cosmossdk.io/runtime/v2/services" - "cosmossdk.io/server/v2/stf" - "cosmossdk.io/store/v2/root" -) - -var ( - _ appmodulev2.AppModule = appModule[transaction.Tx]{} - _ hasServicesV1 = appModule[transaction.Tx]{} -) - -type appModule[T transaction.Tx] struct { - app *App[T] -} - -func (m appModule[T]) IsOnePerModuleType() {} -func (m appModule[T]) IsAppModule() {} - -func (m appModule[T]) RegisterServices(registrar grpc.ServiceRegistrar) error { - autoCliQueryService, err := services.NewAutoCLIQueryService(m.app.moduleManager.modules) - if err != nil { - return err - } - - autocliv1.RegisterQueryServer(registrar, autoCliQueryService) - - reflectionSvc, err := services.NewReflectionService() - if err != nil { - return err - } - reflectionv1.RegisterReflectionServiceServer(registrar, reflectionSvc) - - return nil -} - -func (m appModule[T]) AutoCLIOptions() *autocliv1.ModuleOptions { - return &autocliv1.ModuleOptions{ - Query: &autocliv1.ServiceCommandDescriptor{ - Service: appv1alpha1.Query_ServiceDesc.ServiceName, - RpcCommandOptions: []*autocliv1.RpcCommandOptions{ - { - RpcMethod: "Config", - Short: "Query the current app config", - }, - }, - SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{ - "autocli": { - Service: autocliv1.Query_ServiceDesc.ServiceName, - RpcCommandOptions: []*autocliv1.RpcCommandOptions{ - { - RpcMethod: "AppOptions", - Short: "Query the custom autocli options", - }, - }, - }, - "reflection": { - Service: reflectionv1.ReflectionService_ServiceDesc.ServiceName, - RpcCommandOptions: []*autocliv1.RpcCommandOptions{ - { - RpcMethod: "FileDescriptors", - Short: "Query the app's protobuf file descriptors", - }, - }, - }, - }, - }, - } -} - -func init() { - appconfig.Register(&runtimev2.Module{}, - appconfig.Provide( - ProvideAppBuilder[transaction.Tx], - ProvideModuleManager[transaction.Tx], - ProvideEnvironment, - ProvideKVService, - ), - appconfig.Invoke(SetupAppBuilder), - ) -} - -func ProvideAppBuilder[T transaction.Tx]( - interfaceRegistrar registry.InterfaceRegistrar, - amino registry.AminoRegistrar, - storeBuilder root.Builder, -) ( - *AppBuilder[T], - *stf.MsgRouterBuilder, - appmodulev2.AppModule, - protodesc.Resolver, - protoregistry.MessageTypeResolver, -) { - protoFiles := proto.HybridResolver - protoTypes := protoregistry.GlobalTypes - - // At startup, check that all proto annotations are correct. - if err := validateProtoAnnotations(protoFiles); err != nil { - // Once we switch to using protoreflect-based ante handlers, we might - // want to panic here instead of logging a warning. - _, _ = fmt.Fprintln(os.Stderr, err.Error()) - } - - msgRouterBuilder := stf.NewMsgRouterBuilder() - app := &App[T]{ - interfaceRegistrar: interfaceRegistrar, - amino: amino, - msgRouterBuilder: msgRouterBuilder, - queryRouterBuilder: stf.NewMsgRouterBuilder(), // TODO dedicated query router - queryHandlers: map[string]appmodulev2.Handler{}, - storeLoader: DefaultStoreLoader, - } - appBuilder := &AppBuilder[T]{app: app, storeBuilder: storeBuilder} - - return appBuilder, msgRouterBuilder, appModule[T]{app}, protoFiles, protoTypes -} - -type AppInputs struct { - depinject.In - - Config *runtimev2.Module - AppBuilder *AppBuilder[transaction.Tx] - ModuleManager *MM[transaction.Tx] - InterfaceRegistrar registry.InterfaceRegistrar - LegacyAmino registry.AminoRegistrar - Logger log.Logger - StoreBuilder root.Builder -} - -func SetupAppBuilder(inputs AppInputs) { - app := inputs.AppBuilder.app - app.config = inputs.Config - app.logger = inputs.Logger - app.moduleManager = inputs.ModuleManager - app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar) - app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino) - // STF requires some state to run - inputs.StoreBuilder.RegisterKey("stf") -} - -func ProvideModuleManager[T transaction.Tx]( - logger log.Logger, - config *runtimev2.Module, - modules map[string]appmodulev2.AppModule, -) *MM[T] { - return NewModuleManager[T](logger, config, modules) -} - -func ProvideKVService( - config *runtimev2.Module, - key depinject.ModuleKey, - kvFactory store.KVStoreServiceFactory, - storeBuilder root.Builder, -) (store.KVStoreService, store.MemoryStoreService) { - // skips modules that have no store - if slices.Contains(config.SkipStoreKeys, key.Name()) { - return &failingStoreService{}, &failingStoreService{} - } - var kvStoreKey string - override := storeKeyOverride(config, key.Name()) - if override != nil { - kvStoreKey = override.KvStoreKey - } else { - kvStoreKey = key.Name() - } - - storeBuilder.RegisterKey(kvStoreKey) - return kvFactory([]byte(kvStoreKey)), stf.NewMemoryStoreService([]byte(fmt.Sprintf("memory:%s", kvStoreKey))) -} - -func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig { - for _, cfg := range config.OverrideStoreKeys { - if cfg.ModuleName == moduleName { - return cfg - } - } - return nil -} - -// ProvideEnvironment provides the environment for keeper modules, while maintaining backward compatibility and provide services directly as well. -func ProvideEnvironment( - logger log.Logger, - key depinject.ModuleKey, - kvService store.KVStoreService, - memKvService store.MemoryStoreService, - headerService header.Service, - eventService event.Service, -) appmodulev2.Environment { - return appmodulev2.Environment{ - Logger: logger, - BranchService: stf.BranchService{}, - EventService: eventService, - GasService: stf.NewGasMeterService(), - HeaderService: headerService, - QueryRouterService: stf.NewQueryRouterService(), - MsgRouterService: stf.NewMsgRouterService([]byte(key.Name())), - TransactionService: services.NewContextAwareTransactionService(), - KVStoreService: kvService, - MemStoreService: memKvService, - } -} - -// DefaultServiceBindings provides default services for the following service interfaces: -// - store.KVStoreServiceFactory -// - header.Service -// - comet.Service -// - event.Service -// - store/v2/root.Builder -// -// They are all required. For most use cases these default services bindings should be sufficient. -// Power users (or tests) may wish to provide their own services bindings, in which case they must -// supply implementations for each of the above interfaces. -func DefaultServiceBindings() depinject.Config { - var ( - kvServiceFactory store.KVStoreServiceFactory = func(actor []byte) store.KVStoreService { - return services.NewGenesisKVService( - actor, - stf.NewKVStoreService(actor), - ) - } - cometService comet.Service = &services.ContextAwareCometInfoService{} - headerService = services.NewGenesisHeaderService(stf.HeaderService{}) - eventService = services.NewGenesisEventService(stf.NewEventService()) - storeBuilder = root.NewBuilder() - ) - return depinject.Supply( - kvServiceFactory, - headerService, - cometService, - eventService, - storeBuilder, - ) -} diff --git a/server/v2/api/grpc/server.go b/server/v2/api/grpc/server.go deleted file mode 100644 index 10e22a514a1e..000000000000 --- a/server/v2/api/grpc/server.go +++ /dev/null @@ -1,221 +0,0 @@ -package grpc - -import ( - "context" - "errors" - "fmt" - "io" - "maps" - "net" - "slices" - "strconv" - "strings" - "sync" - - gogoproto "github.com/cosmos/gogoproto/proto" - "github.com/spf13/pflag" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/reflect/protoreflect" - - appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - serverv2 "cosmossdk.io/server/v2" - "cosmossdk.io/server/v2/api/grpc/gogoreflection" -) - -const ( - ServerName = "grpc" - - BlockHeightHeader = "x-cosmos-block-height" -) - -type Server[T transaction.Tx] struct { - logger log.Logger - config *Config - cfgOptions []CfgOption - - grpcSrv *grpc.Server -} - -// New creates a new grpc server. -func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] { - return &Server[T]{ - cfgOptions: cfgOptions, - } -} - -// Init returns a correctly configured and initialized gRPC server. -// Note, the caller is responsible for starting the server. -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - serverCfg := s.Config().(*Config) - if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } - } - methodsMap := appI.QueryHandlers() - - grpcSrv := grpc.NewServer( - grpc.ForceServerCodec(newProtoCodec(appI.InterfaceRegistry()).GRPCCodec()), - grpc.MaxSendMsgSize(serverCfg.MaxSendMsgSize), - grpc.MaxRecvMsgSize(serverCfg.MaxRecvMsgSize), - grpc.UnknownServiceHandler( - makeUnknownServiceHandler(methodsMap, appI), - ), - ) - - // Reflection allows external clients to see what services and methods the gRPC server exposes. - gogoreflection.Register(grpcSrv, slices.Collect(maps.Keys(methodsMap)), logger.With("sub-module", "grpc-reflection")) - - s.grpcSrv = grpcSrv - s.config = serverCfg - s.logger = logger.With(log.ModuleKey, s.Name()) - - return nil -} - -func (s *Server[T]) StartCmdFlags() *pflag.FlagSet { - flags := pflag.NewFlagSet(s.Name(), pflag.ExitOnError) - flags.String(FlagAddress, "localhost:9090", "Listen address") - return flags -} - -func makeUnknownServiceHandler(handlers map[string]appmodulev2.Handler, querier interface { - Query(ctx context.Context, version uint64, msg transaction.Msg) (transaction.Msg, error) -}, -) grpc.StreamHandler { - getRegistry := sync.OnceValues(gogoproto.MergedRegistry) - - return func(srv any, stream grpc.ServerStream) error { - method, ok := grpc.MethodFromServerStream(stream) - if !ok { - return status.Error(codes.InvalidArgument, "unable to get method") - } - // if this fails we cannot serve queries anymore... - registry, err := getRegistry() - if err != nil { - return fmt.Errorf("failed to get registry: %w", err) - } - - method = strings.TrimPrefix(method, "/") - fullName := protoreflect.FullName(strings.ReplaceAll(method, "/", ".")) - // get descriptor from the invoke method - desc, err := registry.FindDescriptorByName(fullName) - if err != nil { - return fmt.Errorf("failed to find descriptor %s: %w", method, err) - } - md, ok := desc.(protoreflect.MethodDescriptor) - if !ok { - return fmt.Errorf("%s is not a method", method) - } - // find handler - handler, exists := handlers[string(md.Input().FullName())] - if !exists { - return status.Errorf(codes.Unimplemented, "gRPC method %s is not handled", method) - } - - for { - req := handler.MakeMsg() - err := stream.RecvMsg(req) - if err != nil { - if errors.Is(err, io.EOF) { - return nil - } - return err - } - - // extract height header - ctx := stream.Context() - height, err := getHeightFromCtx(ctx) - if err != nil { - return status.Errorf(codes.InvalidArgument, "invalid get height from context: %v", err) - } - resp, err := querier.Query(ctx, height, req) - if err != nil { - return err - } - err = stream.SendMsg(resp) - if err != nil { - return err - } - } - } -} - -func getHeightFromCtx(ctx context.Context) (uint64, error) { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return 0, nil - } - values := md.Get(BlockHeightHeader) - if len(values) == 0 { - return 0, nil - } - if len(values) != 1 { - return 0, fmt.Errorf("gRPC height metadata must be of length 1, got: %d", len(values)) - } - - heightStr := values[0] - height, err := strconv.ParseUint(heightStr, 10, 64) - if err != nil { - return 0, fmt.Errorf("unable to parse height string from gRPC metadata %s: %w", heightStr, err) - } - - return height, nil -} - -func (s *Server[T]) Name() string { - return ServerName -} - -func (s *Server[T]) Config() any { - if s.config == nil || s.config.Address == "" { - cfg := DefaultConfig() - // overwrite the default config with the provided options - for _, opt := range s.cfgOptions { - opt(cfg) - } - - return cfg - } - - return s.config -} - -func (s *Server[T]) Start(ctx context.Context) error { - if !s.config.Enable { - s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) - return nil - } - - listener, err := net.Listen("tcp", s.config.Address) - if err != nil { - return fmt.Errorf("failed to listen on address %s: %w", s.config.Address, err) - } - - s.logger.Info("starting gRPC server...", "address", s.config.Address) - if err := s.grpcSrv.Serve(listener); err != nil { - return fmt.Errorf("failed to start gRPC server: %w", err) - } - - return nil -} - -func (s *Server[T]) Stop(ctx context.Context) error { - if !s.config.Enable { - return nil - } - - s.logger.Info("stopping gRPC server...", "address", s.config.Address) - s.grpcSrv.GracefulStop() - return nil -} - -// GetGRPCServer returns the underlying gRPC server. -func (s *Server[T]) GetGRPCServer() *grpc.Server { - return s.grpcSrv -} diff --git a/server/v2/api/rest/handler.go b/server/v2/api/rest/handler.go deleted file mode 100644 index 8159e23ba3bb..000000000000 --- a/server/v2/api/rest/handler.go +++ /dev/null @@ -1,99 +0,0 @@ -package rest - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "reflect" - "strings" - - "github.com/cosmos/gogoproto/jsonpb" - gogoproto "github.com/cosmos/gogoproto/proto" - - "cosmossdk.io/core/transaction" - "cosmossdk.io/server/v2/appmanager" -) - -const ( - ContentTypeJSON = "application/json" - MaxBodySize = 1 << 20 // 1 MB -) - -func NewDefaultHandler[T transaction.Tx](appManager appmanager.AppManager[T]) http.Handler { - return &DefaultHandler[T]{appManager: appManager} -} - -type DefaultHandler[T transaction.Tx] struct { - appManager appmanager.AppManager[T] -} - -func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if err := h.validateMethodIsPOST(r); err != nil { - http.Error(w, err.Error(), http.StatusMethodNotAllowed) - return - } - - if err := h.validateContentTypeIsJSON(r); err != nil { - http.Error(w, err.Error(), http.StatusUnsupportedMediaType) - return - } - - msg, err := h.createMessage(r) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - query, err := h.appManager.Query(r.Context(), 0, msg) - if err != nil { - http.Error(w, "Error querying", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", ContentTypeJSON) - if err := json.NewEncoder(w).Encode(query); err != nil { - http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError) - } -} - -// validateMethodIsPOST validates that the request method is POST. -func (h *DefaultHandler[T]) validateMethodIsPOST(r *http.Request) error { - if r.Method != http.MethodPost { - return fmt.Errorf("method not allowed") - } - return nil -} - -// validateContentTypeIsJSON validates that the request content type is JSON. -func (h *DefaultHandler[T]) validateContentTypeIsJSON(r *http.Request) error { - contentType := r.Header.Get("Content-Type") - if contentType != ContentTypeJSON { - return fmt.Errorf("unsupported content type, expected %s", ContentTypeJSON) - } - - return nil -} - -// createMessage creates the message by unmarshalling the request body. -func (h *DefaultHandler[T]) createMessage(r *http.Request) (gogoproto.Message, error) { - path := strings.TrimPrefix(r.URL.Path, "/") - requestType := gogoproto.MessageType(path) - if requestType == nil { - return nil, fmt.Errorf("unknown request type") - } - - msg, ok := reflect.New(requestType.Elem()).Interface().(gogoproto.Message) - if !ok { - return nil, fmt.Errorf("failed to create message instance") - } - - defer r.Body.Close() - limitedReader := io.LimitReader(r.Body, MaxBodySize) - err := jsonpb.Unmarshal(limitedReader, msg) - if err != nil { - return nil, fmt.Errorf("error parsing body: %w", err) - } - - return msg, nil -} diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go deleted file mode 100644 index 0f2b1777973a..000000000000 --- a/server/v2/api/rest/server.go +++ /dev/null @@ -1,96 +0,0 @@ -package rest - -import ( - "context" - "errors" - "fmt" - "net/http" - - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - serverv2 "cosmossdk.io/server/v2" -) - -const ( - ServerName = "rest" -) - -type Server[T transaction.Tx] struct { - logger log.Logger - router *http.ServeMux - - httpServer *http.Server - config *Config - cfgOptions []CfgOption -} - -func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] { - return &Server[T]{ - cfgOptions: cfgOptions, - } -} - -func (s *Server[T]) Name() string { - return ServerName -} - -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - s.logger = logger.With(log.ModuleKey, s.Name()) - - serverCfg := s.Config().(*Config) - if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } - } - - s.router = http.NewServeMux() - s.router.Handle("/", NewDefaultHandler(appI)) - s.config = serverCfg - - return nil -} - -func (s *Server[T]) Start(ctx context.Context) error { - if !s.config.Enable { - s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) - return nil - } - - s.httpServer = &http.Server{ - Addr: s.config.Address, - Handler: s.router, - } - - s.logger.Info("starting HTTP server", "address", s.config.Address) - if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("failed to start HTTP server", "error", err) - return err - } - - return nil -} - -func (s *Server[T]) Stop(ctx context.Context) error { - if !s.config.Enable { - return nil - } - - s.logger.Info("stopping HTTP server") - - return s.httpServer.Shutdown(ctx) -} - -func (s *Server[T]) Config() any { - if s.config == nil || s.config.Address == "" { - cfg := DefaultConfig() - - for _, opt := range s.cfgOptions { - opt(cfg) - } - - return cfg - } - - return s.config -} diff --git a/server/v2/appmanager/appmanager.go b/server/v2/appmanager/appmanager.go deleted file mode 100644 index af54936ebf03..000000000000 --- a/server/v2/appmanager/appmanager.go +++ /dev/null @@ -1,221 +0,0 @@ -package appmanager - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - - "cosmossdk.io/core/server" - corestore "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" -) - -// AppManager is a coordinator for all things related to an application -// It is responsible for interacting with stf and store. -// Runtime/v2 is an extension of this interface. -type AppManager[T transaction.Tx] interface { - // InitGenesis initializes the genesis state of the application. - InitGenesis( - ctx context.Context, - blockRequest *server.BlockRequest[T], - initGenesisJSON []byte, - txDecoder transaction.Codec[T], - ) (*server.BlockResponse, corestore.WriterMap, error) - - // ExportGenesis exports the genesis state of the application. - ExportGenesis(ctx context.Context, version uint64) ([]byte, error) - - // DeliverBlock executes a block of transactions. - DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], - ) (*server.BlockResponse, corestore.WriterMap, error) - - // ValidateTx will validate the tx against the latest storage state. This means that - // only the stateful validation will be run, not the execution portion of the tx. - // If full execution is needed, Simulate must be used. - ValidateTx(ctx context.Context, tx T) (server.TxResult, error) - - // Simulate runs validation and execution flow of a Tx. - Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) - - // Query queries the application at the provided version. - // CONTRACT: Version must always be provided, if 0, get latest - Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) - - // QueryWithState executes a query with the provided state. This allows to process a query - // independently of the db state. For example, it can be used to process a query with temporary - // and uncommitted state - QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) -} - -// Store defines the underlying storage behavior needed by AppManager. -type Store interface { - // StateLatest returns a readonly view over the latest - // committed state of the store. Alongside the version - // associated with it. - StateLatest() (uint64, corestore.ReaderMap, error) - - // StateAt returns a readonly view over the provided - // state. Must error when the version does not exist. - StateAt(version uint64) (corestore.ReaderMap, error) -} - -// appManager is a coordinator for all things related to an application -type appManager[T transaction.Tx] struct { - // Gas limits for validating, querying, and simulating transactions. - config Config - // InitGenesis is a function that initializes the application state from a genesis file. - // It takes a context, a source reader for the genesis file, and a transaction handler function. - initGenesis InitGenesis - // ExportGenesis is a function that exports the application state to a genesis file. - // It takes a context and a version number for the genesis file. - exportGenesis ExportGenesis - // The database for storing application data. - db Store - // The state transition function for processing transactions. - stf StateTransitionFunction[T] -} - -func New[T transaction.Tx]( - config Config, - db Store, - stf StateTransitionFunction[T], - initGenesisImpl InitGenesis, - exportGenesisImpl ExportGenesis, -) AppManager[T] { - return &appManager[T]{ - config: config, - db: db, - stf: stf, - initGenesis: initGenesisImpl, - exportGenesis: exportGenesisImpl, - } -} - -// InitGenesis initializes the genesis state of the application. -func (a appManager[T]) InitGenesis( - ctx context.Context, - blockRequest *server.BlockRequest[T], - initGenesisJSON []byte, - txDecoder transaction.Codec[T], -) (*server.BlockResponse, corestore.WriterMap, error) { - var genTxs []T - genesisState, err := a.initGenesis( - ctx, - bytes.NewBuffer(initGenesisJSON), - func(jsonTx json.RawMessage) error { - genTx, err := txDecoder.DecodeJSON(jsonTx) - if err != nil { - return fmt.Errorf("failed to decode genesis transaction: %w", err) - } - genTxs = append(genTxs, genTx) - return nil - }, - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to import genesis state: %w", err) - } - // run block - blockRequest.Txs = genTxs - - blockResponse, blockZeroState, err := a.stf.DeliverBlock(ctx, blockRequest, genesisState) - if err != nil { - return blockResponse, nil, fmt.Errorf("failed to deliver block %d: %w", blockRequest.Height, err) - } - - // after executing block 0, we extract the changes and apply them to the genesis state. - stateChanges, err := blockZeroState.GetStateChanges() - if err != nil { - return nil, nil, fmt.Errorf("failed to get block zero state changes: %w", err) - } - - err = genesisState.ApplyStateChanges(stateChanges) - if err != nil { - return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err) - } - - return blockResponse, genesisState, err -} - -// ExportGenesis exports the genesis state of the application. -func (a appManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) { - if a.exportGenesis == nil { - return nil, errors.New("export genesis function not set") - } - - return a.exportGenesis(ctx, version) -} - -// DeliverBlock executes a block of transactions. -func (a appManager[T]) DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], -) (*server.BlockResponse, corestore.WriterMap, error) { - latestVersion, currentState, err := a.db.StateLatest() - if err != nil { - return nil, nil, fmt.Errorf("unable to create new state for height %d: %w", block.Height, err) - } - - if latestVersion+1 != block.Height { - return nil, nil, fmt.Errorf("invalid DeliverBlock height wanted %d, got %d", latestVersion+1, block.Height) - } - - blockResponse, newState, err := a.stf.DeliverBlock(ctx, block, currentState) - if err != nil { - return nil, nil, fmt.Errorf("block delivery failed: %w", err) - } - - return blockResponse, newState, nil -} - -// ValidateTx will validate the tx against the latest storage state. This means that -// only the stateful validation will be run, not the execution portion of the tx. -// If full execution is needed, Simulate must be used. -func (a appManager[T]) ValidateTx(ctx context.Context, tx T) (server.TxResult, error) { - _, latestState, err := a.db.StateLatest() - if err != nil { - return server.TxResult{}, err - } - res := a.stf.ValidateTx(ctx, latestState, a.config.ValidateTxGasLimit, tx) - return res, res.Error -} - -// Simulate runs validation and execution flow of a Tx. -func (a appManager[T]) Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) { - _, state, err := a.db.StateLatest() - if err != nil { - return server.TxResult{}, nil, err - } - result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler - return result, cs, nil -} - -// Query queries the application at the provided version. -// CONTRACT: Version must always be provided, if 0, get latest -func (a appManager[T]) Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) { - // if version is provided attempt to do a height query. - if version != 0 { - queryState, err := a.db.StateAt(version) - if err != nil { - return nil, err - } - return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request) - } - - // otherwise rely on latest available state. - _, queryState, err := a.db.StateLatest() - if err != nil { - return nil, err - } - return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request) -} - -// QueryWithState executes a query with the provided state. This allows to process a query -// independently of the db state. For example, it can be used to process a query with temporary -// and uncommitted state -func (a appManager[T]) QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) { - return a.stf.Query(ctx, state, a.config.QueryGasLimit, request) -} diff --git a/server/v2/appmanager/config.go b/server/v2/appmanager/config.go deleted file mode 100644 index fb3c20341f35..000000000000 --- a/server/v2/appmanager/config.go +++ /dev/null @@ -1,8 +0,0 @@ -package appmanager - -// Config represents the configuration options for the app manager. -type Config struct { - ValidateTxGasLimit uint64 `mapstructure:"validate-tx-gas-limit"` // TODO: check how this works on app mempool - QueryGasLimit uint64 `mapstructure:"query-gas-limit"` - SimulationGasLimit uint64 `mapstructure:"simulation-gas-limit"` -} diff --git a/server/v2/appmanager/genesis.go b/server/v2/appmanager/genesis.go deleted file mode 100644 index 347d0f30e07b..000000000000 --- a/server/v2/appmanager/genesis.go +++ /dev/null @@ -1,28 +0,0 @@ -package appmanager - -import ( - "context" - "encoding/json" - "io" - - "cosmossdk.io/core/store" -) - -type ( - // InitGenesis is a function that will run at application genesis, it will be called with - // the following arguments: - // - ctx: the context of the genesis operation - // - src: the source containing the raw genesis state - // - txHandler: a function capable of decoding a json tx, will be run for each genesis - // transaction - // - // It must return a map of the dirty state after the genesis operation. - InitGenesis func( - ctx context.Context, - src io.Reader, - txHandler func(json.RawMessage) error, - ) (store.WriterMap, error) - - // ExportGenesis is a function type that represents the export of the genesis state. - ExportGenesis func(ctx context.Context, version uint64) ([]byte, error) -) diff --git a/server/v2/appmanager/stf.go b/server/v2/appmanager/stf.go deleted file mode 100644 index 1e769c13ff9c..000000000000 --- a/server/v2/appmanager/stf.go +++ /dev/null @@ -1,43 +0,0 @@ -package appmanager - -import ( - "context" - - "cosmossdk.io/core/server" - "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" -) - -// StateTransitionFunction is an interface for processing transactions and blocks. -type StateTransitionFunction[T transaction.Tx] interface { - // DeliverBlock executes a block of transactions. - DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], - state store.ReaderMap, - ) (blockResult *server.BlockResponse, newState store.WriterMap, err error) - - // ValidateTx validates a transaction. - ValidateTx( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - tx T, - ) server.TxResult - - // Simulate executes a transaction in simulation mode. - Simulate( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - tx T, - ) (server.TxResult, store.WriterMap) - - // Query executes a query on the application. - Query( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - req transaction.Msg, - ) (transaction.Msg, error) -} diff --git a/server/v2/server_test.go b/server/v2/server_test.go deleted file mode 100644 index a53b71fc35b9..000000000000 --- a/server/v2/server_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package serverv2_test - -import ( - "context" - "os" - "path/filepath" - "testing" - "time" - - gogoproto "github.com/cosmos/gogoproto/proto" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" - - appmodulev2 "cosmossdk.io/core/appmodule/v2" - coreserver "cosmossdk.io/core/server" - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - serverv2 "cosmossdk.io/server/v2" - grpc "cosmossdk.io/server/v2/api/grpc" - "cosmossdk.io/server/v2/store" - storev2 "cosmossdk.io/store/v2" -) - -type mockInterfaceRegistry struct{} - -func (*mockInterfaceRegistry) Resolve(typeUrl string) (gogoproto.Message, error) { - panic("not implemented") -} - -func (*mockInterfaceRegistry) ListImplementations(ifaceTypeURL string) []string { - panic("not implemented") -} -func (*mockInterfaceRegistry) ListAllInterfaces() []string { panic("not implemented") } - -type mockApp[T transaction.Tx] struct { - serverv2.AppI[T] -} - -func (*mockApp[T]) QueryHandlers() map[string]appmodulev2.Handler { - return map[string]appmodulev2.Handler{} -} - -func (*mockApp[T]) InterfaceRegistry() coreserver.InterfaceRegistry { - return &mockInterfaceRegistry{} -} - -func (*mockApp[T]) Store() storev2.RootStore { - return nil -} - -func TestServer(t *testing.T) { - currentDir, err := os.Getwd() - require.NoError(t, err) - configPath := filepath.Join(currentDir, "testdata") - - v, err := serverv2.ReadConfig(configPath) - if err != nil { - v = viper.New() - } - cfg := v.AllSettings() - - logger := log.NewLogger(os.Stdout) - - ctx, err := serverv2.SetServerContext(context.Background(), v, logger) - require.NoError(t, err) - - grpcServer := grpc.New[transaction.Tx]() - err = grpcServer.Init(&mockApp[transaction.Tx]{}, cfg, logger) - require.NoError(t, err) - - storeServer := store.New[transaction.Tx]() - err = storeServer.Init(&mockApp[transaction.Tx]{}, cfg, logger) - require.NoError(t, err) - - mockServer := &mockServer{name: "mock-server-1", ch: make(chan string, 100)} - - server := serverv2.NewServer( - serverv2.DefaultServerConfig(), - grpcServer, - storeServer, - mockServer, - ) - - serverCfgs := server.Configs() - require.Equal(t, serverCfgs[grpcServer.Name()].(*grpc.Config).Address, grpc.DefaultConfig().Address) - require.Equal(t, serverCfgs[mockServer.Name()].(*mockServerConfig).MockFieldOne, MockServerDefaultConfig().MockFieldOne) - - // write config - err = server.WriteConfig(configPath) - require.NoError(t, err) - - v, err = serverv2.ReadConfig(configPath) - require.NoError(t, err) - - require.Equal(t, v.GetString(grpcServer.Name()+".address"), grpc.DefaultConfig().Address) - - // start empty - ctx, cancelFn := context.WithCancel(ctx) - go func() { - // wait 5sec and cancel context - <-time.After(5 * time.Second) - cancelFn() - - err = server.Stop(ctx) - require.NoError(t, err) - }() - - err = server.Start(ctx) - require.NoError(t, err) -} diff --git a/server/v2/stf/stf.go b/server/v2/stf/stf.go deleted file mode 100644 index 6b6f6b2c53da..000000000000 --- a/server/v2/stf/stf.go +++ /dev/null @@ -1,635 +0,0 @@ -package stf - -import ( - "context" - "errors" - "fmt" - - appmodulev2 "cosmossdk.io/core/appmodule/v2" - corecontext "cosmossdk.io/core/context" - "cosmossdk.io/core/event" - "cosmossdk.io/core/gas" - "cosmossdk.io/core/header" - "cosmossdk.io/core/log" - "cosmossdk.io/core/router" - "cosmossdk.io/core/server" - "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" - "cosmossdk.io/schema/appdata" - stfgas "cosmossdk.io/server/v2/stf/gas" - "cosmossdk.io/server/v2/stf/internal" -) - -type eContextKey struct{} - -var executionContextKey = eContextKey{} - -// STF is a struct that manages the state transition component of the app. -type STF[T transaction.Tx] struct { - logger log.Logger - - msgRouter coreRouterImpl - queryRouter coreRouterImpl - - doPreBlock func(ctx context.Context, txs []T) error - doBeginBlock func(ctx context.Context) error - doEndBlock func(ctx context.Context) error - doValidatorUpdate func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) - - doTxValidation func(ctx context.Context, tx T) error - postTxExec func(ctx context.Context, tx T, success bool) error - - branchFn branchFn // branchFn is a function that given a readonly state it returns a writable version of it. - makeGasMeter makeGasMeterFn - makeGasMeteredState makeGasMeteredStateFn -} - -// New returns a new STF instance. -func New[T transaction.Tx]( - logger log.Logger, - msgRouterBuilder *MsgRouterBuilder, - queryRouterBuilder *MsgRouterBuilder, - doPreBlock func(ctx context.Context, txs []T) error, - doBeginBlock func(ctx context.Context) error, - doEndBlock func(ctx context.Context) error, - doTxValidation func(ctx context.Context, tx T) error, - doValidatorUpdate func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error), - postTxExec func(ctx context.Context, tx T, success bool) error, - branch func(store store.ReaderMap) store.WriterMap, -) (*STF[T], error) { - msgRouter, err := msgRouterBuilder.build() - if err != nil { - return nil, fmt.Errorf("build msg router: %w", err) - } - queryRouter, err := queryRouterBuilder.build() - if err != nil { - return nil, fmt.Errorf("build query router: %w", err) - } - - return &STF[T]{ - logger: logger, - msgRouter: msgRouter, - queryRouter: queryRouter, - doPreBlock: doPreBlock, - doBeginBlock: doBeginBlock, - doEndBlock: doEndBlock, - doValidatorUpdate: doValidatorUpdate, - doTxValidation: doTxValidation, - postTxExec: postTxExec, // TODO - branchFn: branch, - makeGasMeter: stfgas.DefaultGasMeter, - makeGasMeteredState: stfgas.DefaultWrapWithGasMeter, - }, nil -} - -// DeliverBlock is our state transition function. -// It takes a read only view of the state to apply the block to, -// executes the block and returns the block results and the new state. -func (s STF[T]) DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], - state store.ReaderMap, -) (blockResult *server.BlockResponse, newState store.WriterMap, err error) { - // creates a new branchFn state, from the readonly view of the state - // that can be written to. - newState = s.branchFn(state) - hi := header.Info{ - Hash: block.Hash, - AppHash: block.AppHash, - ChainID: block.ChainId, - Time: block.Time, - Height: int64(block.Height), - } - // set header info - err = s.setHeaderInfo(newState, hi) - if err != nil { - return nil, nil, fmt.Errorf("unable to set initial header info, %w", err) - } - - exCtx := s.makeContext(ctx, ConsensusIdentity, newState, internal.ExecModeFinalize) - exCtx.setHeaderInfo(hi) - - // reset events - exCtx.events = make([]event.Event, 0) - // pre block is called separate from begin block in order to prepopulate state - preBlockEvents, err := s.preBlock(exCtx, block.Txs) - if err != nil { - return nil, nil, err - } - - if err = isCtxCancelled(ctx); err != nil { - return nil, nil, err - } - - // reset events - exCtx.events = make([]event.Event, 0) - // begin block - var beginBlockEvents []event.Event - if !block.IsGenesis { - // begin block - beginBlockEvents, err = s.beginBlock(exCtx) - if err != nil { - return nil, nil, err - } - } - - // check if we need to return early - if err = isCtxCancelled(ctx); err != nil { - return nil, nil, err - } - - // execute txs - txResults := make([]server.TxResult, len(block.Txs)) - // TODO: skip first tx if vote extensions are enabled (marko) - for i, txBytes := range block.Txs { - // check if we need to return early or continue delivering txs - if err = isCtxCancelled(ctx); err != nil { - return nil, nil, err - } - txResults[i] = s.deliverTx(exCtx, newState, txBytes, transaction.ExecModeFinalize, hi, int32(i+1)) - } - // reset events - exCtx.events = make([]event.Event, 0) - // end block - endBlockEvents, valset, err := s.endBlock(exCtx) - if err != nil { - return nil, nil, err - } - - return &server.BlockResponse{ - ValidatorUpdates: valset, - PreBlockEvents: preBlockEvents, - BeginBlockEvents: beginBlockEvents, - TxResults: txResults, - EndBlockEvents: endBlockEvents, - }, newState, nil -} - -// deliverTx executes a TX and returns the result. -func (s STF[T]) deliverTx( - ctx context.Context, - state store.WriterMap, - tx T, - execMode transaction.ExecMode, - hi header.Info, - txIndex int32, -) server.TxResult { - // recover in the case of a panic - var recoveryError error - defer func() { - if r := recover(); r != nil { - recoveryError = fmt.Errorf("panic during transaction execution: %s", r) - s.logger.Error("panic during transaction execution", "error", recoveryError) - } - }() - // handle error from GetGasLimit - gasLimit, gasLimitErr := tx.GetGasLimit() - if gasLimitErr != nil { - return server.TxResult{ - Error: gasLimitErr, - } - } - - if recoveryError != nil { - return server.TxResult{ - Error: recoveryError, - } - } - validateGas, validationEvents, err := s.validateTx(ctx, state, gasLimit, tx, execMode) - if err != nil { - return server.TxResult{ - Error: err, - } - } - events := make([]event.Event, 0) - // set the event indexes, set MsgIndex to 0 in validation events - for i, e := range validationEvents { - e.BlockStage = appdata.TxProcessingStage - e.TxIndex = txIndex - e.MsgIndex = 0 - e.EventIndex = int32(i + 1) - events = append(events, e) - } - - execResp, execGas, execEvents, err := s.execTx(ctx, state, gasLimit-validateGas, tx, execMode, hi) - // set the TxIndex in the exec events - for _, e := range execEvents { - e.BlockStage = appdata.TxProcessingStage - e.TxIndex = txIndex - events = append(events, e) - } - - return server.TxResult{ - Events: events, - GasUsed: execGas + validateGas, - GasWanted: gasLimit, - Resp: execResp, - Error: err, - } -} - -// validateTx validates a transaction given the provided WritableState and gas limit. -// If the validation is successful, state is committed -func (s STF[T]) validateTx( - ctx context.Context, - state store.WriterMap, - gasLimit uint64, - tx T, - execMode transaction.ExecMode, -) (gasUsed uint64, events []event.Event, err error) { - validateState := s.branchFn(state) - hi, err := s.getHeaderInfo(validateState) - if err != nil { - return 0, nil, err - } - validateCtx := s.makeContext(ctx, RuntimeIdentity, validateState, execMode) - validateCtx.setHeaderInfo(hi) - validateCtx.setGasLimit(gasLimit) - err = s.doTxValidation(validateCtx, tx) - if err != nil { - return 0, nil, err - } - - consumed := validateCtx.meter.Limit() - validateCtx.meter.Remaining() - - return consumed, validateCtx.events, applyStateChanges(state, validateState) -} - -// execTx executes the tx messages on the provided state. If the tx fails then the state is discarded. -func (s STF[T]) execTx( - ctx context.Context, - state store.WriterMap, - gasLimit uint64, - tx T, - execMode transaction.ExecMode, - hi header.Info, -) ([]transaction.Msg, uint64, []event.Event, error) { - execState := s.branchFn(state) - - msgsResp, gasUsed, runTxMsgsEvents, txErr := s.runTxMsgs(ctx, execState, gasLimit, tx, execMode, hi) - if txErr != nil { - // in case of error during message execution, we do not apply the exec state. - // instead we run the post exec handler in a new branchFn from the initial state. - postTxState := s.branchFn(state) - postTxCtx := s.makeContext(ctx, RuntimeIdentity, postTxState, execMode) - postTxCtx.setHeaderInfo(hi) - - postTxErr := s.postTxExec(postTxCtx, tx, false) - if postTxErr != nil { - // if the post tx handler fails, then we do not apply any state change to the initial state. - // we just return the exec gas used and a joined error from TX error and post TX error. - return nil, gasUsed, nil, errors.Join(txErr, postTxErr) - } - // in case post tx is successful, then we commit the post tx state to the initial state, - // and we return post tx events alongside exec gas used and the error of the tx. - applyErr := applyStateChanges(state, postTxState) - if applyErr != nil { - return nil, 0, nil, applyErr - } - // set the event indexes, set MsgIndex to -1 in post tx events - for i := range postTxCtx.events { - postTxCtx.events[i].EventIndex = int32(i + 1) - postTxCtx.events[i].MsgIndex = -1 - } - - return nil, gasUsed, postTxCtx.events, txErr - } - // tx execution went fine, now we use the same state to run the post tx exec handler, - // in case the execution of the post tx fails, then no state change is applied and the - // whole execution step is rolled back. - postTxCtx := s.makeContext(ctx, RuntimeIdentity, execState, execMode) // NO gas limit. - postTxCtx.setHeaderInfo(hi) - postTxErr := s.postTxExec(postTxCtx, tx, true) - if postTxErr != nil { - // if post tx fails, then we do not apply any state change, we return the post tx error, - // alongside the gas used. - return nil, gasUsed, nil, postTxErr - } - // both the execution and post tx execution step were successful, so we apply the state changes - // to the provided state, and we return responses, and events from exec tx and post tx exec. - applyErr := applyStateChanges(state, execState) - if applyErr != nil { - return nil, 0, nil, applyErr - } - // set the event indexes, set MsgIndex to -1 in post tx events - for i := range postTxCtx.events { - postTxCtx.events[i].EventIndex = int32(i + 1) - postTxCtx.events[i].MsgIndex = -1 - } - - return msgsResp, gasUsed, append(runTxMsgsEvents, postTxCtx.events...), nil -} - -// runTxMsgs will execute the messages contained in the TX with the provided state. -func (s STF[T]) runTxMsgs( - ctx context.Context, - state store.WriterMap, - gasLimit uint64, - tx T, - execMode transaction.ExecMode, - hi header.Info, -) ([]transaction.Msg, uint64, []event.Event, error) { - txSenders, err := tx.GetSenders() - if err != nil { - return nil, 0, nil, err - } - msgs, err := tx.GetMessages() - if err != nil { - return nil, 0, nil, err - } - msgResps := make([]transaction.Msg, len(msgs)) - - execCtx := s.makeContext(ctx, RuntimeIdentity, state, execMode) - execCtx.setHeaderInfo(hi) - execCtx.setGasLimit(gasLimit) - events := make([]event.Event, 0) - for i, msg := range msgs { - execCtx.sender = txSenders[i] - execCtx.events = make([]event.Event, 0) // reset events - resp, err := s.msgRouter.Invoke(execCtx, msg) - if err != nil { - return nil, 0, nil, err // do not wrap the error or we lose the original error type - } - msgResps[i] = resp - for j, e := range execCtx.events { - e.MsgIndex = int32(i + 1) - e.EventIndex = int32(j + 1) - events = append(events, e) - } - } - - consumed := execCtx.meter.Limit() - execCtx.meter.Remaining() - return msgResps, consumed, events, nil -} - -// preBlock executes the pre block logic. -func (s STF[T]) preBlock( - ctx *executionContext, - txs []T, -) ([]event.Event, error) { - err := s.doPreBlock(ctx, txs) - if err != nil { - return nil, err - } - - for i := range ctx.events { - ctx.events[i].BlockStage = appdata.PreBlockStage - ctx.events[i].EventIndex = int32(i + 1) - } - - return ctx.events, nil -} - -// beginBlock executes the begin block logic. -func (s STF[T]) beginBlock( - ctx *executionContext, -) (beginBlockEvents []event.Event, err error) { - err = s.doBeginBlock(ctx) - if err != nil { - return nil, err - } - - for i := range ctx.events { - ctx.events[i].BlockStage = appdata.BeginBlockStage - ctx.events[i].EventIndex = int32(i + 1) - } - - return ctx.events, nil -} - -// endBlock executes the end block logic. -func (s STF[T]) endBlock( - ctx *executionContext, -) ([]event.Event, []appmodulev2.ValidatorUpdate, error) { - err := s.doEndBlock(ctx) - if err != nil { - return nil, nil, err - } - events := ctx.events - ctx.events = make([]event.Event, 0) // reset events - valsetUpdates, err := s.validatorUpdates(ctx) - if err != nil { - return nil, nil, err - } - events = append(events, ctx.events...) - for i := range events { - events[i].BlockStage = appdata.EndBlockStage - events[i].EventIndex = int32(i + 1) - } - - return events, valsetUpdates, nil -} - -// validatorUpdates returns the validator updates for the current block. It is called by endBlock after the endblock execution has concluded -func (s STF[T]) validatorUpdates( - ctx *executionContext, -) ([]appmodulev2.ValidatorUpdate, error) { - valSetUpdates, err := s.doValidatorUpdate(ctx) - if err != nil { - return nil, err - } - return valSetUpdates, nil -} - -// Simulate simulates the execution of a tx on the provided state. -func (s STF[T]) Simulate( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - tx T, -) (server.TxResult, store.WriterMap) { - simulationState := s.branchFn(state) - hi, err := s.getHeaderInfo(simulationState) - if err != nil { - return server.TxResult{}, nil - } - txr := s.deliverTx(ctx, simulationState, tx, internal.ExecModeSimulate, hi, 0) - - return txr, simulationState -} - -// ValidateTx will run only the validation steps required for a transaction. -// Validations are run over the provided state, with the provided gas limit. -func (s STF[T]) ValidateTx( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - tx T, -) server.TxResult { - validationState := s.branchFn(state) - gasUsed, events, err := s.validateTx(ctx, validationState, gasLimit, tx, transaction.ExecModeCheck) - return server.TxResult{ - Events: events, - GasUsed: gasUsed, - Error: err, - } -} - -// Query executes the query on the provided state with the provided gas limits. -func (s STF[T]) Query( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - req transaction.Msg, -) (transaction.Msg, error) { - queryState := s.branchFn(state) - hi, err := s.getHeaderInfo(queryState) - if err != nil { - return nil, err - } - queryCtx := s.makeContext(ctx, nil, queryState, internal.ExecModeSimulate) - queryCtx.setHeaderInfo(hi) - queryCtx.setGasLimit(gasLimit) - return s.queryRouter.Invoke(queryCtx, req) -} - -// clone clones STF. -func (s STF[T]) clone() STF[T] { - return STF[T]{ - logger: s.logger, - msgRouter: s.msgRouter, - queryRouter: s.queryRouter, - doPreBlock: s.doPreBlock, - doBeginBlock: s.doBeginBlock, - doEndBlock: s.doEndBlock, - doValidatorUpdate: s.doValidatorUpdate, - doTxValidation: s.doTxValidation, - postTxExec: s.postTxExec, - branchFn: s.branchFn, - makeGasMeter: s.makeGasMeter, - makeGasMeteredState: s.makeGasMeteredState, - } -} - -// executionContext is a struct that holds the context for the execution of a tx. -type executionContext struct { - context.Context - - // unmeteredState is storage without metering. Changes here are propagated to state which is the metered - // version. - unmeteredState store.WriterMap - // state is the gas metered state. - state store.WriterMap - // meter is the gas meter. - meter gas.Meter - // events are the current events. - events []event.Event - // sender is the causer of the state transition. - sender transaction.Identity - // headerInfo contains the block info. - headerInfo header.Info - // execMode retains information about the exec mode. - execMode transaction.ExecMode - - branchFn branchFn - makeGasMeter makeGasMeterFn - makeGasMeteredStore makeGasMeteredStateFn - - msgRouter router.Service - queryRouter router.Service -} - -// setHeaderInfo sets the header info in the state to be used by queries in the future. -func (e *executionContext) setHeaderInfo(hi header.Info) { - e.headerInfo = hi -} - -// setGasLimit will update the gas limit of the *executionContext -func (e *executionContext) setGasLimit(limit uint64) { - meter := e.makeGasMeter(limit) - meteredState := e.makeGasMeteredStore(meter, e.unmeteredState) - - e.meter = meter - e.state = meteredState -} - -func (e *executionContext) Value(key any) any { - if key == executionContextKey { - return e - } - - return e.Context.Value(key) -} - -// TODO: too many calls to makeContext can be expensive -// makeContext creates and returns a new execution context for the STF[T] type. -// It takes in the following parameters: -// - ctx: The context.Context object for the execution. -// - sender: The transaction.Identity object representing the sender of the transaction. -// - state: The store.WriterMap object for accessing and modifying the state. -// - gasLimit: The maximum amount of gas allowed for the execution. -// - execMode: The corecontext.ExecMode object representing the execution mode. -// -// It returns a pointer to the executionContext struct -func (s STF[T]) makeContext( - ctx context.Context, - sender transaction.Identity, - store store.WriterMap, - execMode transaction.ExecMode, -) *executionContext { - valuedCtx := context.WithValue(ctx, corecontext.ExecModeKey, execMode) - return newExecutionContext( - valuedCtx, - s.makeGasMeter, - s.makeGasMeteredState, - s.branchFn, - sender, - store, - execMode, - s.msgRouter, - s.queryRouter, - ) -} - -func newExecutionContext( - ctx context.Context, - makeGasMeterFn makeGasMeterFn, - makeGasMeteredStoreFn makeGasMeteredStateFn, - branchFn branchFn, - sender transaction.Identity, - state store.WriterMap, - execMode transaction.ExecMode, - msgRouter coreRouterImpl, - queryRouter coreRouterImpl, -) *executionContext { - meter := makeGasMeterFn(gas.NoGasLimit) - meteredState := makeGasMeteredStoreFn(meter, state) - - return &executionContext{ - Context: ctx, - unmeteredState: state, - state: meteredState, - meter: meter, - events: make([]event.Event, 0), - headerInfo: header.Info{}, - execMode: execMode, - sender: sender, - branchFn: branchFn, - makeGasMeter: makeGasMeterFn, - makeGasMeteredStore: makeGasMeteredStoreFn, - msgRouter: msgRouter, - queryRouter: queryRouter, - } -} - -// applyStateChanges applies the state changes from the source store to the destination store. -// It retrieves the state changes from the source store using GetStateChanges method, -// and then applies those changes to the destination store using ApplyStateChanges method. -// If an error occurs during the retrieval or application of state changes, it is returned. -func applyStateChanges(dst, src store.WriterMap) error { - changes, err := src.GetStateChanges() - if err != nil { - return err - } - return dst.ApplyStateChanges(changes) -} - -// isCtxCancelled reports if the context was canceled. -func isCtxCancelled(ctx context.Context) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - return nil - } -} diff --git a/server/v2/store/server.go b/server/v2/store/server.go deleted file mode 100644 index 1fafe4e25d53..000000000000 --- a/server/v2/store/server.go +++ /dev/null @@ -1,91 +0,0 @@ -package store - -import ( - "context" - "fmt" - - "github.com/spf13/cobra" - - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - serverv2 "cosmossdk.io/server/v2" - storev2 "cosmossdk.io/store/v2" - "cosmossdk.io/store/v2/root" -) - -var ( - _ serverv2.ServerComponent[transaction.Tx] = (*Server[transaction.Tx])(nil) - _ serverv2.HasConfig = (*Server[transaction.Tx])(nil) - _ serverv2.HasCLICommands = (*Server[transaction.Tx])(nil) -) - -const ServerName = "store" - -// Server manages store config and contains prune & snapshot commands -type Server[T transaction.Tx] struct { - config *root.Config - backend storev2.Backend -} - -func New[T transaction.Tx]() *Server[T] { - return &Server[T]{} -} - -func (s *Server[T]) Init(app serverv2.AppI[T], v map[string]any, _ log.Logger) (err error) { - s.backend = app.Store() - s.config, err = UnmarshalConfig(v) - return err -} - -func (s *Server[T]) Name() string { - return ServerName -} - -func (s *Server[T]) Start(context.Context) error { - return nil -} - -func (s *Server[T]) Stop(context.Context) error { - return nil -} - -func (s *Server[T]) CLICommands() serverv2.CLIConfig { - return serverv2.CLIConfig{ - Commands: []*cobra.Command{ - s.PrunesCmd(), - s.ExportSnapshotCmd(), - s.DeleteSnapshotCmd(), - s.ListSnapshotsCmd(), - s.DumpArchiveCmd(), - s.LoadArchiveCmd(), - s.RestoreSnapshotCmd(s.backend), - }, - } -} - -func (s *Server[T]) Config() any { - if s.config == nil || s.config.AppDBBackend == "" { - return root.DefaultConfig() - } - - return s.config -} - -// UnmarshalConfig unmarshals the store config from the given map. -// If the config is not found in the map, the default config is returned. -// If the home directory is found in the map, it sets the home directory in the config. -// An empty home directory *is* permitted at this stage, but attempting to build -// the store with an empty home directory will fail. -func UnmarshalConfig(cfg map[string]any) (*root.Config, error) { - config := &root.Config{ - Options: root.DefaultStoreOptions(), - } - if err := serverv2.UnmarshalSubConfig(cfg, ServerName, config); err != nil { - return nil, fmt.Errorf("failed to unmarshal store config: %w", err) - } - home := cfg[serverv2.FlagHome] - if home != nil { - config.Home = home.(string) - } - return config, nil -} diff --git a/server/v2/types.go b/server/v2/types.go deleted file mode 100644 index 40d51e42375c..000000000000 --- a/server/v2/types.go +++ /dev/null @@ -1,26 +0,0 @@ -package serverv2 - -import ( - "github.com/spf13/viper" - - appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/server" - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - "cosmossdk.io/schema/decoding" - "cosmossdk.io/server/v2/appmanager" - "cosmossdk.io/store/v2" -) - -type AppCreator[T transaction.Tx] func(log.Logger, *viper.Viper) AppI[T] - -type AppI[T transaction.Tx] interface { - appmanager.AppManager[T] - - Name() string - InterfaceRegistry() server.InterfaceRegistry - QueryHandlers() map[string]appmodulev2.Handler - Store() store.RootStore - SchemaDecoderResolver() decoding.DecoderResolver - Close() error -}