cosmos-sdk/x/auth/middleware/recovery.go

102 lines
2.8 KiB
Go

package middleware
import (
"context"
"runtime/debug"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
)
type recoveryTxHandler struct {
next tx.Handler
}
// RecoveryTxMiddleware defines a middleware that catches all panics that
// happen in inner middlewares.
//
// Be careful, it won't catch any panics happening outside!
func RecoveryTxMiddleware(txh tx.Handler) tx.Handler {
return recoveryTxHandler{next: txh}
}
var _ tx.Handler = recoveryTxHandler{}
// CheckTx implements tx.Handler.CheckTx method.
func (txh recoveryTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (res tx.Response, resCheckTx tx.ResponseCheckTx, err error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// Panic recovery.
defer func() {
if r := recover(); r != nil {
err = handleRecovery(r, sdkCtx)
}
}()
return txh.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler.DeliverTx method.
func (txh recoveryTxHandler) DeliverTx(ctx context.Context, req tx.Request) (res tx.Response, err error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// only run the tx if there is block gas remaining
if sdkCtx.BlockGasMeter().IsOutOfGas() {
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
return
}
startingGas := sdkCtx.BlockGasMeter().GasConsumed()
// Panic recovery.
defer func() {
if r := recover(); r != nil {
err = handleRecovery(r, sdkCtx)
}
}()
// If BlockGasMeter() panics it will be caught by the above recover and will
// return an error - in any case BlockGasMeter will consume gas past the limit.
//
// NOTE: This must exist in a separate defer function for the above recovery
// to recover from this one.
defer func() {
sdkCtx.BlockGasMeter().ConsumeGas(
sdkCtx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
if sdkCtx.BlockGasMeter().GasConsumed() < startingGas {
panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"})
}
}()
return txh.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler.SimulateTx method.
func (txh recoveryTxHandler) SimulateTx(ctx context.Context, req tx.Request) (res tx.Response, err error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// Panic recovery.
defer func() {
if r := recover(); r != nil {
err = handleRecovery(r, sdkCtx)
}
}()
return txh.next.SimulateTx(ctx, req)
}
func handleRecovery(r interface{}, sdkCtx sdk.Context) error {
switch r := r.(type) {
case sdk.ErrorOutOfGas:
return sdkerrors.Wrapf(sdkerrors.ErrOutOfGas,
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
r.Descriptor, sdkCtx.GasMeter().Limit(), sdkCtx.GasMeter().GasConsumed(),
)
default:
return sdkerrors.ErrPanic.Wrapf(
"recovered: %v\nstack:\n%v", r, string(debug.Stack()),
)
}
}