From ffeae5184c585b287b90c85a3f9790e4ee2aed09 Mon Sep 17 00:00:00 2001 From: Lukasz Cwik Date: Tue, 30 Jan 2024 15:52:30 -0800 Subject: [PATCH] [STAB-31] Allow AnteHandler to control locking semantics. Updated existing usages of AnteHandler in cosmos to use the instance which always locks restoring the existing contract. --- baseapp/abci_test.go | 155 ++++++++++++++++++++++------------------ baseapp/baseapp.go | 42 +++++------ baseapp/baseapp_test.go | 48 ++++++++++--- baseapp/locking.go | 34 +++++++++ baseapp/locking_test.go | 133 ++++++++++++++++++++++++++++++++++ baseapp/utils_test.go | 4 +- simapp/ante.go | 2 + x/auth/ante/ante.go | 2 + 8 files changed, 316 insertions(+), 104 deletions(-) create mode 100644 baseapp/locking.go create mode 100644 baseapp/locking_test.go diff --git a/baseapp/abci_test.go b/baseapp/abci_test.go index ab75512f06bd..5160a6ba9f10 100644 --- a/baseapp/abci_test.go +++ b/baseapp/abci_test.go @@ -657,6 +657,13 @@ func TestABCI_FinalizeBlock_DeliverTx(t *testing.T) { } } +func wrapWithLockAndCacheContextDecorator(handler sdk.AnteHandler) sdk.AnteHandler { + lockingHandler := baseapp.NewLockAndCacheContextAnteDecorator() + return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + return lockingHandler.AnteHandle(ctx, tx, simulate, handler) + } +} + func TestABCI_FinalizeBlock_MultiMsg(t *testing.T) { anteKey := []byte("ante-key") anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } @@ -735,10 +742,12 @@ func TestABCI_FinalizeBlock_MultiMsg(t *testing.T) { func TestABCI_Query_SimulateTx(t *testing.T) { gasConsumed := uint64(5) anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasConsumed)) - return - }) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasConsumed)) + return + }), + ) } suite := NewBaseAppSuite(t, anteOpt) @@ -796,9 +805,11 @@ func TestABCI_Query_SimulateTx(t *testing.T) { func TestABCI_InvalidTransaction(t *testing.T) { anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - return - }) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + return + }), + ) } suite := NewBaseAppSuite(t, anteOpt) @@ -922,29 +933,31 @@ func TestABCI_InvalidTransaction(t *testing.T) { func TestABCI_TxGasLimits(t *testing.T) { gasGranted := uint64(10) anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasGranted)) - - // AnteHandlers must have their own defer/recover in order for the BaseApp - // to know how much gas was used! This is because the GasMeter is created in - // the AnteHandler, but if it panics the context won't be set properly in - // runTx's recover call. - defer func() { - if r := recover(); r != nil { - switch rType := r.(type) { - case storetypes.ErrorOutOfGas: - err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor) - default: - panic(r) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasGranted)) + + // AnteHandlers must have their own defer/recover in order for the BaseApp + // to know how much gas was used! This is because the GasMeter is created in + // the AnteHandler, but if it panics the context won't be set properly in + // runTx's recover call. + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case storetypes.ErrorOutOfGas: + err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor) + default: + panic(r) + } } - } - }() + }() - count, _ := parseTxMemo(t, tx) - newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante") + count, _ := parseTxMemo(t, tx) + newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante") - return newCtx, nil - }) + return newCtx, nil + }), + ) } suite := NewBaseAppSuite(t, anteOpt) @@ -1018,25 +1031,27 @@ func TestABCI_TxGasLimits(t *testing.T) { func TestABCI_MaxBlockGasLimits(t *testing.T) { gasGranted := uint64(10) anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasGranted)) - - defer func() { - if r := recover(); r != nil { - switch rType := r.(type) { - case storetypes.ErrorOutOfGas: - err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor) - default: - panic(r) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasGranted)) + + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case storetypes.ErrorOutOfGas: + err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor) + default: + panic(r) + } } - } - }() + }() - count, _ := parseTxMemo(t, tx) - newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante") + count, _ := parseTxMemo(t, tx) + newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante") - return - }) + return + }), + ) } suite := NewBaseAppSuite(t, anteOpt) @@ -1115,29 +1130,31 @@ func TestABCI_MaxBlockGasLimits(t *testing.T) { func TestABCI_GasConsumptionBadTx(t *testing.T) { gasWanted := uint64(5) anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasWanted)) - - defer func() { - if r := recover(); r != nil { - switch rType := r.(type) { - case storetypes.ErrorOutOfGas: - log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) - err = errorsmod.Wrap(sdkerrors.ErrOutOfGas, log) - default: - panic(r) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasWanted)) + + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case storetypes.ErrorOutOfGas: + log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) + err = errorsmod.Wrap(sdkerrors.ErrOutOfGas, log) + default: + panic(r) + } } - } - }() + }() - counter, failOnAnte := parseTxMemo(t, tx) - newCtx.GasMeter().ConsumeGas(uint64(counter), "counter-ante") - if failOnAnte { - return newCtx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure") - } + counter, failOnAnte := parseTxMemo(t, tx) + newCtx.GasMeter().ConsumeGas(uint64(counter), "counter-ante") + if failOnAnte { + return newCtx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure") + } - return - }) + return + }), + ) } suite := NewBaseAppSuite(t, anteOpt) @@ -1172,11 +1189,13 @@ func TestABCI_GasConsumptionBadTx(t *testing.T) { func TestABCI_Query(t *testing.T) { key, value := []byte("hello"), []byte("goodbye") anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - store := ctx.KVStore(capKey1) - store.Set(key, value) - return - }) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + store := ctx.KVStore(capKey1) + store.Set(key, value) + return + }), + ) } suite := NewBaseAppSuite(t, anteOpt) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 85c2d21b2ad4..f01da2a4d1da 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -189,7 +189,7 @@ type BaseApp struct { optimisticExec *oe.OptimisticExecution // Used to synchronize the application when using an unsynchronized ABCI++ client. - mtx sync.Mutex + mtx sync.RWMutex } // NewBaseApp returns a reference to an initialized BaseApp. It accepts a @@ -653,6 +653,11 @@ func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context { WithTxBytes(txBytes) // WithVoteInfos(app.voteInfos) // TODO: identify if this is needed + // Use a new gas meter since loading the consensus params below consumes gas and causes a race condition. + // + // TODO(STAB-35): See if this can be removed. + ctx = ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) + ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) if mode == execModeReCheck { @@ -668,7 +673,7 @@ func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context { // cacheTxContext returns a new context based off of the provided context with // a branched multi-store. -func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) { +func cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) { ms := ctx.MultiStore() // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 msCache := ms.CacheMultiStore() @@ -842,8 +847,8 @@ func (app *BaseApp) runCheckTxConcurrently(mode execMode, txBytes []byte) (gInfo // embedded here to ensure that the lifetime of the mutex is limited to only this function allowing // for the return values to be computed without holding the lock. func() { - app.mtx.Lock() - defer app.mtx.Unlock() + app.mtx.RLock() + defer app.mtx.RUnlock() ctx := app.getContextForTx(mode, txBytes) ms := ctx.MultiStore() @@ -859,25 +864,17 @@ func (app *BaseApp) runCheckTxConcurrently(mode execMode, txBytes []byte) (gInfo }() if app.anteHandler != nil { - var ( - anteCtx sdk.Context - msCache storetypes.CacheMultiStore - newCtx sdk.Context - ) + var newCtx sdk.Context - // Branch context before AnteHandler call in case it aborts. - // This is required for both CheckTx and DeliverTx. - // Ref: https://github.com/cosmos/cosmos-sdk/issues/2772 - // - // NOTE: Alternatively, we could require that AnteHandler ensures that - // writes do not happen if aborted/failed. This may have some - // performance benefits, but it'll be more difficult to get right. - anteCtx, msCache = app.cacheTxContext(ctx, txBytes) - anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) - newCtx, err = app.anteHandler(anteCtx, tx, mode == execModeSimulate) + // Typically the Cosmos SDK branches the context before passing it to the ante handler but here we + // allow the application's AnteHandler to control branching semantics for the context itself. + // We also guarantee that the passed in context is held with a read lock allowing for concurrent + // execution. + anteCtx := ctx.WithEventManager(sdk.NewEventManager()) + newCtx, err = app.anteHandler(anteCtx, tx, false /* mode == execModeSimulate */) if !newCtx.IsZero() { - // At this point, newCtx.MultiStore() is a store branch, or something else + // At this point, ctx.MultiStore() is a store branch, or something else // replaced by the AnteHandler. We want the original multistore. // // Also, in the case of the tx aborting, we need to track gas consumed via @@ -899,7 +896,6 @@ func (app *BaseApp) runCheckTxConcurrently(mode execMode, txBytes []byte) (gInfo return } - msCache.Write() anteEvents = events.ToABCIEvents() } @@ -1022,7 +1018,7 @@ func (app *BaseApp) runTx(mode execMode, txBytes []byte) (gInfo sdk.GasInfo, res // NOTE: Alternatively, we could require that AnteHandler ensures that // writes do not happen if aborted/failed. This may have some // performance benefits, but it'll be more difficult to get right. - anteCtx, msCache = app.cacheTxContext(ctx, txBytes) + anteCtx, msCache = cacheTxContext(ctx, txBytes) anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate) @@ -1065,7 +1061,7 @@ func (app *BaseApp) runTx(mode execMode, txBytes []byte) (gInfo sdk.GasInfo, res // Create a new Context based off of the existing Context with a MultiStore branch // in case message processing fails. At this point, the MultiStore // is a branch of a branch. - runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) + runMsgCtx, msCache := cacheTxContext(ctx, txBytes) // Attempt to execute all messages and only update state if all messages pass // and we're in DeliverTx. Note, runMsgs will never return a reference to a diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 1ba3fb82d4d8..f150c5af9c79 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -5,7 +5,9 @@ import ( "context" "crypto/sha256" "fmt" + "io" "math/rand" + "sync" "testing" "time" @@ -49,7 +51,7 @@ type ( baseApp *baseapp.BaseApp cdc *codec.ProtoCodec txConfig client.TxConfig - logBuffer *bytes.Buffer + logBuffer *SyncBuffer } SnapshotsConfig struct { @@ -61,13 +63,33 @@ type ( } ) +var _ io.Writer = &SyncBuffer{} + +type SyncBuffer struct { + mtx sync.Mutex + buffer bytes.Buffer +} + +func (s *SyncBuffer) Write(p []byte) (n int, err error) { + s.mtx.Lock() + defer s.mtx.Unlock() + return s.buffer.Write(p) +} + +func (s *SyncBuffer) String() string { + s.mtx.Lock() + defer s.mtx.Unlock() + return s.buffer.String() +} + func NewBaseAppSuite(t *testing.T, opts ...func(*baseapp.BaseApp)) *BaseAppSuite { cdc := codectestutil.CodecOptions{}.NewCodec() baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes) db := dbm.NewMemDB() - logBuffer := new(bytes.Buffer) + // Prevent race conditions during collection of logs in tests. + logBuffer := &SyncBuffer{} logger := log.NewLogger(logBuffer, log.ColorOption(false)) app := baseapp.NewBaseApp(t.Name(), logger, db, txConfig.TxDecoder(), opts...) @@ -197,12 +219,14 @@ func NewBaseAppSuiteWithSnapshots(t *testing.T, cfg SnapshotsConfig, opts ...fun func TestAnteHandlerGasMeter(t *testing.T) { // run BeginBlock and assert that the gas meter passed into the first Txn is zeroed out anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - gasMeter := ctx.BlockGasMeter() - require.NotNil(t, gasMeter) - require.Equal(t, storetypes.Gas(0), gasMeter.GasConsumed()) - return ctx, nil - }) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + gasMeter := ctx.BlockGasMeter() + require.NotNil(t, gasMeter) + require.Equal(t, storetypes.Gas(0), gasMeter.GasConsumed()) + return ctx, nil + }), + ) } // set the beginBlocker to use some gas beginBlockerOpt := func(bapp *baseapp.BaseApp) { @@ -516,9 +540,11 @@ func TestCustomRunTxPanicHandler(t *testing.T) { customPanicMsg := "test panic" anteErr := errorsmod.Register("fakeModule", 100500, "fakeError") anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - panic(errorsmod.Wrap(anteErr, "anteHandler")) - }) + bapp.SetAnteHandler(wrapWithLockAndCacheContextDecorator( + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + panic(errorsmod.Wrap(anteErr, "anteHandler")) + }), + ) } suite := NewBaseAppSuite(t, anteOpt) diff --git a/baseapp/locking.go b/baseapp/locking.go new file mode 100644 index 000000000000..b601425c4748 --- /dev/null +++ b/baseapp/locking.go @@ -0,0 +1,34 @@ +package baseapp + +import ( + "sync" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ sdk.AnteDecorator = lockAndCacheContextDecorator{} + +func NewLockAndCacheContextAnteDecorator() sdk.AnteDecorator { + return lockAndCacheContextDecorator{ + mtx: &sync.Mutex{}, + } +} + +type lockAndCacheContextDecorator struct { + mtx *sync.Mutex +} + +func (l lockAndCacheContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + l.mtx.Lock() + defer l.mtx.Unlock() + + var cacheMs storetypes.CacheMultiStore + ctx, cacheMs = cacheTxContext(ctx, ctx.TxBytes()) + newCtx, err := next(ctx, tx, simulate) + if err == nil { + cacheMs.Write() + } + return newCtx, err +} diff --git a/baseapp/locking_test.go b/baseapp/locking_test.go new file mode 100644 index 000000000000..43193df34bd3 --- /dev/null +++ b/baseapp/locking_test.go @@ -0,0 +1,133 @@ +package baseapp_test + +import ( + "encoding/binary" + "fmt" + "sync" + "testing" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + "cosmossdk.io/store" + storemetrics "cosmossdk.io/store/metrics" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestLockAndCacheContextDecorator(t *testing.T) { + tests := map[string]struct { + err error + }{ + "AnteHandle succeeds": {}, + "AnteHandle returns error": { + err: fmt.Errorf("Fake test failure"), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + key := []byte("key") + db := dbm.NewMemDB() + + storeKey := storetypes.NewKVStoreKey("test") + cms := store.NewCommitMultiStore(db, log.NewNopLogger(), storemetrics.NewNoOpMetrics()) + cms.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + ctx := sdk.NewContext(cms, cmtproto.Header{}, true, log.NewNopLogger()) + cms.LoadLatestVersion() + + cms.GetKVStore(storeKey).Set(key, encode(0)) + ctx.WithTxBytes([]byte("fake tx bytes")) + + l := baseapp.NewLockAndCacheContextAnteDecorator() + + _, err := l.AnteHandle( + ctx, + nil, + false, + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + store := ctx.MultiStore().GetKVStore(storeKey) + store.Set(key, encode(decode(store.Get(key))+1)) + return ctx, tc.err + }, + ) + + actual := int(decode(cms.GetKVStore(storeKey).Get(key))) + + require.Equal(t, tc.err, err) + if tc.err != nil { + require.Equal(t, 0, actual) + } else { + require.Equal(t, 1, actual) + } + }) + } +} + +func TestLockAndCacheContextDecorator_Concurrency(t *testing.T) { + const numThreads = 999 + key := []byte("key") + db := dbm.NewMemDB() + + errResp := fmt.Errorf("Fake test failure") + + storeKey := storetypes.NewKVStoreKey("test") + cms := store.NewCommitMultiStore(db, log.NewNopLogger(), storemetrics.NewNoOpMetrics()) + cms.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + ctx := sdk.NewContext(cms, cmtproto.Header{}, true, log.NewNopLogger()) + cms.LoadLatestVersion() + + cms.GetKVStore(storeKey).Set(key, encode(0)) + ctx.WithTxBytes([]byte("fake tx bytes")) + + l := baseapp.NewLockAndCacheContextAnteDecorator() + + wg := sync.WaitGroup{} + wg.Add(numThreads) + for i := 0; i < numThreads; i++ { + ii := i + go func() { + _, err := l.AnteHandle( + ctx, + nil, + false, + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + store := ctx.MultiStore().GetKVStore(storeKey) + store.Set(key, encode(decode(store.Get(key))+1)) + if ii%3 == 0 { + return ctx, nil + } + return ctx, errResp + }, + ) + wg.Done() + + if ii%3 == 0 { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }() + } + wg.Wait() + + // We run 1/3 the goroutines with failures and 2/3 with successes. Since each run is expected to execute + // independently of each other after the locking decorator we know that exactly 2/3's must succeed. + + actual := int(decode(cms.GetKVStore(storeKey).Get(key))) + require.Equal(t, numThreads/3, actual) +} + +func encode(v uint32) []byte { + rval := make([]byte, 4) + binary.LittleEndian.PutUint32(rval, v) + return rval +} + +func decode(v []byte) uint32 { + return binary.LittleEndian.Uint32(v) +} diff --git a/baseapp/utils_test.go b/baseapp/utils_test.go index 1de526953fae..74b4e6ec62b0 100644 --- a/baseapp/utils_test.go +++ b/baseapp/utils_test.go @@ -210,7 +210,7 @@ func counterEvent(evType string, msgCount int64) sdk.Events { } func anteHandlerTxTest(t *testing.T, capKey storetypes.StoreKey, storeKey []byte) sdk.AnteHandler { - return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + return wrapWithLockAndCacheContextDecorator(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { store := ctx.KVStore(capKey) counter, failOnAnte := parseTxMemo(t, tx) @@ -229,7 +229,7 @@ func anteHandlerTxTest(t *testing.T, capKey storetypes.StoreKey, storeKey []byte ctx = ctx.WithPriority(testTxPriority) return ctx, nil - } + }) } func incrementingCounter(t *testing.T, store storetypes.KVStore, counterKey []byte, counter int64) (*sdk.Result, error) { diff --git a/simapp/ante.go b/simapp/ante.go index 58e85a7597dc..ef4ce5626bc6 100644 --- a/simapp/ante.go +++ b/simapp/ante.go @@ -5,6 +5,7 @@ import ( circuitante "cosmossdk.io/x/circuit/ante" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/ante" ) @@ -32,6 +33,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { } anteDecorators := []sdk.AnteDecorator{ + baseapp.NewLockAndCacheContextAnteDecorator(), ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper), ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go index 8b2277aeb88b..b0ced66b145c 100644 --- a/x/auth/ante/ante.go +++ b/x/auth/ante/ante.go @@ -5,6 +5,7 @@ import ( storetypes "cosmossdk.io/store/types" txsigning "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -39,6 +40,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { } anteDecorators := []sdk.AnteDecorator{ + baseapp.NewLockAndCacheContextAnteDecorator(), NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first NewExtensionOptionsDecorator(options.ExtensionOptionChecker), NewValidateBasicDecorator(),