From 3a20261c78d7594e478ec7601eca67b4dc80fd6c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 1 Oct 2024 03:01:06 -0500 Subject: [PATCH] refactor(runtime/v2): use StoreBuilder (#21989) --- runtime/v2/builder.go | 49 ++++--------------------------------- runtime/v2/module.go | 39 +++++++++++++++++++++++------ runtime/v2/store.go | 57 +++++++++++++++++++++++++++++++++++++++++++ runtime/v2/types.go | 2 +- simapp/v2/app_di.go | 30 +++++++++++++---------- 5 files changed, 112 insertions(+), 65 deletions(-) diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index c6fad39f41aa..b28ddbc741b4 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -6,28 +6,22 @@ import ( "errors" "fmt" "io" - "path/filepath" "cosmossdk.io/core/appmodule" appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/server" "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/db" - rootstore "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] - config server.DynamicConfig - storeOptions *rootstore.Options + app *App[T] // the following fields are used to overwrite the default branch func(state store.ReaderMap) store.WriterMap @@ -99,6 +93,10 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { } } + if a.app.db == nil { + return nil, fmt.Errorf("app.db is not set, it is required to build the app") + } + if err := a.app.moduleManager.RegisterServices(a.app); err != nil { return nil, err } @@ -122,37 +120,6 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { } a.app.stf = stf - home := a.config.GetString(FlagHome) - scRawDb, err := db.NewDB( - db.DBType(a.config.GetString("store.app-db-backend")), - "application", - filepath.Join(home, "data"), - nil, - ) - if err != nil { - panic(err) - } - - var storeOptions rootstore.Options - if a.storeOptions != nil { - storeOptions = *a.storeOptions - } else { - storeOptions = rootstore.DefaultStoreOptions() - } - factoryOptions := &rootstore.FactoryOptions{ - Logger: a.app.logger, - RootDir: home, - Options: storeOptions, - StoreKeys: append(a.app.storeKeys, "stf"), - SCRawDB: scRawDb, - } - - rs, err := rootstore.CreateRootStore(factoryOptions) - if err != nil { - return nil, fmt.Errorf("failed to create root store: %w", err) - } - a.app.db = rs - appManagerBuilder := appmanager.Builder[T]{ STF: a.app.stf, DB: a.app.db, @@ -251,9 +218,3 @@ func AppBuilderWithPostTxExec[T transaction.Tx](postTxExec func(ctx context.Cont a.postTxExec = postTxExec } } - -func AppBuilderWithStoreOptions[T transaction.Tx](opts *rootstore.Options) AppBuilderOption[T] { - return func(a *AppBuilder[T]) { - a.storeOptions = opts - } -} diff --git a/runtime/v2/module.go b/runtime/v2/module.go index d4a9b4b534d9..8e8b7e0ce832 100644 --- a/runtime/v2/module.go +++ b/runtime/v2/module.go @@ -16,6 +16,7 @@ import ( 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/server" @@ -26,6 +27,7 @@ import ( "cosmossdk.io/log" "cosmossdk.io/runtime/v2/services" "cosmossdk.io/server/v2/stf" + rootstore "cosmossdk.io/store/v2/root" ) var ( @@ -40,19 +42,19 @@ type appModule[T transaction.Tx] struct { func (m appModule[T]) IsOnePerModuleType() {} func (m appModule[T]) IsAppModule() {} -func (m appModule[T]) RegisterServices(registar grpc.ServiceRegistrar) error { +func (m appModule[T]) RegisterServices(registrar grpc.ServiceRegistrar) error { autoCliQueryService, err := services.NewAutoCLIQueryService(m.app.moduleManager.modules) if err != nil { return err } - autocliv1.RegisterQueryServer(registar, autoCliQueryService) + autocliv1.RegisterQueryServer(registrar, autoCliQueryService) reflectionSvc, err := services.NewReflectionService() if err != nil { return err } - reflectionv1.RegisterReflectionServiceServer(registar, reflectionSvc) + reflectionv1.RegisterReflectionServiceServer(registrar, reflectionSvc) return nil } @@ -97,6 +99,7 @@ func init() { ProvideAppBuilder[transaction.Tx], ProvideEnvironment[transaction.Tx], ProvideModuleManager[transaction.Tx], + ProvideStoreBuilder, ), appconfig.Invoke(SetupAppBuilder), ) @@ -146,7 +149,12 @@ type AppInputs struct { InterfaceRegistrar registry.InterfaceRegistrar LegacyAmino registry.AminoRegistrar Logger log.Logger - DynamicConfig server.DynamicConfig `optional:"true"` // can be nil in client wiring + // 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"` } func SetupAppBuilder(inputs AppInputs) { @@ -157,8 +165,22 @@ func SetupAppBuilder(inputs AppInputs) { app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar) app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino) - if inputs.DynamicConfig != nil { - inputs.AppBuilder.config = inputs.DynamicConfig + 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) } } @@ -178,6 +200,7 @@ func ProvideEnvironment[T transaction.Tx]( appBuilder *AppBuilder[T], kvFactory store.KVStoreServiceFactory, headerService header.Service, + eventService event.Service, ) ( appmodulev2.Environment, store.KVStoreService, @@ -209,7 +232,7 @@ func ProvideEnvironment[T transaction.Tx]( env := appmodulev2.Environment{ Logger: logger, BranchService: stf.BranchService{}, - EventService: stf.NewEventService(), + EventService: eventService, GasService: stf.NewGasMeterService(), HeaderService: headerService, QueryRouterService: stf.NewQueryRouterService(), @@ -254,10 +277,12 @@ func DefaultServiceBindings() depinject.Config { } headerService header.Service = services.NewGenesisHeaderService(stf.HeaderService{}) cometService comet.Service = &services.ContextAwareCometInfoService{} + eventService = stf.NewEventService() ) return depinject.Supply( kvServiceFactory, headerService, cometService, + eventService, ) } diff --git a/runtime/v2/store.go b/runtime/v2/store.go index 40912ea41f48..6c45dd5e72db 100644 --- a/runtime/v2/store.go +++ b/runtime/v2/store.go @@ -3,11 +3,16 @@ package runtime import ( "errors" "fmt" + "path/filepath" + "cosmossdk.io/core/server" "cosmossdk.io/core/store" + "cosmossdk.io/log" "cosmossdk.io/server/v2/stf" storev2 "cosmossdk.io/store/v2" + "cosmossdk.io/store/v2/db" "cosmossdk.io/store/v2/proof" + "cosmossdk.io/store/v2/root" ) // NewKVStoreService creates a new KVStoreService. @@ -59,6 +64,58 @@ type Store interface { LastCommitID() (proof.CommitID, error) } +// StoreBuilder is a builder for a store/v2 RootStore satisfying the Store interface. +type StoreBuilder struct { + store Store +} + +// Build creates a new store/v2 RootStore. +func (sb *StoreBuilder) Build( + logger log.Logger, + storeKeys []string, + config server.DynamicConfig, + options root.Options, +) (Store, error) { + if sb.store != nil { + return sb.store, nil + } + home := config.GetString(flagHome) + scRawDb, err := db.NewDB( + db.DBType(config.GetString("store.app-db-backend")), + "application", + filepath.Join(home, "data"), + nil, + ) + if err != nil { + return nil, fmt.Errorf("failed to create SCRawDB: %w", err) + } + + factoryOptions := &root.FactoryOptions{ + Logger: logger, + RootDir: home, + Options: options, + // STF needs to store a bit of state + StoreKeys: append(storeKeys, "stf"), + SCRawDB: scRawDb, + } + + rs, err := root.CreateRootStore(factoryOptions) + if err != nil { + return nil, fmt.Errorf("failed to create root store: %w", err) + } + sb.store = rs + return sb.store, nil +} + +// Get returns the Store. Build must be called before calling Get or the result will be nil. +func (sb *StoreBuilder) Get() Store { + return sb.store +} + +func ProvideStoreBuilder() *StoreBuilder { + return &StoreBuilder{} +} + // StoreLoader allows for custom loading of the store, this is useful when upgrading the store from a previous version type StoreLoader func(store Store) error diff --git a/runtime/v2/types.go b/runtime/v2/types.go index baa20182b512..9018af921b1c 100644 --- a/runtime/v2/types.go +++ b/runtime/v2/types.go @@ -14,7 +14,7 @@ import ( const ( ModuleName = "runtime" - FlagHome = "home" + flagHome = "home" ) // validateProtoAnnotations validates that the proto annotations are correct. diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index a2fbb958dc0d..43ca45f99aed 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -64,10 +64,9 @@ func NewSimApp[T transaction.Tx]( viper *viper.Viper, ) *SimApp[T] { var ( - app = &SimApp[T]{} - appBuilder *runtime.AppBuilder[T] - err error - storeOptions = &root.Options{} + app = &SimApp[T]{} + appBuilder *runtime.AppBuilder[T] + err error // merge the AppConfig and other configuration in one config appConfig = depinject.Configs( @@ -149,6 +148,19 @@ func NewSimApp[T transaction.Tx]( ) ) + // the subsection of config that contains the store options (in app.toml [store.options] header) + // is unmarshaled into a store.Options struct and passed to the store builder. + // future work may move this specification and retrieval into store/v2. + // If these options are not specified then default values will be used. + if sub := viper.Sub("store.options"); sub != nil { + storeOptions := &root.Options{} + err := sub.Unmarshal(storeOptions) + if err != nil { + panic(err) + } + appConfig = depinject.Configs(appConfig, depinject.Supply(storeOptions)) + } + if err := depinject.Inject(appConfig, &appBuilder, &app.appCodec, @@ -160,15 +172,7 @@ func NewSimApp[T transaction.Tx]( panic(err) } - var builderOpts []runtime.AppBuilderOption[T] - if sub := viper.Sub("store.options"); sub != nil { - err = sub.Unmarshal(storeOptions) - if err != nil { - panic(err) - } - builderOpts = append(builderOpts, runtime.AppBuilderWithStoreOptions[T](storeOptions)) - } - app.App, err = appBuilder.Build(builderOpts...) + app.App, err = appBuilder.Build() if err != nil { panic(err) }