Merge PR #5421: Refactor Error Handling
This commit is contained in:
+16
-8
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
@@ -33,6 +34,13 @@ var (
|
||||
tenInt = big.NewInt(10)
|
||||
)
|
||||
|
||||
// Decimal errors
|
||||
var (
|
||||
ErrEmptyDecimalStr = errors.New("decimal string cannot be empty")
|
||||
ErrInvalidDecimalLength = errors.New("invalid decimal length")
|
||||
ErrInvalidDecimalStr = errors.New("invalid decimal string")
|
||||
)
|
||||
|
||||
// Set precision multipliers
|
||||
func init() {
|
||||
precisionMultipliers = make([]*big.Int, Precision+1)
|
||||
@@ -123,9 +131,9 @@ func NewDecFromIntWithPrec(i Int, prec int64) Dec {
|
||||
// are provided in the string than the constant Precision.
|
||||
//
|
||||
// CONTRACT - This function does not mutate the input str.
|
||||
func NewDecFromStr(str string) (d Dec, err Error) {
|
||||
func NewDecFromStr(str string) (Dec, error) {
|
||||
if len(str) == 0 {
|
||||
return d, ErrUnknownRequest("decimal string is empty")
|
||||
return Dec{}, ErrEmptyDecimalStr
|
||||
}
|
||||
|
||||
// first extract any negative symbol
|
||||
@@ -136,7 +144,7 @@ func NewDecFromStr(str string) (d Dec, err Error) {
|
||||
}
|
||||
|
||||
if len(str) == 0 {
|
||||
return d, ErrUnknownRequest("decimal string is empty")
|
||||
return Dec{}, ErrEmptyDecimalStr
|
||||
}
|
||||
|
||||
strs := strings.Split(str, ".")
|
||||
@@ -146,17 +154,16 @@ func NewDecFromStr(str string) (d Dec, err Error) {
|
||||
if len(strs) == 2 { // has a decimal place
|
||||
lenDecs = len(strs[1])
|
||||
if lenDecs == 0 || len(combinedStr) == 0 {
|
||||
return d, ErrUnknownRequest("bad decimal length")
|
||||
return Dec{}, ErrInvalidDecimalLength
|
||||
}
|
||||
combinedStr += strs[1]
|
||||
|
||||
} else if len(strs) > 2 {
|
||||
return d, ErrUnknownRequest("too many periods to be a decimal string")
|
||||
return Dec{}, ErrInvalidDecimalStr
|
||||
}
|
||||
|
||||
if lenDecs > Precision {
|
||||
return d, ErrUnknownRequest(
|
||||
fmt.Sprintf("too much precision, maximum %v, len decimal %v", Precision, lenDecs))
|
||||
return Dec{}, fmt.Errorf("invalid precision; max: %d, got: %d", Precision, lenDecs)
|
||||
}
|
||||
|
||||
// add some extra zero's to correct to the Precision factor
|
||||
@@ -166,11 +173,12 @@ func NewDecFromStr(str string) (d Dec, err Error) {
|
||||
|
||||
combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10
|
||||
if !ok {
|
||||
return d, ErrUnknownRequest(fmt.Sprintf("bad string to integer conversion, combinedStr: %v", combinedStr))
|
||||
return Dec{}, fmt.Errorf("failed to set decimal string: %s", combinedStr)
|
||||
}
|
||||
if neg {
|
||||
combined = new(big.Int).Neg(combined)
|
||||
}
|
||||
|
||||
return Dec{combined}, nil
|
||||
}
|
||||
|
||||
|
||||
-361
@@ -1,361 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// CodeType - ABCI code identifier within codespace
|
||||
type CodeType uint32
|
||||
|
||||
// CodespaceType - codespace identifier
|
||||
type CodespaceType string
|
||||
|
||||
// IsOK - is everything okay?
|
||||
func (code CodeType) IsOK() bool {
|
||||
return code == CodeOK
|
||||
}
|
||||
|
||||
// SDK error codes
|
||||
const (
|
||||
// Base error codes
|
||||
CodeOK CodeType = 0
|
||||
CodeInternal CodeType = 1
|
||||
CodeTxDecode CodeType = 2
|
||||
CodeInvalidSequence CodeType = 3
|
||||
CodeUnauthorized CodeType = 4
|
||||
CodeInsufficientFunds CodeType = 5
|
||||
CodeUnknownRequest CodeType = 6
|
||||
CodeInvalidAddress CodeType = 7
|
||||
CodeInvalidPubKey CodeType = 8
|
||||
CodeUnknownAddress CodeType = 9
|
||||
CodeInsufficientCoins CodeType = 10
|
||||
CodeInvalidCoins CodeType = 11
|
||||
CodeOutOfGas CodeType = 12
|
||||
CodeMemoTooLarge CodeType = 13
|
||||
CodeInsufficientFee CodeType = 14
|
||||
CodeTooManySignatures CodeType = 15
|
||||
CodeGasOverflow CodeType = 16
|
||||
CodeNoSignatures CodeType = 17
|
||||
CodeTxInMempoolCache CodeType = 18
|
||||
CodeMempoolIsFull CodeType = 19
|
||||
CodeTxTooLarge CodeType = 20
|
||||
|
||||
// CodespaceRoot is a codespace for error codes in this file only.
|
||||
// Notice that 0 is an "unset" codespace, which can be overridden with
|
||||
// Error.WithDefaultCodespace().
|
||||
CodespaceUndefined CodespaceType = ""
|
||||
CodespaceRoot CodespaceType = "sdk"
|
||||
)
|
||||
|
||||
func unknownCodeMsg(code CodeType) string {
|
||||
return fmt.Sprintf("unknown code %d", code)
|
||||
}
|
||||
|
||||
// NOTE: Don't stringer this, we'll put better messages in later.
|
||||
func CodeToDefaultMsg(code CodeType) string {
|
||||
switch code {
|
||||
case CodeInternal:
|
||||
return "internal error"
|
||||
case CodeTxDecode:
|
||||
return "tx parse error"
|
||||
case CodeInvalidSequence:
|
||||
return "invalid sequence"
|
||||
case CodeUnauthorized:
|
||||
return "unauthorized"
|
||||
case CodeInsufficientFunds:
|
||||
return "insufficient funds"
|
||||
case CodeUnknownRequest:
|
||||
return "unknown request"
|
||||
case CodeInvalidAddress:
|
||||
return "invalid address"
|
||||
case CodeInvalidPubKey:
|
||||
return "invalid pubkey"
|
||||
case CodeUnknownAddress:
|
||||
return "unknown address"
|
||||
case CodeInsufficientCoins:
|
||||
return "insufficient coins"
|
||||
case CodeInvalidCoins:
|
||||
return "invalid coins"
|
||||
case CodeOutOfGas:
|
||||
return "out of gas"
|
||||
case CodeMemoTooLarge:
|
||||
return "memo too large"
|
||||
case CodeInsufficientFee:
|
||||
return "insufficient fee"
|
||||
case CodeTooManySignatures:
|
||||
return "maximum numer of signatures exceeded"
|
||||
case CodeNoSignatures:
|
||||
return "no signatures supplied"
|
||||
default:
|
||||
return unknownCodeMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// All errors are created via constructors so as to enable us to hijack them
|
||||
// and inject stack traces if we really want to.
|
||||
|
||||
// nolint
|
||||
func ErrInternal(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInternal, msg)
|
||||
}
|
||||
func ErrTxDecode(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeTxDecode, msg)
|
||||
}
|
||||
func ErrInvalidSequence(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInvalidSequence, msg)
|
||||
}
|
||||
func ErrUnauthorized(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeUnauthorized, msg)
|
||||
}
|
||||
func ErrInsufficientFunds(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInsufficientFunds, msg)
|
||||
}
|
||||
func ErrUnknownRequest(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeUnknownRequest, msg)
|
||||
}
|
||||
func ErrInvalidAddress(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInvalidAddress, msg)
|
||||
}
|
||||
func ErrUnknownAddress(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeUnknownAddress, msg)
|
||||
}
|
||||
func ErrInvalidPubKey(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInvalidPubKey, msg)
|
||||
}
|
||||
func ErrInsufficientCoins(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInsufficientCoins, msg)
|
||||
}
|
||||
func ErrInvalidCoins(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInvalidCoins, msg)
|
||||
}
|
||||
func ErrOutOfGas(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeOutOfGas, msg)
|
||||
}
|
||||
func ErrMemoTooLarge(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeMemoTooLarge, msg)
|
||||
}
|
||||
func ErrInsufficientFee(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeInsufficientFee, msg)
|
||||
}
|
||||
func ErrTooManySignatures(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeTooManySignatures, msg)
|
||||
}
|
||||
func ErrNoSignatures(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeNoSignatures, msg)
|
||||
}
|
||||
func ErrGasOverflow(msg string) Error {
|
||||
return newErrorWithRootCodespace(CodeGasOverflow, msg)
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// Error & sdkError
|
||||
|
||||
type cmnError = cmn.Error
|
||||
|
||||
// sdk Error type
|
||||
type Error interface {
|
||||
// Implements cmn.Error
|
||||
// Error() string
|
||||
// Stacktrace() cmn.Error
|
||||
// Trace(offset int, format string, args ...interface{}) cmn.Error
|
||||
// Data() interface{}
|
||||
cmnError
|
||||
|
||||
// convenience
|
||||
TraceSDK(format string, args ...interface{}) Error
|
||||
|
||||
// set codespace
|
||||
WithDefaultCodespace(CodespaceType) Error
|
||||
|
||||
Code() CodeType
|
||||
Codespace() CodespaceType
|
||||
ABCILog() string
|
||||
Result() Result
|
||||
QueryResult() abci.ResponseQuery
|
||||
}
|
||||
|
||||
// NewError - create an error.
|
||||
func NewError(codespace CodespaceType, code CodeType, format string, args ...interface{}) Error {
|
||||
return newError(codespace, code, format, args...)
|
||||
}
|
||||
|
||||
func newErrorWithRootCodespace(code CodeType, format string, args ...interface{}) *sdkError {
|
||||
return newError(CodespaceRoot, code, format, args...)
|
||||
}
|
||||
|
||||
func newError(codespace CodespaceType, code CodeType, format string, args ...interface{}) *sdkError {
|
||||
if format == "" {
|
||||
format = CodeToDefaultMsg(code)
|
||||
}
|
||||
return &sdkError{
|
||||
codespace: codespace,
|
||||
code: code,
|
||||
cmnError: cmn.NewError(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
type sdkError struct {
|
||||
codespace CodespaceType
|
||||
code CodeType
|
||||
cmnError
|
||||
}
|
||||
|
||||
// Implements Error.
|
||||
func (err *sdkError) WithDefaultCodespace(cs CodespaceType) Error {
|
||||
codespace := err.codespace
|
||||
if codespace == CodespaceUndefined {
|
||||
codespace = cs
|
||||
}
|
||||
return &sdkError{
|
||||
codespace: cs,
|
||||
code: err.code,
|
||||
cmnError: err.cmnError,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements ABCIError.
|
||||
// nolint: errcheck
|
||||
func (err *sdkError) TraceSDK(format string, args ...interface{}) Error {
|
||||
err.Trace(1, format, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Implements ABCIError.
|
||||
func (err *sdkError) Error() string {
|
||||
return fmt.Sprintf(`ERROR:
|
||||
Codespace: %s
|
||||
Code: %d
|
||||
Message: %#v
|
||||
`, err.codespace, err.code, err.cmnError.Error())
|
||||
}
|
||||
|
||||
// Implements Error.
|
||||
func (err *sdkError) Codespace() CodespaceType {
|
||||
return err.codespace
|
||||
}
|
||||
|
||||
// Implements Error.
|
||||
func (err *sdkError) Code() CodeType {
|
||||
return err.code
|
||||
}
|
||||
|
||||
// Implements ABCIError.
|
||||
func (err *sdkError) ABCILog() string {
|
||||
errMsg := err.cmnError.Error()
|
||||
return encodeErrorLog(err.codespace, err.code, errMsg)
|
||||
}
|
||||
|
||||
func encodeErrorLog(codespace CodespaceType, code CodeType, msg string) string {
|
||||
jsonErr := humanReadableError{
|
||||
Codespace: codespace,
|
||||
Code: code,
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
enc := json.NewEncoder(&buff)
|
||||
enc.SetEscapeHTML(false)
|
||||
|
||||
if err := enc.Encode(jsonErr); err != nil {
|
||||
panic(errors.Wrap(err, "failed to encode ABCI error log"))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buff.String())
|
||||
}
|
||||
|
||||
func (err *sdkError) Result() Result {
|
||||
return Result{
|
||||
Code: err.Code(),
|
||||
Codespace: err.Codespace(),
|
||||
Log: err.ABCILog(),
|
||||
}
|
||||
}
|
||||
|
||||
// QueryResult allows us to return sdk.Error.QueryResult() in query responses
|
||||
func (err *sdkError) QueryResult() abci.ResponseQuery {
|
||||
return abci.ResponseQuery{
|
||||
Code: uint32(err.Code()),
|
||||
Codespace: string(err.Codespace()),
|
||||
Log: err.ABCILog(),
|
||||
}
|
||||
}
|
||||
|
||||
// ResultFromError will return err.Result() if it implements sdk.Error
|
||||
// Otherwise, it will use the reflecton from types/error to determine
|
||||
// the code, codespace, and log.
|
||||
//
|
||||
// This is intended to provide a bridge to allow both error types
|
||||
// to live side-by-side.
|
||||
func ResultFromError(err error) Result {
|
||||
if sdk, ok := err.(Error); ok {
|
||||
return sdk.Result()
|
||||
}
|
||||
space, code, log := sdkerrors.ABCIInfo(err, false)
|
||||
return Result{
|
||||
Codespace: CodespaceType(space),
|
||||
Code: CodeType(code),
|
||||
Log: encodeErrorLog(CodespaceType(space), CodeType(code), log),
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertError accepts a standard error and attempts to convert it to an sdk.Error.
|
||||
// If the given error is already an sdk.Error, it'll simply be returned. Otherwise,
|
||||
// it'll convert it to a types.Error. This is meant to provide a migration path
|
||||
// away from sdk.Error in favor of types.Error.
|
||||
func ConvertError(err error) Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if sdkError, ok := err.(Error); ok {
|
||||
return sdkError
|
||||
}
|
||||
|
||||
space, code, log := sdkerrors.ABCIInfo(err, false)
|
||||
return NewError(CodespaceType(space), CodeType(code), log)
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// REST error utilities
|
||||
|
||||
// appends a message to the head of the given error
|
||||
func AppendMsgToErr(msg string, err string) string {
|
||||
msgIdx := strings.Index(err, "message\":\"")
|
||||
if msgIdx != -1 {
|
||||
errMsg := err[msgIdx+len("message\":\"") : len(err)-2]
|
||||
errMsg = fmt.Sprintf("%s; %s", msg, errMsg)
|
||||
return fmt.Sprintf("%s%s%s",
|
||||
err[:msgIdx+len("message\":\"")],
|
||||
errMsg,
|
||||
err[len(err)-2:],
|
||||
)
|
||||
}
|
||||
return fmt.Sprintf("%s; %s", msg, err)
|
||||
}
|
||||
|
||||
// returns the index of the message in the ABCI Log
|
||||
// nolint:deadcode,unused
|
||||
func mustGetMsgIndex(abciLog string) int {
|
||||
msgIdx := strings.Index(abciLog, "message\":\"")
|
||||
if msgIdx == -1 {
|
||||
panic(fmt.Sprintf("invalid error format: %s", abciLog))
|
||||
}
|
||||
return msgIdx + len("message\":\"")
|
||||
}
|
||||
|
||||
// parses the error into an object-like struct for exporting
|
||||
type humanReadableError struct {
|
||||
Codespace CodespaceType `json:"codespace"`
|
||||
Code CodeType `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,6 +42,43 @@ func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string)
|
||||
return abciCodespace(err), abciCode(err), encode(err)
|
||||
}
|
||||
|
||||
// ResponseCheckTx returns an ABCI ResponseCheckTx object with fields filled in
|
||||
// from the given error and gas values.
|
||||
func ResponseCheckTx(err error, gw, gu uint64) abci.ResponseCheckTx {
|
||||
space, code, log := ABCIInfo(err, false)
|
||||
return abci.ResponseCheckTx{
|
||||
Codespace: space,
|
||||
Code: code,
|
||||
Log: log,
|
||||
GasWanted: int64(gw),
|
||||
GasUsed: int64(gu),
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseDeliverTx returns an ABCI ResponseDeliverTx object with fields filled in
|
||||
// from the given error and gas values.
|
||||
func ResponseDeliverTx(err error, gw, gu uint64) abci.ResponseDeliverTx {
|
||||
space, code, log := ABCIInfo(err, false)
|
||||
return abci.ResponseDeliverTx{
|
||||
Codespace: space,
|
||||
Code: code,
|
||||
Log: log,
|
||||
GasWanted: int64(gw),
|
||||
GasUsed: int64(gu),
|
||||
}
|
||||
}
|
||||
|
||||
// QueryResult returns a ResponseQuery from an error. It will try to parse ABCI
|
||||
// info from the error.
|
||||
func QueryResult(err error) abci.ResponseQuery {
|
||||
space, code, log := ABCIInfo(err, false)
|
||||
return abci.ResponseQuery{
|
||||
Codespace: space,
|
||||
Code: code,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// The debugErrEncoder encodes the error with a stacktrace.
|
||||
func debugErrEncoder(err error) string {
|
||||
return fmt.Sprintf("%+v", err)
|
||||
|
||||
+13
-13
@@ -15,17 +15,17 @@ func TestABCInfo(t *testing.T) {
|
||||
wantSpace string
|
||||
wantLog string
|
||||
}{
|
||||
"plain weave error": {
|
||||
"plain SDK error": {
|
||||
err: ErrUnauthorized,
|
||||
debug: false,
|
||||
wantLog: "unauthorized",
|
||||
wantCode: ErrUnauthorized.code,
|
||||
wantSpace: RootCodespace,
|
||||
},
|
||||
"wrapped weave error": {
|
||||
"wrapped SDK error": {
|
||||
err: Wrap(Wrap(ErrUnauthorized, "foo"), "bar"),
|
||||
debug: false,
|
||||
wantLog: "bar: foo: unauthorized",
|
||||
wantLog: "unauthorized: foo: bar",
|
||||
wantCode: ErrUnauthorized.code,
|
||||
wantSpace: RootCodespace,
|
||||
},
|
||||
@@ -36,7 +36,7 @@ func TestABCInfo(t *testing.T) {
|
||||
wantCode: 0,
|
||||
wantSpace: "",
|
||||
},
|
||||
"nil weave error is not an error": {
|
||||
"nil SDK error is not an error": {
|
||||
err: (*Error)(nil),
|
||||
debug: false,
|
||||
wantLog: "",
|
||||
@@ -112,23 +112,23 @@ func TestABCIInfoStacktrace(t *testing.T) {
|
||||
wantStacktrace bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
"wrapped weave error in debug mode provides stacktrace": {
|
||||
"wrapped SDK error in debug mode provides stacktrace": {
|
||||
err: Wrap(ErrUnauthorized, "wrapped"),
|
||||
debug: true,
|
||||
wantStacktrace: true,
|
||||
wantErrMsg: "wrapped: unauthorized",
|
||||
wantErrMsg: "unauthorized: wrapped",
|
||||
},
|
||||
"wrapped weave error in non-debug mode does not have stacktrace": {
|
||||
"wrapped SDK error in non-debug mode does not have stacktrace": {
|
||||
err: Wrap(ErrUnauthorized, "wrapped"),
|
||||
debug: false,
|
||||
wantStacktrace: false,
|
||||
wantErrMsg: "wrapped: unauthorized",
|
||||
wantErrMsg: "unauthorized: wrapped",
|
||||
},
|
||||
"wrapped stdlib error in debug mode provides stacktrace": {
|
||||
err: Wrap(fmt.Errorf("stdlib"), "wrapped"),
|
||||
debug: true,
|
||||
wantStacktrace: true,
|
||||
wantErrMsg: "wrapped: stdlib",
|
||||
wantErrMsg: "stdlib: wrapped",
|
||||
},
|
||||
"wrapped stdlib error in non-debug mode does not have stacktrace": {
|
||||
err: Wrap(fmt.Errorf("stdlib"), "wrapped"),
|
||||
@@ -163,7 +163,7 @@ func TestABCIInfoHidesStacktrace(t *testing.T) {
|
||||
err := Wrap(ErrUnauthorized, "wrapped")
|
||||
_, _, log := ABCIInfo(err, false)
|
||||
|
||||
if log != "wrapped: unauthorized" {
|
||||
if log != "unauthorized: wrapped" {
|
||||
t.Fatalf("unexpected message in non debug mode: %s", log)
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func TestRedact(t *testing.T) {
|
||||
t.Error("reduct must not pass through panic error")
|
||||
}
|
||||
if err := Redact(ErrUnauthorized); !ErrUnauthorized.Is(err) {
|
||||
t.Error("reduct should pass through weave error")
|
||||
t.Error("reduct should pass through SDK error")
|
||||
}
|
||||
|
||||
var cerr customErr
|
||||
@@ -203,12 +203,12 @@ func TestABCIInfoSerializeErr(t *testing.T) {
|
||||
"single error": {
|
||||
src: myErrDecode,
|
||||
debug: false,
|
||||
exp: "test: tx parse error",
|
||||
exp: "tx parse error: test",
|
||||
},
|
||||
"second error": {
|
||||
src: myErrAddr,
|
||||
debug: false,
|
||||
exp: "tester: invalid address",
|
||||
exp: "invalid address: tester",
|
||||
},
|
||||
"single error with debug": {
|
||||
src: myErrDecode,
|
||||
|
||||
+26
-17
@@ -44,32 +44,43 @@ var (
|
||||
// ErrUnknownAddress to doc
|
||||
ErrUnknownAddress = Register(RootCodespace, 9, "unknown address")
|
||||
|
||||
// ErrInsufficientCoins to doc (what is the difference between ErrInsufficientFunds???)
|
||||
ErrInsufficientCoins = Register(RootCodespace, 10, "insufficient coins")
|
||||
|
||||
// ErrInvalidCoins to doc
|
||||
ErrInvalidCoins = Register(RootCodespace, 11, "invalid coins")
|
||||
ErrInvalidCoins = Register(RootCodespace, 10, "invalid coins")
|
||||
|
||||
// ErrOutOfGas to doc
|
||||
ErrOutOfGas = Register(RootCodespace, 12, "out of gas")
|
||||
ErrOutOfGas = Register(RootCodespace, 11, "out of gas")
|
||||
|
||||
// ErrMemoTooLarge to doc
|
||||
ErrMemoTooLarge = Register(RootCodespace, 13, "memo too large")
|
||||
ErrMemoTooLarge = Register(RootCodespace, 12, "memo too large")
|
||||
|
||||
// ErrInsufficientFee to doc
|
||||
ErrInsufficientFee = Register(RootCodespace, 14, "insufficient fee")
|
||||
ErrInsufficientFee = Register(RootCodespace, 13, "insufficient fee")
|
||||
|
||||
// ErrTooManySignatures to doc
|
||||
ErrTooManySignatures = Register(RootCodespace, 15, "maximum numer of signatures exceeded")
|
||||
ErrTooManySignatures = Register(RootCodespace, 14, "maximum number of signatures exceeded")
|
||||
|
||||
// ErrNoSignatures to doc
|
||||
ErrNoSignatures = Register(RootCodespace, 16, "no signatures supplied")
|
||||
ErrNoSignatures = Register(RootCodespace, 15, "no signatures supplied")
|
||||
|
||||
// ErrJSONMarshal defines an ABCI typed JSON marshalling error
|
||||
ErrJSONMarshal = Register(RootCodespace, 17, "failed to marshal JSON bytes")
|
||||
ErrJSONMarshal = Register(RootCodespace, 16, "failed to marshal JSON bytes")
|
||||
|
||||
// ErrJSONUnmarshal defines an ABCI typed JSON unmarshalling error
|
||||
ErrJSONUnmarshal = Register(RootCodespace, 18, "failed to unmarshal JSON bytes")
|
||||
ErrJSONUnmarshal = Register(RootCodespace, 17, "failed to unmarshal JSON bytes")
|
||||
|
||||
// ErrInvalidRequest defines an ABCI typed error where the request contains
|
||||
// invalid data.
|
||||
ErrInvalidRequest = Register(RootCodespace, 18, "invalid request")
|
||||
|
||||
// ErrTxInMempoolCache defines an ABCI typed error where a tx already exists
|
||||
// in the mempool.
|
||||
ErrTxInMempoolCache = Register(RootCodespace, 19, "tx already in mempool")
|
||||
|
||||
// ErrMempoolIsFull defines an ABCI typed error where the mempool is full.
|
||||
ErrMempoolIsFull = Register(RootCodespace, 20, "mempool is full")
|
||||
|
||||
// ErrTxTooLarge defines an ABCI typed error where tx is too large.
|
||||
ErrTxTooLarge = Register(RootCodespace, 21, "tx too large")
|
||||
|
||||
// ErrPanic is only set when we recover from a panic, so we know to
|
||||
// redact potentially sensitive system info
|
||||
@@ -89,12 +100,10 @@ func Register(codespace string, code uint32, description string) *Error {
|
||||
if e := getUsed(codespace, code); e != nil {
|
||||
panic(fmt.Sprintf("error with code %d is already registered: %q", code, e.desc))
|
||||
}
|
||||
err := &Error{
|
||||
code: code,
|
||||
codespace: codespace,
|
||||
desc: description,
|
||||
}
|
||||
|
||||
err := New(codespace, code, description)
|
||||
setUsed(err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -247,7 +256,7 @@ type wrappedError struct {
|
||||
}
|
||||
|
||||
func (e *wrappedError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.msg, e.parent.Error())
|
||||
return fmt.Sprintf("%s: %s", e.parent.Error(), e.msg)
|
||||
}
|
||||
|
||||
func (e *wrappedError) Cause() error {
|
||||
|
||||
@@ -15,19 +15,19 @@ func TestStackTrace(t *testing.T) {
|
||||
}{
|
||||
"New gives us a stacktrace": {
|
||||
err: Wrap(ErrNoSignatures, "name"),
|
||||
wantError: "name: no signatures supplied",
|
||||
wantError: "no signatures supplied: name",
|
||||
},
|
||||
"Wrapping stderr gives us a stacktrace": {
|
||||
err: Wrap(fmt.Errorf("foo"), "standard"),
|
||||
wantError: "standard: foo",
|
||||
wantError: "foo: standard",
|
||||
},
|
||||
"Wrapping pkg/errors gives us clean stacktrace": {
|
||||
err: Wrap(errors.New("bar"), "pkg"),
|
||||
wantError: "pkg: bar",
|
||||
wantError: "bar: pkg",
|
||||
},
|
||||
"Wrapping inside another function is still clean": {
|
||||
err: Wrap(fmt.Errorf("indirect"), "do the do"),
|
||||
wantError: "do the do: indirect",
|
||||
wantError: "indirect: do the do",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
var codeTypes = []CodeType{
|
||||
CodeInternal,
|
||||
CodeTxDecode,
|
||||
CodeInvalidSequence,
|
||||
CodeUnauthorized,
|
||||
CodeInsufficientFunds,
|
||||
CodeUnknownRequest,
|
||||
CodeInvalidAddress,
|
||||
CodeInvalidPubKey,
|
||||
CodeUnknownAddress,
|
||||
CodeInsufficientCoins,
|
||||
CodeInvalidCoins,
|
||||
CodeOutOfGas,
|
||||
CodeMemoTooLarge,
|
||||
}
|
||||
|
||||
type errFn func(msg string) Error
|
||||
|
||||
var errFns = []errFn{
|
||||
ErrInternal,
|
||||
ErrTxDecode,
|
||||
ErrInvalidSequence,
|
||||
ErrUnauthorized,
|
||||
ErrInsufficientFunds,
|
||||
ErrUnknownRequest,
|
||||
ErrInvalidAddress,
|
||||
ErrInvalidPubKey,
|
||||
ErrUnknownAddress,
|
||||
ErrInsufficientCoins,
|
||||
ErrInvalidCoins,
|
||||
ErrOutOfGas,
|
||||
ErrMemoTooLarge,
|
||||
}
|
||||
|
||||
func TestCodeType(t *testing.T) {
|
||||
require.True(t, CodeOK.IsOK())
|
||||
|
||||
for tcnum, c := range codeTypes {
|
||||
msg := CodeToDefaultMsg(c)
|
||||
require.NotEqual(t, unknownCodeMsg(c), msg, "Code expected to be known. tc #%d, code %d, msg %s", tcnum, c, msg)
|
||||
}
|
||||
|
||||
msg := CodeToDefaultMsg(CodeOK)
|
||||
require.Equal(t, unknownCodeMsg(CodeOK), msg)
|
||||
}
|
||||
|
||||
func TestErrFn(t *testing.T) {
|
||||
for i, errFn := range errFns {
|
||||
err := errFn("")
|
||||
codeType := codeTypes[i]
|
||||
require.Equal(t, err.Code(), codeType, "Err function expected to return proper code. tc #%d", i)
|
||||
require.Equal(t, err.Codespace(), CodespaceRoot, "Err function expected to return proper codespace. tc #%d", i)
|
||||
require.Equal(t, err.QueryResult().Code, uint32(err.Code()), "Err function expected to return proper Code from QueryResult. tc #%d")
|
||||
require.Equal(t, err.QueryResult().Log, err.ABCILog(), "Err function expected to return proper ABCILog from QueryResult. tc #%d")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendMsgToErr(t *testing.T) {
|
||||
for i, errFn := range errFns {
|
||||
err := errFn("")
|
||||
errMsg := err.Stacktrace().Error()
|
||||
abciLog := err.ABCILog()
|
||||
|
||||
// plain msg error
|
||||
msg := AppendMsgToErr("something unexpected happened", errMsg)
|
||||
require.Equal(
|
||||
t,
|
||||
fmt.Sprintf("something unexpected happened; %s", errMsg),
|
||||
msg,
|
||||
fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i),
|
||||
)
|
||||
|
||||
// ABCI Log msg error
|
||||
msg = AppendMsgToErr("something unexpected happened", abciLog)
|
||||
msgIdx := mustGetMsgIndex(abciLog)
|
||||
require.Equal(
|
||||
t,
|
||||
fmt.Sprintf("%s%s; %s}",
|
||||
abciLog[:msgIdx],
|
||||
"something unexpected happened",
|
||||
abciLog[msgIdx:len(abciLog)-1],
|
||||
),
|
||||
msg,
|
||||
fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultFromError(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
err error
|
||||
expect Result
|
||||
}{
|
||||
"sdk.Error": {
|
||||
err: ErrUnauthorized("not owner"),
|
||||
expect: Result{
|
||||
Codespace: CodespaceRoot,
|
||||
Code: CodeUnauthorized,
|
||||
Log: `{"codespace":"sdk","code":4,"message":"not owner"}`,
|
||||
},
|
||||
},
|
||||
"types/errors": {
|
||||
err: sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "not owner"),
|
||||
expect: Result{
|
||||
Codespace: CodespaceRoot,
|
||||
Code: CodeUnauthorized,
|
||||
Log: `{"codespace":"sdk","code":4,"message":"not owner: unauthorized"}`,
|
||||
},
|
||||
},
|
||||
"stdlib errors": {
|
||||
err: fmt.Errorf("not owner"),
|
||||
expect: Result{
|
||||
Codespace: CodespaceType("undefined"),
|
||||
Code: CodeInternal,
|
||||
// note that we redact the internal errors in the new package to not leak eg. panics
|
||||
Log: `{"codespace":"undefined","code":1,"message":"internal error"}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res := ResultFromError(tc.err)
|
||||
require.Equal(t, tc.expect, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package types
|
||||
|
||||
// Handler defines the core of the state transition function of an application.
|
||||
type Handler func(ctx Context, msg Msg) Result
|
||||
type Handler func(ctx Context, msg Msg) (*Result, error)
|
||||
|
||||
// AnteHandler authenticates transactions, before their internal messages are handled.
|
||||
// If newCtx.IsZero(), ctx is used instead.
|
||||
|
||||
+6
-3
@@ -1,6 +1,9 @@
|
||||
package types
|
||||
|
||||
import abci "github.com/tendermint/tendermint/abci/types"
|
||||
import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// Type for querier functions on keepers to implement to handle custom queries
|
||||
type Querier = func(ctx Context, path []string, req abci.RequestQuery) (res []byte, err Error)
|
||||
// Querier defines a function type that a module querier must implement to handle
|
||||
// custom client queries.
|
||||
type Querier = func(ctx Context, path []string, req abci.RequestQuery) ([]byte, error)
|
||||
|
||||
+19
-29
@@ -12,36 +12,27 @@ import (
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// Result is the union of ResponseFormat and ResponseCheckTx.
|
||||
type Result struct {
|
||||
// Code is the response code, is stored back on the chain.
|
||||
Code CodeType
|
||||
|
||||
// Codespace is the string referring to the domain of an error
|
||||
Codespace CodespaceType
|
||||
|
||||
// Data is any data returned from the app.
|
||||
// Data has to be length prefixed in order to separate
|
||||
// results from multiple msgs executions
|
||||
Data []byte
|
||||
|
||||
// Log contains the txs log information. NOTE: nondeterministic.
|
||||
Log string
|
||||
|
||||
// GasInfo defines tx execution gas context.
|
||||
type GasInfo struct {
|
||||
// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
GasWanted uint64
|
||||
|
||||
// GasUsed is the amount of gas actually consumed. NOTE: unimplemented
|
||||
GasUsed uint64
|
||||
|
||||
// Events contains a slice of Event objects that were emitted during some
|
||||
// execution.
|
||||
Events Events
|
||||
}
|
||||
|
||||
// TODO: In the future, more codes may be OK.
|
||||
func (res Result) IsOK() bool {
|
||||
return res.Code.IsOK()
|
||||
// Result is the union of ResponseFormat and ResponseCheckTx.
|
||||
type Result struct {
|
||||
// Data is any data returned from message or handler execution. It MUST be length
|
||||
// prefixed in order to separate data from multiple message executions.
|
||||
Data []byte
|
||||
|
||||
// Log contains the log information from message or handler execution.
|
||||
Log string
|
||||
|
||||
// Events contains a slice of Event objects that were emitted during message or
|
||||
// handler execution.
|
||||
Events Events
|
||||
}
|
||||
|
||||
// ABCIMessageLogs represents a slice of ABCIMessageLog.
|
||||
@@ -50,7 +41,6 @@ type ABCIMessageLogs []ABCIMessageLog
|
||||
// ABCIMessageLog defines a structure containing an indexed tx ABCI message log.
|
||||
type ABCIMessageLog struct {
|
||||
MsgIndex uint16 `json:"msg_index"`
|
||||
Success bool `json:"success"`
|
||||
Log string `json:"log"`
|
||||
|
||||
// Events contains a slice of Event objects that were emitted during some
|
||||
@@ -58,10 +48,9 @@ type ABCIMessageLog struct {
|
||||
Events StringEvents `json:"events"`
|
||||
}
|
||||
|
||||
func NewABCIMessageLog(i uint16, success bool, log string, events Events) ABCIMessageLog {
|
||||
func NewABCIMessageLog(i uint16, log string, events Events) ABCIMessageLog {
|
||||
return ABCIMessageLog{
|
||||
MsgIndex: i,
|
||||
Success: success,
|
||||
Log: log,
|
||||
Events: StringifyEvents(events.ToABCIEvents()),
|
||||
}
|
||||
@@ -84,6 +73,7 @@ func (logs ABCIMessageLogs) String() (str string) {
|
||||
type TxResponse struct {
|
||||
Height int64 `json:"height"`
|
||||
TxHash string `json:"txhash"`
|
||||
Codespace string `json:"codespace,omitempty"`
|
||||
Code uint32 `json:"code,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
RawLog string `json:"raw_log,omitempty"`
|
||||
@@ -91,7 +81,6 @@ type TxResponse struct {
|
||||
Info string `json:"info,omitempty"`
|
||||
GasWanted int64 `json:"gas_wanted,omitempty"`
|
||||
GasUsed int64 `json:"gas_used,omitempty"`
|
||||
Codespace string `json:"codespace,omitempty"`
|
||||
Tx Tx `json:"tx,omitempty"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
|
||||
@@ -111,6 +100,7 @@ func NewResponseResultTx(res *ctypes.ResultTx, tx Tx, timestamp string) TxRespon
|
||||
return TxResponse{
|
||||
TxHash: res.Hash.String(),
|
||||
Height: res.Height,
|
||||
Codespace: res.TxResult.Codespace,
|
||||
Code: res.TxResult.Code,
|
||||
Data: strings.ToUpper(hex.EncodeToString(res.TxResult.Data)),
|
||||
RawLog: res.TxResult.Log,
|
||||
@@ -153,6 +143,7 @@ func newTxResponseCheckTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
|
||||
return TxResponse{
|
||||
Height: res.Height,
|
||||
TxHash: txHash,
|
||||
Codespace: res.CheckTx.Codespace,
|
||||
Code: res.CheckTx.Code,
|
||||
Data: strings.ToUpper(hex.EncodeToString(res.CheckTx.Data)),
|
||||
RawLog: res.CheckTx.Log,
|
||||
@@ -161,7 +152,6 @@ func newTxResponseCheckTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
|
||||
GasWanted: res.CheckTx.GasWanted,
|
||||
GasUsed: res.CheckTx.GasUsed,
|
||||
Events: StringifyEvents(res.CheckTx.Events),
|
||||
Codespace: res.CheckTx.Codespace,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +170,7 @@ func newTxResponseDeliverTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
|
||||
return TxResponse{
|
||||
Height: res.Height,
|
||||
TxHash: txHash,
|
||||
Codespace: res.DeliverTx.Codespace,
|
||||
Code: res.DeliverTx.Code,
|
||||
Data: strings.ToUpper(hex.EncodeToString(res.DeliverTx.Data)),
|
||||
RawLog: res.DeliverTx.Log,
|
||||
@@ -188,7 +179,6 @@ func newTxResponseDeliverTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
|
||||
GasWanted: res.DeliverTx.GasWanted,
|
||||
GasUsed: res.DeliverTx.GasUsed,
|
||||
Events: StringifyEvents(res.DeliverTx.Events),
|
||||
Codespace: res.DeliverTx.Codespace,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-13
@@ -7,17 +7,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResult(t *testing.T) {
|
||||
var res Result
|
||||
require.True(t, res.IsOK())
|
||||
|
||||
res.Data = []byte("data")
|
||||
require.True(t, res.IsOK())
|
||||
|
||||
res.Code = CodeType(1)
|
||||
require.False(t, res.IsOK())
|
||||
}
|
||||
|
||||
func TestParseABCILog(t *testing.T) {
|
||||
logs := `[{"log":"","msg_index":1,"success":true}]`
|
||||
|
||||
@@ -26,12 +15,11 @@ func TestParseABCILog(t *testing.T) {
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, res[0].Log, "")
|
||||
require.Equal(t, res[0].MsgIndex, uint16(1))
|
||||
require.True(t, res[0].Success)
|
||||
}
|
||||
|
||||
func TestABCIMessageLog(t *testing.T) {
|
||||
events := Events{NewEvent("transfer", NewAttribute("sender", "foo"))}
|
||||
msgLog := NewABCIMessageLog(0, true, "", events)
|
||||
msgLog := NewABCIMessageLog(0, "", events)
|
||||
|
||||
msgLogs := ABCIMessageLogs{msgLog}
|
||||
bz, err := codec.Cdc.MarshalJSON(msgLogs)
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@ type Msg interface {
|
||||
|
||||
// ValidateBasic does a simple validation check that
|
||||
// doesn't require access to any other information.
|
||||
ValidateBasic() Error
|
||||
ValidateBasic() error
|
||||
|
||||
// Get the canonical byte representation of the Msg.
|
||||
GetSignBytes() []byte
|
||||
@@ -37,13 +37,13 @@ type Tx interface {
|
||||
|
||||
// ValidateBasic does a simple and lightweight validation check that doesn't
|
||||
// require access to any other information.
|
||||
ValidateBasic() Error
|
||||
ValidateBasic() error
|
||||
}
|
||||
|
||||
//__________________________________________________________
|
||||
|
||||
// TxDecoder unmarshals transaction bytes
|
||||
type TxDecoder func(txBytes []byte) (Tx, Error)
|
||||
type TxDecoder func(txBytes []byte) (Tx, error)
|
||||
|
||||
// TxEncoder marshals transaction to bytes
|
||||
type TxEncoder func(tx Tx) ([]byte, error)
|
||||
@@ -73,7 +73,7 @@ func (msg *TestMsg) GetSignBytes() []byte {
|
||||
}
|
||||
return MustSortJSON(bz)
|
||||
}
|
||||
func (msg *TestMsg) ValidateBasic() Error { return nil }
|
||||
func (msg *TestMsg) ValidateBasic() error { return nil }
|
||||
func (msg *TestMsg) GetSigners() []AccAddress {
|
||||
return msg.signers
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user