fix: do not panic if registering the same error to global registry (#24568)

This commit is contained in:
Alex | Interchain Labs 2025-05-08 12:59:45 -04:00 committed by GitHub
parent 05ac584d94
commit 5d28bf5540
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 40 deletions

View File

@ -31,6 +31,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
### Changes
* [#24568](https://github.com/cosmos/cosmos-sdk/pull/24568) Registering the same error code twice no longer will panic - a warning error will be logged to `stderr`.
## [v1.0.2](https://github.com/cosmos/cosmos-sdk/releases/tag/errors%2Fv1.0.2)
### Improvements

1
errors/README.md Normal file
View File

@ -0,0 +1 @@
# Errors This package provides structured error handling for Cosmos SDK apps. It supports: - Custom error codes and messages - Stack traces when wrapping errors - ABCI-compatible responses for Tendermint - Optional gRPC status codes ## Usage ### Registering Errors Define root errors with a unique code and description: ```go var ErrInvalidInput = errors.Register("app", 1001, "invalid input") ``` You can wrap errors to add context: ```go return errors.Wrap(ErrInvalidInput, "missing field") ``` ### Getting ABCI Error Info To convert an error to ABCI-compatible output: ```go codespace, code, log := errors.ABCIInfo(err, debug) ``` Set `debug = true` to include stack traces in logs. ### Suppress Duplicate Error Warnings To prevent logging when the same error is registered twice, set: ```bash export COSMOS_SDK_SUPPRESS_DUPLICATE_ERROR_CODE_LOG=true ``` Useful in tests or modules that may re-register the same error.

View File

@ -2,6 +2,8 @@ package errors
import (
"fmt"
"io"
"os"
"reflect"
"github.com/pkg/errors"
@ -10,7 +12,11 @@ import (
)
// UndefinedCodespace when we explicitly declare no codespace
const UndefinedCodespace = "undefined"
const (
UndefinedCodespace = "undefined"
// EnvSuppressErrorDuplicateRegister can be set to 'true' to suppress any logging when errors are double-registered.
EnvSuppressErrorDuplicateRegister = "COSMOS_SDK_SUPPRESS_DUPLICATE_ERROR_CODE_LOG"
)
var (
// errInternal should never be exposed, but we reserve this code for non-specified errors
@ -19,7 +25,7 @@ var (
// ErrStopIterating is used to break out of an iteration
ErrStopIterating = Register(UndefinedCodespace, 2, "stop iterating")
// ErrPanic should only be set when we recovering from a panic
// ErrPanic should only be set when recovering from a panic
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
)
@ -38,9 +44,13 @@ func Register(codespace string, code uint32, description string) *Error {
// RegisterWithGRPCCode is a version of Register that associates a gRPC error
// code with a registered error.
func RegisterWithGRPCCode(codespace string, code uint32, grpcCode grpccodes.Code, 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))
if os.Getenv(EnvSuppressErrorDuplicateRegister) != "true" {
_, err := io.WriteString(os.Stderr, "error with code "+errorID(codespace, code)+" is already registered: "+e.desc+". Overwriting with current error...\n")
if err != nil {
panic(err)
}
}
}
err := &Error{codespace: codespace, code: code, desc: description, grpcCode: grpcCode}

View File

@ -11,6 +11,40 @@ import (
grpcstatus "google.golang.org/grpc/status"
)
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 = RegisterWithGRPCCode(testCodespace, 36, codes.FailedPrecondition, "conflict")
ErrNotSupported = RegisterWithGRPCCode(testCodespace, 37, codes.Unimplemented, "feature not supported")
ErrNotFound = RegisterWithGRPCCode(testCodespace, 38, codes.NotFound, "not found")
ErrIO = Register(testCodespace, 39, "Internal IO error")
)
type errorsTestSuite struct {
suite.Suite
}
@ -19,10 +53,6 @@ func TestErrorsTestSuite(t *testing.T) {
suite.Run(t, new(errorsTestSuite))
}
func (s *errorsTestSuite) SetupSuite() {
s.T().Parallel()
}
func (s *errorsTestSuite) TestCause() {
std := stdlib.New("this is a stdlib error")
@ -226,36 +256,18 @@ func (s *errorsTestSuite) TestGRPCStatus() {
s.Require().Equal("codespace testtesttest code 38: not found: test", status.Message())
}
const testCodespace = "testtesttest"
func (s *errorsTestSuite) TestDoubleRegister() {
s.Require().NotPanics(func() {
_ = Register(testCodespace, 50, "Internal IO error")
_ = Register(testCodespace, 50, "Internal IO error")
})
}
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 = RegisterWithGRPCCode(testCodespace, 36, codes.FailedPrecondition, "conflict")
ErrNotSupported = RegisterWithGRPCCode(testCodespace, 37, codes.Unimplemented, "feature not supported")
ErrNotFound = RegisterWithGRPCCode(testCodespace, 38, codes.NotFound, "not found")
ErrIO = Register(testCodespace, 39, "Internal IO error")
)
func (s *errorsTestSuite) TestDoubleRegisterDuplicateErrorRegistration() {
s.T().Setenv(EnvSuppressErrorDuplicateRegister, "true")
s.Require().NotPanics(func() {
_ = Register(testCodespace, 50, "Internal IO error")
_ = Register(testCodespace, 50, "Internal IO error")
})
}