Skip to content

Commit

Permalink
Merge pull request #709 from iotaledger/error-handling-improvements
Browse files Browse the repository at this point in the history
Fix error message order and reconsider panics
  • Loading branch information
muXxer authored Mar 13, 2024
2 parents 0904c71 + 74f8cf1 commit 6304dd5
Show file tree
Hide file tree
Showing 66 changed files with 517 additions and 498 deletions.
16 changes: 7 additions & 9 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (
)

var (
// ErrUnknownAddrType gets returned for unknown address types.
ErrUnknownAddrType = ierrors.New("unknown address type")
// ErrInvalidAddressType gets returned when an address type is invalid.
ErrInvalidAddressType = ierrors.New("invalid address type")
// ErrInvalidRestrictedAddress gets returned when a RestrictedAddress is invalid.
Expand Down Expand Up @@ -175,7 +173,7 @@ type UTXOIDChainID interface {
FromOutputID(id OutputID) ChainID
}

func newAddress(addressType AddressType) (address Address, err error) {
func newAddress(addressType AddressType) (Address, error) {
switch addressType {
case AddressEd25519:
return &Ed25519Address{}, nil
Expand All @@ -192,7 +190,7 @@ func newAddress(addressType AddressType) (address Address, err error) {
case AddressRestricted:
return &RestrictedAddress{}, nil
default:
return nil, ierrors.Wrapf(ErrUnknownAddrType, "type %d", addressType)
return nil, ierrors.Errorf("unknown address type %d", addressType)
}
}

Expand All @@ -209,7 +207,7 @@ func bech32StringBytes(hrp NetworkPrefix, bytes []byte) string {
func ParseBech32(s string) (NetworkPrefix, Address, error) {
hrp, addrData, err := bech32.Decode(s)
if err != nil {
return "", nil, ierrors.Errorf("invalid bech32 encoding: %w", err)
return "", nil, ierrors.Wrap(err, "invalid bech32 encoding")
}

if len(addrData) == 0 {
Expand All @@ -225,7 +223,7 @@ func ParseBech32(s string) (NetworkPrefix, Address, error) {
case AddressMulti:
multiAddrRef, _, err := MultiAddressReferenceFromBytes(addrData)
if err != nil {
return "", nil, ierrors.Errorf("invalid multi address: %w", err)
return "", nil, ierrors.Wrap(err, "invalid multi address")
}

return NetworkPrefix(hrp), multiAddrRef, nil
Expand All @@ -238,13 +236,13 @@ func ParseBech32(s string) (NetworkPrefix, Address, error) {
if underlyingAddrType == AddressMulti {
multiAddrRef, consumed, err := MultiAddressReferenceFromBytes(addrData[1:])
if err != nil {
return "", nil, ierrors.Errorf("invalid multi address: %w", err)
return "", nil, ierrors.Wrap(err, "invalid multi address")
}

// get the address capabilities from the remaining bytes
capabilities, _, err := AddressCapabilitiesBitMaskFromBytes(addrData[1+consumed:])
if err != nil {
return "", nil, ierrors.Errorf("invalid address capabilities: %w", err)
return "", nil, ierrors.Wrap(err, "invalid restricted address capabilities")
}

return NetworkPrefix(hrp), &RestrictedAddress{
Expand Down Expand Up @@ -312,6 +310,6 @@ func AddressFromReader(reader io.ReadSeeker) (Address, error) {
return RestrictedAddressFromReader(reader)

default:
return nil, ierrors.Wrapf(ErrUnknownAddrType, "type %d", addressType)
return nil, ierrors.Errorf("unknown address type %d", addressType)
}
}
21 changes: 12 additions & 9 deletions address_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package iotago
import (
"crypto"
"crypto/ed25519"
"fmt"

"github.com/iotaledger/hive.go/ierrors"
)
Expand Down Expand Up @@ -56,8 +57,10 @@ func NewAddressKeysForRestrictedEd25519Address(addr *RestrictedAddress, prvKey e
switch addr.Address.(type) {
case *Ed25519Address:
return AddressKeys{Address: addr, Keys: prvKey}, nil
case *ImplicitAccountCreationAddress:
panic("ImplicitAccountCreationAddress is not allowed in restricted addresses")
default:
return AddressKeys{}, ierrors.Wrapf(ErrUnknownAddrType, "unknown underlying address type %T in restricted address", addr)
panic(fmt.Sprintf("address type %T is not supported in the address signer since it only handles addresses backed by keypairs", addr))
}
}

Expand Down Expand Up @@ -108,7 +111,7 @@ func (s *InMemoryAddressSigner) privateKeyForAddress(addr Address) (crypto.Priva

prvKey, ok := maybePrvKey.(ed25519.PrivateKey)
if !ok {
return nil, ierrors.Wrapf(ErrAddressKeysWrongType, "Ed25519 address needs to have a %T private key mapped but got %T", ed25519.PrivateKey{}, maybePrvKey)
return nil, ierrors.WithMessagef(ErrAddressKeysWrongType, "Ed25519 address needs to have a %T private key mapped but got %T", ed25519.PrivateKey{}, maybePrvKey)
}

return prvKey, nil
Expand All @@ -123,14 +126,14 @@ func (s *InMemoryAddressSigner) privateKeyForAddress(addr Address) (crypto.Priva
case *Ed25519Address:
return privateKeyForEd25519Address(underlyingAddr)
default:
return nil, ierrors.Wrapf(ErrUnknownAddrType, "unknown underlying address type %T in restricted address", addr)
panic(fmt.Sprintf("underlying address type %T in restricted address is not supported in the the address signer since it only handles addresses backed by keypairs", addr))
}

case *ImplicitAccountCreationAddress:
return privateKeyForEd25519Address(address)

default:
return nil, ierrors.Wrapf(ErrUnknownAddrType, "type %T", addr)
panic(fmt.Sprintf("address type %T is not supported in the address signer since it only handles addresses backed by keypairs", addr))
}
}

Expand All @@ -144,7 +147,7 @@ func (s *InMemoryAddressSigner) SignerUIDForAddress(addr Address) (Identifier, e

ed25519PrvKey, ok := prvKey.(ed25519.PrivateKey)
if !ok {
return EmptyIdentifier, ierrors.Wrapf(ErrAddressKeysWrongType, "Ed25519 address needs to have a %T private key mapped but got %T", ed25519.PrivateKey{}, prvKey)
return EmptyIdentifier, ierrors.WithMessagef(ErrAddressKeysWrongType, "Ed25519 address needs to have a %T private key mapped but got %T", ed25519.PrivateKey{}, prvKey)
}

// the UID is the blake2b 256 hash of the public key
Expand All @@ -155,12 +158,12 @@ func (s *InMemoryAddressSigner) SignerUIDForAddress(addr Address) (Identifier, e
func (s *InMemoryAddressSigner) Sign(addr Address, msg []byte) (signature Signature, err error) {
prvKey, err := s.privateKeyForAddress(addr)
if err != nil {
return nil, ierrors.Errorf("can't sign message for address: %w", err)
return nil, ierrors.Wrap(err, "can't sign message for address")
}

ed25519PrvKey, ok := prvKey.(ed25519.PrivateKey)
if !ok {
return nil, ierrors.Wrapf(ErrAddressKeysWrongType, "Ed25519 address needs to have a %T private key mapped but got %T", ed25519.PrivateKey{}, prvKey)
return nil, ierrors.WithMessagef(ErrAddressKeysWrongType, "Ed25519 address needs to have a %T private key mapped but got %T", ed25519.PrivateKey{}, prvKey)
}

ed25519Sig := &Ed25519Signature{}
Expand All @@ -183,12 +186,12 @@ func (s *InMemoryAddressSigner) EmptySignatureForAddress(addr Address) (signatur
case *Ed25519Address:
return &Ed25519Signature{}, nil
default:
return nil, ierrors.Wrapf(ErrUnknownAddrType, "unknown underlying address type %T in restricted address", addr)
panic(fmt.Sprintf("underlying address type %T in restricted address is not supported in the address signer since it only handles addresses backed by keypairs", addr))
}
case *ImplicitAccountCreationAddress:
return &Ed25519Signature{}, nil

default:
return nil, ierrors.Wrapf(ErrUnknownAddrType, "type %T", addr)
panic(fmt.Sprintf("address type %T is not supported in the address signer since it only handles addresses backed by keypairs", addr))
}
}
4 changes: 2 additions & 2 deletions allotment.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ func allotmentMaxManaValidator(maxManaValue Mana) ElementValidationFunc[*Allotme
var err error
sum, err = safemath.SafeAdd(sum, next.Mana)
if err != nil {
return ierrors.Errorf("%w: %w: allotment mana sum calculation failed at allotment %d", ErrMaxManaExceeded, err, index)
return ierrors.Join(ErrMaxManaExceeded, ierrors.Wrapf(err, "allotment mana sum calculation failed at allotment %d", index))
}

if sum > maxManaValue {
return ierrors.Wrapf(ErrMaxManaExceeded, "sum of allotted mana exceeds max value with allotment %d", index)
return ierrors.WithMessagef(ErrMaxManaExceeded, "sum of allotted mana exceeds max value with allotment %d", index)
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ func (v Version) Bytes() ([]byte, error) {
}

func VersionFromBytes(b []byte) (Version, int, error) {
if len(b) < 1 {
if len(b) < VersionLength {
return 0, 0, ierrors.New("invalid version bytes length")
}

return Version(b[0]), 1, nil
return Version(b[0]), VersionLength, nil
}

// VersionSignalingParameters defines the parameters used by signaling protocol parameters upgrade.
Expand Down
31 changes: 20 additions & 11 deletions api_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package iotago

import (
"context"
"fmt"

"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/serializer/v2/serix"
Expand Down Expand Up @@ -35,29 +36,33 @@ var (
case *NFTAddress:
case *AnchorAddress:
case *ImplicitAccountCreationAddress:
return ierrors.Wrapf(ErrInvalidNestedAddressType, "address with index %d is an implicit account creation address inside a multi address", idx)
return ierrors.WithMessagef(ErrInvalidNestedAddressType, "address with index %d is an implicit account creation address inside a multi address", idx)
case *MultiAddress:
return ierrors.Wrapf(ErrInvalidNestedAddressType, "address with index %d is a multi address inside a multi address", idx)
return ierrors.WithMessagef(ErrInvalidNestedAddressType, "address with index %d is a multi address inside a multi address", idx)
case *RestrictedAddress:
return ierrors.Wrapf(ErrInvalidNestedAddressType, "address with index %d is a restricted address inside a multi address", idx)
return ierrors.WithMessagef(ErrInvalidNestedAddressType, "address with index %d is a restricted address inside a multi address", idx)
default:
return ierrors.Wrapf(ErrUnknownAddrType, "address with index %d has an unknown address type (%T) inside a multi address", idx, addr)
// We're switching on the Go address type here, so we can only run into the default case
// if we added a new address type and have not handled it above or a user passed a type
// implementing the address interface (only possible when iota.go is used as a library).
// In both cases we want to panic.
panic(fmt.Sprintf("address with index %d has an unknown address type (%T) inside a multi address", idx, addr))
}

// check for minimum address weight
if address.Weight == 0 {
return ierrors.Wrapf(ErrMultiAddressWeightInvalid, "address with index %d needs to have at least weight=1", idx)
return ierrors.WithMessagef(ErrMultiAddressWeightInvalid, "address with index %d needs to have at least weight=1", idx)
}

cumulativeWeight += uint16(address.Weight)
}

// check for valid threshold
if addr.Threshold > cumulativeWeight {
return ierrors.Wrapf(ErrMultiAddressThresholdInvalid, "the threshold value exceeds the cumulative weight of all addresses (%d>%d)", addr.Threshold, cumulativeWeight)
return ierrors.WithMessagef(ErrMultiAddressThresholdInvalid, "the threshold value exceeds the cumulative weight of all addresses (%d>%d)", addr.Threshold, cumulativeWeight)
}
if addr.Threshold < 1 {
return ierrors.Wrap(ErrMultiAddressThresholdInvalid, "multi addresses need to have at least threshold=1")
return ierrors.WithMessage(ErrMultiAddressThresholdInvalid, "multi addresses need to have at least threshold=1")
}

return nil
Expand All @@ -69,18 +74,22 @@ var (
// 3. The bitmask does not contain trailing zero bytes.
restrictedAddressValidatorFunc = func(_ context.Context, addr RestrictedAddress) error {
if err := BitMaskNonTrailingZeroBytesValidatorFunc(addr.AllowedCapabilities); err != nil {
return ierrors.Wrapf(ErrInvalidRestrictedAddress, "invalid allowed capabilities bitmask: %w", err)
return ierrors.WithMessagef(ErrInvalidRestrictedAddress, "invalid allowed capabilities bitmask: %w", err)
}

switch addr.Address.(type) {
case *Ed25519Address, *AccountAddress, *NFTAddress, *AnchorAddress, *MultiAddress:
// allowed address types
case *ImplicitAccountCreationAddress:
return ierrors.Wrap(ErrInvalidNestedAddressType, "underlying address is an implicit account creation address inside a restricted address")
return ierrors.WithMessage(ErrInvalidNestedAddressType, "underlying address is an implicit account creation address inside a restricted address")
case *RestrictedAddress:
return ierrors.Wrap(ErrInvalidNestedAddressType, "underlying address is a restricted address inside a restricted address")
return ierrors.WithMessage(ErrInvalidNestedAddressType, "underlying address is a restricted address inside a restricted address")
default:
return ierrors.Wrapf(ErrUnknownAddrType, "underlying address has an unknown address type (%T) inside a restricted address", addr)
// We're switching on the Go address type here, so we can only run into the default case
// if we added a new address type and have not handled it above or a user passed a type
// implementing the address interface (only possible when iota.go is used as a library).
// In both cases we want to panic.
panic(fmt.Sprintf("underlying address of the restricted address is of unknown address type (%T)", addr))
}

return nil
Expand Down
6 changes: 3 additions & 3 deletions attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ func (a *Attestation) Compare(other *Attestation) int {
func (a *Attestation) BlockID() (BlockID, error) {
signatureBytes, err := a.API.Encode(a.Signature)
if err != nil {
return EmptyBlockID, ierrors.Errorf("failed to create blockID: %w", err)
return EmptyBlockID, ierrors.Wrap(err, "failed to create blockID")
}

headerHash, err := a.Header.Hash(a.API)
if err != nil {
return EmptyBlockID, ierrors.Errorf("failed to create blockID: %w", err)
return EmptyBlockID, ierrors.Wrap(err, "failed to create blockID")
}

id := blockIdentifier(headerHash, a.BodyHash, signatureBytes)
Expand All @@ -91,7 +91,7 @@ func (a *Attestation) BlockID() (BlockID, error) {
func (a *Attestation) signingMessage() ([]byte, error) {
headerHash, err := a.Header.Hash(a.API)
if err != nil {
return nil, ierrors.Errorf("failed to create signing message: %w", err)
return nil, ierrors.Wrap(err, "failed to create signing message")
}

return blockSigningMessage(headerHash, a.BodyHash), nil
Expand Down
14 changes: 7 additions & 7 deletions bech32/bech32.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ var charset = newEncoding("qpzry9x8gf2tvdw0s3jn54khce6mua7l")
func Encode(hrp string, src []byte) (string, error) {
dataLen := base32.EncodedLen(len(src))
if len(hrp)+dataLen+checksumLength+1 > maxStringLength {
return "", ierrors.Wrapf(ErrInvalidLength, "String length=%d, data length=%d", len(hrp), dataLen)
return "", ierrors.WithMessagef(ErrInvalidLength, "hrp length=%d, data length=%d", len(hrp), dataLen)
}
// validate the human-readable part
if len(hrp) < 1 {
return "", ierrors.Wrap(ErrInvalidLength, "String must not be empty")
return "", ierrors.WithMessage(ErrInvalidLength, "hrp must not be empty")
}
for _, c := range hrp {
if !isValidHRPChar(c) {
return "", ierrors.Wrap(ErrInvalidCharacter, "not US-ASCII character in human-readable part")
return "", ierrors.WithMessage(ErrInvalidCharacter, "non US-ASCII character in human-readable part")
}
}
if err := validateCase(hrp); err != nil {
Expand Down Expand Up @@ -66,20 +66,20 @@ func Encode(hrp string, src []byte) (string, error) {
// An SyntaxError is returned when the error can be matched to a certain position in s.
func Decode(s string) (string, []byte, error) {
if len(s) > maxStringLength {
return "", nil, &SyntaxError{ierrors.Wrap(ErrInvalidLength, "maximum length exceeded"), maxStringLength}
return "", nil, &SyntaxError{ierrors.WithMessage(ErrInvalidLength, "maximum length exceeded"), maxStringLength}
}
// validate the separator
hrpLen := strings.LastIndex(s, string(separator))
if hrpLen == -1 {
return "", nil, ErrMissingSeparator
}
if hrpLen < 1 || hrpLen+checksumLength > len(s) {
return "", nil, &SyntaxError{ierrors.Wrap(ErrInvalidSeparator, "invalid position"), hrpLen}
return "", nil, &SyntaxError{ierrors.WithMessage(ErrInvalidSeparator, "invalid position"), hrpLen}
}
// validate characters in human-readable part
for i, c := range s[:hrpLen] {
if !isValidHRPChar(c) {
return "", nil, &SyntaxError{ierrors.Wrap(ErrInvalidCharacter, "not US-ASCII character in human-readable part"), i}
return "", nil, &SyntaxError{ierrors.WithMessage(ErrInvalidCharacter, "non US-ASCII character in human-readable part"), i}
}
}
// validate that the case of the entire string is consistent
Expand All @@ -95,7 +95,7 @@ func Decode(s string) (string, []byte, error) {
// decode the data part
data, err := charset.decode(chars)
if err != nil {
return "", nil, &SyntaxError{ierrors.Wrap(ErrInvalidCharacter, "non-charset character in data part"), hrpLen + 1 + len(data)}
return "", nil, &SyntaxError{ierrors.WithMessage(ErrInvalidCharacter, "non-charset character in data part"), hrpLen + 1 + len(data)}
}

// validate the checksum
Expand Down
12 changes: 6 additions & 6 deletions bech32/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import "github.com/iotaledger/hive.go/ierrors"

// Errors reported during bech32 decoding.
var (
ErrInvalidLength = ierrors.New("invalid length")
ErrMissingSeparator = ierrors.New("missing separator '" + string(separator) + "'")
ErrInvalidSeparator = ierrors.New("separator '" + string(separator) + "' at invalid position")
ErrMixedCase = ierrors.New("mixed case")
ErrInvalidCharacter = ierrors.New("invalid character")
ErrInvalidChecksum = ierrors.New("invalid checksum")
ErrInvalidLength = ierrors.New("invalid bech32 length")
ErrMissingSeparator = ierrors.New("missing bech32 separator '" + string(separator) + "'")
ErrInvalidSeparator = ierrors.New("bech32 separator '" + string(separator) + "' at invalid position")
ErrMixedCase = ierrors.New("mixed case in bech32 string")
ErrInvalidCharacter = ierrors.New("invalid bech32 character")
ErrInvalidChecksum = ierrors.New("invalid bech32 checksum")
)

// A SyntaxError is a description of a Bech32 syntax error.
Expand Down
4 changes: 2 additions & 2 deletions bech32/internal/base32/base32.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ func Encode(dst []uint8, src []byte) int {

var (
// ErrInvalidLength reports an attempt to decode an input of invalid length.
ErrInvalidLength = ierrors.New("invalid length")
ErrInvalidLength = ierrors.New("invalid base32 length")
// ErrNonZeroPadding reports an attempt to decode an input without zero padding.
ErrNonZeroPadding = ierrors.New("non-zero padding")
ErrNonZeroPadding = ierrors.New("non-zero padding in base32")
)

// A CorruptInputError is a description of a base32 syntax error.
Expand Down
Loading

0 comments on commit 6304dd5

Please sign in to comment.