diff --git a/.changelog/unreleased/improvements/1901-export-p2p-package-errors.md b/.changelog/unreleased/improvements/1901-export-p2p-package-errors.md new file mode 100644 index 0000000000..97dae672c8 --- /dev/null +++ b/.changelog/unreleased/improvements/1901-export-p2p-package-errors.md @@ -0,0 +1 @@ +- `[p2p]` Export p2p package errors ([\#1901](https://github.com/cometbft/cometbft/pull/1901)) (contributes to [\#1140](https://github.com/cometbft/cometbft/issues/1140)) diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 2065073412..8ce52f58b8 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -852,6 +852,10 @@ func (ch *Channel) nextPacketMsg() tmp2p.PacketMsg { func (ch *Channel) writePacketMsgTo(w io.Writer) (n int, err error) { packet := ch.nextPacketMsg() n, err = protoio.NewDelimitedWriter(w).WriteMsg(mustWrapPacket(&packet)) + if err != nil { + err = ErrPacketWrite{Source: err} + } + atomic.AddInt64(&ch.recentlySent, int64(n)) return n, err } @@ -863,8 +867,9 @@ func (ch *Channel) recvPacketMsg(packet tmp2p.PacketMsg) ([]byte, error) { ch.Logger.Debug("Read PacketMsg", "conn", ch.conn, "packet", packet) recvCap, recvReceived := ch.desc.RecvMessageCapacity, len(ch.recving)+len(packet.Data) if recvCap < recvReceived { - return nil, fmt.Errorf("received message exceeds available capacity: %v < %v", recvCap, recvReceived) + return nil, ErrPacketTooBig{Max: recvCap, Received: recvReceived} } + ch.recving = append(ch.recving, packet.Data...) if packet.EOF { msgBytes := ch.recving diff --git a/p2p/conn/errors.go b/p2p/conn/errors.go new file mode 100644 index 0000000000..01ee5586a1 --- /dev/null +++ b/p2p/conn/errors.go @@ -0,0 +1,64 @@ +package conn + +import ( + "errors" + "fmt" +) + +var ( + ErrInvalidSecretConnKeySend = errors.New("send invalid secret connection key") + ErrInvalidSecretConnKeyRecv = errors.New("invalid receive SecretConnection Key") + ErrChallengeVerification = errors.New("challenge verification failed") +) + +// ErrPacketWrite Packet error when writing. +type ErrPacketWrite struct { + Source error +} + +func (e ErrPacketWrite) Error() string { + return fmt.Sprintf("failed to write packet message: %v", e.Source) +} + +func (e ErrPacketWrite) Unwrap() error { + return e.Source +} + +type ErrUnexpectedPubKeyType struct { + Expected string + Got string +} + +func (e ErrUnexpectedPubKeyType) Error() string { + return fmt.Sprintf("expected pubkey type %s, got %s", e.Expected, e.Got) +} + +type ErrDecryptFrame struct { + Source error +} + +func (e ErrDecryptFrame) Error() string { + return fmt.Sprintf("SecretConnection: failed to decrypt the frame: %v", e.Source) +} + +func (e ErrDecryptFrame) Unwrap() error { + return e.Source +} + +type ErrPacketTooBig struct { + Received int + Max int +} + +func (e ErrPacketTooBig) Error() string { + return fmt.Sprintf("received message exceeds available capacity (max: %d, got: %d)", e.Max, e.Received) +} + +type ErrChunkTooBig struct { + Received int + Max int +} + +func (e ErrChunkTooBig) Error() string { + return fmt.Sprintf("chunk too big (max: %d, got %d)", e.Max, e.Received) +} diff --git a/p2p/conn/evil_secret_connection_test.go b/p2p/conn/evil_secret_connection_test.go index cc63a7a6ec..b952260974 100644 --- a/p2p/conn/evil_secret_connection_test.go +++ b/p2p/conn/evil_secret_connection_test.go @@ -244,15 +244,15 @@ func (c *evilConn) signChallenge() []byte { // MakeSecretConnection errors at different stages. func TestMakeSecretConnection(t *testing.T) { testCases := []struct { - name string - conn *evilConn - errMsg string + name string + conn *evilConn + checkError func(error) bool // Function to check if the error matches the expectation }{ - {"refuse to share ethimeral key", newEvilConn(false, false, false, false), "EOF"}, - {"share bad ethimeral key", newEvilConn(true, true, false, false), "wrong wireType"}, - {"refuse to share auth signature", newEvilConn(true, false, false, false), "EOF"}, - {"share bad auth signature", newEvilConn(true, false, true, true), "failed to decrypt SecretConnection"}, - {"all good", newEvilConn(true, false, true, false), ""}, + {"refuse to share ethimeral key", newEvilConn(false, false, false, false), func(err error) bool { return err == io.EOF }}, + {"share bad ethimeral key", newEvilConn(true, true, false, false), func(err error) bool { return assert.Contains(t, err.Error(), "wrong wireType") }}, + {"refuse to share auth signature", newEvilConn(true, false, false, false), func(err error) bool { return err == io.EOF }}, + {"share bad auth signature", newEvilConn(true, false, true, true), func(err error) bool { return errors.As(err, &ErrDecryptFrame{}) }}, + {"all good", newEvilConn(true, false, true, false), func(err error) bool { return err == nil }}, } for _, tc := range testCases { @@ -260,10 +260,8 @@ func TestMakeSecretConnection(t *testing.T) { t.Run(tc.name, func(t *testing.T) { privKey := ed25519.GenPrivKey() _, err := MakeSecretConnection(tc.conn, privKey) - if tc.errMsg != "" { - if assert.Error(t, err) { //nolint:testifylint // require.Error doesn't work with the conditional here - assert.Contains(t, err.Error(), tc.errMsg) - } + if tc.checkError != nil { + assert.True(t, tc.checkError(err)) } else { require.NoError(t, err) } diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index bbd438d571..2458427776 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "encoding/binary" "errors" - "fmt" "io" "math" "net" @@ -46,8 +45,7 @@ const ( ) var ( - ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer") - + ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer") secretConnKeyAndChallengeGen = []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN") ) @@ -133,11 +131,12 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* sendAead, err := chacha20poly1305.New(sendSecret[:]) if err != nil { - return nil, errors.New("invalid send SecretConnection Key") + return nil, ErrInvalidSecretConnKeySend } + recvAead, err := chacha20poly1305.New(recvSecret[:]) if err != nil { - return nil, errors.New("invalid receive SecretConnection Key") + return nil, ErrInvalidSecretConnKeyRecv } sc := &SecretConnection{ @@ -162,11 +161,16 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* } remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig + // Usage in your function if _, ok := remPubKey.(ed25519.PubKey); !ok { - return nil, fmt.Errorf("expected ed25519 pubkey, got %T", remPubKey) + return nil, ErrUnexpectedPubKeyType{ + Expected: ed25519.KeyType, + Got: remPubKey.Type(), + } } + if !remPubKey.VerifySignature(challenge[:], remSignature) { - return nil, errors.New("challenge verification failed") + return nil, ErrChallengeVerification } // We've authorized. @@ -214,6 +218,7 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) { if err != nil { return err } + n += len(chunk) return nil }(); err != nil { @@ -249,8 +254,9 @@ func (sc *SecretConnection) Read(data []byte) (n int, err error) { defer pool.Put(frame) _, err = sc.recvAead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil) if err != nil { - return n, fmt.Errorf("failed to decrypt SecretConnection: %w", err) + return n, ErrDecryptFrame{Source: err} } + incrNonce(sc.recvNonce) // end decryption @@ -258,8 +264,12 @@ func (sc *SecretConnection) Read(data []byte) (n int, err error) { // set recvBuffer to the rest. chunkLength := binary.LittleEndian.Uint32(frame) // read the first four bytes if chunkLength > dataMaxSize { - return 0, errors.New("chunkLength is greater than dataMaxSize") + return 0, ErrChunkTooBig{ + Received: int(chunkLength), + Max: dataMaxSize, + } } + chunk := frame[dataLenSize : dataLenSize+chunkLength] n = copy(data, chunk) if n < len(chunk) { @@ -289,7 +299,7 @@ func genEphKeys() (ephPub, ephPriv *[32]byte) { // see: https://github.com/dalek-cryptography/x25519-dalek/blob/34676d336049df2bba763cc076a75e47ae1f170f/src/x25519.rs#L56-L74 ephPub, ephPriv, err = box.GenerateKey(crand.Reader) if err != nil { - panic("Could not generate ephemeral key-pair") + panic("failed to generate ephemeral key-pair") } return ephPub, ephPriv } diff --git a/p2p/conn/secret_connection_test.go b/p2p/conn/secret_connection_test.go index b635fd1935..e85441c411 100644 --- a/p2p/conn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -3,6 +3,7 @@ package conn import ( "bufio" "encoding/hex" + "errors" "flag" "fmt" "io" @@ -155,7 +156,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { readBuffer := make([]byte, dataMaxSize) for { n, err := nodeSecretConn.Read(readBuffer) - if err == io.EOF { + if errors.Is(err, io.EOF) { if err := nodeConn.PipeReader.Close(); err != nil { t.Error(err) return nil, true, err diff --git a/p2p/errors.go b/p2p/errors.go index 934a204027..61e3d1e8bc 100644 --- a/p2p/errors.go +++ b/p2p/errors.go @@ -1,8 +1,18 @@ package p2p import ( + "errors" "fmt" "net" + + "github.com/cometbft/cometbft/libs/bytes" +) + +var ( + ErrEmptyHost = errors.New("host is empty") + ErrNoIP = errors.New("no IP address found") + ErrNoNodeInfo = errors.New("no node info found") + ErrInvalidIP = errors.New("invalid IP address") ) // ErrFilterTimeout indicates that a filter operation timed out. @@ -171,6 +181,8 @@ func (e ErrNetAddressInvalid) Error() string { return fmt.Sprintf("invalid address (%s): %v", e.Addr, e.Err) } +func (e ErrNetAddressInvalid) Unwrap() error { return e.Err } + type ErrNetAddressLookup struct { Addr string Err error @@ -180,6 +192,8 @@ func (e ErrNetAddressLookup) Error() string { return fmt.Sprintf("error looking up host (%s): %v", e.Addr, e.Err) } +func (e ErrNetAddressLookup) Unwrap() error { return e.Err } + // ErrCurrentlyDialingOrExistingAddress indicates that we're currently // dialing this address or it belongs to an existing peer. type ErrCurrentlyDialingOrExistingAddress struct { @@ -189,3 +203,132 @@ type ErrCurrentlyDialingOrExistingAddress struct { func (e ErrCurrentlyDialingOrExistingAddress) Error() string { return fmt.Sprintf("connection with %s has been established or dialed", e.Addr) } + +type ErrInvalidPort struct { + Port uint32 +} + +func (e ErrInvalidPort) Error() string { + return fmt.Sprintf("invalid port: %d", e.Port) +} + +type ErrInvalidPeerID struct { + ID ID + Source error +} + +func (e ErrInvalidPeerID) Error() string { + return fmt.Sprintf("invalid peer ID (%v): %v", e.ID, e.Source) +} + +func (e ErrInvalidPeerID) Unwrap() error { + return e.Source +} + +type ErrInvalidNodeVersion struct { + Version string +} + +func (e ErrInvalidNodeVersion) Error() string { + return fmt.Sprintf("invalid version %s: version must be valid ASCII text without tabs", e.Version) +} + +type ErrDuplicateChannelID struct { + ID byte +} + +func (e ErrDuplicateChannelID) Error() string { + return fmt.Sprintf("channels contains duplicate channel id %v", e.ID) +} + +type ErrChannelsTooLong struct { + Length int + Max int +} + +func (e ErrChannelsTooLong) Error() string { + return fmt.Sprintf("channels is too long (max: %d, got: %d)", e.Max, e.Length) +} + +type ErrInvalidMoniker struct { + Moniker string +} + +func (e ErrInvalidMoniker) Error() string { + return fmt.Sprintf("moniker must be valid non-empty ASCII text without tabs, but got %v", e.Moniker) +} + +type ErrInvalidTxIndex struct { + TxIndex string +} + +func (e ErrInvalidTxIndex) Error() string { + return fmt.Sprintf("tx index must be either 'on', 'off', or empty string, got '%v'", e.TxIndex) +} + +type ErrInvalidRPCAddress struct { + RPCAddress string +} + +func (e ErrInvalidRPCAddress) Error() string { + return fmt.Sprintf("rpc address must be valid ASCII text without tabs, but got %v", e.RPCAddress) +} + +type ErrInvalidNodeInfoType struct { + Type string + Expected string +} + +func (e ErrInvalidNodeInfoType) Error() string { + return fmt.Sprintf("invalid NodeInfo type, Expected %s but got %s", e.Expected, e.Type) +} + +type ErrDifferentBlockVersion struct { + Other uint64 + Our uint64 +} + +func (e ErrDifferentBlockVersion) Error() string { + return fmt.Sprintf("peer is on a different Block version. Got %d, expected %d", + e.Other, e.Our) +} + +type ErrDifferentNetwork struct { + Other string + Our string +} + +func (e ErrDifferentNetwork) Error() string { + return fmt.Sprintf("peer is on a different network. Got %s, expected %s", e.Other, e.Our) +} + +type ErrNoCommonChannels struct { + OtherChannels bytes.HexBytes + OurChannels bytes.HexBytes +} + +func (e ErrNoCommonChannels) Error() string { + return fmt.Sprintf("no common channels between us (%v) and peer (%v)", e.OurChannels, e.OtherChannels) +} + +type ErrStart struct { + Service any + Err error +} + +func (e ErrStart) Error() string { + return fmt.Sprintf("failed to start %v: %v", e.Service, e.Err) +} + +func (e ErrStart) Unwrap() error { + return e.Err +} + +type ErrInvalidPeerIDLength struct { + Got int + Expected int +} + +func (e ErrInvalidPeerIDLength) Error() string { + return fmt.Sprintf("invalid peer ID length, got %d, expected %d", e.Expected, e.Got) +} diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 8fa960320d..56a79517c0 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -6,7 +6,6 @@ package p2p import ( "encoding/hex" - "errors" "flag" "fmt" "net" @@ -71,7 +70,7 @@ func NewNetAddressString(addr string) (*NetAddress, error) { addrWithoutProtocol := removeProtocolIfDefined(addr) spl := strings.Split(addrWithoutProtocol, "@") if len(spl) != 2 { - return nil, ErrNetAddressNoID{addr} + return nil, ErrNetAddressInvalid{Addr: addr, Err: ErrNetAddressNoID{addr}} } // get ID @@ -89,7 +88,7 @@ func NewNetAddressString(addr string) (*NetAddress, error) { if len(host) == 0 { return nil, ErrNetAddressInvalid{ addrWithoutProtocol, - errors.New("host is empty"), + ErrEmptyHost, } } @@ -141,10 +140,11 @@ func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { func NetAddressFromProto(pb tmp2p.NetAddress) (*NetAddress, error) { ip := net.ParseIP(pb.IP) if ip == nil { - return nil, fmt.Errorf("invalid IP address %v", pb.IP) + return nil, ErrNetAddressInvalid{Addr: pb.IP, Err: ErrInvalidIP} } + if pb.Port >= 1<<16 { - return nil, fmt.Errorf("invalid port number %v", pb.Port) + return nil, ErrNetAddressInvalid{Addr: pb.IP, Err: ErrInvalidPort{pb.Port}} } return &NetAddress{ ID: ID(pb.ID), @@ -264,14 +264,14 @@ func (na *NetAddress) Routable() bool { // address or one that matches the RFC3849 documentation address format. func (na *NetAddress) Valid() error { if err := validateID(na.ID); err != nil { - return fmt.Errorf("invalid ID: %w", err) + return ErrInvalidPeerID{na.ID, err} } if na.IP == nil { - return errors.New("no IP") + return ErrNoIP } if na.IP.IsUnspecified() || na.RFC3849() || na.IP.Equal(net.IPv4bcast) { - return errors.New("invalid IP") + return ErrNetAddressInvalid{na.IP.String(), ErrInvalidIP} } return nil } @@ -408,14 +408,14 @@ func removeProtocolIfDefined(addr string) string { func validateID(id ID) error { if len(id) == 0 { - return errors.New("no ID") + return ErrNoIP } idBytes, err := hex.DecodeString(string(id)) if err != nil { return err } if len(idBytes) != IDByteLength { - return fmt.Errorf("invalid hex length - got %d, expected %d", len(idBytes), IDByteLength) + return ErrInvalidPeerIDLength{Got: len(idBytes), Expected: IDByteLength} } return nil } diff --git a/p2p/netaddress_test.go b/p2p/netaddress_test.go index b122a55b20..85ee7c8a16 100644 --- a/p2p/netaddress_test.go +++ b/p2p/netaddress_test.go @@ -118,7 +118,7 @@ func TestNewNetAddressString(t *testing.T) { assert.Equal(t, tc.expected, addr.String()) } } else { - require.Error(t, err, tc.addr) + require.ErrorAs(t, err, &ErrNetAddressInvalid{Addr: addr.String(), Err: err}) } }) } diff --git a/p2p/node_info.go b/p2p/node_info.go index c58b85e4bb..81c3d55bb2 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -2,7 +2,6 @@ package p2p import ( "bytes" - "errors" "fmt" "reflect" @@ -133,25 +132,26 @@ func (info DefaultNodeInfo) Validate() error { // Validate Version if len(info.Version) > 0 && (!cmtstrings.IsASCIIText(info.Version) || cmtstrings.ASCIITrim(info.Version) == "") { - return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version) + return ErrInvalidNodeVersion{Version: info.Version} } // Validate Channels - ensure max and check for duplicates. if len(info.Channels) > maxNumChannels { - return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) + return ErrChannelsTooLong{Length: len(info.Channels), Max: maxNumChannels} } + channels := make(map[byte]struct{}) for _, ch := range info.Channels { _, ok := channels[ch] if ok { - return fmt.Errorf("info.Channels contains duplicate channel id %v", ch) + return ErrDuplicateChannelID{ID: ch} } channels[ch] = struct{}{} } // Validate Moniker. if !cmtstrings.IsASCIIText(info.Moniker) || cmtstrings.ASCIITrim(info.Moniker) == "" { - return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker) + return ErrInvalidMoniker{Moniker: info.Moniker} } // Validate Other. @@ -160,12 +160,12 @@ func (info DefaultNodeInfo) Validate() error { switch txIndex { case "", "on", "off": default: - return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex) + return ErrInvalidTxIndex{TxIndex: txIndex} } // XXX: Should we be more strict about address formats? rpcAddr := other.RPCAddress if len(rpcAddr) > 0 && (!cmtstrings.IsASCIIText(rpcAddr) || cmtstrings.ASCIITrim(rpcAddr) == "") { - return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr) + return ErrInvalidRPCAddress{RPCAddress: rpcAddr} } return nil @@ -177,17 +177,25 @@ func (info DefaultNodeInfo) Validate() error { func (info DefaultNodeInfo) CompatibleWith(otherInfo NodeInfo) error { other, ok := otherInfo.(DefaultNodeInfo) if !ok { - return fmt.Errorf("wrong NodeInfo type. Expected DefaultNodeInfo, got %v", reflect.TypeOf(otherInfo)) + return ErrInvalidNodeInfoType{ + Type: reflect.TypeOf(otherInfo).String(), + Expected: fmt.Sprintf("%T", DefaultNodeInfo{}), + } } if info.ProtocolVersion.Block != other.ProtocolVersion.Block { - return fmt.Errorf("peer is on a different Block version. Got %v, expected %v", - other.ProtocolVersion.Block, info.ProtocolVersion.Block) + return ErrDifferentBlockVersion{ + Other: other.ProtocolVersion.Block, + Our: info.ProtocolVersion.Block, + } } // nodes must be on the same network if info.Network != other.Network { - return fmt.Errorf("peer is on a different network. Got %v, expected %v", other.Network, info.Network) + return ErrDifferentNetwork{ + Other: other.Network, + Our: info.Network, + } } // if we have no channels, we're just testing @@ -207,7 +215,10 @@ OUTER_LOOP: } } if !found { - return fmt.Errorf("peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels) + return ErrNoCommonChannels{ + OtherChannels: other.Channels, + OurChannels: info.Channels, + } } return nil } @@ -249,8 +260,9 @@ func (info DefaultNodeInfo) ToProto() *tmp2p.DefaultNodeInfo { func DefaultNodeInfoFromToProto(pb *tmp2p.DefaultNodeInfo) (DefaultNodeInfo, error) { if pb == nil { - return DefaultNodeInfo{}, errors.New("nil node info") + return DefaultNodeInfo{}, ErrNoNodeInfo } + dni := DefaultNodeInfo{ ProtocolVersion: ProtocolVersion{ P2P: pb.ProtocolVersion.P2P, diff --git a/p2p/peer.go b/p2p/peer.go index ec228e2af4..1538df9ea2 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -53,7 +53,7 @@ type Peer interface { type peerConn struct { outbound bool persistent bool - conn net.Conn // source connection + conn net.Conn // Source connection socketAddr *NetAddress diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index c6a066ffe9..1d73d99b06 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -518,7 +518,7 @@ func (a *addrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAd func (a *addrBook) addToNewBucket(ka *knownAddress, bucketIdx int) error { // Consistency check to ensure we don't add an already known address if ka.isOld() { - return errAddrBookOldAddressNewBucket{ka.Addr, bucketIdx} + return ErrAddrBookOldAddressNewBucket{ka.Addr, bucketIdx} } addrStr := ka.Addr.String() @@ -694,7 +694,7 @@ func (a *addrBook) randomPickAddresses(bucketType byte, num int) []*p2p.NetAddre case bucketTypeOld: buckets = a.bucketsOld default: - panic("unexpected bucketType") + panic("unexpected bucket type") } total := 0 for _, bucket := range buckets { diff --git a/p2p/pex/errors.go b/p2p/pex/errors.go index 7e256c640d..fa9c7eb834 100644 --- a/p2p/pex/errors.go +++ b/p2p/pex/errors.go @@ -3,10 +3,17 @@ package pex import ( "errors" "fmt" + "time" "github.com/cometbft/cometbft/p2p" ) +var ( + ErrEmptyAddressBook = errors.New("address book is empty and couldn't resolve any seed nodes") + // ErrUnsolicitedList is thrown when a peer provides a list of addresses that have not been asked for. + ErrUnsolicitedList = errors.New("unsolicited pexAddrsMessage") +) + type ErrAddrBookNonRoutable struct { Addr *p2p.NetAddress } @@ -15,12 +22,12 @@ func (err ErrAddrBookNonRoutable) Error() string { return fmt.Sprintf("Cannot add non-routable address %v", err.Addr) } -type errAddrBookOldAddressNewBucket struct { +type ErrAddrBookOldAddressNewBucket struct { Addr *p2p.NetAddress BucketID int } -func (err errAddrBookOldAddressNewBucket) Error() string { +func (err ErrAddrBookOldAddressNewBucket) Error() string { return fmt.Sprintf("failed consistency check!"+ " Cannot add pre-existing address %v into new bucket %v", err.Addr, err.BucketID) @@ -85,5 +92,55 @@ func (err ErrAddressBanned) Error() string { return fmt.Sprintf("Address: %v is currently banned", err.Addr) } -// ErrUnsolicitedList is thrown when a peer provides a list of addresses that have not been asked for. -var ErrUnsolicitedList = errors.New("unsolicited pexAddrsMessage") +// ErrReceivedPEXRequestTooSoon is thrown when a peer sends a PEX request too soon after the last one. +type ErrReceivedPEXRequestTooSoon struct { + Peer p2p.ID + LastReceived time.Time + Now time.Time + MinInterval time.Duration +} + +func (err ErrReceivedPEXRequestTooSoon) Error() string { + return fmt.Sprintf("received PEX request from peer %v too soon (last received %v, now %v, min interval %v), Disconnecting peer", + err.Peer, err.LastReceived, err.Now, err.MinInterval) +} + +type ErrMaxAttemptsToDial struct { + Max int +} + +func (e ErrMaxAttemptsToDial) Error() string { + return fmt.Sprintf("reached max attempts %d to dial", e.Max) +} + +type ErrTooEarlyToDial struct { + BackoffDuration time.Duration + LastDialed time.Time +} + +func (e ErrTooEarlyToDial) Error() string { + return fmt.Sprintf( + "too early to dial (backoff duration: %d, last dialed: %v, time since: %v)", + e.BackoffDuration, e.LastDialed, time.Since(e.LastDialed)) +} + +type ErrFailedToDial struct { + TotalAttempts int + Err error +} + +func (e ErrFailedToDial) Error() string { + return fmt.Sprintf("failed to dial after %d attempts: %v", e.TotalAttempts, e.Err) +} + +func (e ErrFailedToDial) Unwrap() error { return e.Err } + +type ErrSeedNodeConfig struct { + Err error +} + +func (e ErrSeedNodeConfig) Error() string { + return fmt.Sprintf("failed to parse seed node config: %v", e.Err) +} + +func (e ErrSeedNodeConfig) Unwrap() error { return e.Err } diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 428f3b4482..ec9ea6fc2f 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -52,23 +52,6 @@ const ( defaultBanTime = 24 * time.Hour ) -type errMaxAttemptsToDial struct{} - -func (errMaxAttemptsToDial) Error() string { - return fmt.Sprintf("reached max attempts %d to dial", maxAttemptsToDial) -} - -type errTooEarlyToDial struct { - backoffDuration time.Duration - lastDialed time.Time -} - -func (e errTooEarlyToDial) Error() string { - return fmt.Sprintf( - "too early to dial (backoff duration: %d, last dialed: %v, time since: %v)", - e.backoffDuration, e.lastDialed, time.Since(e.lastDialed)) -} - // Reactor handles PEX (peer exchange) and ensures that an // adequate number of peers are connected to the switch. // @@ -144,7 +127,7 @@ func NewReactor(b AddrBook, config *ReactorConfig) *Reactor { // OnStart implements BaseService. func (r *Reactor) OnStart() error { err := r.book.Start() - if err != nil && err != service.ErrAlreadyStarted { + if err != nil && !errors.Is(err, service.ErrAlreadyStarted) { return err } @@ -152,7 +135,7 @@ func (r *Reactor) OnStart() error { if err != nil { return err } else if numOnline == 0 && r.book.Empty() { - return errors.New("address book is empty and couldn't resolve any seed nodes") + return ErrEmptyAddressBook } r.seedAddrs = seedAddrs @@ -325,13 +308,12 @@ func (r *Reactor) receiveRequest(src Peer) error { now := time.Now() minInterval := r.minReceiveRequestInterval() if now.Sub(lastReceived) < minInterval { - return fmt.Errorf( - "peer (%v) sent next PEX request too soon. lastReceived: %v, now: %v, minInterval: %v. Disconnecting", - src.ID(), - lastReceived, - now, - minInterval, - ) + return ErrReceivedPEXRequestTooSoon{ + Peer: src.ID(), + LastReceived: lastReceived, + Now: now, + MinInterval: minInterval, + } } r.lastReceivedRequests.Set(id, now) return nil @@ -392,7 +374,7 @@ func (r *Reactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { err := r.dialPeer(addr) if err != nil { switch err.(type) { - case errMaxAttemptsToDial, errTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress: + case ErrMaxAttemptsToDial, ErrTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress: r.Logger.Debug(err.Error(), "addr", addr) default: r.Logger.Debug(err.Error(), "addr", addr) @@ -512,7 +494,7 @@ func (r *Reactor) ensurePeers() { err := r.dialPeer(addr) if err != nil { switch err.(type) { - case errMaxAttemptsToDial, errTooEarlyToDial: + case ErrMaxAttemptsToDial, ErrTooEarlyToDial: r.Logger.Debug(err.Error(), "addr", addr) default: r.Logger.Debug(err.Error(), "addr", addr) @@ -557,7 +539,7 @@ func (r *Reactor) dialPeer(addr *p2p.NetAddress) error { attempts, lastDialed := r.dialAttemptsInfo(addr) if !r.Switch.IsPeerPersistent(addr) && attempts > maxAttemptsToDial { r.book.MarkBad(addr, defaultBanTime) - return errMaxAttemptsToDial{} + return ErrMaxAttemptsToDial{Max: maxAttemptsToDial} } // exponential backoff if it's not our first attempt to dial given address @@ -567,7 +549,7 @@ func (r *Reactor) dialPeer(addr *p2p.NetAddress) error { backoffDuration = r.maxBackoffDurationForPeer(addr, backoffDuration) sinceLastDialed := time.Since(lastDialed) if sinceLastDialed < backoffDuration { - return errTooEarlyToDial{backoffDuration, lastDialed} + return ErrTooEarlyToDial{backoffDuration, lastDialed} } } @@ -585,7 +567,7 @@ func (r *Reactor) dialPeer(addr *p2p.NetAddress) error { default: r.attemptsToDial.Store(addr.DialString(), _attemptsToDial{attempts + 1, time.Now()}) } - return fmt.Errorf("dialing failed (attempts: %d): %w", attempts+1, err) + return ErrFailedToDial{attempts + 1, err} } // cleanup any history @@ -620,7 +602,7 @@ func (r *Reactor) checkSeeds() (numOnline int, netAddrs []*p2p.NetAddress, err e case p2p.ErrNetAddressLookup: r.Logger.Error("Connecting to seed failed", "err", e) default: - return 0, nil, fmt.Errorf("seed node configuration has error: %w", e) + return 0, nil, ErrSeedNodeConfig{Err: err} } } return numOnline, netAddrs, nil @@ -726,7 +708,7 @@ func (r *Reactor) crawlPeers(addrs []*p2p.NetAddress) { err := r.dialPeer(addr) if err != nil { switch err.(type) { - case errMaxAttemptsToDial, errTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress: + case ErrMaxAttemptsToDial, ErrTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress: r.Logger.Debug(err.Error(), "addr", addr) default: r.Logger.Debug(err.Error(), "addr", addr) diff --git a/p2p/switch.go b/p2p/switch.go index a8fb4b5e8a..6aa0b57c36 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -236,7 +236,7 @@ func (sw *Switch) OnStart() error { for _, reactor := range sw.reactors { err := reactor.Start() if err != nil { - return fmt.Errorf("failed to start %v: %w", reactor, err) + return ErrStart{reactor, err} } } @@ -484,7 +484,7 @@ func (sw *Switch) DialPeersAsync(peers []string) error { } // return first non-ErrNetAddressLookup error for _, err := range errs { - if _, ok := err.(ErrNetAddressLookup); ok { + if errors.As(err, &ErrNetAddressLookup{}) { continue } return err @@ -587,7 +587,7 @@ func (sw *Switch) AddPersistentPeers(addrs []string) error { } // return first non-ErrNetAddressLookup error for _, err := range errs { - if _, ok := err.(ErrNetAddressLookup); ok { + if errors.As(err, &ErrNetAddressLookup{}) { continue } return err @@ -598,11 +598,12 @@ func (sw *Switch) AddPersistentPeers(addrs []string) error { func (sw *Switch) AddUnconditionalPeerIDs(ids []string) error { sw.Logger.Info("Adding unconditional peer ids", "ids", ids) - for i, id := range ids { + for _, id := range ids { err := validateID(ID(id)) if err != nil { - return fmt.Errorf("wrong ID #%d: %w", i, err) + return ErrInvalidPeerID{ID: ID(id), Source: err} } + sw.unconditionalPeerIDs[ID(id)] = struct{}{} } return nil @@ -610,11 +611,12 @@ func (sw *Switch) AddUnconditionalPeerIDs(ids []string) error { func (sw *Switch) AddPrivatePeerIDs(ids []string) error { validIDs := make([]string, 0, len(ids)) - for i, id := range ids { + for _, id := range ids { err := validateID(ID(id)) if err != nil { - return fmt.Errorf("wrong ID #%d: %w", i, err) + return ErrInvalidPeerID{ID: ID(id), Source: err} } + validIDs = append(validIDs, id) }