Skip to content

Commit

Permalink
refactor(server/v2): clean up storage use and config (#22008)
Browse files Browse the repository at this point in the history
  • Loading branch information
kocubinski authored Oct 8, 2024
1 parent 43c41be commit 05fb492
Showing 23 changed files with 344 additions and 277 deletions.
20 changes: 0 additions & 20 deletions runtime/v2/app.go
Original file line number Diff line number Diff line change
@@ -2,8 +2,6 @@ package runtime

import (
"encoding/json"
"errors"
"slices"

runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
@@ -36,8 +34,6 @@ type App[T transaction.Tx] struct {
logger log.Logger
config *runtimev2.Module

// modules configuration
storeKeys []string
interfaceRegistrar registry.InterfaceRegistrar
amino registry.AminoRegistrar
moduleManager *MM[T]
@@ -93,22 +89,6 @@ func (a *App[T]) Close() error {
return nil
}

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

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

return a.storeKeys[i], nil
}

// GetStore returns the app store.
func (a *App[T]) GetStore() Store {
return a.db
27 changes: 15 additions & 12 deletions runtime/v2/builder.go
Original file line number Diff line number Diff line change
@@ -15,13 +15,15 @@ import (
"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]
app *App[T]
storeBuilder root.Builder

// the following fields are used to overwrite the default
branch func(state store.ReaderMap) store.WriterMap
@@ -62,14 +64,6 @@ func (a *AppBuilder[T]) RegisterModules(modules map[string]appmodulev2.AppModule
return nil
}

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

// Build builds an *App instance.
func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) {
for _, opt := range opts {
@@ -93,8 +87,9 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) {
}
}

a.app.db = a.storeBuilder.Get()
if a.app.db == nil {
return nil, fmt.Errorf("app.db is not set, it is required to build the app")
return nil, fmt.Errorf("storeBuilder did not return a db")
}

if err := a.app.moduleManager.RegisterServices(a.app); err != nil {
@@ -205,15 +200,23 @@ func AppBuilderWithBranch[T transaction.Tx](branch func(state store.ReaderMap) s

// 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] {
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] {
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
}
118 changes: 42 additions & 76 deletions runtime/v2/module.go
Original file line number Diff line number Diff line change
@@ -19,15 +19,14 @@ import (
"cosmossdk.io/core/event"
"cosmossdk.io/core/header"
"cosmossdk.io/core/registry"
"cosmossdk.io/core/server"
"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"
rootstore "cosmossdk.io/store/v2/root"
"cosmossdk.io/store/v2/root"
)

var (
@@ -97,9 +96,9 @@ func init() {
appconfig.Register(&runtimev2.Module{},
appconfig.Provide(
ProvideAppBuilder[transaction.Tx],
ProvideEnvironment[transaction.Tx],
ProvideModuleManager[transaction.Tx],
ProvideStoreBuilder,
ProvideEnvironment,
ProvideKVService,
),
appconfig.Invoke(SetupAppBuilder),
)
@@ -108,6 +107,7 @@ func init() {
func ProvideAppBuilder[T transaction.Tx](
interfaceRegistrar registry.InterfaceRegistrar,
amino registry.AminoRegistrar,
storeBuilder root.Builder,
) (
*AppBuilder[T],
*stf.MsgRouterBuilder,
@@ -127,15 +127,14 @@ func ProvideAppBuilder[T transaction.Tx](

msgRouterBuilder := stf.NewMsgRouterBuilder()
app := &App[T]{
storeKeys: nil,
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}
appBuilder := &AppBuilder[T]{app: app, storeBuilder: storeBuilder}

return appBuilder, msgRouterBuilder, appModule[T]{app}, protoFiles, protoTypes
}
@@ -149,12 +148,7 @@ type AppInputs struct {
InterfaceRegistrar registry.InterfaceRegistrar
LegacyAmino registry.AminoRegistrar
Logger log.Logger
// StoreBuilder is a builder for a store/v2 RootStore satisfying the Store interface
StoreBuilder *StoreBuilder
// StoreOptions are required as input for the StoreBuilder. If not provided, the default options are used.
StoreOptions *rootstore.Options `optional:"true"`
// DynamicConfig can be nil in client wiring, but is required in server wiring.
DynamicConfig server.DynamicConfig `optional:"true"`
StoreBuilder root.Builder
}

func SetupAppBuilder(inputs AppInputs) {
@@ -164,24 +158,8 @@ func SetupAppBuilder(inputs AppInputs) {
app.moduleManager = inputs.ModuleManager
app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar)
app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino)

if inputs.DynamicConfig == nil {
return
}
storeOptions := rootstore.DefaultStoreOptions()
if inputs.StoreOptions != nil {
storeOptions = *inputs.StoreOptions
}
var err error
app.db, err = inputs.StoreBuilder.Build(
inputs.Logger,
app.storeKeys,
inputs.DynamicConfig,
storeOptions,
)
if err != nil {
panic(err)
}
// STF requires some state to run
inputs.StoreBuilder.RegisterKey("stf")
}

func ProvideModuleManager[T transaction.Tx](
@@ -192,44 +170,47 @@ func ProvideModuleManager[T transaction.Tx](
return NewModuleManager[T](logger, config, modules)
}

// ProvideEnvironment provides the environment for keeper modules, while maintaining backward compatibility and provide services directly as well.
func ProvideEnvironment[T transaction.Tx](
logger log.Logger,
func ProvideKVService(
config *runtimev2.Module,
key depinject.ModuleKey,
appBuilder *AppBuilder[T],
kvFactory store.KVStoreServiceFactory,
headerService header.Service,
eventService event.Service,
) (
appmodulev2.Environment,
store.KVStoreService,
store.MemoryStoreService,
) {
var (
kvService store.KVStoreService = failingStoreService{}
memKvService store.MemoryStoreService = failingStoreService{}
)

storeBuilder root.Builder,
) (store.KVStoreService, store.MemoryStoreService) {
// skips modules that have no store
if !slices.Contains(config.SkipStoreKeys, key.Name()) {
var kvStoreKey string
storeKeyOverride := storeKeyOverride(config, key.Name())
if storeKeyOverride != nil {
kvStoreKey = storeKeyOverride.KvStoreKey
} else {
kvStoreKey = key.Name()
}
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()
}

registerStoreKey(appBuilder, kvStoreKey)
kvService = kvFactory([]byte(kvStoreKey))
storeBuilder.RegisterKey(kvStoreKey)
return kvFactory([]byte(kvStoreKey)), stf.NewMemoryStoreService([]byte(fmt.Sprintf("memory:%s", kvStoreKey)))
}

memStoreKey := fmt.Sprintf("memory:%s", key.Name())
registerStoreKey(appBuilder, memStoreKey)
memKvService = stf.NewMemoryStoreService([]byte(memStoreKey))
func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig {
for _, cfg := range config.OverrideStoreKeys {
if cfg.ModuleName == moduleName {
return cfg
}
}
return nil
}

env := appmodulev2.Environment{
// 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,
@@ -241,28 +222,13 @@ func ProvideEnvironment[T transaction.Tx](
KVStoreService: kvService,
MemStoreService: memKvService,
}

return env, kvService, memKvService
}

func registerStoreKey[T transaction.Tx](builder *AppBuilder[T], key string) {
builder.app.storeKeys = append(builder.app.storeKeys, key)
}

func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig {
for _, cfg := range config.OverrideStoreKeys {
if cfg.ModuleName == moduleName {
return cfg
}
}

return nil
}

// DefaultServiceBindings provides default services for the following service interfaces:
// - store.KVStoreServiceFactory
// - header.Service
// - comet.Service
// - event.Service
//
// 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
Loading

0 comments on commit 05fb492

Please sign in to comment.