refactor(runtime/v2): use StoreBuilder (#21989)

This commit is contained in:
Matt Kocubinski 2024-10-01 03:01:06 -05:00 committed by GitHub
parent 73ee336359
commit 3a20261c78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 112 additions and 65 deletions

View File

@ -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
}
}

View File

@ -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,
)
}

View File

@ -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

View File

@ -14,7 +14,7 @@ import (
const (
ModuleName = "runtime"
FlagHome = "home"
flagHome = "home"
)
// validateProtoAnnotations validates that the proto annotations are correct.

View File

@ -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)
}