From 1dde9decd22b327c430038f51c376f06d43107d9 Mon Sep 17 00:00:00 2001 From: Randy Grok <98407738+randygrok@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:56:29 +0200 Subject: [PATCH] feat: unify version modifier for v2 (#21508) (cherry picked from commit dce0365c234b60b355988e57fa275bf9bd3ea667) # Conflicts: # baseapp/utils_test.go # core/server/app.go # runtime/v2/module.go # server/v2/stf/core_branch_service.go # server/v2/stf/core_branch_service_test.go --- baseapp/baseapp.go | 15 +- baseapp/baseapp_test.go | 1 + baseapp/options.go | 27 ++- baseapp/utils_test.go | 90 ++++++++ core/server/app.go | 59 ++++++ runtime/module.go | 6 - runtime/v2/module.go | 238 ++++++++++++++++++++++ server/v2/stf/core_branch_service.go | 85 ++++++++ server/v2/stf/core_branch_service_test.go | 110 ++++++++++ simapp/CHANGELOG.md | 2 + simapp/app.go | 3 + x/consensus/depinject.go | 39 +++- 12 files changed, 644 insertions(+), 31 deletions(-) create mode 100644 core/server/app.go create mode 100644 runtime/v2/module.go create mode 100644 server/v2/stf/core_branch_service.go create mode 100644 server/v2/stf/core_branch_service_test.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index f2c75f5f5a51..2638cbe60ae7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -2,6 +2,7 @@ package baseapp import ( "context" + "cosmossdk.io/core/server" "errors" "fmt" "maps" @@ -89,6 +90,7 @@ type BaseApp struct { verifyVoteExt sdk.VerifyVoteExtensionHandler // ABCI VerifyVoteExtension handler prepareCheckStater sdk.PrepareCheckStater // logic to run during commit using the checkState precommiter sdk.Precommiter // logic to run during commit using the deliverState + versionModifier server.VersionModifier // interface to get and set the app version addrPeerFilter sdk.PeerFilter // filter peers by address and port idPeerFilter sdk.PeerFilter // filter peers by node ID @@ -249,18 +251,11 @@ func (app *BaseApp) Name() string { // AppVersion returns the application's protocol version. func (app *BaseApp) AppVersion(ctx context.Context) (uint64, error) { - if app.paramStore == nil { - return 0, errors.New("app.paramStore is nil") + if app.versionModifier == nil { + return 0, errors.New("app.versionModifier is nil") } - cp, err := app.paramStore.Get(ctx) - if err != nil { - return 0, fmt.Errorf("failed to get consensus params: %w", err) - } - if cp.Version == nil { - return 0, nil - } - return cp.Version.App, nil + return app.versionModifier.AppVersion(ctx) } // Version returns the application's version string. diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index ecaf6894b400..22b046cf8a63 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -81,6 +81,7 @@ func NewBaseAppSuite(t *testing.T, opts ...func(*baseapp.BaseApp)) *BaseAppSuite app.SetParamStore(paramStore{db: dbm.NewMemDB()}) app.SetTxDecoder(txConfig.TxDecoder()) app.SetTxEncoder(txConfig.TxEncoder()) + app.SetVersionModifier(newMockedVersionModifier(0)) // mount stores and seal require.Nil(t, app.LoadLatestVersion()) diff --git a/baseapp/options.go b/baseapp/options.go index 431f64b70906..7500e070710c 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -2,6 +2,7 @@ package baseapp import ( "context" + "cosmossdk.io/core/server" "errors" "fmt" "io" @@ -146,22 +147,11 @@ func (app *BaseApp) SetVersion(v string) { // SetAppVersion sets the application's version this is used as part of the // header in blocks and is returned to the consensus engine in EndBlock. func (app *BaseApp) SetAppVersion(ctx context.Context, v uint64) error { - if app.paramStore == nil { - return errors.New("param store must be set to set app version") + if app.versionModifier == nil { + return errors.New("version modifier must be set to set app version") } - cp, err := app.paramStore.Get(ctx) - if err != nil { - return fmt.Errorf("failed to get consensus params: %w", err) - } - if cp.Version == nil { - return errors.New("version is not set in param store") - } - cp.Version.App = v - if err := app.paramStore.Set(ctx, cp); err != nil { - return err - } - return nil + return app.versionModifier.SetAppVersion(ctx, v) } func (app *BaseApp) SetDB(db corestore.KVStoreWithBatch) { @@ -323,6 +313,15 @@ func (app *BaseApp) SetTxEncoder(txEncoder sdk.TxEncoder) { app.txEncoder = txEncoder } +// SetVersionModifier sets the version modifier for the BaseApp that allows to set the app version. +func (app *BaseApp) SetVersionModifier(versionModifier server.VersionModifier) { + if app.sealed { + panic("SetVersionModifier() on sealed BaseApp") + } + + app.versionModifier = versionModifier +} + // SetQueryMultiStore set a alternative MultiStore implementation to support grpc query service. // // Ref: https://github.com/cosmos/cosmos-sdk/issues/13317 diff --git a/baseapp/utils_test.go b/baseapp/utils_test.go index 9a47fde242d7..bb3ca0422e57 100644 --- a/baseapp/utils_test.go +++ b/baseapp/utils_test.go @@ -3,6 +3,7 @@ package baseapp_test import ( "bytes" "context" + "cosmossdk.io/core/server" "encoding/binary" "encoding/json" "errors" @@ -375,3 +376,92 @@ func wonkyMsg(t *testing.T, cfg client.TxConfig, tx signing.Tx) signing.Tx { require.NoError(t, err) return builder.GetTx() } +<<<<<<< HEAD +======= + +type SendServerImpl struct { + gas uint64 +} + +func (s SendServerImpl) Send(ctx context.Context, send *baseapptestutil.MsgSend) (*baseapptestutil.MsgSendResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + if send.From == "" { + return nil, errors.New("from address cannot be empty") + } + if send.To == "" { + return nil, errors.New("to address cannot be empty") + } + + _, err := sdk.ParseCoinNormalized(send.Amount) + if err != nil { + return nil, err + } + gas := s.gas + if gas == 0 { + gas = 5 + } + sdkCtx.GasMeter().ConsumeGas(gas, "send test") + return &baseapptestutil.MsgSendResponse{}, nil +} + +type NestedMessgesServerImpl struct { + gas uint64 +} + +func (n NestedMessgesServerImpl) Check(ctx context.Context, message *baseapptestutil.MsgNestedMessages) (*baseapptestutil.MsgCreateNestedMessagesResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + cdc := codectestutil.CodecOptions{}.NewCodec() + baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) + + signer, _, err := cdc.GetMsgSigners(message) + if err != nil { + return nil, err + } + if len(signer) != 1 { + return nil, fmt.Errorf("expected 1 signer, got %d", len(signer)) + } + + msgs, err := message.GetMsgs() + if err != nil { + return nil, err + } + + for _, msg := range msgs { + s, _, err := cdc.GetMsgSigners(msg) + if err != nil { + return nil, err + } + if len(s) != 1 { + return nil, fmt.Errorf("expected 1 signer, got %d", len(s)) + } + if !bytes.Equal(signer[0], s[0]) { + return nil, errors.New("signer does not match") + } + + } + + gas := n.gas + if gas == 0 { + gas = 5 + } + sdkCtx.GasMeter().ConsumeGas(gas, "nested messages test") + return nil, nil +} + +func newMockedVersionModifier(startingVersion uint64) server.VersionModifier { + return &mockedVersionModifier{version: startingVersion} +} + +type mockedVersionModifier struct { + version uint64 +} + +func (m *mockedVersionModifier) SetAppVersion(ctx context.Context, u uint64) error { + m.version = u + return nil +} + +func (m *mockedVersionModifier) AppVersion(ctx context.Context) (uint64, error) { + return m.version, nil +} +>>>>>>> dce0365c2 (feat: unify version modifier for v2 (#21508)) diff --git a/core/server/app.go b/core/server/app.go new file mode 100644 index 000000000000..9576fa8825f8 --- /dev/null +++ b/core/server/app.go @@ -0,0 +1,59 @@ +package server + +import ( + "context" + "time" + + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/event" + "cosmossdk.io/core/transaction" +) + +// BlockRequest defines the request structure for a block coming from consensus server to the state transition function. +type BlockRequest[T transaction.Tx] struct { + Height uint64 + Time time.Time + Hash []byte + ChainId string + AppHash []byte + Txs []T + + // IsGenesis indicates if this block is the first block of the chain. + IsGenesis bool +} + +// BlockResponse defines the response structure for a block coming from the state transition function to consensus server. +type BlockResponse struct { + ValidatorUpdates []appmodulev2.ValidatorUpdate + PreBlockEvents []event.Event + BeginBlockEvents []event.Event + TxResults []TxResult + EndBlockEvents []event.Event +} + +// TxResult defines the result of a transaction execution. +type TxResult struct { + // Events produced by the transaction. + Events []event.Event + // Response messages produced by the transaction. + Resp []transaction.Msg + // Error produced by the transaction. + Error error + // Code produced by the transaction. + // A non-zero code is an error that is either define by the module via the cosmossdk.io/errors/v2 package + // or injected through the antehandler along the execution of the transaction. + Code uint32 + // GasWanted is the maximum units of work we allow this tx to perform. + GasWanted uint64 + // GasUsed is the amount of gas actually consumed. + GasUsed uint64 +} + +// VersionModifier defines the interface fulfilled by BaseApp +// which allows getting and setting its appVersion field. This +// in turn updates the consensus params that are sent to the +// consensus engine in EndBlock +type VersionModifier interface { + SetAppVersion(context.Context, uint64) error + AppVersion(context.Context) (uint64, error) +} diff --git a/runtime/module.go b/runtime/module.go index 2b2a9bb1b9df..95595fc209f8 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -15,7 +15,6 @@ import ( "cosmossdk.io/core/appmodule" "cosmossdk.io/core/comet" "cosmossdk.io/core/registry" - "cosmossdk.io/core/server" "cosmossdk.io/core/store" "cosmossdk.io/depinject" "cosmossdk.io/depinject/appconfig" @@ -103,7 +102,6 @@ func init() { ProvideEnvironment, ProvideTransientStoreService, ProvideModuleManager, - ProvideAppVersionModifier, ProvideCometService, ), appconfig.Invoke(SetupAppBuilder), @@ -293,10 +291,6 @@ func ProvideTransientStoreService( return transientStoreService{key: storeKey} } -func ProvideAppVersionModifier(app *AppBuilder) server.VersionModifier { - return app.app -} - func ProvideCometService() comet.Service { return NewContextAwareCometInfoService() } diff --git a/runtime/v2/module.go b/runtime/v2/module.go new file mode 100644 index 000000000000..1cda0a577fd4 --- /dev/null +++ b/runtime/v2/module.go @@ -0,0 +1,238 @@ +package runtime + +import ( + "fmt" + "os" + "slices" + + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/viper" + "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/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" +) + +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(registar grpc.ServiceRegistrar) error { + autoCliQueryService, err := services.NewAutoCLIQueryService(m.app.moduleManager.modules) + if err != nil { + return err + } + + autocliv1.RegisterQueryServer(registar, autoCliQueryService) + + reflectionSvc, err := services.NewReflectionService() + if err != nil { + return err + } + reflectionv1.RegisterReflectionServiceServer(registar, 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], + ProvideEnvironment[transaction.Tx], + ProvideModuleManager[transaction.Tx], + ProvideCometService, + ), + appconfig.Invoke(SetupAppBuilder), + ) +} + +func ProvideAppBuilder[T transaction.Tx]( + interfaceRegistrar registry.InterfaceRegistrar, + amino registry.AminoRegistrar, +) ( + *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]{ + storeKeys: nil, + interfaceRegistrar: interfaceRegistrar, + amino: amino, + msgRouterBuilder: msgRouterBuilder, + queryRouterBuilder: stf.NewMsgRouterBuilder(), // TODO dedicated query router + GRPCMethodsToMessageMap: map[string]func() proto.Message{}, + } + appBuilder := &AppBuilder[T]{app: app} + + 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 + Viper *viper.Viper `optional:"true"` // can be nil in client wiring +} + +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) + + if inputs.Viper != nil { + inputs.AppBuilder.viper = inputs.Viper + } +} + +func ProvideModuleManager[T transaction.Tx]( + logger log.Logger, + config *runtimev2.Module, + modules map[string]appmodulev2.AppModule, +) *MM[T] { + 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, + config *runtimev2.Module, + key depinject.ModuleKey, + appBuilder *AppBuilder[T], +) ( + appmodulev2.Environment, + store.KVStoreService, + store.MemoryStoreService, +) { + var ( + kvService store.KVStoreService = failingStoreService{} + memKvService store.MemoryStoreService = failingStoreService{} + ) + + // 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 = stf.NewKVStoreService([]byte(kvStoreKey)) + + memStoreKey := fmt.Sprintf("memory:%s", key.Name()) + registerStoreKey(appBuilder, memStoreKey) + memKvService = stf.NewMemoryStoreService([]byte(memStoreKey)) + } + + env := appmodulev2.Environment{ + Logger: logger, + BranchService: stf.BranchService{}, + EventService: stf.NewEventService(), + GasService: stf.NewGasMeterService(), + HeaderService: stf.HeaderService{}, + QueryRouterService: stf.NewQueryRouterService(), + MsgRouterService: stf.NewMsgRouterService([]byte(key.Name())), + TransactionService: services.NewContextAwareTransactionService(), + KVStoreService: kvService, + MemStoreService: memKvService, + } + + return env, kvService, memKvService +} + +func registerStoreKey[T transaction.Tx](wrapper *AppBuilder[T], key string) { + wrapper.app.storeKeys = append(wrapper.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 +} + +func ProvideCometService() comet.Service { + return &services.ContextAwareCometInfoService{} +} diff --git a/server/v2/stf/core_branch_service.go b/server/v2/stf/core_branch_service.go new file mode 100644 index 000000000000..5a83dff6be7c --- /dev/null +++ b/server/v2/stf/core_branch_service.go @@ -0,0 +1,85 @@ +package stf + +import ( + "context" + + "cosmossdk.io/core/branch" + "cosmossdk.io/core/store" +) + +type branchFn func(state store.ReaderMap) store.WriterMap + +var _ branch.Service = (*BranchService)(nil) + +type BranchService struct{} + +func (bs BranchService) Execute(ctx context.Context, f func(ctx context.Context) error) error { + exCtx, err := getExecutionCtxFromContext(ctx) + if err != nil { + return err + } + + return bs.execute(exCtx, f) +} + +func (bs BranchService) ExecuteWithGasLimit( + ctx context.Context, + gasLimit uint64, + f func(ctx context.Context) error, +) (gasUsed uint64, err error) { + exCtx, err := getExecutionCtxFromContext(ctx) + if err != nil { + return 0, err + } + + originalGasMeter := exCtx.meter + + exCtx.setGasLimit(gasLimit) + + // execute branched, with predefined gas limit. + err = bs.execute(exCtx, f) + // restore original context + gasUsed = exCtx.meter.Limit() - exCtx.meter.Remaining() + _ = originalGasMeter.Consume(gasUsed, "execute-with-gas-limit") + exCtx.setGasLimit(originalGasMeter.Remaining()) + + return gasUsed, err +} + +func (bs BranchService) execute(ctx *executionContext, f func(ctx context.Context) error) error { + branchedState := ctx.branchFn(ctx.unmeteredState) + meteredBranchedState := ctx.makeGasMeteredStore(ctx.meter, branchedState) + + branchedCtx := &executionContext{ + Context: ctx.Context, + unmeteredState: branchedState, + state: meteredBranchedState, + meter: ctx.meter, + events: nil, + sender: ctx.sender, + headerInfo: ctx.headerInfo, + execMode: ctx.execMode, + branchFn: ctx.branchFn, + makeGasMeter: ctx.makeGasMeter, + makeGasMeteredStore: ctx.makeGasMeteredStore, + msgRouter: ctx.msgRouter, + queryRouter: ctx.queryRouter, + } + + err := f(branchedCtx) + if err != nil { + return err + } + + // apply state changes to original state + if len(branchedCtx.events) != 0 { + ctx.events = append(ctx.events, branchedCtx.events...) + } + + err = applyStateChanges(ctx.state, branchedCtx.unmeteredState) + if err != nil { + return err + } + + return nil +} diff --git a/server/v2/stf/core_branch_service_test.go b/server/v2/stf/core_branch_service_test.go new file mode 100644 index 000000000000..0394ce0f3b1a --- /dev/null +++ b/server/v2/stf/core_branch_service_test.go @@ -0,0 +1,110 @@ +package stf + +import ( + "context" + "errors" + "testing" + + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/server/v2/stf/branch" + "cosmossdk.io/server/v2/stf/gas" + "cosmossdk.io/server/v2/stf/mock" + gogotypes "github.com/cosmos/gogoproto/types" +) + +func TestBranchService(t *testing.T) { + s := &STF[mock.Tx]{ + doPreBlock: func(ctx context.Context, txs []mock.Tx) error { return nil }, + doBeginBlock: func(ctx context.Context) error { + kvSet(t, ctx, "begin-block") + return nil + }, + doEndBlock: func(ctx context.Context) error { + kvSet(t, ctx, "end-block") + return nil + }, + doValidatorUpdate: func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) { return nil, nil }, + doTxValidation: func(ctx context.Context, tx mock.Tx) error { + kvSet(t, ctx, "validate") + return nil + }, + postTxExec: func(ctx context.Context, tx mock.Tx, success bool) error { + kvSet(t, ctx, "post-tx-exec") + return nil + }, + branchFn: branch.DefaultNewWriterMap, + makeGasMeter: gas.DefaultGasMeter, + makeGasMeteredState: gas.DefaultWrapWithGasMeter, + } + addMsgHandlerToSTF(t, s, func(ctx context.Context, msg *gogotypes.BoolValue) (*gogotypes.BoolValue, error) { + kvSet(t, ctx, "exec") + return nil, nil + }) + + makeContext := func() *executionContext { + state := mock.DB() + writableState := s.branchFn(state) + ctx := s.makeContext(context.Background(), []byte("cookies"), writableState, 0) + ctx.setGasLimit(1000000) + return ctx + } + + branchService := BranchService{} + + // TODO: add events check + gas limit precision test + + t.Run("ok", func(t *testing.T) { + stfCtx := makeContext() + gasUsed, err := branchService.ExecuteWithGasLimit(stfCtx, 10000, func(ctx context.Context) error { + kvSet(t, ctx, "cookies") + return nil + }) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if gasUsed == 0 { + t.Error("expected non-zero gasUsed") + } + stateHas(t, stfCtx.state, "cookies") + }) + + t.Run("fail - reverts state", func(t *testing.T) { + stfCtx := makeContext() + originalGas := stfCtx.meter.Remaining() + gasUsed, err := branchService.ExecuteWithGasLimit(stfCtx, 10000, func(ctx context.Context) error { + kvSet(t, ctx, "cookies") + return errors.New("fail") + }) + if err == nil { + t.Error("expected error") + } + if gasUsed == 0 { + t.Error("expected non-zero gasUsed") + } + if stfCtx.meter.Remaining() != originalGas-gasUsed { + t.Error("expected gas to be reverted") + } + + stateNotHas(t, stfCtx.state, "cookies") + }) + + t.Run("fail - out of gas", func(t *testing.T) { + stfCtx := makeContext() + + gasUsed, err := branchService.ExecuteWithGasLimit(stfCtx, 4000, func(ctx context.Context) error { + state, _ := ctx.(*executionContext).state.GetWriter(actorName) + _ = state.Set([]byte("not out of gas"), []byte{}) + return state.Set([]byte("out of gas"), []byte{}) + }) + if err == nil { + t.Error("expected error") + } + if gasUsed == 0 { + t.Error("expected non-zero gasUsed") + } + stateNotHas(t, stfCtx.state, "cookies") + if stfCtx.meter.Limit()-stfCtx.meter.Remaining() != 1000 { + t.Error("expected gas limit precision to be 1000") + } + }) +} diff --git a/simapp/CHANGELOG.md b/simapp/CHANGELOG.md index 5e6dae65ec2b..678870d8b74a 100644 --- a/simapp/CHANGELOG.md +++ b/simapp/CHANGELOG.md @@ -46,6 +46,8 @@ Always refer to the [UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/mai * [#20490](https://github.com/cosmos/cosmos-sdk/pull/20490) Refactor simulations to make use of `testutil/sims` instead of `runsims`. * [#19726](https://github.com/cosmos/cosmos-sdk/pull/19726) Update APIs to match CometBFT v1. * [#21466](https://github.com/cosmos/cosmos-sdk/pull/21466) Allow chains to plug in their own public key types in `base.Account` +* [#21508](https://github.com/cosmos/cosmos-sdk/pull/21508) Abstract the way we update the version of the app state in `app.go` using the interface `VersionModifier`. + ## v0.47 to v0.50 diff --git a/simapp/app.go b/simapp/app.go index 2cc114103fc6..6cb7a326cf6d 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -294,6 +294,9 @@ func NewSimApp( app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[consensustypes.StoreKey]), logger.With(log.ModuleKey, "x/consensus")), govModuleAddr) bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) + // set the version modifier + bApp.SetVersionModifier(consensus.ProvideAppVersionModifier(app.ConsensusParamsKeeper)) + // add keepers accountsKeeper, err := accounts.NewKeeper( appCodec, diff --git a/x/consensus/depinject.go b/x/consensus/depinject.go index 127144d91a51..07048b4c8684 100644 --- a/x/consensus/depinject.go +++ b/x/consensus/depinject.go @@ -1,13 +1,14 @@ package consensus import ( + "context" modulev1 "cosmossdk.io/api/cosmos/consensus/module/v1" "cosmossdk.io/core/address" "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/server" "cosmossdk.io/depinject" "cosmossdk.io/depinject/appconfig" "cosmossdk.io/x/consensus/keeper" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/runtime" @@ -23,6 +24,7 @@ func init() { appconfig.RegisterModule( &modulev1.Module{}, appconfig.Provide(ProvideModule), + appconfig.Provide(ProvideAppVersionModifier), ) } @@ -64,6 +66,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { m := NewAppModule(in.Cdc, k) baseappOpt := func(app *baseapp.BaseApp) { app.SetParamStore(k.ParamsStore) + app.SetVersionModifier(versionModifier{Keeper: k}) } return ModuleOutputs{ @@ -72,3 +75,37 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { BaseAppOption: baseappOpt, } } + +type versionModifier struct { + Keeper keeper.Keeper +} + +func (v versionModifier) SetAppVersion(ctx context.Context, version uint64) error { + params, err := v.Keeper.Params(ctx, nil) + if err != nil { + return err + } + + updatedParams := params.Params + updatedParams.Version.App = version + + err = v.Keeper.ParamsStore.Set(ctx, *updatedParams) + if err != nil { + return err + } + + return nil +} + +func (v versionModifier) AppVersion(ctx context.Context) (uint64, error) { + params, err := v.Keeper.Params(ctx, nil) + if err != nil { + return 0, err + } + + return params.Params.Version.GetApp(), nil +} + +func ProvideAppVersionModifier(k keeper.Keeper) server.VersionModifier { + return versionModifier{Keeper: k} +}