diff --git a/catchup/ledgerFetcher.go b/catchup/ledgerFetcher.go index 269aed1ff4..e5119578b3 100644 --- a/catchup/ledgerFetcher.go +++ b/catchup/ledgerFetcher.go @@ -33,6 +33,7 @@ import ( "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/util" ) @@ -75,11 +76,11 @@ func makeLedgerFetcher(net network.GossipNode, accessor ledger.CatchpointCatchup func (lf *ledgerFetcher) requestLedger(ctx context.Context, peer network.HTTPPeer, round basics.Round, method string) (*http.Response, error) { var ledgerURL string - if network.IsMultiaddr(peer.GetAddress()) { + if addr.IsMultiaddr(peer.GetAddress()) { ledgerURL = network.SubstituteGenesisID(lf.net, "/v1/{genesisID}/ledger/"+strconv.FormatUint(uint64(round), 36)) } else { - parsedURL, err := network.ParseHostOrURL(peer.GetAddress()) + parsedURL, err := addr.ParseHostOrURL(peer.GetAddress()) if err != nil { return nil, err } diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 4e4d920c23..a66278189b 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" ) @@ -221,10 +222,10 @@ type HTTPFetcher struct { func (hf *HTTPFetcher) getBlockBytes(ctx context.Context, r basics.Round) (data []byte, err error) { var blockURL string - if network.IsMultiaddr(hf.rootURL) { + if addr.IsMultiaddr(hf.rootURL) { blockURL = rpcs.FormatBlockQuery(uint64(r), "", hf.net) } else { - if parsedURL, err0 := network.ParseHostOrURL(hf.rootURL); err0 == nil { + if parsedURL, err0 := addr.ParseHostOrURL(hf.rootURL); err0 == nil { parsedURL.Path = rpcs.FormatBlockQuery(uint64(r), parsedURL.Path, hf.net) blockURL = parsedURL.String() } else { diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 603f543b89..b2d48687a5 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -32,7 +32,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" - "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" toolsnet "github.com/algorand/go-algorand/tools/network" "github.com/algorand/go-algorand/util" @@ -276,7 +276,7 @@ func run() int { // make sure that the format of each entry is valid: for idx, peer := range peerOverrideArray { - addr, addrErr := network.ParseHostOrURLOrMultiaddr(peer) + addr, addrErr := addr.ParseHostOrURLOrMultiaddr(peer) if addrErr != nil { fmt.Fprintf(os.Stderr, "Provided command line parameter '%s' is not a valid host:port pair\n", peer) return 1 diff --git a/cmd/goal/node.go b/cmd/goal/node.go index 17de96a81a..2db08fd4e5 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -39,7 +39,7 @@ import ( "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/libgoal" - "github.com/algorand/go-algorand/network" + naddr "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/tokens" @@ -751,7 +751,7 @@ func verifyPeerDialArg() bool { // make sure that the format of each entry is valid: for _, peer := range strings.Split(peerDial, ";") { - _, err := network.ParseHostOrURLOrMultiaddr(peer) + _, err := naddr.ParseHostOrURLOrMultiaddr(peer) if err != nil { reportErrorf("Provided peer '%s' is not a valid peer address : %v", peer, err) return false diff --git a/network/addr.go b/network/addr.go index c12a09a4af..00eb368881 100644 --- a/network/addr.go +++ b/network/addr.go @@ -17,82 +17,17 @@ package network import ( - "errors" - "net/url" "path" - "regexp" "strings" - "github.com/multiformats/go-multiaddr" + "github.com/algorand/go-algorand/network/addr" ) -var errURLNoHost = errors.New("could not parse a host from url") - -var errURLColonHost = errors.New("host name starts with a colon") - -// HostColonPortPattern matches "^[-a-zA-Z0-9.]+:\\d+$" e.g. "foo.com.:1234" -var HostColonPortPattern = regexp.MustCompile(`^[-a-zA-Z0-9.]+:\d+$`) - -// ParseHostOrURL handles "host:port" or a full URL. -// Standard library net/url.Parse chokes on "host:port". -func ParseHostOrURL(addr string) (*url.URL, error) { - // If the entire addr is "host:port" grab that right away. - // Don't try url.Parse() because that will grab "host:" as if it were "scheme:" - if HostColonPortPattern.MatchString(addr) { - return &url.URL{Scheme: "http", Host: addr}, nil - } - parsed, err := url.Parse(addr) - if err == nil { - if parsed.Host == "" { - return nil, errURLNoHost - } - return parsed, nil - } - if strings.HasPrefix(addr, "http:") || strings.HasPrefix(addr, "https:") || strings.HasPrefix(addr, "ws:") || strings.HasPrefix(addr, "wss:") || strings.HasPrefix(addr, "://") || strings.HasPrefix(addr, "//") { - return parsed, err - } - // This turns "[::]:4601" into "http://[::]:4601" which url.Parse can do - parsed, e2 := url.Parse("http://" + addr) - if e2 == nil { - // https://datatracker.ietf.org/doc/html/rfc1123#section-2 - // first character is relaxed to allow either a letter or a digit - if parsed.Host[0] == ':' && (len(parsed.Host) < 2 || parsed.Host[1] != ':') { - return nil, errURLColonHost - } - return parsed, nil - } - return parsed, err /* return original err, not our prefix altered try */ -} - -// IsMultiaddr returns true if the provided string is a valid multiaddr. -func IsMultiaddr(addr string) bool { - if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS - _, err := multiaddr.NewMultiaddr(addr) - return err == nil - } - return false -} - -// ParseHostOrURLOrMultiaddr returns an error if it could not parse the provided -// string as a valid "host:port", full URL, or multiaddr. If no error, it returns -// a host:port address, or a multiaddr. -func ParseHostOrURLOrMultiaddr(addr string) (string, error) { - if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS - _, err := multiaddr.NewMultiaddr(addr) - return addr, err - } - url, err := ParseHostOrURL(addr) - if err != nil { - return "", err - } - return url.Host, nil -} - // addrToGossipAddr parses host:port or a URL and returns the URL to the websocket interface at that address. -func (wn *WebsocketNetwork) addrToGossipAddr(addr string) (string, error) { - parsedURL, err := ParseHostOrURL(addr) +func (wn *WebsocketNetwork) addrToGossipAddr(a string) (string, error) { + parsedURL, err := addr.ParseHostOrURL(a) if err != nil { - wn.log.Warnf("could not parse addr %#v: %s", addr, err) + wn.log.Warnf("could not parse addr %#v: %s", a, err) return "", errBadAddr } parsedURL.Scheme = websocketsScheme[parsedURL.Scheme] diff --git a/network/addr/addr.go b/network/addr/addr.go new file mode 100644 index 0000000000..c8c0c0b6ab --- /dev/null +++ b/network/addr/addr.go @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2024 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package addr + +import ( + "errors" + "net/url" + "regexp" + "strings" + + "github.com/multiformats/go-multiaddr" +) + +var errURLNoHost = errors.New("could not parse a host from url") + +var errURLColonHost = errors.New("host name starts with a colon") + +// HostColonPortPattern matches "^[-a-zA-Z0-9.]+:\\d+$" e.g. "foo.com.:1234" +var HostColonPortPattern = regexp.MustCompile(`^[-a-zA-Z0-9.]+:\d+$`) + +// ParseHostOrURL handles "host:port" or a full URL. +// Standard library net/url.Parse chokes on "host:port". +func ParseHostOrURL(addr string) (*url.URL, error) { + // If the entire addr is "host:port" grab that right away. + // Don't try url.Parse() because that will grab "host:" as if it were "scheme:" + if HostColonPortPattern.MatchString(addr) { + return &url.URL{Scheme: "http", Host: addr}, nil + } + parsed, err := url.Parse(addr) + if err == nil { + if parsed.Host == "" { + return nil, errURLNoHost + } + return parsed, nil + } + if strings.HasPrefix(addr, "http:") || strings.HasPrefix(addr, "https:") || strings.HasPrefix(addr, "ws:") || strings.HasPrefix(addr, "wss:") || strings.HasPrefix(addr, "://") || strings.HasPrefix(addr, "//") { + return parsed, err + } + // This turns "[::]:4601" into "http://[::]:4601" which url.Parse can do + parsed, e2 := url.Parse("http://" + addr) + if e2 == nil { + // https://datatracker.ietf.org/doc/html/rfc1123#section-2 + // first character is relaxed to allow either a letter or a digit + if parsed.Host[0] == ':' && (len(parsed.Host) < 2 || parsed.Host[1] != ':') { + return nil, errURLColonHost + } + return parsed, nil + } + return parsed, err /* return original err, not our prefix altered try */ +} + +// IsMultiaddr returns true if the provided string is a valid multiaddr. +func IsMultiaddr(addr string) bool { + if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS + _, err := multiaddr.NewMultiaddr(addr) + return err == nil + } + return false +} + +// ParseHostOrURLOrMultiaddr returns an error if it could not parse the provided +// string as a valid "host:port", full URL, or multiaddr. If no error, it returns +// a host:port address, or a multiaddr. +func ParseHostOrURLOrMultiaddr(addr string) (string, error) { + if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS + _, err := multiaddr.NewMultiaddr(addr) + return addr, err + } + url, err := ParseHostOrURL(addr) + if err != nil { + return "", err + } + return url.Host, nil +} diff --git a/network/addr_test.go b/network/addr/addr_test.go similarity index 99% rename from network/addr_test.go rename to network/addr/addr_test.go index c373b2efc6..c651352dd2 100644 --- a/network/addr_test.go +++ b/network/addr/addr_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package addr import ( "net/url" diff --git a/network/dialer.go b/network/limitcaller/dialer.go similarity index 91% rename from network/dialer.go rename to network/limitcaller/dialer.go index 3aa59f493d..ee9b2e364a 100644 --- a/network/dialer.go +++ b/network/limitcaller/dialer.go @@ -14,13 +14,14 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package limitcaller import ( "context" "net" "time" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/tools/network/dnssec" "github.com/algorand/go-algorand/util" ) @@ -31,14 +32,13 @@ type netDialer interface { // Dialer establish tcp-level connection with the destination type Dialer struct { - phonebook Phonebook + phonebook phonebook.Phonebook innerDialer netDialer - resolver *net.Resolver } -// makeRateLimitingDialer creates a rate limiting dialer that would limit the connections +// MakeRateLimitingDialer creates a rate limiting dialer that would limit the connections // according to the entries in the phonebook. -func makeRateLimitingDialer(phonebook Phonebook, resolver dnssec.ResolverIf) Dialer { +func MakeRateLimitingDialer(phonebook phonebook.Phonebook, resolver dnssec.ResolverIf) Dialer { var innerDialer netDialer = &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, diff --git a/network/rateLimitingTransport.go b/network/limitcaller/rateLimitingTransport.go similarity index 58% rename from network/rateLimitingTransport.go rename to network/limitcaller/rateLimitingTransport.go index 461a468da5..5b2e4e67f0 100644 --- a/network/rateLimitingTransport.go +++ b/network/limitcaller/rateLimitingTransport.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package limitcaller import ( "errors" @@ -24,22 +24,32 @@ import ( "github.com/algorand/go-algorand/util" ) -// rateLimitingTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request. -type rateLimitingTransport struct { - phonebook Phonebook - innerTransport *http.Transport +// ConnectionTimeStore is a subset of the phonebook that is used to store the connection times. +type ConnectionTimeStore interface { + GetConnectionWaitTime(addrOrInfo interface{}) (bool, time.Duration, time.Time) + UpdateConnectionTime(addrOrInfo interface{}, provisionalTime time.Time) bool +} + +// RateLimitingTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request. +type RateLimitingTransport struct { + phonebook ConnectionTimeStore + innerTransport http.RoundTripper queueingTimeout time.Duration + targetAddr interface{} // target address for the p2p http request } +// DefaultQueueingTimeout is the default timeout for queueing the request. +const DefaultQueueingTimeout = 10 * time.Second + // ErrConnectionQueueingTimeout indicates that we've exceeded the time allocated for // queueing the current request before the request attempt could be made. var ErrConnectionQueueingTimeout = errors.New("rateLimitingTransport: queueing timeout") -// makeRateLimitingTransport creates a rate limiting http transport that would limit the requests rate +// MakeRateLimitingTransport creates a rate limiting http transport that would limit the requests rate // according to the entries in the phonebook. -func makeRateLimitingTransport(phonebook Phonebook, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) rateLimitingTransport { +func MakeRateLimitingTransport(phonebook ConnectionTimeStore, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) RateLimitingTransport { defaultTransport := http.DefaultTransport.(*http.Transport) - return rateLimitingTransport{ + return RateLimitingTransport{ phonebook: phonebook, innerTransport: &http.Transport{ Proxy: defaultTransport.Proxy, @@ -54,14 +64,30 @@ func makeRateLimitingTransport(phonebook Phonebook, queueingTimeout time.Duratio } } +// MakeRateLimitingTransportWithTransport creates a rate limiting http transport that would limit the requests rate +// according to the entries in the phonebook. +func MakeRateLimitingTransportWithTransport(phonebook ConnectionTimeStore, queueingTimeout time.Duration, rt http.RoundTripper, target interface{}, maxIdleConnsPerHost int) RateLimitingTransport { + return RateLimitingTransport{ + phonebook: phonebook, + innerTransport: rt, + queueingTimeout: queueingTimeout, + targetAddr: target, + } +} + // RoundTrip connects to the address on the named network using the provided context. // It waits if needed not to exceed connectionsRateLimitingCount. -func (r *rateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { +func (r *RateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { var waitTime time.Duration var provisionalTime time.Time queueingDeadline := time.Now().Add(r.queueingTimeout) + var host interface{} = req.Host + if len(req.Host) == 0 && req.URL != nil && len(req.URL.Host) == 0 { + // p2p/http clients have per-connection transport and address info so use that + host = r.targetAddr + } for { - _, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(req.Host) + _, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(host) if waitTime == 0 { break // break out of the loop and proceed to the connection } @@ -73,6 +99,6 @@ func (r *rateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response return nil, ErrConnectionQueueingTimeout } res, err = r.innerTransport.RoundTrip(req) - r.phonebook.UpdateConnectionTime(req.Host, provisionalTime) + r.phonebook.UpdateConnectionTime(host, provisionalTime) return } diff --git a/network/p2p/http.go b/network/p2p/http.go index 33a0ede570..d12b51034d 100644 --- a/network/p2p/http.go +++ b/network/p2p/http.go @@ -19,7 +19,9 @@ package p2p import ( "net/http" "sync" + "time" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/gorilla/mux" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/host" @@ -74,3 +76,15 @@ func MakeHTTPClient(addrInfo *peer.AddrInfo) (*http.Client, error) { return &http.Client{Transport: rt}, nil } + +// MakeHTTPClientWithRateLimit creates a http.Client that uses libp2p transport for a given protocol and peer address. +func MakeHTTPClientWithRateLimit(addrInfo *peer.AddrInfo, pstore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration, maxIdleConnsPerHost int) (*http.Client, error) { + cl, err := MakeHTTPClient(addrInfo) + if err != nil { + return nil, err + } + rlrt := limitcaller.MakeRateLimitingTransportWithTransport(pstore, queueingTimeout, cl.Transport, addrInfo, maxIdleConnsPerHost) + cl.Transport = &rlrt + return cl, nil + +} diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go index fa572c5912..d2a088e433 100644 --- a/network/p2p/peerstore/peerstore.go +++ b/network/p2p/peerstore/peerstore.go @@ -22,6 +22,7 @@ import ( "math/rand" "time" + "github.com/algorand/go-algorand/network/phonebook" "github.com/libp2p/go-libp2p/core/peer" libp2p "github.com/libp2p/go-libp2p/core/peerstore" mempstore "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" @@ -32,12 +33,6 @@ import ( // of how many addresses the phonebook actually has. ( with the retry-after logic applied ) const getAllAddresses = math.MaxInt32 -// PhoneBookEntryRoles defines the roles that a single entry on the phonebook can take. -// currently, we have two roles : relay role and archiver role, which are mutually exclusive. -// -//msgp:ignore PhoneBookEntryRoles -type PhoneBookEntryRoles int - const addressDataKey string = "addressData" // PeerStore implements Peerstore and CertifiedAddrBook. @@ -60,7 +55,7 @@ type addressData struct { networkNames map[string]bool // role is the role that this address serves. - role PhoneBookEntryRoles + role phonebook.PhoneBookEntryRoles // persistent is set true for peers whose record should not be removed for the peer list persistent bool @@ -103,13 +98,13 @@ func MakePhonebook(connectionsRateLimitingCount uint, } // GetAddresses returns up to N addresses, but may return fewer -func (ps *PeerStore) GetAddresses(n int, role PhoneBookEntryRoles) []string { +func (ps *PeerStore) GetAddresses(n int, role phonebook.PhoneBookEntryRoles) []string { return shuffleSelect(ps.filterRetryTime(time.Now(), role), n) } // UpdateRetryAfter updates the retryAfter time for the given address. func (ps *PeerStore) UpdateRetryAfter(addr string, retryAfter time.Time) { - info, err := PeerInfoFromDomainPort(addr) + info, err := peerInfoFromDomainPort(addr) if err != nil { return } @@ -130,12 +125,9 @@ func (ps *PeerStore) UpdateRetryAfter(addr string, retryAfter time.Time) { // The connection should be established when the waitTime is 0. // It will register a provisional next connection time when the waitTime is 0. // The provisional time should be updated after the connection with UpdateConnectionTime -func (ps *PeerStore) GetConnectionWaitTime(addr string) (bool, time.Duration, time.Time) { +func (ps *PeerStore) GetConnectionWaitTime(addr interface{}) (bool, time.Duration, time.Time) { curTime := time.Now() - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return false, 0 /* not used */, curTime /* not used */ - } + info := addr.(*peer.AddrInfo) var timeSince time.Duration var numElmtsToRemove int metadata, err := ps.Get(info.ID, addressDataKey) @@ -157,7 +149,7 @@ func (ps *PeerStore) GetConnectionWaitTime(addr string) (bool, time.Duration, ti } // Remove the expired elements from e.data[addr].recentConnectionTimes - ps.popNElements(numElmtsToRemove, peer.ID(addr)) + ps.popNElements(numElmtsToRemove, info.ID) // If there are max number of connections within the time window, wait metadata, _ = ps.Get(info.ID, addressDataKey) ad, ok = metadata.(addressData) @@ -180,11 +172,8 @@ func (ps *PeerStore) GetConnectionWaitTime(addr string) (bool, time.Duration, ti } // UpdateConnectionTime updates the connection time for the given address. -func (ps *PeerStore) UpdateConnectionTime(addr string, provisionalTime time.Time) bool { - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return false - } +func (ps *PeerStore) UpdateConnectionTime(addr interface{}, provisionalTime time.Time) bool { + info := addr.(*peer.AddrInfo) metadata, err := ps.Get(info.ID, addressDataKey) if err != nil { return false @@ -217,7 +206,7 @@ func (ps *PeerStore) UpdateConnectionTime(addr string, provisionalTime time.Time } // ReplacePeerList replaces the peer list for the given networkName and role. -func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, role PhoneBookEntryRoles) { +func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, role phonebook.PhoneBookEntryRoles) { // prepare a map of items we'd like to remove. removeItems := make(map[peer.ID]bool, 0) peerIDs := ps.Peers() @@ -232,7 +221,7 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, } for _, addr := range addressesThey { - info, err := PeerInfoFromDomainPort(addr) + info, err := peerInfoFromDomainPort(addr) if err != nil { return } @@ -261,13 +250,9 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, // AddPersistentPeers stores addresses of peers which are persistent. // i.e. they won't be replaced by ReplacePeerList calls -func (ps *PeerStore) AddPersistentPeers(dnsAddresses []string, networkName string, role PhoneBookEntryRoles) { - +func (ps *PeerStore) AddPersistentPeers(dnsAddresses []interface{}, networkName string, role phonebook.PhoneBookEntryRoles) { for _, addr := range dnsAddresses { - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return - } + info := addr.(*peer.AddrInfo) data, _ := ps.Get(info.ID, addressDataKey) if data != nil { // we already have this. @@ -291,7 +276,7 @@ func (ps *PeerStore) Length() int { } // makePhonebookEntryData creates a new address entry for provided network name and role. -func makePhonebookEntryData(networkName string, role PhoneBookEntryRoles, persistent bool) addressData { +func makePhonebookEntryData(networkName string, role phonebook.PhoneBookEntryRoles, persistent bool) addressData { pbData := addressData{ networkNames: make(map[string]bool), recentConnectionTimes: make([]time.Time, 0), @@ -334,7 +319,7 @@ func (ps *PeerStore) popNElements(n int, peerID peer.ID) { _ = ps.Put(peerID, addressDataKey, ad) } -func (ps *PeerStore) filterRetryTime(t time.Time, role PhoneBookEntryRoles) []string { +func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.PhoneBookEntryRoles) []string { o := make([]string, 0, len(ps.Peers())) for _, peerID := range ps.Peers() { data, _ := ps.Get(peerID, addressDataKey) diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go index bebc2a7fe8..ebe45b87af 100644 --- a/network/p2p/peerstore/peerstore_test.go +++ b/network/p2p/peerstore/peerstore_test.go @@ -28,6 +28,7 @@ import ( libp2p "github.com/libp2p/go-libp2p/core/peerstore" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -154,7 +155,7 @@ func TestArrayPhonebookAll(t *testing.T) { require.NoError(t, err) for _, addr := range set { entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false) - info, _ := PeerInfoFromDomainPort(addr) + info, _ := peerInfoFromDomainPort(addr) ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) ph.Put(info.ID, addressDataKey, entry) } @@ -169,7 +170,7 @@ func TestArrayPhonebookUniform1(t *testing.T) { require.NoError(t, err) for _, addr := range set { entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false) - info, _ := PeerInfoFromDomainPort(addr) + info, _ := peerInfoFromDomainPort(addr) ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) ph.Put(info.ID, addressDataKey, entry) } @@ -184,7 +185,7 @@ func TestArrayPhonebookUniform3(t *testing.T) { require.NoError(t, err) for _, addr := range set { entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false) - info, _ := PeerInfoFromDomainPort(addr) + info, _ := peerInfoFromDomainPort(addr) ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) ph.Put(info.ID, addressDataKey, entry) } @@ -219,7 +220,9 @@ func TestMultiPhonebook(t *testing.T) { func TestMultiPhonebookPersistentPeers(t *testing.T) { partitiontest.PartitionTest(t) - persistentPeers := []string{"a:4041"} + info, err := peerInfoFromDomainPort("a:4041") + require.NoError(t, err) + persistentPeers := []interface{}{info} set := []string{"b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} pha := make([]string, 0) for _, e := range set[:5] { @@ -236,10 +239,12 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) { ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole) ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole) - testPhonebookAll(t, append(set, persistentPeers...), ph) + testPhonebookAll(t, append(set, "a:4041"), ph) allAddresses := ph.GetAddresses(len(set)+len(persistentPeers), PhoneBookEntryRelayRole) for _, pp := range persistentPeers { - require.Contains(t, allAddresses, pp) + pp := pp.(*peer.AddrInfo) + // TODO: modify as needed when completely switching from peerID = "host:port" to peer.AddrInfo + require.Contains(t, allAddresses, string(pp.ID)) } } @@ -277,21 +282,21 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { require.NoError(t, err) addr1 := "addrABC:4040" addr2 := "addrXYZ:4041" - info1, _ := PeerInfoFromDomainPort(addr1) - info2, _ := PeerInfoFromDomainPort(addr2) + info1, _ := peerInfoFromDomainPort(addr1) + info2, _ := peerInfoFromDomainPort(addr2) // Address not in. Should return false - addrInPhonebook, _, provisionalTime := entries.GetConnectionWaitTime(addr1) + addrInPhonebook, _, provisionalTime := entries.GetConnectionWaitTime(info1) require.Equal(t, false, addrInPhonebook) - require.Equal(t, false, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, false, entries.UpdateConnectionTime(info1, provisionalTime)) // Test the addresses are populated in the phonebook and a // time can be added to one of them entries.ReplacePeerList([]string{addr1, addr2}, "default", PhoneBookEntryRelayRole) - addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(addr1) + addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(info1) require.Equal(t, true, addrInPhonebook) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info1, provisionalTime)) data, _ := entries.Get(info1.ID, addressDataKey) require.NotNil(t, data) ad := data.(addressData) @@ -304,9 +309,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { } // add another value to addr - addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1) + addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(info1) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info1, provisionalTime)) data, _ = entries.Get(info1.ID, addressDataKey) ad = data.(addressData) phBookData = ad.recentConnectionTimes @@ -319,9 +324,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { // the first time should be removed and a new one added // there should not be any wait - addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1) + addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(info1) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info1, provisionalTime)) data, _ = entries.Get(info1.ID, addressDataKey) ad = data.(addressData) phBookData2 := ad.recentConnectionTimes @@ -336,9 +341,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { // add 3 values to another address. should not wait // value 1 - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(info2) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info2, provisionalTime)) // introduce a gap between the two requests so that only the first will be removed later when waited // simulate passing a unit of time @@ -350,13 +355,13 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { } // value 2 - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(info2) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info2, provisionalTime)) // value 3 - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(info2) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info2, provisionalTime)) data2, _ = entries.Get(info2.ID, addressDataKey) ad2 = data2.(addressData) @@ -365,7 +370,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { require.Equal(t, 3, len(phBookData)) // add another element to trigger wait - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(info2) require.Greater(t, int64(waitTime), int64(0)) // no element should be removed data2, _ = entries.Get(info2.ID, addressDataKey) @@ -380,9 +385,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { } // The wait should be sufficient - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(info2) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(info2, provisionalTime)) // only one element should be removed, and one added data2, _ = entries.Get(info2.ID, addressDataKey) ad2 = data2.(addressData) @@ -409,7 +414,7 @@ func TestPhonebookRoles(t *testing.T) { require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.Peers())) require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length()) - for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { + for _, role := range []phonebook.PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { for k := 0; k < 100; k++ { for l := 0; l < 3; l++ { entries := ph.GetAddresses(l, role) diff --git a/network/p2p/peerstore/utils.go b/network/p2p/peerstore/utils.go index 02c6b2d8e6..90b0af497c 100644 --- a/network/p2p/peerstore/utils.go +++ b/network/p2p/peerstore/utils.go @@ -53,8 +53,8 @@ func PeerInfoFromAddr(addr string) (*peer.AddrInfo, error) { return info, nil } -// PeerInfoFromDomainPort converts a string of the form domain:port to AddrInfo -func PeerInfoFromDomainPort(domainPort string) (*peer.AddrInfo, error) { +// peerInfoFromDomainPort converts a string of the form domain:port to AddrInfo +func peerInfoFromDomainPort(domainPort string) (*peer.AddrInfo, error) { parts := strings.Split(domainPort, ":") if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return nil, fmt.Errorf("invalid domain port string %s, found %d colon-separated parts", domainPort, len(parts)) diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 36b9e74eb0..945be51455 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/p2p" "github.com/algorand/go-algorand/network/p2p/dnsaddr" "github.com/algorand/go-algorand/network/p2p/peerstore" @@ -472,13 +473,13 @@ func (n *P2PNetwork) GetPeers(options ...PeerOption) []Peer { // query known archival nodes from DHT if enabled if n.config.EnableDHTProviders { const nodesToFind = 5 - info, err := n.capabilitiesDiscovery.PeersForCapability(p2p.Archival, nodesToFind) + infos, err := n.capabilitiesDiscovery.PeersForCapability(p2p.Archival, nodesToFind) if err != nil { n.log.Warnf("Error getting archival nodes from capabilities discovery: %v", err) return peers } - n.log.Debugf("Got %d archival node(s) from DHT", len(info)) - for _, addrInfo := range info { + n.log.Debugf("Got %d archival node(s) from DHT", len(infos)) + for _, addrInfo := range infos { info := addrInfo mas, err := peer.AddrInfoToP2pAddrs(&info) if err != nil { @@ -490,7 +491,9 @@ func (n *P2PNetwork) GetPeers(options ...PeerOption) []Peer { continue } addr := mas[0].String() - client, err := p2p.MakeHTTPClient(&info) + + maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) + client, err := p2p.MakeHTTPClientWithRateLimit(&info, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) if err != nil { n.log.Warnf("MakeHTTPClient failed: %v", err) continue @@ -549,7 +552,8 @@ func (n *P2PNetwork) GetHTTPClient(p HTTPPeer) (*http.Client, error) { if err != nil { return nil, err } - return p2p.MakeHTTPClient(addrInfo) + maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) + return p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) } // OnNetworkAdvance notifies the network library that the agreement protocol was able to make a notable progress. @@ -622,7 +626,10 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2ppeer peer.ID, strea n.log.Warnf("Could not get address for peer %s", p2ppeer) } // create a wsPeer for this stream and added it to the peers map. - client, err := p2p.MakeHTTPClient(&peer.AddrInfo{ID: p2ppeer, Addrs: []multiaddr.Multiaddr{ma}}) + + addrInfo := &peer.AddrInfo{ID: p2ppeer, Addrs: []multiaddr.Multiaddr{ma}} + maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) + client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) if err != nil { client = nil } diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index e64f4df85e..c76967290a 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -28,8 +28,11 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/p2p" "github.com/algorand/go-algorand/network/p2p/dnsaddr" + "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -621,6 +624,7 @@ func TestP2PHTTPHandler(t *testing.T) { require.NoError(t, err) require.Equal(t, "hello", string(body)) + // check another endpoint that also access the underlying connection/stream httpClient, err = p2p.MakeHTTPClient(&peerInfoA) require.NoError(t, err) resp, err = httpClient.Get("/check-conn") @@ -631,4 +635,13 @@ func TestP2PHTTPHandler(t *testing.T) { require.NoError(t, err) require.Equal(t, "world", string(body)) + // check rate limiting client: + // zero clients allowed, rate limiting window (10s) is greater than queue deadline (1s) + pstore, err := peerstore.MakePhonebook(0, 10*time.Second) + require.NoError(t, err) + pstore.AddPersistentPeers([]interface{}{&peerInfoA}, "net", phonebook.PhoneBookEntryRelayRole) + httpClient, err = p2p.MakeHTTPClientWithRateLimit(&peerInfoA, pstore, 1*time.Second, 1) + require.NoError(t, err) + _, err = httpClient.Get("/test") + require.ErrorIs(t, err, limitcaller.ErrConnectionQueueingTimeout) } diff --git a/network/phonebook.go b/network/phonebook/phonebook.go similarity index 96% rename from network/phonebook.go rename to network/phonebook/phonebook.go index 3f196e0605..f0c4275026 100644 --- a/network/phonebook.go +++ b/network/phonebook/phonebook.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package phonebook import ( "math" @@ -55,12 +55,12 @@ type Phonebook interface { // The connection should be established when the waitTime is 0. // It will register a provisional next connection time when the waitTime is 0. // The provisional time should be updated after the connection with UpdateConnectionTime - GetConnectionWaitTime(addr string) (addrInPhonebook bool, + GetConnectionWaitTime(addr interface{}) (addrInPhonebook bool, waitTime time.Duration, provisionalTime time.Time) // UpdateConnectionTime will update the provisional connection time. // Returns true of the addr was in the phonebook - UpdateConnectionTime(addr string, provisionalTime time.Time) bool + UpdateConnectionTime(addr interface{}, provisionalTime time.Time) bool // ReplacePeerList merges a set of addresses with that passed in for networkName // new entries in dnsAddresses are being added @@ -231,8 +231,10 @@ func (e *phonebookImpl) UpdateRetryAfter(addr string, retryAfter time.Time) { // The connection should be established when the waitTime is 0. // It will register a provisional next connection time when the waitTime is 0. // The provisional time should be updated after the connection with UpdateConnectionTime -func (e *phonebookImpl) GetConnectionWaitTime(addr string) (addrInPhonebook bool, +func (e *phonebookImpl) GetConnectionWaitTime(a interface{}) (addrInPhonebook bool, waitTime time.Duration, provisionalTime time.Time) { + + addr := a.(string) e.lock.Lock() defer e.lock.Unlock() @@ -276,7 +278,8 @@ func (e *phonebookImpl) GetConnectionWaitTime(addr string) (addrInPhonebook bool // UpdateConnectionTime will update the provisional connection time. // Returns true of the addr was in the phonebook -func (e *phonebookImpl) UpdateConnectionTime(addr string, provisionalTime time.Time) bool { +func (e *phonebookImpl) UpdateConnectionTime(a interface{}, provisionalTime time.Time) bool { + addr := a.(string) e.lock.Lock() defer e.lock.Unlock() diff --git a/network/phonebook_test.go b/network/phonebook/phonebook_test.go similarity index 99% rename from network/phonebook_test.go rename to network/phonebook/phonebook_test.go index 36365c5916..66e34e3aee 100644 --- a/network/phonebook_test.go +++ b/network/phonebook/phonebook_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package phonebook import ( "testing" diff --git a/network/requestLogger_test.go b/network/requestLogger_test.go index cb1d7b963d..af45c6cc08 100644 --- a/network/requestLogger_test.go +++ b/network/requestLogger_test.go @@ -25,6 +25,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -51,7 +52,7 @@ func TestRequestLogger(t *testing.T) { netA := &WebsocketNetwork{ log: dl, config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), GenesisID: "go-test-network-genesis", NetworkID: config.Devtestnet, } @@ -67,8 +68,8 @@ func TestRequestLogger(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook = MakePhonebook(1, 1*time.Millisecond) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook = phonebook.MakePhonebook(1, 1*time.Millisecond) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer func() { t.Log("stopping B"); netB.Stop(); t.Log("B done") }() diff --git a/network/requestTracker.go b/network/requestTracker.go index c88d4e5cf0..2e08f94cee 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -31,6 +31,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/addr" ) const ( @@ -94,12 +95,12 @@ func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime ti // - remoteAddr is used otherwise. func (tr *TrackerRequest) remoteAddress() string { if len(tr.otherPublicAddr) != 0 { - url, err := ParseHostOrURL(tr.otherPublicAddr) + url, err := addr.ParseHostOrURL(tr.otherPublicAddr) if err == nil && len(tr.remoteHost) > 0 && url.Hostname() == tr.remoteHost { return tr.otherPublicAddr } } - url, err := ParseHostOrURL(tr.remoteAddr) + url, err := addr.ParseHostOrURL(tr.remoteAddr) if err != nil { // tr.remoteAddr can't be parsed so try to use tr.remoteHost // there is a chance it came from a proxy and has a meaningful value diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 0a8c934c53..f2c098b388 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -88,7 +89,7 @@ func TestRateLimiting(t *testing.T) { wn := &WebsocketNetwork{ log: log, config: testConfig, - phonebook: MakePhonebook(1, 1), + phonebook: phonebook.MakePhonebook(1, 1), GenesisID: "go-test-network-genesis", NetworkID: config.Devtestnet, } @@ -115,15 +116,15 @@ func TestRateLimiting(t *testing.T) { clientsCount := int(testConfig.ConnectionsRateLimitingCount + 5) networks := make([]*WebsocketNetwork, clientsCount) - phonebooks := make([]Phonebook, clientsCount) + phonebooks := make([]phonebook.Phonebook, clientsCount) for i := 0; i < clientsCount; i++ { networks[i] = makeTestWebsocketNodeWithConfig(t, noAddressConfig) networks[i].config.GossipFanout = 1 - phonebooks[i] = MakePhonebook(networks[i].config.ConnectionsRateLimitingCount, + phonebooks[i] = phonebook.MakePhonebook(networks[i].config.ConnectionsRateLimitingCount, time.Duration(networks[i].config.ConnectionsRateLimitingWindowSeconds)*time.Second) - phonebooks[i].ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) - networks[i].phonebook = MakePhonebook(1, 1*time.Millisecond) - networks[i].phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + phonebooks[i].ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) + networks[i].phonebook = phonebook.MakePhonebook(1, 1*time.Millisecond) + networks[i].phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) defer func(net *WebsocketNetwork, i int) { t.Logf("stopping network %d", i) net.Stop() @@ -153,7 +154,7 @@ func TestRateLimiting(t *testing.T) { case <-readyCh: // it's closed, so this client got connected. connectedClients++ - phonebookLen := len(phonebooks[i].GetAddresses(1, PhoneBookEntryRelayRole)) + phonebookLen := len(phonebooks[i].GetAddresses(1, phonebook.PhoneBookEntryRelayRole)) // if this channel is ready, than we should have an address, since it didn't get blocked. require.Equal(t, 1, phonebookLen) default: diff --git a/network/websocketProxy_test.go b/network/websocketProxy_test.go index 73298ccd64..cefe4b687a 100644 --- a/network/websocketProxy_test.go +++ b/network/websocketProxy_test.go @@ -28,6 +28,7 @@ import ( "testing" "time" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/websocket" "github.com/stretchr/testify/require" @@ -71,7 +72,7 @@ func (w *websocketProxy) ServeHTTP(response http.ResponseWriter, request *http.R } // set X-Forwarded-For - url, err := ParseHostOrURL(request.RemoteAddr) + url, err := addr.ParseHostOrURL(request.RemoteAddr) if err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return @@ -254,7 +255,7 @@ func TestWebsocketProxyWsNet(t *testing.T) { gossipA, err := netA.addrToGossipAddr(addrA) require.NoError(t, err) - parsedA, err := ParseHostOrURL(gossipA) + parsedA, err := addr.ParseHostOrURL(gossipA) require.NoError(t, err) // setup the proxy diff --git a/network/wsNetwork.go b/network/wsNetwork.go index cdb3b4c635..2ead8ff019 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -43,8 +43,11 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/addr" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/limitlistener" "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" tools_network "github.com/algorand/go-algorand/tools/network" "github.com/algorand/go-algorand/tools/network/dnssec" @@ -196,7 +199,7 @@ type WebsocketNetwork struct { broadcaster msgBroadcaster handler msgHandler - phonebook Phonebook + phonebook phonebook.Phonebook GenesisID string NetworkID protocol.NetworkID @@ -258,8 +261,8 @@ type WebsocketNetwork struct { // transport and dialer are customized to limit the number of // connection in compliance with connectionsRateLimitingCount. - transport rateLimitingTransport - dialer Dialer + transport limitcaller.RateLimitingTransport + dialer limitcaller.Dialer // messagesOfInterest specifies the message types that this node // wants to receive. nil means default. non-nil causes this @@ -551,14 +554,14 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { case PeersPhonebookRelays: // return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory) var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) + addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryRelayRole) for _, addr := range addrs { peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.getRoundTripper(nil), "" /*origin address*/) outPeers = append(outPeers, &peerCore) } case PeersPhonebookArchivalNodes: var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) + addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryRelayRole) for _, addr := range addrs { peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.getRoundTripper(nil), "" /*origin address*/) outPeers = append(outPeers, &peerCore) @@ -566,7 +569,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { case PeersPhonebookArchivers: // return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory) var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchiverRole) + addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryArchiverRole) for _, addr := range addrs { peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.getRoundTripper(nil), "" /*origin address*/) outPeers = append(outPeers, &peerCore) @@ -593,8 +596,8 @@ func (wn *WebsocketNetwork) setup() { wn.nodeInfo = &nopeNodeInfo{} } maxIdleConnsPerHost := int(wn.config.ConnectionsRateLimitingCount) - wn.dialer = makeRateLimitingDialer(wn.phonebook, preferredResolver) - wn.transport = makeRateLimitingTransport(wn.phonebook, 10*time.Second, &wn.dialer, maxIdleConnsPerHost) + wn.dialer = limitcaller.MakeRateLimitingDialer(wn.phonebook, preferredResolver) + wn.transport = limitcaller.MakeRateLimitingTransport(wn.phonebook, limitcaller.DefaultQueueingTimeout, &wn.dialer, maxIdleConnsPerHost) wn.upgrader.ReadBufferSize = 4096 wn.upgrader.WriteBufferSize = 4096 @@ -1640,12 +1643,12 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() { func (wn *WebsocketNetwork) updatePhonebookAddresses(relayAddrs []string, archiveAddrs []string) { if len(relayAddrs) > 0 { wn.log.Debugf("got %d relay dns addrs, %#v", len(relayAddrs), relayAddrs[:imin(5, len(relayAddrs))]) - wn.phonebook.ReplacePeerList(relayAddrs, string(wn.NetworkID), PhoneBookEntryRelayRole) + wn.phonebook.ReplacePeerList(relayAddrs, string(wn.NetworkID), phonebook.PhoneBookEntryRelayRole) } else { wn.log.Infof("got no relay DNS addrs for network %s", wn.NetworkID) } if len(archiveAddrs) > 0 { - wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchiverRole) + wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), phonebook.PhoneBookEntryArchiverRole) } } @@ -1662,7 +1665,7 @@ func (wn *WebsocketNetwork) checkNewConnectionsNeeded() bool { return false } // get more than we need so that we can ignore duplicates - newAddrs := wn.phonebook.GetAddresses(desired+numOutgoingTotal, PhoneBookEntryRelayRole) + newAddrs := wn.phonebook.GetAddresses(desired+numOutgoingTotal, phonebook.PhoneBookEntryRelayRole) for _, na := range newAddrs { if na == wn.config.PublicAddress { // filter out self-public address, so we won't try to connect to ourselves. @@ -2267,13 +2270,21 @@ func (wn *WebsocketNetwork) SetPeerData(peer Peer, key string, value interface{} // NewWebsocketNetwork constructor for websockets based gossip network func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo, peerID p2p.PeerID, idSigner identityChallengeSigner) (wn *WebsocketNetwork, err error) { - phonebook := MakePhonebook(config.ConnectionsRateLimitingCount, + pb := phonebook.MakePhonebook(config.ConnectionsRateLimitingCount, time.Duration(config.ConnectionsRateLimitingWindowSeconds)*time.Second) - phonebook.AddPersistentPeers(phonebookAddresses, string(networkID), PhoneBookEntryRelayRole) + + addresses := make([]string, 0, len(phonebookAddresses)) + for _, a := range phonebookAddresses { + _, err := addr.ParseHostOrURL(a) + if err == nil { + addresses = append(addresses, a) + } + } + pb.AddPersistentPeers(addresses, string(networkID), phonebook.PhoneBookEntryRelayRole) wn = &WebsocketNetwork{ log: log, config: config, - phonebook: phonebook, + phonebook: pb, GenesisID: genesisID, NetworkID: networkID, nodeInfo: nodeInfo, diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 32b9e4e5b9..1db042cb85 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -41,6 +41,7 @@ import ( "github.com/algorand/go-algorand/internal/rapidgen" "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/network/phonebook" "pgregory.net/rapid" "github.com/stretchr/testify/assert" @@ -130,7 +131,7 @@ func makeTestWebsocketNodeWithConfig(t testing.TB, conf config.Local, opts ...te wn := &WebsocketNetwork{ log: log, config: conf, - phonebook: MakePhonebook(1, 1*time.Millisecond), + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), GenesisID: genesisID, NetworkID: config.Devtestnet, } @@ -323,7 +324,7 @@ func setupWebsocketNetworkABwithLogger(t *testing.T, countTarget int, log loggin addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer func() { if !success { @@ -459,7 +460,7 @@ func TestWebsocketProposalPayloadCompression(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") messages := [][]byte{ @@ -638,7 +639,7 @@ func TestWebsocketNetworkNoAddress(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -703,7 +704,7 @@ func lineNetwork(t *testing.T, numNodes int) (nodes []*WebsocketNetwork, counter if i > 0 { addrPrev, postListen := nodes[i-1].Address() require.True(t, postListen) - nodes[i].phonebook.ReplacePeerList([]string{addrPrev}, "default", PhoneBookEntryRelayRole) + nodes[i].phonebook.ReplacePeerList([]string{addrPrev}, "default", phonebook.PhoneBookEntryRelayRole) nodes[i].RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: &counters[i]}}) } nodes[i].Start() @@ -1056,7 +1057,7 @@ func makeTestFilterWebsocketNode(t *testing.T, nodename string) *WebsocketNetwor wn := &WebsocketNetwork{ log: logging.TestingLog(t).With("node", nodename), config: dc, - phonebook: MakePhonebook(1, 1*time.Millisecond), + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), GenesisID: genesisID, NetworkID: config.Devtestnet, } @@ -1079,7 +1080,7 @@ func TestDupFilter(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") counter := &messageCounterHandler{t: t, limit: 1, done: make(chan struct{})} @@ -1092,7 +1093,7 @@ func TestDupFilter(t *testing.T) { require.True(t, postListen) netC := makeTestFilterWebsocketNode(t, "c") netC.config.GossipFanout = 1 - netC.phonebook.ReplacePeerList([]string{addrB}, "default", PhoneBookEntryRelayRole) + netC.phonebook.ReplacePeerList([]string{addrB}, "default", phonebook.PhoneBookEntryRelayRole) netC.Start() defer netC.Stop() @@ -1170,8 +1171,8 @@ func TestGetPeers(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - phbMulti := MakePhonebook(1, 1*time.Millisecond) - phbMulti.ReplacePeerList([]string{addrA}, "phba", PhoneBookEntryRelayRole) + phbMulti := phonebook.MakePhonebook(1, 1*time.Millisecond) + phbMulti.ReplacePeerList([]string{addrA}, "phba", phonebook.PhoneBookEntryRelayRole) netB.phonebook = phbMulti netB.Start() defer netB.Stop() @@ -1182,7 +1183,7 @@ func TestGetPeers(t *testing.T) { waitReady(t, netB, readyTimeout.C) t.Log("b ready") - phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", PhoneBookEntryRelayRole) + phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", phonebook.PhoneBookEntryRelayRole) //addrB, _ := netB.Address() @@ -2179,7 +2180,7 @@ func BenchmarkWebsocketNetworkBasic(t *testing.B) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") returns := make(chan uint64, 100) @@ -2261,7 +2262,7 @@ func TestWebsocketNetworkPrio(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -2308,7 +2309,7 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) { netB.SetPrioScheme(&prioB) netB.config.GossipFanout = 1 netB.config.NetAddress = "" - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counterB}}) netB.Start() defer netStop(t, netB, "B") @@ -2322,7 +2323,7 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) { netC.SetPrioScheme(&prioC) netC.config.GossipFanout = 1 netC.config.NetAddress = "" - netC.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netC.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counterC}}) netC.Start() defer func() { t.Log("stopping C"); netC.Stop(); t.Log("C done") }() @@ -2407,7 +2408,7 @@ func TestWebsocketNetworkManyIdle(t *testing.T) { for i := 0; i < numClients; i++ { client := makeTestWebsocketNodeWithConfig(t, clientConf) client.config.GossipFanout = 1 - client.phonebook.ReplacePeerList([]string{relayAddr}, "default", PhoneBookEntryRelayRole) + client.phonebook.ReplacePeerList([]string{relayAddr}, "default", phonebook.PhoneBookEntryRelayRole) client.Start() defer client.Stop() @@ -2532,7 +2533,7 @@ func TestDelayedMessageDrop(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") counter := newMessageCounter(t, 5) @@ -2564,7 +2565,7 @@ func TestSlowPeerDisconnection(t *testing.T) { wn := &WebsocketNetwork{ log: log, config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), GenesisID: genesisID, NetworkID: config.Devtestnet, } @@ -2585,7 +2586,7 @@ func TestSlowPeerDisconnection(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -2639,7 +2640,7 @@ func TestForceMessageRelaying(t *testing.T) { wn := &WebsocketNetwork{ log: log, config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), GenesisID: genesisID, NetworkID: config.Devtestnet, } @@ -2662,14 +2663,14 @@ func TestForceMessageRelaying(t *testing.T) { noAddressConfig.NetAddress = "" netB := makeTestWebsocketNodeWithConfig(t, noAddressConfig) netB.config.GossipFanout = 1 - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") noAddressConfig.ForceRelayMessages = true netC := makeTestWebsocketNodeWithConfig(t, noAddressConfig) netC.config.GossipFanout = 1 - netC.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netC.Start() defer func() { t.Log("stopping C"); netC.Stop(); t.Log("C done") }() @@ -2733,7 +2734,7 @@ func TestCheckProtocolVersionMatch(t *testing.T) { wn := &WebsocketNetwork{ log: log, config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), GenesisID: genesisID, NetworkID: config.Devtestnet, } @@ -2813,7 +2814,7 @@ func TestWebsocketNetworkTopicRoundtrip(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -2913,7 +2914,7 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Logf("netA %s", addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) // have netB asking netA to send it ft2, deregister ping handler to make sure that we aren't exceeding the maximum MOI messagesize // Max MOI size is calculated by encoding all of the valid tags, since we are using a custom tag here we must deregister one in the default set. @@ -3039,7 +3040,7 @@ func TestWebsocketNetworkTXMessageOfInterestRelay(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -3123,7 +3124,7 @@ func TestWebsocketNetworkTXMessageOfInterestForceTx(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -3205,7 +3206,7 @@ func TestWebsocketNetworkTXMessageOfInterestNPN(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") require.False(t, netB.relayMessages) @@ -3313,7 +3314,7 @@ func TestWebsocketNetworkTXMessageOfInterestPN(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") require.False(t, netB.relayMessages) @@ -3435,7 +3436,7 @@ func testWebsocketDisconnection(t *testing.T, disconnectFunc func(wn *WebsocketN addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -3630,7 +3631,7 @@ func BenchmarkVariableTransactionMessageBlockSizes(t *testing.B) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer func() { netB.Stop() }() @@ -4543,3 +4544,23 @@ func TestSendMessageCallbackDrain(t *testing.T) { 50*time.Millisecond, ) } + +// TestWsNetworkPhonebookMix ensures p2p addresses are not added into wsNetwork via phonebook +func TestWsNetworkPhonebookMix(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + net, err := NewWebsocketNetwork( + logging.TestingLog(t), + config.GetDefaultLocal(), + []string{"127.0.0.1:1234", "/ip4/127.0.0.1/tcp/1234", "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"}, + "test", + "net", + nil, + "", + nil, + ) + require.NoError(t, err) + addrs := net.phonebook.GetAddresses(10, phonebook.PhoneBookEntryRelayRole) + require.Len(t, addrs, 1) +} diff --git a/node/node_test.go b/node/node_test.go index f82df98b84..a83ea7ab39 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -841,7 +841,7 @@ func TestMaxSizesCorrect(t *testing.T) { // N -- R -- A and ensures N can discover A and download blocks from it. // // N is a non-part node that joins the network later -// R is a non-arhival relay node with block service disabled. It MUST NOT service blocks to force N to discover A. +// R is a non-archival relay node with block service disabled. It MUST NOT service blocks to force N to discover A. // A is a archival node that can only provide blocks. // Nodes N and A have only R in their initial phonebook, and all nodes are in hybrid mode. func TestNodeHybridTopology(t *testing.T) { diff --git a/rpcs/blockService.go b/rpcs/blockService.go index ed27c9a703..380c2cd998 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -41,6 +41,7 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/metrics" ) @@ -389,10 +390,10 @@ func (bs *BlockService) redirectRequest(round uint64, response http.ResponseWrit } var redirectURL string - if network.IsMultiaddr(peerAddress) { + if addr.IsMultiaddr(peerAddress) { redirectURL = strings.Replace(FormatBlockQuery(round, "", bs.net), "{genesisID}", bs.genesisID, 1) } else { - parsedURL, err := network.ParseHostOrURL(peerAddress) + parsedURL, err := addr.ParseHostOrURL(peerAddress) if err != nil { bs.log.Debugf("redirectRequest: %s", err.Error()) return false @@ -498,10 +499,10 @@ func makeFallbackEndpoints(log logging.Logger, customFallbackEndpoints string) ( } endpoints := strings.Split(customFallbackEndpoints, ",") for _, ep := range endpoints { - if network.IsMultiaddr(ep) { + if addr.IsMultiaddr(ep) { fe.endpoints = append(fe.endpoints, ep) } else { - parsed, err := network.ParseHostOrURL(ep) + parsed, err := addr.ParseHostOrURL(ep) if err != nil { log.Warnf("makeFallbackEndpoints: error parsing %s %s", ep, err.Error()) continue diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index d7ad406675..5ff2153c9e 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -39,6 +39,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -168,7 +169,7 @@ func TestRedirectFallbackEndpoints(t *testing.T) { nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -213,7 +214,7 @@ func TestBlockServiceShutdown(t *testing.T) { nodeA.start() defer nodeA.stop() - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -295,7 +296,7 @@ func TestRedirectOnFullCapacity(t *testing.T) { nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -476,7 +477,7 @@ func TestRedirectExceptions(t *testing.T) { nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -495,7 +496,7 @@ func TestRedirectExceptions(t *testing.T) { require.NoError(t, err) require.Equal(t, response.StatusCode, http.StatusNotFound) - parsedURLNodeB, err := network.ParseHostOrURL(nodeB.rootURL()) + parsedURLNodeB, err := addr.ParseHostOrURL(nodeB.rootURL()) require.NoError(t, err) parsedURLNodeB.Path = FormatBlockQuery(uint64(4), parsedURLNodeB.Path, net2) diff --git a/rpcs/httpTxSync.go b/rpcs/httpTxSync.go index 4d803bddda..4e1fd29883 100644 --- a/rpcs/httpTxSync.go +++ b/rpcs/httpTxSync.go @@ -31,6 +31,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/bloom" ) @@ -112,10 +113,10 @@ func (hts *HTTPTxSync) Sync(ctx context.Context, bloom *bloom.Filter) (txgroups return nil, fmt.Errorf("HTTPTxSync cannot create a HTTP client for a peer %T %#v: %s", peer, peer, err.Error()) } } - if network.IsMultiaddr(hts.rootURL) { + if addr.IsMultiaddr(hts.rootURL) { syncURL = network.SubstituteGenesisID(hts.peers, path.Join("", TxServiceHTTPPath)) } else { - parsedURL, err0 := network.ParseHostOrURL(hts.rootURL) + parsedURL, err0 := addr.ParseHostOrURL(hts.rootURL) if err0 != nil { hts.log.Warnf("txSync bad url %v: %s", hts.rootURL, err0) return nil, err0