Skip to content

Commit eeb3797

Browse files
authored
Merge pull request cosmos#322 from CosmWasm/handle_oog_321
Handle panics in query contract smart
2 parents ccc378e + 1d3bc0e commit eeb3797

File tree

4 files changed

+78
-10
lines changed

4 files changed

+78
-10
lines changed

x/wasm/internal/keeper/keeper.go

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package keeper
33
import (
44
"bytes"
55
"encoding/binary"
6+
"fmt"
67
"path/filepath"
78

89
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
@@ -18,6 +19,7 @@ import (
1819
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
1920
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
2021
"github.com/tendermint/tendermint/crypto"
22+
"github.com/tendermint/tendermint/libs/log"
2123
)
2224

2325
// GasMultiplier is how many cosmwasm gas points = 1 sdk gas point
@@ -710,3 +712,8 @@ func gasMeter(ctx sdk.Context) MultipiedGasMeter {
710712
originalMeter: ctx.GasMeter(),
711713
}
712714
}
715+
716+
// Logger returns a module-specific logger.
717+
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
718+
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
719+
}

x/wasm/internal/keeper/querier.go

+25-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package keeper
33
import (
44
"context"
55
"encoding/binary"
6+
"runtime/debug"
67

78
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
89
"github.com/cosmos/cosmos-sdk/store/prefix"
@@ -146,21 +147,41 @@ func (q grpcQuerier) RawContractState(c context.Context, req *types.QueryRawCont
146147
return &types.QueryRawContractStateResponse{Data: rsp}, nil
147148
}
148149

149-
func (q grpcQuerier) SmartContractState(c context.Context, req *types.QuerySmartContractStateRequest) (*types.QuerySmartContractStateResponse, error) {
150+
func (q grpcQuerier) SmartContractState(c context.Context, req *types.QuerySmartContractStateRequest) (rsp *types.QuerySmartContractStateResponse, err error) {
150151
contractAddr, err := sdk.AccAddressFromBech32(req.Address)
151152
if err != nil {
152153
return nil, err
153154
}
154155
ctx := sdk.UnwrapSDKContext(c).WithGasMeter(sdk.NewGasMeter(q.keeper.queryGasLimit))
156+
// recover from out-of-gas panic
157+
defer func() {
158+
if r := recover(); r != nil {
159+
switch rType := r.(type) {
160+
case sdk.ErrorOutOfGas:
161+
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas,
162+
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
163+
rType.Descriptor, ctx.GasMeter().Limit(), ctx.GasMeter().GasConsumed(),
164+
)
165+
default:
166+
err = sdkerrors.ErrPanic
167+
}
168+
rsp = nil
169+
q.keeper.Logger(ctx).
170+
Debug("smart query contract",
171+
"error", "recovering panic",
172+
"contract-address", req.Address,
173+
"stacktrace", string(debug.Stack()))
174+
}
175+
}()
155176

156-
rsp, err := q.keeper.QuerySmart(ctx, contractAddr, req.QueryData)
177+
bz, err := q.keeper.QuerySmart(ctx, contractAddr, req.QueryData)
157178
switch {
158179
case err != nil:
159180
return nil, err
160-
case rsp == nil:
181+
case bz == nil:
161182
return nil, types.ErrNotFound
162183
}
163-
return &types.QuerySmartContractStateResponse{Data: rsp}, nil
184+
return &types.QuerySmartContractStateResponse{Data: bz}, nil
164185

165186
}
166187

x/wasm/internal/keeper/querier_test.go

+43-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import (
88
"testing"
99

1010
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
11+
"github.com/CosmWasm/wasmvm"
12+
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
1113
sdk "github.com/cosmos/cosmos-sdk/types"
1214
sdkErrors "github.com/cosmos/cosmos-sdk/types/errors"
1315
"github.com/cosmos/cosmos-sdk/types/query"
1416
"github.com/stretchr/testify/assert"
1517
"github.com/stretchr/testify/require"
18+
"github.com/tendermint/tendermint/libs/log"
1619
)
1720

1821
func TestQueryAllContractState(t *testing.T) {
@@ -136,7 +139,7 @@ func TestQuerySmartContractState(t *testing.T) {
136139
for msg, spec := range specs {
137140
t.Run(msg, func(t *testing.T) {
138141
got, err := q.SmartContractState(sdk.WrapSDKContext(ctx), spec.srcQuery)
139-
require.True(t, spec.expErr.Is(err), err)
142+
require.True(t, spec.expErr.Is(err), "but got %+v", err)
140143
if spec.expErr != nil {
141144
return
142145
}
@@ -145,6 +148,45 @@ func TestQuerySmartContractState(t *testing.T) {
145148
}
146149
}
147150

151+
func TestQuerySmartContractPanics(t *testing.T) {
152+
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
153+
exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers)
154+
ctx = ctx.WithGasMeter(sdk.NewGasMeter(InstanceCost)).WithLogger(log.TestingLogger())
155+
156+
specs := map[string]struct {
157+
doInContract func()
158+
expErr *sdkErrors.Error
159+
}{
160+
"out of gas": {
161+
doInContract: func() {
162+
ctx.GasMeter().ConsumeGas(ctx.GasMeter().Limit()+1, "test - consume more than limit")
163+
},
164+
expErr: sdkErrors.ErrOutOfGas,
165+
},
166+
"other panic": {
167+
doInContract: func() {
168+
panic("my panic")
169+
},
170+
expErr: sdkErrors.ErrPanic,
171+
},
172+
}
173+
for msg, spec := range specs {
174+
t.Run(msg, func(t *testing.T) {
175+
keepers.WasmKeeper.wasmer = &MockWasmer{QueryFn: func(code cosmwasm.CodeID, env wasmvmtypes.Env, queryMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) ([]byte, uint64, error) {
176+
spec.doInContract()
177+
return nil, 0, nil
178+
}}
179+
// when
180+
q := NewQuerier(keepers.WasmKeeper)
181+
got, err := q.SmartContractState(sdk.WrapSDKContext(ctx), &types.QuerySmartContractStateRequest{
182+
Address: exampleContract.Contract.String(),
183+
})
184+
require.True(t, spec.expErr.Is(err), "got error: %+v", err)
185+
assert.Nil(t, got)
186+
})
187+
}
188+
}
189+
148190
func TestQueryRawContractState(t *testing.T) {
149191
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
150192
keeper := keepers.WasmKeeper

x/wasm/internal/keeper/recurse_test.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,10 @@ func initRecurseContract(t *testing.T) (contract sdk.AccAddress, creator sdk.Acc
5555
}
5656

5757
func TestGasCostOnQuery(t *testing.T) {
58-
t.Skip("Alex: enable later when the model + gas costs become clear")
5958
const (
60-
GasNoWork uint64 = InstanceCost + 2_938
59+
GasNoWork uint64 = 43229
6160
// Note: about 100 SDK gas (10k wasmer gas) for each round of sha256
62-
GasWork50 uint64 = 48646 // this is a little shy of 50k gas - to keep an eye on the limit
61+
GasWork50 uint64 = 48937 // this is a little shy of 50k gas - to keep an eye on the limit
6362

6463
GasReturnUnhashed uint64 = 393
6564
GasReturnHashed uint64 = 342
@@ -214,7 +213,6 @@ func TestGasOnExternalQuery(t *testing.T) {
214213
}
215214

216215
func TestLimitRecursiveQueryGas(t *testing.T) {
217-
t.Skip("Alex: enable later when the model + gas costs become clear")
218216
// The point of this test from https://github.com/CosmWasm/cosmwasm/issues/456
219217
// Basically, if I burn 90% of gas in CPU loop, then query out (to my self)
220218
// the sub-query will have all the original gas (minus the 40k instance charge)
@@ -224,7 +222,7 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
224222

225223
const (
226224
// Note: about 100 SDK gas (10k wasmer gas) for each round of sha256
227-
GasWork2k uint64 = 273_560 // = InstanceCost + x // we have 6x gas used in cpu than in the instance
225+
GasWork2k uint64 = 273_851 // = InstanceCost + x // we have 6x gas used in cpu than in the instance
228226
// This is overhead for calling into a sub-contract
229227
GasReturnHashed uint64 = 349
230228
)

0 commit comments

Comments
 (0)