refactor(server/v2): clean up storage use and config (#22008)

This commit is contained in:
Matt Kocubinski 2024-10-08 12:12:06 -05:00 committed by GitHub
parent 43c41be136
commit 05fb492bd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 346 additions and 279 deletions

View File

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

View File

@ -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,7 +200,11 @@ 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
}
@ -213,7 +212,11 @@ func AppBuilderWithTxValidator[T transaction.Tx](txValidators func(ctx context.C
// 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
}

View File

@ -19,7 +19,6 @@ 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"
@ -27,7 +26,7 @@ import (
"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,7 +127,6 @@ func ProvideAppBuilder[T transaction.Tx](
msgRouterBuilder := stf.NewMsgRouterBuilder()
app := &App[T]{
storeKeys: nil,
interfaceRegistrar: interfaceRegistrar,
amino: amino,
msgRouterBuilder: msgRouterBuilder,
@ -135,7 +134,7 @@ func ProvideAppBuilder[T transaction.Tx](
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()
}
registerStoreKey(appBuilder, kvStoreKey)
kvService = kvFactory([]byte(kvStoreKey))
memStoreKey := fmt.Sprintf("memory:%s", key.Name())
registerStoreKey(appBuilder, memStoreKey)
memKvService = stf.NewMemoryStoreService([]byte(memStoreKey))
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()
}
env := appmodulev2.Environment{
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,
@ -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

View File

@ -3,18 +3,35 @@ package runtime
import (
"errors"
"fmt"
"path/filepath"
"sync"
"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"
)
var (
storeBuilderSingleton root.Builder
storeBuilderSingletonOnce sync.Once
)
// ProvideSingletonScopedStoreBuilder returns a store builder that is a singleton
// in the scope of the process lifetime.
func ProvideSingletonScopedStoreBuilder() root.Builder {
storeBuilderSingletonOnce.Do(func() {
storeBuilderSingleton = root.NewBuilder()
})
return storeBuilderSingleton
}
// ResetSingletonScopedStoreBuilder resets the singleton store builder. Applications
// should not ever need to call this, but it may be useful in tests.
func ResetSingletonScopedStoreBuilder() {
storeBuilderSingletonOnce = sync.Once{}
}
// NewKVStoreService creates a new KVStoreService.
// This wrapper is kept for backwards compatibility.
// When migrating from runtime to runtime/v2, use runtimev2.NewKVStoreService(storeKey.Name()) instead of runtime.NewKVStoreService(storeKey).
@ -64,58 +81,6 @@ 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

@ -8,6 +8,9 @@ import (
"os"
"path/filepath"
"cosmossdk.io/server/v2/store"
"cosmossdk.io/store/v2/root"
abciserver "github.com/cometbft/cometbft/abci/server"
cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
cmtcfg "github.com/cometbft/cometbft/config"
@ -23,7 +26,6 @@ import (
serverv2 "cosmossdk.io/server/v2"
cometlog "cosmossdk.io/server/v2/cometbft/log"
"cosmossdk.io/server/v2/cometbft/mempool"
"cosmossdk.io/server/v2/cometbft/types"
"cosmossdk.io/store/v2/snapshots"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
@ -43,14 +45,21 @@ type CometBFTServer[T transaction.Tx] struct {
initTxCodec transaction.Codec[T]
logger log.Logger
storeBuilder root.Builder
serverOptions ServerOptions[T]
config Config
cfgOptions []CfgOption
}
func New[T transaction.Tx](txCodec transaction.Codec[T], serverOptions ServerOptions[T], cfgOptions ...CfgOption) *CometBFTServer[T] {
func New[T transaction.Tx](
txCodec transaction.Codec[T],
storeBuilder root.Builder,
serverOptions ServerOptions[T],
cfgOptions ...CfgOption,
) *CometBFTServer[T] {
return &CometBFTServer[T]{
initTxCodec: txCodec,
storeBuilder: storeBuilder,
serverOptions: serverOptions,
cfgOptions: cfgOptions,
}
@ -97,8 +106,16 @@ func (s *CometBFTServer[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logg
indexEvents[e] = struct{}{}
}
storeCfg, err := store.UnmarshalConfig(cfg)
if err != nil {
return err
}
rs, err := s.storeBuilder.Build(logger, storeCfg)
if err != nil {
return err
}
s.logger = logger.With(log.ModuleKey, s.Name())
store := appI.GetStore().(types.Store)
consensus := NewConsensus(
s.logger,
appI.Name(),
@ -106,7 +123,7 @@ func (s *CometBFTServer[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logg
s.serverOptions.Mempool(cfg),
indexEvents,
appI.GetQueryHandlers(),
store,
rs,
s.config,
s.initTxCodec,
chainID,
@ -119,8 +136,8 @@ func (s *CometBFTServer[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logg
consensus.addrPeerFilter = s.serverOptions.AddrPeerFilter
consensus.idPeerFilter = s.serverOptions.IdPeerFilter
ss := store.GetStateStorage().(snapshots.StorageSnapshotter)
sc := store.GetStateCommitment().(snapshots.CommitSnapshotter)
ss := rs.GetStateStorage().(snapshots.StorageSnapshotter)
sc := rs.GetStateCommitment().(snapshots.CommitSnapshotter)
snapshotStore, err := GetSnapshotStore(s.config.ConfigTomlConfig.RootDir)
if err != nil {

View File

@ -7,6 +7,8 @@ import (
)
type Store interface {
storev2.Backend
// GetLatestVersion returns the latest version that consensus has been made on
GetLatestVersion() (uint64, error)
// StateLatest returns a readonly view over the latest
@ -30,10 +32,4 @@ type Store interface {
// LastCommitID returns a CommitID pertaining to the last commitment.
LastCommitID() (proof.CommitID, error)
// GetStateStorage returns the SS backend.
GetStateStorage() storev2.VersionedDatabase
// GetStateCommitment returns the SC backend.
GetStateCommitment() storev2.Committer
}

View File

@ -33,7 +33,7 @@ func Execute(rootCmd *cobra.Command, envPrefix, defaultHome string) error {
}
// AddCommands add the server commands to the root command
// It configure the config handling and the logger handling
// It configures the config handling and the logger handling
func AddCommands[T transaction.Tx](
rootCmd *cobra.Command,
newApp AppCreator[T],

View File

@ -10,6 +10,7 @@ import (
serverv2 "cosmossdk.io/server/v2"
grpc "cosmossdk.io/server/v2/api/grpc"
store "cosmossdk.io/server/v2/store"
"cosmossdk.io/store/v2/root"
)
func TestReadConfig(t *testing.T) {
@ -21,7 +22,7 @@ func TestReadConfig(t *testing.T) {
require.NoError(t, err)
require.Equal(t, v.GetString(grpc.FlagAddress), grpc.DefaultConfig().Address)
require.Equal(t, v.GetString(store.FlagAppDBBackend), store.DefaultConfig().AppDBBackend)
require.Equal(t, v.GetString(store.FlagAppDBBackend), root.DefaultConfig().AppDBBackend)
}
func TestUnmarshalSubConfig(t *testing.T) {
@ -40,8 +41,8 @@ func TestUnmarshalSubConfig(t *testing.T) {
require.True(t, grpc.DefaultConfig().Enable)
require.False(t, grpcConfig.Enable)
storeConfig := store.Config{}
storeConfig := root.Config{}
err = serverv2.UnmarshalSubConfig(cfg, "store", &storeConfig)
require.NoError(t, err)
require.Equal(t, *store.DefaultConfig(), storeConfig)
require.Equal(t, *root.DefaultConfig(), storeConfig)
}

View File

@ -61,6 +61,7 @@ const (
var _ ServerComponent[transaction.Tx] = (*Server[transaction.Tx])(nil)
// Server is the top-level server component which contains all other server components.
type Server[T transaction.Tx] struct {
logger log.Logger
components []ServerComponent[T]

View File

@ -19,6 +19,8 @@ import (
grpc "cosmossdk.io/server/v2/api/grpc"
"cosmossdk.io/server/v2/appmanager"
"cosmossdk.io/server/v2/store"
storev2 "cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/root"
)
type mockInterfaceRegistry struct{}
@ -48,6 +50,18 @@ func (*mockApp[T]) InterfaceRegistry() coreserver.InterfaceRegistry {
return &mockInterfaceRegistry{}
}
var _ root.Builder = &mockStoreBuilder{}
type mockStoreBuilder struct{}
func (m mockStoreBuilder) Build(logger log.Logger, config *root.Config) (storev2.RootStore, error) {
return nil, nil
}
func (m mockStoreBuilder) RegisterKey(string) {}
func (m mockStoreBuilder) Get() storev2.RootStore { return nil }
func TestServer(t *testing.T) {
currentDir, err := os.Getwd()
require.NoError(t, err)
@ -64,7 +78,7 @@ func TestServer(t *testing.T) {
err = grpcServer.Init(&mockApp[transaction.Tx]{}, cfg, logger)
require.NoError(t, err)
storeServer := store.New[transaction.Tx](nil /* nil appCreator as not using CLI commands */)
storeServer := store.New[transaction.Tx](&mockStoreBuilder{})
err = storeServer.Init(&mockApp[transaction.Tx]{}, cfg, logger)
require.NoError(t, err)

View File

@ -1,17 +0,0 @@
package store
import (
"cosmossdk.io/store/v2/root"
)
func DefaultConfig() *Config {
return &Config{
AppDBBackend: "goleveldb",
Options: root.DefaultStoreOptions(),
}
}
type Config struct {
AppDBBackend string `mapstructure:"app-db-backend" toml:"app-db-backend" comment:"The type of database for application and snapshots databases."`
Options root.Options `mapstructure:"options" toml:"options"`
}

View File

@ -9,6 +9,8 @@ import (
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
serverv2 "cosmossdk.io/server/v2"
storev2 "cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/root"
)
var (
@ -21,24 +23,26 @@ const ServerName = "store"
// Server manages store config and contains prune & snapshot commands
type Server[T transaction.Tx] struct {
config *Config
// saving appCreator for only RestoreSnapshotCmd
appCreator serverv2.AppCreator[T]
config *root.Config
builder root.Builder
backend storev2.Backend
}
func New[T transaction.Tx](appCreator serverv2.AppCreator[T]) *Server[T] {
return &Server[T]{appCreator: appCreator}
func New[T transaction.Tx](builder root.Builder) *Server[T] {
return &Server[T]{builder: builder}
}
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)
}
func (s *Server[T]) Init(_ serverv2.AppI[T], cfg map[string]any, log log.Logger) error {
var err error
s.config, err = UnmarshalConfig(cfg)
if err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
}
s.backend, err = s.builder.Build(log, s.config)
if err != nil {
return fmt.Errorf("failed to create store backend: %w", err)
}
s.config = serverCfg
return nil
}
@ -46,11 +50,11 @@ func (s *Server[T]) Name() string {
return ServerName
}
func (s *Server[T]) Start(ctx context.Context) error {
func (s *Server[T]) Start(context.Context) error {
return nil
}
func (s *Server[T]) Stop(ctx context.Context) error {
func (s *Server[T]) Stop(context.Context) error {
return nil
}
@ -63,15 +67,34 @@ func (s *Server[T]) CLICommands() serverv2.CLIConfig {
s.ListSnapshotsCmd(),
s.DumpArchiveCmd(),
s.LoadArchiveCmd(),
s.RestoreSnapshotCmd(s.appCreator),
s.RestoreSnapshotCmd(s.backend),
},
}
}
func (s *Server[T]) Config() any {
if s.config == nil || s.config.AppDBBackend == "" {
return DefaultConfig()
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 config: %w", err)
}
home := cfg[serverv2.FlagHome]
if home != nil {
config.Home = home.(string)
}
return config, nil
}

View File

@ -76,7 +76,7 @@ func (s *Server[T]) ExportSnapshotCmd() *cobra.Command {
}
// RestoreSnapshotCmd returns a command to restore a snapshot
func (s *Server[T]) RestoreSnapshotCmd(newApp serverv2.AppCreator[T]) *cobra.Command {
func (s *Server[T]) RestoreSnapshotCmd(rootStore storev2.Backend) *cobra.Command {
cmd := &cobra.Command{
Use: "restore <height> <format>",
Short: "Restore app state from local snapshot",
@ -95,8 +95,6 @@ func (s *Server[T]) RestoreSnapshotCmd(newApp serverv2.AppCreator[T]) *cobra.Com
}
logger := log.NewLogger(cmd.OutOrStdout())
app := newApp(logger, v)
rootStore := app.GetStore().(storev2.RootStore)
sm, err := createSnapshotsManager(cmd, v, logger, rootStore)
if err != nil {
@ -350,7 +348,9 @@ func (s *Server[T]) LoadArchiveCmd() *cobra.Command {
}
}
func createSnapshotsManager(cmd *cobra.Command, v *viper.Viper, logger log.Logger, store storev2.RootStore) (*snapshots.Manager, error) {
func createSnapshotsManager(
cmd *cobra.Command, v *viper.Viper, logger log.Logger, store storev2.Backend,
) (*snapshots.Manager, error) {
home := v.GetString(serverv2.FlagHome)
snapshotStore, err := snapshots.NewStore(filepath.Join(home, "data", "snapshots"))
if err != nil {
@ -371,7 +371,11 @@ func createSnapshotsManager(cmd *cobra.Command, v *viper.Viper, logger log.Logge
}
}
sm := snapshots.NewManager(snapshotStore, snapshots.NewSnapshotOptions(interval, uint32(keepRecent)), store.GetStateCommitment().(snapshots.CommitSnapshotter), store.GetStateStorage().(snapshots.StorageSnapshotter), nil, logger)
sm := snapshots.NewManager(
snapshotStore, snapshots.NewSnapshotOptions(interval, uint32(keepRecent)),
store.GetStateCommitment().(snapshots.CommitSnapshotter),
store.GetStateStorage().(snapshots.StorageSnapshotter),
nil, logger)
return sm, nil
}

View File

@ -17,5 +17,4 @@ type AppI[T transaction.Tx] interface {
InterfaceRegistry() server.InterfaceRegistry
GetAppManager() *appmanager.AppManager[T]
GetQueryHandlers() map[string]appmodulev2.Handler
GetStore() any
}

View File

@ -12,6 +12,7 @@ import (
"cosmossdk.io/depinject"
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2"
serverstore "cosmossdk.io/server/v2/store"
"cosmossdk.io/store/v2/root"
basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject"
lockupdepinject "cosmossdk.io/x/accounts/defaults/lockup/depinject"
@ -55,6 +56,18 @@ func init() {
func AppConfig() depinject.Config {
return depinject.Configs(
appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML)
runtime.DefaultServiceBindings(),
depinject.Provide(
codec.ProvideInterfaceRegistry,
codec.ProvideAddressCodec,
codec.ProvideProtoCodec,
codec.ProvideLegacyAmino,
runtime.ProvideSingletonScopedStoreBuilder,
),
depinject.Invoke(
std.RegisterInterfaces,
std.RegisterLegacyAminoCodec,
),
)
}
@ -64,14 +77,14 @@ func NewSimApp[T transaction.Tx](
viper *viper.Viper,
) *SimApp[T] {
var (
app = &SimApp[T]{}
appBuilder *runtime.AppBuilder[T]
err error
app = &SimApp[T]{}
appBuilder *runtime.AppBuilder[T]
storeBuilder root.Builder
err error
// merge the AppConfig and other configuration in one config
appConfig = depinject.Configs(
AppConfig(),
runtime.DefaultServiceBindings(),
depinject.Supply(
logger,
viper,
@ -117,10 +130,6 @@ func NewSimApp[T transaction.Tx](
// interface.
),
depinject.Provide(
codec.ProvideInterfaceRegistry,
codec.ProvideAddressCodec,
codec.ProvideProtoCodec,
codec.ProvideLegacyAmino,
// inject desired account types:
multisigdepinject.ProvideAccount,
basedepinject.ProvideAccount,
@ -141,27 +150,11 @@ func NewSimApp[T transaction.Tx](
// }
// })
),
depinject.Invoke(
std.RegisterInterfaces,
std.RegisterLegacyAminoCodec,
),
)
)
// 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,
&storeBuilder,
&appBuilder,
&app.appCodec,
&app.legacyAmino,
@ -172,6 +165,16 @@ func NewSimApp[T transaction.Tx](
panic(err)
}
// store/v2 follows a slightly more eager config life cycle than server components
storeConfig, err := serverstore.UnmarshalConfig(viper.AllSettings())
if err != nil {
panic(err)
}
_, err = storeBuilder.Build(logger, storeConfig)
if err != nil {
panic(err)
}
app.App, err = appBuilder.Build()
if err != nil {
panic(err)

View File

@ -18,6 +18,7 @@ import (
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
sdkmath "cosmossdk.io/math"
"cosmossdk.io/runtime/v2"
serverv2 "cosmossdk.io/server/v2"
comettypes "cosmossdk.io/server/v2/cometbft/types"
serverv2store "cosmossdk.io/server/v2/store"
@ -40,6 +41,7 @@ func NewTestApp(t *testing.T) (*SimApp[transaction.Tx], context.Context) {
vp.Set(serverv2store.FlagAppDBBackend, string(db.DBTypeGoLevelDB))
vp.Set(serverv2.FlagHome, t.TempDir())
runtime.ResetSingletonScopedStoreBuilder()
app := NewSimApp[transaction.Tx](logger, vp)
genesis := app.ModuleManager().DefaultGenesis()

View File

@ -17,8 +17,9 @@ import (
"cosmossdk.io/server/v2/api/grpc"
"cosmossdk.io/server/v2/api/telemetry"
"cosmossdk.io/server/v2/cometbft"
"cosmossdk.io/server/v2/store"
serverstore "cosmossdk.io/server/v2/store"
"cosmossdk.io/simapp/v2"
"cosmossdk.io/store/v2/root"
confixcmd "cosmossdk.io/tools/confix/cmd"
"github.com/cosmos/cosmos-sdk/client"
@ -43,6 +44,7 @@ func newApp[T transaction.Tx](logger log.Logger, viper *viper.Viper) serverv2.Ap
func initRootCmd[T transaction.Tx](
rootCmd *cobra.Command,
txConfig client.TxConfig,
storeBuilder root.Builder,
moduleManager *runtimev2.MM[T],
) {
cfg := sdk.GetConfig()
@ -52,7 +54,7 @@ func initRootCmd[T transaction.Tx](
genutilcli.InitCmd(moduleManager),
debug.Cmd(),
confixcmd.ConfigCommand(),
NewTestnetCmd(moduleManager),
NewTestnetCmd(storeBuilder, moduleManager),
)
logger, err := serverv2.NewLogger(viper.New(), rootCmd.OutOrStdout())
@ -77,11 +79,12 @@ func initRootCmd[T transaction.Tx](
initServerConfig(),
cometbft.New(
&genericTxDecoder[T]{txConfig},
storeBuilder,
initCometOptions[T](),
initCometConfig(),
),
grpc.New[T](),
store.New[T](newApp),
serverstore.New[T](storeBuilder),
telemetry.New[T](),
); err != nil {
panic(err)

View File

@ -14,13 +14,13 @@ import (
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2"
"cosmossdk.io/simapp/v2"
"cosmossdk.io/store/v2/root"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/config"
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/std"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
authtxconfig "github.com/cosmos/cosmos-sdk/x/auth/tx/config"
"github.com/cosmos/cosmos-sdk/x/auth/types"
@ -32,25 +32,16 @@ func NewRootCmd[T transaction.Tx]() *cobra.Command {
autoCliOpts autocli.AppOptions
moduleManager *runtime.MM[T]
clientCtx client.Context
storeBuilder root.Builder
)
if err := depinject.Inject(
depinject.Configs(
simapp.AppConfig(),
runtime.DefaultServiceBindings(),
depinject.Provide(ProvideClientContext),
depinject.Supply(log.NewNopLogger()),
depinject.Provide(
codec.ProvideInterfaceRegistry,
codec.ProvideAddressCodec,
codec.ProvideProtoCodec,
codec.ProvideLegacyAmino,
ProvideClientContext,
),
depinject.Invoke(
std.RegisterInterfaces,
std.RegisterLegacyAminoCodec,
),
),
&storeBuilder,
&autoCliOpts,
&moduleManager,
&clientCtx,
@ -83,7 +74,7 @@ func NewRootCmd[T transaction.Tx]() *cobra.Command {
},
}
initRootCmd(rootCmd, clientCtx.TxConfig, moduleManager)
initRootCmd(rootCmd, clientCtx.TxConfig, storeBuilder, moduleManager)
nodeCmds := nodeservice.NewNodeCommands()
autoCliOpts.ModuleOptions = make(map[string]*autocliv1.ModuleOptions)

View File

@ -23,6 +23,7 @@ import (
"cosmossdk.io/server/v2/api/grpc"
"cosmossdk.io/server/v2/cometbft"
"cosmossdk.io/server/v2/store"
"cosmossdk.io/store/v2/root"
banktypes "cosmossdk.io/x/bank/types"
bankv2types "cosmossdk.io/x/bank/v2/types"
stakingtypes "cosmossdk.io/x/staking/types"
@ -87,7 +88,7 @@ func addTestnetFlagsToCmd(cmd *cobra.Command) {
// NewTestnetCmd creates a root testnet command with subcommands to run an in-process testnet or initialize
// validator configuration files for running a multi-validator testnet in a separate process
func NewTestnetCmd[T transaction.Tx](mm *runtimev2.MM[T]) *cobra.Command {
func NewTestnetCmd[T transaction.Tx](sb root.Builder, mm *runtimev2.MM[T]) *cobra.Command {
testnetCmd := &cobra.Command{
Use: "testnet",
Short: "subcommands for starting or configuring local testnets",
@ -96,13 +97,13 @@ func NewTestnetCmd[T transaction.Tx](mm *runtimev2.MM[T]) *cobra.Command {
RunE: client.ValidateCmd,
}
testnetCmd.AddCommand(testnetInitFilesCmd(mm))
testnetCmd.AddCommand(testnetInitFilesCmd(sb, mm))
return testnetCmd
}
// testnetInitFilesCmd returns a cmd to initialize all files for CometBFT testnet and application
func testnetInitFilesCmd[T transaction.Tx](mm *runtimev2.MM[T]) *cobra.Command {
func testnetInitFilesCmd[T transaction.Tx](sb root.Builder, mm *runtimev2.MM[T]) *cobra.Command {
cmd := &cobra.Command{
Use: "init-files",
Short: "Initialize config directories & files for a multi-validator testnet running locally via separate processes (e.g. Docker Compose or similar)",
@ -143,7 +144,7 @@ Example:
return err
}
return initTestnetFiles(clientCtx, cmd, config, mm, args)
return initTestnetFiles(clientCtx, sb, cmd, config, mm, args)
},
}
@ -165,6 +166,7 @@ const nodeDirPerm = 0o755
// initTestnetFiles initializes testnet files for a testnet to be run in a separate process
func initTestnetFiles[T transaction.Tx](
clientCtx client.Context,
sb root.Builder,
cmd *cobra.Command,
nodeConfig *cmtconfig.Config,
mm *runtimev2.MM[T],
@ -339,12 +341,13 @@ func initTestnetFiles[T transaction.Tx](
// Write server config
cometServer := cometbft.New[T](
&genericTxDecoder[T]{clientCtx.TxConfig},
sb,
cometbft.ServerOptions[T]{},
cometbft.OverwriteDefaultConfigTomlConfig(nodeConfig),
)
storeServer := store.New[T](newApp)
storeServer := store.New[T](sb)
grpcServer := grpc.New[T](grpc.OverwriteDefaultConfig(grpcConfig))
server := serverv2.NewServer(log.NewNopLogger(), serverCfg, cometServer, grpcServer, storeServer)
server := serverv2.NewServer[T](log.NewNopLogger(), serverCfg, cometServer, grpcServer, storeServer)
err = server.WriteConfig(filepath.Join(nodeDir, "config"))
if err != nil {
return err

95
store/v2/root/builder.go Normal file
View File

@ -0,0 +1,95 @@
package root
import (
"fmt"
"path/filepath"
"cosmossdk.io/log"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/db"
)
// Builder is the interface for a store/v2 RootStore builder.
// RootStores built by the Cosmos SDK typically involve a 2 phase initialization:
// 1. Namespace registration
// 2. Configuration and loading
//
// The Builder interface is used to facilitate this pattern. Namespaces (store keys) are registered
// by calling RegisterKey before Build is called. Build is then called with a Config
// object and a RootStore is returned. Calls to Get may return the `RootStore` if Build
// was successful, but that's left up to the implementation.
type Builder interface {
// Build creates a new store/v2 RootStore from the given Config.
Build(log.Logger, *Config) (store.RootStore, error)
// RegisterKey registers a store key (namespace) to be used when building the RootStore.
RegisterKey(string)
// Get returns the Store. Build should be called before calling Get or the result will be nil.
Get() store.RootStore
}
var _ Builder = (*builder)(nil)
// builder is the default builder for a store/v2 RootStore satisfying the Store interface.
// Tangibly it combines store key registration and a top-level Config to create a RootStore by calling
// the CreateRootStore factory function.
type builder struct {
// input
storeKeys map[string]struct{}
// output
store store.RootStore
}
func NewBuilder() Builder {
return &builder{storeKeys: make(map[string]struct{})}
}
// Build creates a new store/v2 RootStore.
func (sb *builder) Build(
logger log.Logger,
config *Config,
) (store.RootStore, error) {
if sb.store != nil {
return sb.store, nil
}
if config.Home == "" {
return nil, fmt.Errorf("home directory is required")
}
scRawDb, err := db.NewDB(
db.DBType(config.AppDBBackend),
"application",
filepath.Join(config.Home, "data"),
nil,
)
if err != nil {
return nil, fmt.Errorf("failed to create SCRawDB: %w", err)
}
var storeKeys []string
for key := range sb.storeKeys {
storeKeys = append(storeKeys, key)
}
factoryOptions := &FactoryOptions{
Logger: logger,
RootDir: config.Home,
Options: config.Options,
StoreKeys: storeKeys,
SCRawDB: scRawDb,
}
rs, err := CreateRootStore(factoryOptions)
if err != nil {
return nil, fmt.Errorf("failed to create root store: %w", err)
}
sb.store = rs
return sb.store, nil
}
func (sb *builder) Get() store.RootStore {
return sb.store
}
func (sb *builder) RegisterKey(key string) {
sb.storeKeys[key] = struct{}{}
}

14
store/v2/root/config.go Normal file
View File

@ -0,0 +1,14 @@
package root
func DefaultConfig() *Config {
return &Config{
AppDBBackend: "goleveldb",
Options: DefaultStoreOptions(),
}
}
type Config struct {
Home string `toml:"-"` // this field is omitted in the TOML file
AppDBBackend string `mapstructure:"app-db-backend" toml:"app-db-backend" comment:"The type of database for application and snapshots databases."`
Options Options `mapstructure:"options" toml:"options"`
}

View File

@ -33,7 +33,7 @@ const (
SCTypeIavlV2 SCType = "iavl-v2"
)
// app.toml config options
// Options are the options for creating a root store.
type Options struct {
SSType SSType `mapstructure:"ss-type" toml:"ss-type" comment:"SState storage database type. Currently we support: \"sqlite\", \"pebble\" and \"rocksdb\""`
SCType SCType `mapstructure:"sc-type" toml:"sc-type" comment:"State commitment database type. Currently we support: \"iavl\" and \"iavl-v2\""`

View File

@ -12,6 +12,9 @@ import (
// RootStore defines an abstraction layer containing a State Storage (SS) engine
// and one or more State Commitment (SC) engines.
type RootStore interface {
Pruner
Backend
// StateLatest returns a read-only version of the RootStore at the latest
// height, alongside the associated version.
StateLatest() (uint64, corestore.ReaderMap, error)
@ -21,12 +24,6 @@ type RootStore interface {
// an error must be returned.
StateAt(version uint64) (corestore.ReaderMap, error)
// GetStateStorage returns the SS backend.
GetStateStorage() VersionedDatabase
// GetStateCommitment returns the SC backend.
GetStateCommitment() Committer
// Query performs a query on the RootStore for a given store key, version (height),
// and key tuple. Queries should be routed to the underlying SS engine.
Query(storeKey []byte, version uint64, key []byte, prove bool) (QueryResult, error)
@ -67,11 +64,18 @@ type RootStore interface {
// SetMetrics sets the telemetry handler on the RootStore.
SetMetrics(m metrics.Metrics)
Prune(version uint64) error
io.Closer
}
// Backend defines the interface for the RootStore backends.
type Backend interface {
// GetStateStorage returns the SS backend.
GetStateStorage() VersionedDatabase
// GetStateCommitment returns the SC backend.
GetStateCommitment() Committer
}
// UpgradeableStore defines the interface for upgrading store keys.
type UpgradeableStore interface {
// LoadVersionAndUpgrade behaves identically to LoadVersion except it also