362 lines
9.5 KiB
Go
362 lines
9.5 KiB
Go
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"`
|
|
}
|