Merge PR #5421: Refactor Error Handling

This commit is contained in:
Alexander Bezobchuk
2019-12-27 12:57:54 -05:00
committed by GitHub
parent 3c82b66347
commit 9a183ffbcc
170 changed files with 2317 additions and 2934 deletions
+16 -8
View File
@@ -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
View File
@@ -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"`
}
+39
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+4 -4
View File
@@ -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",
},
}
-138
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}