feat: standalone errors go.mod (#10779)
## Description This PR: * moves all of the `types/errors` code to a new `errors` go module, except: * the `RootCodespace` errors in `types/errors` stay there * ABCI stuff that depends on tendermint stays in `types/errors * adds aliases to everything in `types/errors` referencing `errors` so **this is not a breaking change** This will allow standalone go modules to use the same error types as the SDK. In particular, I want the `orm` to reference `errors` and then the SDK will be able to import `orm` and it can stay standalone. The same could apply to the `db` module. After this PR the plan is to: * tag `github.com/cosmos/cosmos-sdk/errors` as `v1.0` 🎉 * remove the `replace` directive for `errors` in the main SDK `go.mod` --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
b4fe191b85
commit
4d0d376c4b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<!--
|
||||
Guiding Principles:
|
||||
|
||||
Changelogs are for humans, not machines.
|
||||
There should be an entry for every single version.
|
||||
The same types of changes should be grouped.
|
||||
Versions and sections should be linkable.
|
||||
The latest version comes first.
|
||||
The release date of each version is displayed.
|
||||
Mention whether you follow Semantic Versioning.
|
||||
|
||||
Usage:
|
||||
|
||||
Change log entries are to be added to the Unreleased section under the
|
||||
appropriate stanza (see below). Each entry should ideally include a tag and
|
||||
the Github issue reference in the following format:
|
||||
|
||||
* (<tag>) \#<issue-number> message
|
||||
|
||||
The issue numbers will later be link-ified during the release process so you do
|
||||
not have to worry about including a link manually, but you can if you wish.
|
||||
|
||||
Types of changes (Stanzas):
|
||||
|
||||
"Features" for new features.
|
||||
"Improvements" for changes in existing functionality.
|
||||
"Deprecated" for soon-to-be removed features.
|
||||
"Bug Fixes" for any bug fixes.
|
||||
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
|
||||
"CLI Breaking" for breaking CLI commands.
|
||||
"API Breaking" for breaking exported APIs used by developers building on SDK.
|
||||
Ref: https://keepachangelog.com/en/1.0.0/
|
||||
-->
|
||||
|
||||
# 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.
|
|
@ -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
|
||||
}
|
|
@ -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,
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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])
|
||||
}
|
||||
}
|
3
go.mod
3
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
"github.com/pkg/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
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue