diff --git a/.gitignore b/.gitignore index e3083e55dc26..146e44fc94f5 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ dependency-graph.png *.aux *.out *.synctex.gz +/x/genutil/config/priv_validator_key.json +/x/genutil/data/priv_validator_state.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ee86691c2ca..ec9de4ee40f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,6 +150,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (types) [\#10630](https://github.com/cosmos/cosmos-sdk/pull/10630) Add an `Events` field to the `TxResponse` type that captures _all_ events emitted by a transaction, unlike `Logs` which only contains events emitted during message execution. * (deps) [\#10706](https://github.com/cosmos/cosmos-sdk/issues/10706) Bump rosetta-sdk-go to v0.7.2 and rosetta-cli to v0.7.3 * (module) [\#10711](https://github.com/cosmos/cosmos-sdk/pull/10711) Panic at startup if the app developer forgot to add modules in the `SetOrder{BeginBlocker, EndBlocker, InitGenesis, ExportGenesis}` functions. This means that all modules, even those who have empty implementations for those methods, need to be added to `SetOrder*`. +* (types/errors) [\#10779](https://github.com/cosmos/cosmos-sdk/pull/10779) Move most functionality in `types/errors` to a standalone `errors` go module, except the `RootCodespace` errors and ABCI response helpers. All functions and types that used to live in `types/errors` are now aliased so this is not a breaking change. ### Bug Fixes diff --git a/errors/CHANGELOG.md b/errors/CHANGELOG.md new file mode 100644 index 000000000000..6b6faf4c5cd5 --- /dev/null +++ b/errors/CHANGELOG.md @@ -0,0 +1,40 @@ + + +# Changelog + +## v1.0.0 + +### Features +* [\#10779](https://github.com/cosmos/cosmos-sdk/pull/10779) Import code from the `github.com/cosmos/cosmos-sdk/types/errors` package. diff --git a/errors/abci.go b/errors/abci.go new file mode 100644 index 000000000000..afd535e198c6 --- /dev/null +++ b/errors/abci.go @@ -0,0 +1,129 @@ +package errors + +import ( + "fmt" + "reflect" +) + +const ( + // SuccessABCICode declares an ABCI response use 0 to signal that the + // processing was successful and no error is returned. + SuccessABCICode uint32 = 0 + + // All unclassified errors that do not provide an ABCI code are clubbed + // under an internal error code and a generic message instead of + // detailed error string. + internalABCICodespace = UndefinedCodespace + internalABCICode uint32 = 1 +) + +// ABCIInfo returns the ABCI error information as consumed by the tendermint +// client. Returned codespace, code, and log message should be used as a ABCI response. +// Any error that does not provide ABCICode information is categorized as error +// with code 1, codespace UndefinedCodespace +// When not running in a debug mode all messages of errors that do not provide +// ABCICode information are replaced with generic "internal error". Errors +// without an ABCICode information as considered internal. +func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string) { + if errIsNil(err) { + return "", SuccessABCICode, "" + } + + encode := defaultErrEncoder + if debug { + encode = debugErrEncoder + } + + return abciCodespace(err), abciCode(err), encode(err) +} + +// The debugErrEncoder encodes the error with a stacktrace. +func debugErrEncoder(err error) string { + return fmt.Sprintf("%+v", err) +} + +// The defaultErrEncoder applies Redact on the error before encoding it with its internal error message. +func defaultErrEncoder(err error) string { + return Redact(err).Error() +} + +type coder interface { + ABCICode() uint32 +} + +// abciCode tests if given error contains an ABCI code and returns the value of +// it if available. This function is testing for the causer interface as well +// and unwraps the error. +func abciCode(err error) uint32 { + if errIsNil(err) { + return SuccessABCICode + } + + for { + if c, ok := err.(coder); ok { + return c.ABCICode() + } + + if c, ok := err.(causer); ok { + err = c.Cause() + } else { + return internalABCICode + } + } +} + +type codespacer interface { + Codespace() string +} + +// abciCodespace tests if given error contains a codespace and returns the value of +// it if available. This function is testing for the causer interface as well +// and unwraps the error. +func abciCodespace(err error) string { + if errIsNil(err) { + return "" + } + + for { + if c, ok := err.(codespacer); ok { + return c.Codespace() + } + + if c, ok := err.(causer); ok { + err = c.Cause() + } else { + return internalABCICodespace + } + } +} + +// errIsNil returns true if value represented by the given error is nil. +// +// Most of the time a simple == check is enough. There is a very narrowed +// spectrum of cases (mostly in tests) where a more sophisticated check is +// required. +func errIsNil(err error) bool { + if err == nil { + return true + } + if val := reflect.ValueOf(err); val.Kind() == reflect.Ptr { + return val.IsNil() + } + return false +} + +var errPanicWithMsg = Wrapf(ErrPanic, "panic message redacted to hide potentially sensitive system info") + +// Redact replaces an error that is not initialized as an ABCI Error with a +// generic internal error instance. If the error is an ABCI Error, that error is +// simply returned. +func Redact(err error) error { + if ErrPanic.Is(err) { + return errPanicWithMsg + } + if abciCode(err) == internalABCICode { + return errInternal + } + + return err +} diff --git a/types/errors/abci_test.go b/errors/abci_test.go similarity index 99% rename from types/errors/abci_test.go rename to errors/abci_test.go index 02c12e7bbdd6..8c653abeafc2 100644 --- a/types/errors/abci_test.go +++ b/errors/abci_test.go @@ -34,14 +34,14 @@ func (s *abciTestSuite) TestABCInfo() { debug: false, wantLog: "unauthorized", wantCode: ErrUnauthorized.code, - wantSpace: RootCodespace, + wantSpace: testCodespace, }, "wrapped SDK error": { err: Wrap(Wrap(ErrUnauthorized, "foo"), "bar"), debug: false, wantLog: "bar: foo: unauthorized", wantCode: ErrUnauthorized.code, - wantSpace: RootCodespace, + wantSpace: testCodespace, }, "nil is empty message": { err: nil, diff --git a/types/errors/doc.go b/errors/doc.go similarity index 100% rename from types/errors/doc.go rename to errors/doc.go diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 000000000000..b01ebaf59ef6 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,266 @@ +package errors + +import ( + "fmt" + "reflect" + + "github.com/pkg/errors" +) + +// UndefinedCodespace when we explicitly declare no codespace +const UndefinedCodespace = "undefined" + +var ( + // errInternal should never be exposed, but we reserve this code for non-specified errors + errInternal = Register(UndefinedCodespace, 1, "internal") + + // ErrPanic is only set when we recover from a panic, so we know to + // redact potentially sensitive system info + ErrPanic = Register(UndefinedCodespace, 111222, "panic") +) + +// Register returns an error instance that should be used as the base for +// creating error instances during runtime. +// +// Popular root errors are declared in this package, but extensions may want to +// declare custom codes. This function ensures that no error code is used +// twice. Attempt to reuse an error code results in panic. +// +// Use this function only during a program startup phase. +func Register(codespace string, code uint32, description string) *Error { + // TODO - uniqueness is (codespace, code) combo + if e := getUsed(codespace, code); e != nil { + panic(fmt.Sprintf("error with code %d is already registered: %q", code, e.desc)) + } + + err := New(codespace, code, description) + setUsed(err) + + return err +} + +// usedCodes is keeping track of used codes to ensure their uniqueness. No two +// error instances should share the same (codespace, code) tuple. +var usedCodes = map[string]*Error{} + +func errorID(codespace string, code uint32) string { + return fmt.Sprintf("%s:%d", codespace, code) +} + +func getUsed(codespace string, code uint32) *Error { + return usedCodes[errorID(codespace, code)] +} + +func setUsed(err *Error) { + usedCodes[errorID(err.codespace, err.code)] = err +} + +// ABCIError will resolve an error code/log from an abci result into +// an error message. If the code is registered, it will map it back to +// the canonical error, so we can do eg. ErrNotFound.Is(err) on something +// we get back from an external API. +// +// This should *only* be used in clients, not in the server side. +// The server (abci app / blockchain) should only refer to registered errors +func ABCIError(codespace string, code uint32, log string) error { + if e := getUsed(codespace, code); e != nil { + return Wrap(e, log) + } + // This is a unique error, will never match on .Is() + // Use Wrap here to get a stack trace + return Wrap(New(codespace, code, "unknown"), log) +} + +// Error represents a root error. +// +// Weave framework is using root error to categorize issues. Each instance +// created during the runtime should wrap one of the declared root errors. This +// allows error tests and returning all errors to the client in a safe manner. +// +// All popular root errors are declared in this package. If an extension has to +// declare a custom root error, always use Register function to ensure +// error code uniqueness. +type Error struct { + codespace string + code uint32 + desc string +} + +func New(codespace string, code uint32, desc string) *Error { + return &Error{codespace: codespace, code: code, desc: desc} +} + +func (e Error) Error() string { + return e.desc +} + +func (e Error) ABCICode() uint32 { + return e.code +} + +func (e Error) Codespace() string { + return e.codespace +} + +// Is check if given error instance is of a given kind/type. This involves +// unwrapping given error using the Cause method if available. +func (e *Error) Is(err error) bool { + // Reflect usage is necessary to correctly compare with + // a nil implementation of an error. + if e == nil { + return isNilErr(err) + } + + for { + if err == e { + return true + } + + // If this is a collection of errors, this function must return + // true if at least one from the group match. + if u, ok := err.(unpacker); ok { + for _, er := range u.Unpack() { + if e.Is(er) { + return true + } + } + } + + if c, ok := err.(causer); ok { + err = c.Cause() + } else { + return false + } + } +} + +// Wrap extends this error with an additional information. +// It's a handy function to call Wrap with sdk errors. +func (e *Error) Wrap(desc string) error { return Wrap(e, desc) } + +// Wrapf extends this error with an additional information. +// It's a handy function to call Wrapf with sdk errors. +func (e *Error) Wrapf(desc string, args ...interface{}) error { return Wrapf(e, desc, args...) } + +func isNilErr(err error) bool { + // Reflect usage is necessary to correctly compare with + // a nil implementation of an error. + if err == nil { + return true + } + if reflect.ValueOf(err).Kind() == reflect.Struct { + return false + } + return reflect.ValueOf(err).IsNil() +} + +// Wrap extends given error with an additional information. +// +// If the wrapped error does not provide ABCICode method (ie. stdlib errors), +// it will be labeled as internal error. +// +// If err is nil, this returns nil, avoiding the need for an if statement when +// wrapping a error returned at the end of a function +func Wrap(err error, description string) error { + if err == nil { + return nil + } + + // If this error does not carry the stacktrace information yet, attach + // one. This should be done only once per error at the lowest frame + // possible (most inner wrap). + if stackTrace(err) == nil { + err = errors.WithStack(err) + } + + return &wrappedError{ + parent: err, + msg: description, + } +} + +// Wrapf extends given error with an additional information. +// +// This function works like Wrap function with additional functionality of +// formatting the input as specified. +func Wrapf(err error, format string, args ...interface{}) error { + desc := fmt.Sprintf(format, args...) + return Wrap(err, desc) +} + +type wrappedError struct { + // This error layer description. + msg string + // The underlying error that triggered this one. + parent error +} + +func (e *wrappedError) Error() string { + return fmt.Sprintf("%s: %s", e.msg, e.parent.Error()) +} + +func (e *wrappedError) Cause() error { + return e.parent +} + +// Is reports whether any error in e's chain matches a target. +func (e *wrappedError) Is(target error) bool { + if e == target { + return true + } + + w := e.Cause() + for { + if w == target { + return true + } + + x, ok := w.(causer) + if ok { + w = x.Cause() + } + if x == nil { + return false + } + } +} + +// Unwrap implements the built-in errors.Unwrap +func (e *wrappedError) Unwrap() error { + return e.parent +} + +// Recover captures a panic and stop its propagation. If panic happens it is +// transformed into a ErrPanic instance and assigned to given error. Call this +// function using defer in order to work as expected. +func Recover(err *error) { + if r := recover(); r != nil { + *err = Wrapf(ErrPanic, "%v", r) + } +} + +// WithType is a helper to augment an error with a corresponding type message +func WithType(err error, obj interface{}) error { + return Wrap(err, fmt.Sprintf("%T", obj)) +} + +// IsOf checks if a received error is caused by one of the target errors. +// It extends the errors.Is functionality to a list of errors. +func IsOf(received error, targets ...error) bool { + for _, t := range targets { + if errors.Is(received, t) { + return true + } + } + return false +} + +// causer is an interface implemented by an error that supports wrapping. Use +// it to test if an error wraps another error instance. +type causer interface { + Cause() error +} + +type unpacker interface { + Unpack() []error +} diff --git a/types/errors/errors_test.go b/errors/errors_test.go similarity index 70% rename from types/errors/errors_test.go rename to errors/errors_test.go index b7e9c478fe26..dbe05615daa3 100644 --- a/types/errors/errors_test.go +++ b/errors/errors_test.go @@ -103,46 +103,6 @@ func (s *errorsTestSuite) TestErrorIs() { b: nil, wantIs: false, }, - // "multierr with the same error": { - // a: ErrUnauthorized, - // b: Append(ErrUnauthorized, ErrState), - // wantIs: true, - // }, - // "multierr with random order": { - // a: ErrUnauthorized, - // b: Append(ErrState, ErrUnauthorized), - // wantIs: true, - // }, - // "multierr with wrapped err": { - // a: ErrUnauthorized, - // b: Append(ErrState, Wrap(ErrUnauthorized, "test")), - // wantIs: true, - // }, - // "multierr with nil error": { - // a: ErrUnauthorized, - // b: Append(nil, nil), - // wantIs: false, - // }, - // "multierr with different error": { - // a: ErrUnauthorized, - // b: Append(ErrState, nil), - // wantIs: false, - // }, - // "multierr from nil": { - // a: nil, - // b: Append(ErrState, ErrUnauthorized), - // wantIs: false, - // }, - // "field error wrapper": { - // a: ErrEmpty, - // b: Field("name", ErrEmpty, "name is required"), - // wantIs: true, - // }, - // "nil field error wrapper": { - // a: nil, - // b: Field("name", nil, "name is required"), - // wantIs: true, - // }, } for testName, tc := range cases { s.Require().Equal(tc.wantIs, tc.a.Is(tc.b), testName) @@ -242,7 +202,7 @@ func (s *errorsTestSuite) TestWrappedUnwrapFail() { } func (s *errorsTestSuite) TestABCIError() { - s.Require().Equal("custom: tx parse error", ABCIError(RootCodespace, 2, "custom").Error()) + s.Require().Equal("custom: tx parse error", ABCIError(testCodespace, 2, "custom").Error()) s.Require().Equal("custom: unknown", ABCIError("unknown", 1, "custom").Error()) } @@ -265,3 +225,37 @@ func ExampleWrapf() { // 90 is smaller than 100: insufficient funds // 90 is smaller than 100: insufficient funds } + +const testCodespace = "testtesttest" + +var ( + ErrTxDecode = Register(testCodespace, 2, "tx parse error") + ErrInvalidSequence = Register(testCodespace, 3, "invalid sequence") + ErrUnauthorized = Register(testCodespace, 4, "unauthorized") + ErrInsufficientFunds = Register(testCodespace, 5, "insufficient funds") + ErrUnknownRequest = Register(testCodespace, 6, "unknown request") + ErrInvalidAddress = Register(testCodespace, 7, "invalid address") + ErrInvalidPubKey = Register(testCodespace, 8, "invalid pubkey") + ErrUnknownAddress = Register(testCodespace, 9, "unknown address") + ErrInvalidCoins = Register(testCodespace, 10, "invalid coins") + ErrOutOfGas = Register(testCodespace, 11, "out of gas") + ErrInsufficientFee = Register(testCodespace, 13, "insufficient fee") + ErrTooManySignatures = Register(testCodespace, 14, "maximum number of signatures exceeded") + ErrNoSignatures = Register(testCodespace, 15, "no signatures supplied") + ErrJSONMarshal = Register(testCodespace, 16, "failed to marshal JSON bytes") + ErrJSONUnmarshal = Register(testCodespace, 17, "failed to unmarshal JSON bytes") + ErrInvalidRequest = Register(testCodespace, 18, "invalid request") + ErrMempoolIsFull = Register(testCodespace, 20, "mempool is full") + ErrTxTooLarge = Register(testCodespace, 21, "tx too large") + ErrKeyNotFound = Register(testCodespace, 22, "key not found") + ErrorInvalidSigner = Register(testCodespace, 24, "tx intended signer does not match the given signer") + ErrInvalidChainID = Register(testCodespace, 28, "invalid chain-id") + ErrInvalidType = Register(testCodespace, 29, "invalid type") + ErrUnknownExtensionOptions = Register(testCodespace, 31, "unknown extension options") + ErrPackAny = Register(testCodespace, 33, "failed packing protobuf message to Any") + ErrLogic = Register(testCodespace, 35, "internal logic error") + ErrConflict = Register(testCodespace, 36, "conflict") + ErrNotSupported = Register(testCodespace, 37, "feature not supported") + ErrNotFound = Register(testCodespace, 38, "not found") + ErrIO = Register(testCodespace, 39, "Internal IO error") +) diff --git a/errors/go.mod b/errors/go.mod new file mode 100644 index 000000000000..be0e25d7a1df --- /dev/null +++ b/errors/go.mod @@ -0,0 +1,11 @@ +module errors + +go 1.17 + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.7.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/errors/go.sum b/errors/go.sum new file mode 100644 index 000000000000..c2a26e73bf7d --- /dev/null +++ b/errors/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/types/errors/handle.go b/errors/handle.go similarity index 100% rename from types/errors/handle.go rename to errors/handle.go diff --git a/types/errors/stacktrace.go b/errors/stacktrace.go similarity index 93% rename from types/errors/stacktrace.go rename to errors/stacktrace.go index f7079c56d831..e7d809a1a113 100644 --- a/types/errors/stacktrace.go +++ b/errors/stacktrace.go @@ -73,7 +73,7 @@ func writeSimpleFrame(s io.Writer, f errors.Frame) { if len(chunks) == 2 { file = chunks[1] } - fmt.Fprintf(s, " [%s:%d]", file, line) + _, _ = fmt.Fprintf(s, " [%s:%d]", file, line) } // Format works like pkg/errors, with additions. @@ -86,16 +86,16 @@ func writeSimpleFrame(s io.Writer, f errors.Frame) { func (e *wrappedError) Format(s fmt.State, verb rune) { // normal output here.... if verb != 'v' { - fmt.Fprint(s, e.Error()) + _, _ = fmt.Fprint(s, e.Error()) return } // work with the stack trace... whole or part stack := trimInternal(stackTrace(e)) if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", stack) - fmt.Fprint(s, e.Error()) + _, _ = fmt.Fprintf(s, "%+v\n", stack) + _, _ = fmt.Fprint(s, e.Error()) } else { - fmt.Fprint(s, e.Error()) + _, _ = fmt.Fprint(s, e.Error()) writeSimpleFrame(s, stack[0]) } } diff --git a/types/errors/stacktrace_test.go b/errors/stacktrace_test.go similarity index 100% rename from types/errors/stacktrace_test.go rename to errors/stacktrace_test.go diff --git a/go.mod b/go.mod index 127ea2d7d48f..9eefff912f71 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/cosmos/btcutil v1.0.4 github.com/cosmos/cosmos-proto v0.0.0-20211123144845-528f5002c9f8 github.com/cosmos/cosmos-sdk/db v0.0.0 + github.com/cosmos/cosmos-sdk/errors v0.0.0 github.com/cosmos/cosmos-sdk/x/group v0.0.0-00010101000000-000000000000 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/iavl v0.17.3 @@ -156,3 +157,5 @@ replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 replace github.com/cosmos/cosmos-sdk/db => ./db replace github.com/cosmos/cosmos-sdk/x/group => ./x/group + +replace github.com/cosmos/cosmos-sdk/errors => ./errors diff --git a/types/errors/abci.go b/types/errors/abci.go index d09b31b15b36..fef8dfa8ab69 100644 --- a/types/errors/abci.go +++ b/types/errors/abci.go @@ -1,44 +1,9 @@ package errors import ( - "fmt" - "reflect" - abci "github.com/tendermint/tendermint/abci/types" ) -const ( - // SuccessABCICode declares an ABCI response use 0 to signal that the - // processing was successful and no error is returned. - SuccessABCICode uint32 = 0 - - // All unclassified errors that do not provide an ABCI code are clubbed - // under an internal error code and a generic message instead of - // detailed error string. - internalABCICodespace = UndefinedCodespace - internalABCICode uint32 = 1 -) - -// ABCIInfo returns the ABCI error information as consumed by the tendermint -// client. Returned codespace, code, and log message should be used as a ABCI response. -// Any error that does not provide ABCICode information is categorized as error -// with code 1, codespace UndefinedCodespace -// When not running in a debug mode all messages of errors that do not provide -// ABCICode information are replaced with generic "internal error". Errors -// without an ABCICode information as considered internal. -func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string) { - if errIsNil(err) { - return "", SuccessABCICode, "" - } - - encode := defaultErrEncoder - if debug { - encode = debugErrEncoder - } - - 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, debug bool) abci.ResponseCheckTx { @@ -75,94 +40,3 @@ func QueryResult(err error, debug bool) abci.ResponseQuery { Log: log, } } - -// The debugErrEncoder encodes the error with a stacktrace. -func debugErrEncoder(err error) string { - return fmt.Sprintf("%+v", err) -} - -// The defaultErrEncoder applies Redact on the error before encoding it with its internal error message. -func defaultErrEncoder(err error) string { - return Redact(err).Error() -} - -type coder interface { - ABCICode() uint32 -} - -// abciCode test if given error contains an ABCI code and returns the value of -// it if available. This function is testing for the causer interface as well -// and unwraps the error. -func abciCode(err error) uint32 { - if errIsNil(err) { - return SuccessABCICode - } - - for { - if c, ok := err.(coder); ok { - return c.ABCICode() - } - - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return internalABCICode - } - } -} - -type codespacer interface { - Codespace() string -} - -// abciCodespace tests if given error contains a codespace and returns the value of -// it if available. This function is testing for the causer interface as well -// and unwraps the error. -func abciCodespace(err error) string { - if errIsNil(err) { - return "" - } - - for { - if c, ok := err.(codespacer); ok { - return c.Codespace() - } - - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return internalABCICodespace - } - } -} - -// errIsNil returns true if value represented by the given error is nil. -// -// Most of the time a simple == check is enough. There is a very narrowed -// spectrum of cases (mostly in tests) where a more sophisticated check is -// required. -func errIsNil(err error) bool { - if err == nil { - return true - } - if val := reflect.ValueOf(err); val.Kind() == reflect.Ptr { - return val.IsNil() - } - return false -} - -var errPanicWithMsg = Wrapf(ErrPanic, "panic message redacted to hide potentially sensitive system info") - -// Redact replaces an error that is not initialized as an ABCI Error with a -// generic internal error instance. If the error is an ABCI Error, that error is -// simply returned. -func Redact(err error) error { - if ErrPanic.Is(err) { - return errPanicWithMsg - } - if abciCode(err) == internalABCICode { - return errInternal - } - - return err -} diff --git a/types/errors/errors.go b/types/errors/errors.go index 12a912cc49ce..2aa53c1deb53 100644 --- a/types/errors/errors.go +++ b/types/errors/errors.go @@ -1,22 +1,41 @@ +//Package errors provides a shared set of errors for use in the SDK, +//aliases functionality in the github.com/cosmos/cosmos-sdk/errors module +//that used to be in this package, and provides some helpers for converting +//errors to ABCI response code. +// +//New code should generally import github.com/cosmos/cosmos-sdk/errors directly +//and define a custom set of errors in custom codespace, rather than importing +//this package. package errors import ( - "fmt" - "reflect" + errorsmod "github.com/cosmos/cosmos-sdk/errors" +) + +var ( + SuccessABCICode = errorsmod.SuccessABCICode + ABCIInfo = errorsmod.ABCIInfo + Redact = errorsmod.Redact + UndefinedCodespace = errorsmod.UndefinedCodespace + Register = errorsmod.Register + ABCIError = errorsmod.ABCIError + New = errorsmod.New + Wrap = errorsmod.Wrap + Wrapf = errorsmod.Wrapf + Recover = errorsmod.Recover + WithType = errorsmod.WithType + IsOf = errorsmod.IsOf + AssertNil = errorsmod.AssertNil +) - "github.com/pkg/errors" +type ( + Error = errorsmod.Error ) // RootCodespace is the codespace for all errors defined in this package const RootCodespace = "sdk" -// UndefinedCodespace when we explicitly declare no codespace -const UndefinedCodespace = "undefined" - var ( - // errInternal should never be exposed, but we reserve this code for non-specified errors - errInternal = Register(UndefinedCodespace, 1, "internal") - // ErrTxDecode is returned if we cannot parse a transaction ErrTxDecode = Register(RootCodespace, 2, "tx parse error") @@ -143,254 +162,8 @@ var ( // ErrPanic is only set when we recover from a panic, so we know to // redact potentially sensitive system info - ErrPanic = Register(UndefinedCodespace, 111222, "panic") + ErrPanic = errorsmod.ErrPanic // ErrAppConfig defines an error occurred if min-gas-prices field in BaseConfig is empty. ErrAppConfig = Register(RootCodespace, 40, "error in app.toml") ) - -// Register returns an error instance that should be used as the base for -// creating error instances during runtime. -// -// Popular root errors are declared in this package, but extensions may want to -// declare custom codes. This function ensures that no error code is used -// twice. Attempt to reuse an error code results in panic. -// -// Use this function only during a program startup phase. -func Register(codespace string, code uint32, description string) *Error { - // TODO - uniqueness is (codespace, code) combo - if e := getUsed(codespace, code); e != nil { - panic(fmt.Sprintf("error with code %d is already registered: %q", code, e.desc)) - } - - err := New(codespace, code, description) - setUsed(err) - - return err -} - -// usedCodes is keeping track of used codes to ensure their uniqueness. No two -// error instances should share the same (codespace, code) tuple. -var usedCodes = map[string]*Error{} - -func errorID(codespace string, code uint32) string { - return fmt.Sprintf("%s:%d", codespace, code) -} - -func getUsed(codespace string, code uint32) *Error { - return usedCodes[errorID(codespace, code)] -} - -func setUsed(err *Error) { - usedCodes[errorID(err.codespace, err.code)] = err -} - -// ABCIError will resolve an error code/log from an abci result into -// an error message. If the code is registered, it will map it back to -// the canonical error, so we can do eg. ErrNotFound.Is(err) on something -// we get back from an external API. -// -// This should *only* be used in clients, not in the server side. -// The server (abci app / blockchain) should only refer to registered errors -func ABCIError(codespace string, code uint32, log string) error { - if e := getUsed(codespace, code); e != nil { - return Wrap(e, log) - } - // This is a unique error, will never match on .Is() - // Use Wrap here to get a stack trace - return Wrap(New(codespace, code, "unknown"), log) -} - -// Error represents a root error. -// -// Weave framework is using root error to categorize issues. Each instance -// created during the runtime should wrap one of the declared root errors. This -// allows error tests and returning all errors to the client in a safe manner. -// -// All popular root errors are declared in this package. If an extension has to -// declare a custom root error, always use Register function to ensure -// error code uniqueness. -type Error struct { - codespace string - code uint32 - desc string -} - -func New(codespace string, code uint32, desc string) *Error { - return &Error{codespace: codespace, code: code, desc: desc} -} - -func (e Error) Error() string { - return e.desc -} - -func (e Error) ABCICode() uint32 { - return e.code -} - -func (e Error) Codespace() string { - return e.codespace -} - -// Is check if given error instance is of a given kind/type. This involves -// unwrapping given error using the Cause method if available. -func (e *Error) Is(err error) bool { - // Reflect usage is necessary to correctly compare with - // a nil implementation of an error. - if e == nil { - return isNilErr(err) - } - - for { - if err == e { - return true - } - - // If this is a collection of errors, this function must return - // true if at least one from the group match. - if u, ok := err.(unpacker); ok { - for _, er := range u.Unpack() { - if e.Is(er) { - return true - } - } - } - - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return false - } - } -} - -// Wrap extends this error with an additional information. -// It's a handy function to call Wrap with sdk errors. -func (e *Error) Wrap(desc string) error { return Wrap(e, desc) } - -// Wrapf extends this error with an additional information. -// It's a handy function to call Wrapf with sdk errors. -func (e *Error) Wrapf(desc string, args ...interface{}) error { return Wrapf(e, desc, args...) } - -func isNilErr(err error) bool { - // Reflect usage is necessary to correctly compare with - // a nil implementation of an error. - if err == nil { - return true - } - if reflect.ValueOf(err).Kind() == reflect.Struct { - return false - } - return reflect.ValueOf(err).IsNil() -} - -// Wrap extends given error with an additional information. -// -// If the wrapped error does not provide ABCICode method (ie. stdlib errors), -// it will be labeled as internal error. -// -// If err is nil, this returns nil, avoiding the need for an if statement when -// wrapping a error returned at the end of a function -func Wrap(err error, description string) error { - if err == nil { - return nil - } - - // If this error does not carry the stacktrace information yet, attach - // one. This should be done only once per error at the lowest frame - // possible (most inner wrap). - if stackTrace(err) == nil { - err = errors.WithStack(err) - } - - return &wrappedError{ - parent: err, - msg: description, - } -} - -// Wrapf extends given error with an additional information. -// -// This function works like Wrap function with additional functionality of -// formatting the input as specified. -func Wrapf(err error, format string, args ...interface{}) error { - desc := fmt.Sprintf(format, args...) - return Wrap(err, desc) -} - -type wrappedError struct { - // This error layer description. - msg string - // The underlying error that triggered this one. - parent error -} - -func (e *wrappedError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.parent.Error()) -} - -func (e *wrappedError) Cause() error { - return e.parent -} - -// Is reports whether any error in e's chain matches a target. -func (e *wrappedError) Is(target error) bool { - if e == target { - return true - } - - w := e.Cause() - for { - if w == target { - return true - } - - x, ok := w.(causer) - if ok { - w = x.Cause() - } - if x == nil { - return false - } - } -} - -// Unwrap implements the built-in errors.Unwrap -func (e *wrappedError) Unwrap() error { - return e.parent -} - -// Recover captures a panic and stop its propagation. If panic happens it is -// transformed into a ErrPanic instance and assigned to given error. Call this -// function using defer in order to work as expected. -func Recover(err *error) { - if r := recover(); r != nil { - *err = Wrapf(ErrPanic, "%v", r) - } -} - -// WithType is a helper to augment an error with a corresponding type message -func WithType(err error, obj interface{}) error { - return Wrap(err, fmt.Sprintf("%T", obj)) -} - -// IsOf checks if a received error is caused by one of the target errors. -// It extends the errors.Is functionality to a list of errors. -func IsOf(received error, targets ...error) bool { - for _, t := range targets { - if errors.Is(received, t) { - return true - } - } - return false -} - -// causer is an interface implemented by an error that supports wrapping. Use -// it to test if an error wraps another error instance. -type causer interface { - Cause() error -} - -type unpacker interface { - Unpack() []error -} diff --git a/x/group/go.mod b/x/group/go.mod index c252cfc07818..c745bb4e32d4 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -42,6 +42,7 @@ require ( github.com/confio/ics23/go v0.6.6 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/cosmos-sdk/db v0.0.0 // indirect + github.com/cosmos/cosmos-sdk/errors v0.0.0 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/iavl v0.17.3 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect @@ -150,3 +151,5 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp replace github.com/cosmos/cosmos-sdk => ../../ replace github.com/cosmos/cosmos-sdk/db => ../../db + +replace github.com/cosmos/cosmos-sdk/errors => ../../errors