Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: upstream runtime/v2 #20320

Merged
merged 23 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions runtime/v2/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package runtime

import (
"context"
"encoding/json"
"errors"

"golang.org/x/exp/slices"

runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2"
appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1"
coreappmanager "cosmossdk.io/core/app"
"cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
"cosmossdk.io/server/v2/appmanager"
"cosmossdk.io/server/v2/stf"

"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
)

var _ AppI[transaction.Tx] = (*App)(nil)

// AppI is an interface that defines the methods required by the App.
type AppI[T transaction.Tx] interface {
DeliverBlock(ctx context.Context, block *coreappmanager.BlockRequest[T]) (*coreappmanager.BlockResponse, store.WriterMap, error)
ValidateTx(ctx context.Context, tx T) (coreappmanager.TxResult, error)
Simulate(ctx context.Context, tx T) (coreappmanager.TxResult, store.WriterMap, error)
Query(ctx context.Context, version uint64, request transaction.Type) (transaction.Type, error)
QueryWithState(ctx context.Context, state store.ReaderMap, request transaction.Type) (transaction.Type, error)

Logger() log.Logger
ModuleManager() *MM
Close() error
}

// 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_.go for an example of this setup.
type App struct {
*appmanager.AppManager[transaction.Tx]

// app manager dependencies
stf *stf.STF[transaction.Tx]
msgRouterBuilder *stf.MsgRouterBuilder
queryRouterBuilder *stf.MsgRouterBuilder
db Store

// app configuration
logger log.Logger
config *runtimev2.Module
appConfig *appv1alpha1.Config

// modules configuration
storeKeys []string
interfaceRegistry codectypes.InterfaceRegistry
cdc codec.Codec
amino *codec.LegacyAmino
moduleManager *MM
}

// Logger returns the app logger.
func (a *App) Logger() log.Logger {
return a.logger
}

// ModuleManager returns the module manager.
func (a *App) ModuleManager() *MM {
return a.moduleManager
}

// DefaultGenesis returns a default genesis from the registered modules.
func (a *App) DefaultGenesis() map[string]json.RawMessage {
return a.moduleManager.DefaultGenesis()
}

// LoadLatest loads the latest version.
func (a *App) LoadLatest() error {
return a.db.LoadLatestVersion()
}

// LoadHeight loads a particular height
func (a *App) LoadHeight(height uint64) error {
return a.db.LoadVersion(height)
}

// Close is called in start cmd to gracefully cleanup resources.
func (a *App) Close() error {
return nil
}

// RegisterStores registers the provided store keys.
// This method should only be used for registering extra stores
// wiich is necessary for modules that not registered using the app config.
// To be used in combination of RegisterModules.
func (a *App) RegisterStores(keys ...string) error {
a.storeKeys = append(a.storeKeys, keys...)

return nil
}

// GetStoreKeys returns all the stored store keys.
func (a *App) GetStoreKeys() []string {
return a.storeKeys
}

// UnsafeFindStoreKey fetches a registered StoreKey from the App in linear time.
// NOTE: This should only be used in testing.
func (a *App) UnsafeFindStoreKey(storeKey string) (string, error) {
i := slices.IndexFunc(a.storeKeys, func(s string) bool { return s == storeKey })
if i == -1 {
return "", errors.New("store key not found")
}

return a.storeKeys[i], nil
}

func (a *App) GetStore() Store {
return a.db
}

func (a *App) GetLogger() log.Logger {
return a.logger
}

func (a *App) ExecuteGenesisTx(_ []byte) error {
panic("not implemented")
}

func (a *App) SetAppVersion(context.Context, uint64) error {
panic("not implemented")
}

func (a *App) AppVersion(context.Context) (uint64, error) {
panic("not implemented")
}
168 changes: 168 additions & 0 deletions runtime/v2/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package runtime

import (
"context"
"encoding/json"
"fmt"
"io"

appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
"cosmossdk.io/server/v2/appmanager"
"cosmossdk.io/server/v2/stf"
"cosmossdk.io/server/v2/stf/branch"
rootstore "cosmossdk.io/store/v2/root"

sdkmodule "github.com/cosmos/cosmos-sdk/types/module"
)

type branchFunc func(state store.ReaderMap) store.WriterMap

// AppBuilder is a type that is injected into a container by the runtime module
// (as *AppBuilder) which can be used to create an app which is compatible with
// the existing app.go initialization conventions.
type AppBuilder struct {
app *App
storeOptions *rootstore.FactoryOptions

// the following fields are used to overwrite the default
branch branchFunc
txValidator func(ctx context.Context, tx transaction.Tx) error
}

// DefaultGenesis returns a default genesis from the registered AppModule's.
func (a *AppBuilder) DefaultGenesis() map[string]json.RawMessage {
return a.app.moduleManager.DefaultGenesis()
}

// RegisterModules registers the provided modules with the module manager.
// This is the primary hook for integrating with modules which are not registered using the app config.
func (a *AppBuilder) RegisterModules(modules ...appmodulev2.AppModule) error {
for _, appModule := range modules {
if mod, ok := appModule.(sdkmodule.HasName); ok {
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.interfaceRegistry)
}

if mod, ok := appModule.(sdkmodule.HasAminoCodec); ok {
mod.RegisterLegacyAminoCodec(a.app.amino)
}
}
}

return nil
kocubinski marked this conversation as resolved.
Show resolved Hide resolved
}

// Build builds an *App instance.
func (a *AppBuilder) Build(opts ...AppBuilderOption) (*App, 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()
}

if err := a.app.moduleManager.RegisterServices(a.app); err != nil {
return nil, err
}

stfMsgHandler, err := a.app.msgRouterBuilder.Build()
if err != nil {
return nil, fmt.Errorf("failed to build STF message handler: %w", err)
}

stfQueryHandler, err := a.app.queryRouterBuilder.Build()
if err != nil {
return nil, fmt.Errorf("failed to build query handler: %w", err)
}

endBlocker, valUpdate := a.app.moduleManager.EndBlock()

// TODO how to set?
// no-op postTxExec
postTxExec := func(ctx context.Context, tx transaction.Tx, success bool) error {
return nil
}

a.app.stf = stf.NewSTF[transaction.Tx](
stfMsgHandler,
stfQueryHandler,
a.app.moduleManager.PreBlocker(),
a.app.moduleManager.BeginBlock(),
endBlocker,
a.txValidator,
valUpdate,
postTxExec,
a.branch,
)

rs, err := rootstore.CreateRootStore(a.storeOptions)
if err != nil {
return nil, fmt.Errorf("failed to create root store: %w", err)
}
a.app.db = rs

appManagerBuilder := appmanager.Builder[transaction.Tx]{
STF: a.app.stf,
DB: a.app.db,
ValidateTxGasLimit: a.app.config.GasConfig.ValidateTxGasLimit,
QueryGasLimit: a.app.config.GasConfig.QueryGasLimit,
SimulationGasLimit: a.app.config.GasConfig.SimulationGasLimit,
InitGenesis: func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) error {
// this implementation assumes that the state is a JSON object
bz, err := io.ReadAll(src)
if err != nil {
return fmt.Errorf("failed to read import state: %w", err)
}
var genesisState map[string]json.RawMessage
if err = json.Unmarshal(bz, &genesisState); err != nil {
return err
}
if err = a.app.moduleManager.InitGenesisJSON(ctx, genesisState, txHandler); err != nil {
return fmt.Errorf("failed to init genesis: %w", err)
}
return nil
},
}

appManager, err := appManagerBuilder.Build()
if err != nil {
return nil, fmt.Errorf("failed to build app manager: %w", err)
}
a.app.AppManager = appManager

return a.app, nil
}

// AppBuilderOption is a function that can be passed to AppBuilder.Build to
// customize the resulting app.
type AppBuilderOption func(*AppBuilder)

// AppBuilderWithBranch sets a custom branch implementation for the app.
func AppBuilderWithBranch(branch branchFunc) AppBuilderOption {
return func(a *AppBuilder) {
a.branch = branch
}
}

// AppBuilderWithTxValidator sets the tx validator for the app.
// It overrides all default tx validators defined by modules.
func AppBuilderWithTxValidator(validator func(ctx context.Context, tx transaction.Tx) error) AppBuilderOption {
return func(a *AppBuilder) {
a.txValidator = validator
}
}
Loading
Loading