cosmos-sdk/runtime/v2/module.go
2024-10-21 13:45:28 +00:00

258 lines
7.5 KiB
Go

package runtime
import (
"fmt"
"os"
"slices"
"github.com/cosmos/gogoproto/proto"
"google.golang.org/grpc"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2"
appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
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/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"
"cosmossdk.io/store/v2/root"
)
var (
_ appmodulev2.AppModule = appModule[transaction.Tx]{}
_ hasServicesV1 = appModule[transaction.Tx]{}
)
type appModule[T transaction.Tx] struct {
app *App[T]
}
func (m appModule[T]) IsOnePerModuleType() {}
func (m appModule[T]) IsAppModule() {}
func (m appModule[T]) RegisterServices(registrar grpc.ServiceRegistrar) error {
autoCliQueryService, err := services.NewAutoCLIQueryService(m.app.moduleManager.modules)
if err != nil {
return err
}
autocliv1.RegisterQueryServer(registrar, autoCliQueryService)
reflectionSvc, err := services.NewReflectionService()
if err != nil {
return err
}
reflectionv1.RegisterReflectionServiceServer(registrar, reflectionSvc)
return nil
}
func (m appModule[T]) AutoCLIOptions() *autocliv1.ModuleOptions {
return &autocliv1.ModuleOptions{
Query: &autocliv1.ServiceCommandDescriptor{
Service: appv1alpha1.Query_ServiceDesc.ServiceName,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "Config",
Short: "Query the current app config",
},
},
SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{
"autocli": {
Service: autocliv1.Query_ServiceDesc.ServiceName,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "AppOptions",
Short: "Query the custom autocli options",
},
},
},
"reflection": {
Service: reflectionv1.ReflectionService_ServiceDesc.ServiceName,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "FileDescriptors",
Short: "Query the app's protobuf file descriptors",
},
},
},
},
},
}
}
func init() {
appconfig.Register(&runtimev2.Module{},
appconfig.Provide(
ProvideAppBuilder[transaction.Tx],
ProvideModuleManager[transaction.Tx],
ProvideEnvironment,
ProvideKVService,
),
appconfig.Invoke(SetupAppBuilder),
)
}
func ProvideAppBuilder[T transaction.Tx](
interfaceRegistrar registry.InterfaceRegistrar,
amino registry.AminoRegistrar,
storeBuilder root.Builder,
) (
*AppBuilder[T],
*stf.MsgRouterBuilder,
appmodulev2.AppModule,
protodesc.Resolver,
protoregistry.MessageTypeResolver,
) {
protoFiles := proto.HybridResolver
protoTypes := protoregistry.GlobalTypes
// At startup, check that all proto annotations are correct.
if err := validateProtoAnnotations(protoFiles); err != nil {
// Once we switch to using protoreflect-based ante handlers, we might
// want to panic here instead of logging a warning.
_, _ = fmt.Fprintln(os.Stderr, err.Error())
}
msgRouterBuilder := stf.NewMsgRouterBuilder()
app := &App[T]{
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, storeBuilder: storeBuilder}
return appBuilder, msgRouterBuilder, appModule[T]{app}, protoFiles, protoTypes
}
type AppInputs struct {
depinject.In
Config *runtimev2.Module
AppBuilder *AppBuilder[transaction.Tx]
ModuleManager *MM[transaction.Tx]
InterfaceRegistrar registry.InterfaceRegistrar
LegacyAmino registry.AminoRegistrar
Logger log.Logger
StoreBuilder root.Builder
}
func SetupAppBuilder(inputs AppInputs) {
app := inputs.AppBuilder.app
app.config = inputs.Config
app.logger = inputs.Logger
app.moduleManager = inputs.ModuleManager
app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar)
app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino)
// STF requires some state to run
inputs.StoreBuilder.RegisterKey("stf")
}
func ProvideModuleManager[T transaction.Tx](
logger log.Logger,
config *runtimev2.Module,
modules map[string]appmodulev2.AppModule,
) *MM[T] {
return NewModuleManager[T](logger, config, modules)
}
func ProvideKVService(
config *runtimev2.Module,
key depinject.ModuleKey,
kvFactory store.KVStoreServiceFactory,
storeBuilder root.Builder,
) (store.KVStoreService, store.MemoryStoreService) {
// skips modules that have no store
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()
}
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,
GasService: stf.NewGasMeterService(),
HeaderService: headerService,
QueryRouterService: stf.NewQueryRouterService(),
MsgRouterService: stf.NewMsgRouterService([]byte(key.Name())),
TransactionService: services.NewContextAwareTransactionService(),
KVStoreService: kvService,
MemStoreService: memKvService,
}
}
// DefaultServiceBindings provides default services for the following service interfaces:
// - store.KVStoreServiceFactory
// - header.Service
// - comet.Service
// - event.Service
// - store/v2/root.Builder
//
// 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
// supply implementations for each of the above interfaces.
func DefaultServiceBindings() depinject.Config {
var (
kvServiceFactory store.KVStoreServiceFactory = func(actor []byte) store.KVStoreService {
return services.NewGenesisKVService(
actor,
stf.NewKVStoreService(actor),
)
}
cometService comet.Service = &services.ContextAwareCometInfoService{}
headerService = services.NewGenesisHeaderService(stf.HeaderService{})
eventService = services.NewGenesisEventService(stf.NewEventService())
storeBuilder = root.NewBuilder()
)
return depinject.Supply(
kvServiceFactory,
headerService,
cometService,
eventService,
storeBuilder,
)
}