Skip to content

Commit

Permalink
multi: update wire error types.
Browse files Browse the repository at this point in the history
This updates the wire error types to leverage go
1.13 errors.Is/As functionality as well as confirm to
the error infrastructure best practices.
  • Loading branch information
dnldd committed Oct 14, 2020
1 parent 2b1dbb4 commit 64e3766
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 201 deletions.
2 changes: 1 addition & 1 deletion blockchain/fullblocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func TestFullBlocks(t *testing.T) {
// Ensure there is an error due to deserializing the block.
var msgBlock wire.MsgBlock
err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0)
var werr *wire.MessageError
var werr wire.MessageError
if !errors.As(err, &werr) {
t.Fatalf("block %q (hash %s, height %d) should have "+
"failed to decode", item.Name, blockHash,
Expand Down
1 change: 1 addition & 0 deletions blockchain/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ replace (
github.com/decred/dcrd/dcrutil/v3 => ../dcrutil
github.com/decred/dcrd/gcs/v2 => ../gcs
github.com/decred/dcrd/txscript/v3 => ../txscript
github.com/decred/dcrd/wire => ../wire
)
3 changes: 0 additions & 3 deletions blockchain/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak1
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY=
github.com/decred/dcrd/dcrutil/v2 v2.0.1 h1:aL+c7o7Q66HV1gIif+XkNYo9DeorN3l01Vns8mh0mqs=
github.com/decred/dcrd/dcrutil/v2 v2.0.1/go.mod h1:JdEgF6eh0TTohPeiqDxqDSikTSvAczq0J7tFMyyeD+k=
github.com/decred/dcrd/wire v1.3.0/go.mod h1:fnKGlUY2IBuqnpxx5dYRU5Oiq392OBqAuVjRVSkIoXM=
github.com/decred/dcrd/wire v1.4.0 h1:KmSo6eTQIvhXS0fLBQ/l7hG7QLcSJQKSwSyzSqJYDk0=
github.com/decred/dcrd/wire v1.4.0/go.mod h1:WxC/0K+cCAnBh+SKsRjIX9YPgvrjhmE+6pZlel1G7Ro=
github.com/decred/slog v1.0.0 h1:Dl+W8O6/JH6n2xIFN2p3DNjCmjYwvrXsjlSJTQQ4MhE=
github.com/decred/slog v1.0.0/go.mod h1:zR98rEZHSnbZ4WHZtO0iqmSZjDLKhkXfrPTZQKtAonQ=
github.com/decred/slog v1.1.0 h1:uz5ZFfmaexj1rEDgZvzQ7wjGkoSPjw2LCh8K+K1VrW4=
Expand Down
6 changes: 3 additions & 3 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1506,9 +1506,9 @@ func (sp *serverPeer) OnAddr(p *peer.Peer, msg *wire.MsgAddr) {
// the bytes received by the server.
func (sp *serverPeer) OnRead(p *peer.Peer, bytesRead int, msg wire.Message, err error) {
// Ban peers sending messages that do not conform to the wire protocol.
var errCode wire.ErrorCode
if errors.As(err, &errCode) {
peerLog.Errorf("Unable to read wire message from %s: %v", sp, err)
var werr wire.MessageError
if errors.As(err, &werr) {
peerLog.Errorf("Unable to read wire message from %s: %v", sp, werr)
sp.server.BanPeer(sp)
}

Expand Down
16 changes: 7 additions & 9 deletions wire/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ func TestVarIntNonCanonical(t *testing.T) {
// Decode from wire format.
rbuf := bytes.NewReader(test.in)
val, err := ReadVarInt(rbuf, test.pver)
var merr *MessageError
var merr MessageError
if !errors.As(err, &merr) {
t.Errorf("ReadVarInt #%d (%s) unexpected error %v", i,
test.name, err)
Expand Down Expand Up @@ -565,11 +565,11 @@ func TestVarStringOverflowErrors(t *testing.T) {
}{
{
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
pver, &MessageError{},
pver, MessageError{},
},
{
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
pver, &MessageError{},
pver, MessageError{},
},
}

Expand Down Expand Up @@ -835,13 +835,11 @@ func TestVarBytesOverflowErrors(t *testing.T) {
}{
{
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
pver,
&MessageError{},
pver, ErrVarBytesTooLong,
},
{
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
pver,
&MessageError{},
pver, ErrVarBytesTooLong,
},
}

Expand All @@ -851,9 +849,9 @@ func TestVarBytesOverflowErrors(t *testing.T) {
rbuf := bytes.NewReader(test.buf)
_, err := ReadVarBytes(rbuf, test.pver, MaxMessagePayload,
"test payload")
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
if !errors.Is(err, test.err) {
t.Errorf("ReadVarBytes #%d wrong error got: %v, "+
"want: %v", i, err, reflect.TypeOf(test.err))
"want: %v", i, err, test.err)
continue
}
}
Expand Down
192 changes: 53 additions & 139 deletions wire/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,246 +5,160 @@

package wire

import (
"fmt"
)

// ErrorCode describes a kind of message error.
type ErrorCode int
// ErrorKind identifies a kind of error. It has full support for errors.Is and
// errors.As, so the caller can directly check against an error kind when
// determining the reason for an error.
type ErrorKind string

// These constants are used to identify a specific Error.
const (
// ErrNonCanonicalVarInt is returned when a variable length integer is
// not canonically encoded.
ErrNonCanonicalVarInt ErrorCode = iota
ErrNonCanonicalVarInt = ErrorKind("ErrNonCanonicalVarInt")

// ErrVarStringTooLong is returned when a variable string exceeds the
// maximum message size allowed.
ErrVarStringTooLong
ErrVarStringTooLong = ErrorKind("ErrVarStringTooLong")

// ErrVarBytesTooLong is returned when a variable-length byte slice
// exceeds the maximum message size allowed.
ErrVarBytesTooLong
ErrVarBytesTooLong = ErrorKind("ErrVarBytesTooLong")

// ErrCmdTooLong is returned when a command exceeds the maximum command
// size allowed.
ErrCmdTooLong
ErrCmdTooLong = ErrorKind("ErrCmdTooLong")

// ErrPayloadTooLarge is returned when a payload exceeds the maximum
// payload size allowed.
ErrPayloadTooLarge
ErrPayloadTooLarge = ErrorKind("ErrPayloadTooLarge")

// ErrWrongNetwork is returned when a message intended for a different
// network is received.
ErrWrongNetwork
ErrWrongNetwork = ErrorKind("ErrWrongNetwork")

// ErrMalformedCmd is returned when a malformed command is received.
ErrMalformedCmd
ErrMalformedCmd = ErrorKind("ErrMalformedCmd")

// ErrUnknownCmd is returned when an unknown command is received.
ErrUnknownCmd
ErrUnknownCmd = ErrorKind("ErrUnknownCmd")

// ErrPayloadChecksum is returned when a message with an invalid checksum
// is received.
ErrPayloadChecksum
ErrPayloadChecksum = ErrorKind("ErrPayloadChecksum")

// ErrTooManyAddrs is returned when an address list exceeds the maximum
// allowed.
ErrTooManyAddrs
ErrTooManyAddrs = ErrorKind("ErrTooManyAddrs")

// ErrTooManyTxs is returned when a the number of transactions exceed the
// maximum allowed.
ErrTooManyTxs
ErrTooManyTxs = ErrorKind("ErrTooManyTxs")

// ErrMsgInvalidForPVer is returned when a message is invalid for
// the expected protocol version.
ErrMsgInvalidForPVer
ErrMsgInvalidForPVer = ErrorKind("ErrMsgInvalidForPVer")

// ErrFilterTooLarge is returned when a committed filter exceeds
// the maximum size allowed.
ErrFilterTooLarge
ErrFilterTooLarge = ErrorKind("ErrFilterTooLarge")

// ErrTooManyProofs is returned when the numeber of proof hashes
// exceeds the maximum allowed.
ErrTooManyProofs
ErrTooManyProofs = ErrorKind("ErrTooManyProofs")

// ErrTooManyFilterTypes is returned when the number of filter types
// exceeds the maximum allowed.
ErrTooManyFilterTypes
ErrTooManyFilterTypes = ErrorKind("ErrTooManyFilterTypes")

// ErrTooManyLocators is returned when the number of block locators exceed
// the maximum allowed.
ErrTooManyLocators
ErrTooManyLocators = ErrorKind("ErrTooManyLocators")

// ErrTooManyVectors is returned when the number of inventory vectors
// exceed the maximum allowed.
ErrTooManyVectors
ErrTooManyVectors = ErrorKind("ErrTooManyVectors")

// ErrTooManyHeaders is returned when the number of block headers exceed
// the maximum allowed.
ErrTooManyHeaders
ErrTooManyHeaders = ErrorKind("ErrTooManyHeaders")

// ErrHeaderContainsTxs is returned when a header's transactions
// count is greater than zero.
ErrHeaderContainsTxs
ErrHeaderContainsTxs = ErrorKind("ErrHeaderContainsTxs")

// ErrTooManyVotes is returned when the number of vote hashes exceed the
// maximum allowed.
ErrTooManyVotes
ErrTooManyVotes = ErrorKind("ErrTooManyVotes")

// ErrTooManyBlocks is returned when the number of block hashes exceed the
// maximum allowed.
ErrTooManyBlocks
ErrTooManyBlocks = ErrorKind("ErrTooManyBlocks")

// ErrMismatchedWitnessCount returned when a transaction has unequal witness
// and prefix txin quantities.
ErrMismatchedWitnessCount
ErrMismatchedWitnessCount = ErrorKind("ErrMismatchedWitnessCount")

// ErrUnknownTxType is returned when a transaction type is unknown.
ErrUnknownTxType
ErrUnknownTxType = ErrorKind("ErrUnknownTxType")

// ErrReadInPrefixFromWitnessOnlyTx is returned when attempting to read a
// transaction input prefix from a witness only transaction.
ErrReadInPrefixFromWitnessOnlyTx
ErrReadInPrefixFromWitnessOnlyTx = ErrorKind("ErrReadInPrefixFromWitnessOnlyTx")

// ErrInvalidMsg is returned for an invalid message structure.
ErrInvalidMsg
ErrInvalidMsg = ErrorKind("ErrInvalidMsg")

// ErrUserAgentTooLong is returned when the provided user agent exceeds
// the maximum allowed.
ErrUserAgentTooLong
ErrUserAgentTooLong = ErrorKind("ErrUserAgentTooLong")

// ErrTooManyFilterHeaders is returned when the number of committed filter
// headers exceed the maximum allowed.
ErrTooManyFilterHeaders
ErrTooManyFilterHeaders = ErrorKind("ErrTooManyFilterHeaders")

// ErrMalformedStrictString is returned when a string that has strict
// formatting requirements does not conform to the requirements.
ErrMalformedStrictString
ErrMalformedStrictString = ErrorKind("ErrMalformedStrictString")

// ErrTooManyInitialStateTypes is returned when the number of initial
// state types is larger than the maximum allowed by the protocol.
ErrTooManyInitStateTypes
ErrTooManyInitStateTypes = ErrorKind("ErrTooManyInitStateTypes")

// ErrInitialStateTypeTooLong is returned when an individual initial
// state type is longer than allowed by the protocol.
ErrInitStateTypeTooLong
ErrInitStateTypeTooLong = ErrorKind("ErrInitStateTypeTooLong")

// ErrTooManyTSpends is returned when the number of tspend hashes
// exceeds the maximum allowed.
ErrTooManyTSpends
ErrTooManyTSpends = ErrorKind("ErrTooManyTSpends")
)

// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrNonCanonicalVarInt: "ErrNonCanonicalVarInt",
ErrVarStringTooLong: "ErrVarStringTooLong",
ErrVarBytesTooLong: "ErrVarBytesTooLong",
ErrCmdTooLong: "ErrCmdTooLong",
ErrPayloadTooLarge: "ErrPayloadTooLarge",
ErrWrongNetwork: "ErrWrongNetwork",
ErrMalformedCmd: "ErrMalformedCmd",
ErrUnknownCmd: "ErrUnknownCmd",
ErrPayloadChecksum: "ErrPayloadChecksum",
ErrTooManyAddrs: "ErrTooManyAddrs",
ErrTooManyTxs: "ErrTooManyTxs",
ErrMsgInvalidForPVer: "ErrMsgInvalidForPVer",
ErrFilterTooLarge: "ErrFilterTooLarge",
ErrTooManyProofs: "ErrTooManyProofs",
ErrTooManyFilterTypes: "ErrTooManyFilterTypes",
ErrTooManyLocators: "ErrTooManyLocators",
ErrTooManyVectors: "ErrTooManyVectors",
ErrTooManyHeaders: "ErrTooManyHeaders",
ErrHeaderContainsTxs: "ErrHeaderContainsTxs",
ErrTooManyVotes: "ErrTooManyVotes",
ErrTooManyBlocks: "ErrTooManyBlocks",
ErrMismatchedWitnessCount: "ErrMismatchedWitnessCount",
ErrUnknownTxType: "ErrUnknownTxType",
ErrReadInPrefixFromWitnessOnlyTx: "ErrReadInPrefixFromWitnessOnlyTx",
ErrInvalidMsg: "ErrInvalidMsg",
ErrUserAgentTooLong: "ErrUserAgentTooLong",
ErrTooManyFilterHeaders: "ErrTooManyFilterHeaders",
ErrMalformedStrictString: "ErrMalformedStrictString",
ErrTooManyInitStateTypes: "ErrTooManyInitStateTypes",
ErrInitStateTypeTooLong: "ErrInitStateTypeTooLong",
ErrTooManyTSpends: "ErrTooManyTSpends",
}

// String returns the ErrorCode as a human-readable name.
func (e ErrorCode) String() string {
if s := errorCodeStrings[e]; s != "" {
return s
}
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
}

// Error implements the error interface.
func (e ErrorCode) Error() string {
return e.String()
}

// Is implements the interface to work with the standard library's errors.Is.
//
// It returns true in the following cases:
// - The target is a *MessageError and the error codes match
// - The target is an ErrorCode and it the error codes match
func (e ErrorCode) Is(target error) bool {
switch target := target.(type) {
case *MessageError:
return e == target.ErrorCode

case ErrorCode:
return e == target
}

return false
// Error satisfies the error interface and prints human-readable errors.
func (e ErrorKind) Error() string {
return string(e)
}

// MessageError describes an issue with a message.
// An example of some potential issues are messages from the wrong decred
// network, invalid commands, mismatched checksums, and exceeding max payloads.
//
// This provides a mechanism for the caller to type assert the error to
// differentiate between general io errors such as io.EOF and issues that
// resulted from malformed messages.
// MessageError identifies an error related to wire messages. It has
// full support for errors.Is and errors.As, so the caller can
// ascertain the specific reason for the error by checking the
// underlying error.
type MessageError struct {
Func string // Function name
ErrorCode ErrorCode // Describes the kind of error
Description string // Human readable description of the issue
Func string
Description string
Err error
}

// Error satisfies the error interface and prints human-readable errors.
func (m MessageError) Error() string {
if m.Func != "" {
return fmt.Sprintf("%v: %v", m.Func, m.Description)
}
return m.Description
}

// messageError creates an Error given a set of arguments.
func messageError(Func string, c ErrorCode, desc string) *MessageError {
return &MessageError{Func: Func, ErrorCode: c, Description: desc}
func (e MessageError) Error() string {
return e.Description
}

// Is implements the interface to work with the standard library's errors.Is.
//
// It returns true in the following cases:
// - The target is a *MessageError and the error codes match
// - The target is an ErrorCode and it the error codes match
func (m *MessageError) Is(target error) bool {
switch target := target.(type) {
case *MessageError:
return m.ErrorCode == target.ErrorCode

case ErrorCode:
return target == m.ErrorCode
}

return false
// Unwrap returns the underlying wrapped error.
func (e MessageError) Unwrap() error {
return e.Err
}

// Unwrap returns the underlying wrapped error if it is not ErrOther.
// Unwrap returns the ErrorCode. Else, it returns nil.
func (m *MessageError) Unwrap() error {
return m.ErrorCode
// messageError creates a MessageError given a set of arguments.
func messageError(fn string, kind ErrorKind, desc string) MessageError {
return MessageError{Func: fn, Err: kind, Description: desc}
}
Loading

0 comments on commit 64e3766

Please sign in to comment.