From f2d19162d2a0704a5834f10c71b33ef22c8af832 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 17:10:08 -0500 Subject: [PATCH 1/9] fixes from caffix review --- docs/specification/new-spec/p2p/connection.md | 2 +- docs/specification/new-spec/p2p/peer.md | 5 +++-- docs/specification/new-spec/p2p/pex.md | 18 +++++++++++------- docs/specification/new-spec/p2p/trustmetric.md | 16 ---------------- 4 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 docs/specification/new-spec/p2p/trustmetric.md diff --git a/docs/specification/new-spec/p2p/connection.md b/docs/specification/new-spec/p2p/connection.md index dfe0d090..400111f4 100644 --- a/docs/specification/new-spec/p2p/connection.md +++ b/docs/specification/new-spec/p2p/connection.md @@ -46,7 +46,7 @@ is returned for processing by the corresponding channels `onReceive` function. Messages are sent from a single `sendRoutine`, which loops over a select statement that results in the sending of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels. Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time. -Messages are chosen for a batch one a time from the channel with the lowest ratio of recently sent bytes to channel priority. +Messages are chosen for a batch one at a time from the channel with the lowest ratio of recently sent bytes to channel priority. ## Sending Messages diff --git a/docs/specification/new-spec/p2p/peer.md b/docs/specification/new-spec/p2p/peer.md index a172764c..39be966b 100644 --- a/docs/specification/new-spec/p2p/peer.md +++ b/docs/specification/new-spec/p2p/peer.md @@ -1,7 +1,8 @@ # Tendermint Peers -This document explains how Tendermint Peers are identified, how they connect to one another, -and how other peers are found. +This document explains how Tendermint Peers are identified and how they connect to one another. + +For details on peer discovery, see the [peer exchange (PEX) reactor doc](pex.md). ## Peer Identity diff --git a/docs/specification/new-spec/p2p/pex.md b/docs/specification/new-spec/p2p/pex.md index a71b9717..43d6f80d 100644 --- a/docs/specification/new-spec/p2p/pex.md +++ b/docs/specification/new-spec/p2p/pex.md @@ -8,10 +8,10 @@ to good peers and to gossip peers to others. Certain peers are special in that they are specified by the user as `persistent`, which means we auto-redial them if the connection fails. -Some such peers can additional be marked as `private`, which means -we will not gossip them to others. +Some peers can be marked as `private`, which means +we will not put them in the address book or gossip them to others. -All others peers are tracked using an address book. +All peers except private peers are tracked using the address book. ## Discovery @@ -31,7 +31,7 @@ Peers are added to the address book from the PEX when they first connect to us o when we hear about them from other peers. The address book is arranged in sets of buckets, and distinguishes between -vetted and unvetted peers. It keeps different sets of buckets for vetted and +vetted (old) and unvetted (new) peers. It keeps different sets of buckets for vetted and unvetted peers. Buckets provide randomization over peer selection. A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets. @@ -52,7 +52,7 @@ If a peer becomes unvetted (either a new peer, or one that was previously vetted a randomly selected one of the unvetted peers is removed from the address book. More fine-grained tracking of peer behaviour can be done using -a Trust Metric, but it's best to start with something simple. +a trust metric (see below), but it's best to start with something simple. ## Select Peers to Dial @@ -75,7 +75,7 @@ Send the selected peers. Note we select peers for sending without bias for vette There are various cases where we decide a peer has misbehaved and we disconnect from them. When this happens, the peer is removed from the address book and black listed for some amount of time. We call this "Disconnect and Mark". -Note that the bad behaviour may be detected outside the PEX reactor itseld +Note that the bad behaviour may be detected outside the PEX reactor itself (for instance, in the mconnection, or another reactor), but it must be communicated to the PEX reactor so it can remove and mark the peer. @@ -86,9 +86,13 @@ we Disconnect and Mark. ## Trust Metric The quality of peers can be tracked in more fine-grained detail using a -Proportional-Integral-Derrivative (PID) controller that incorporates +Proportional-Integral-Derivative (PID) controller that incorporates current, past, and rate-of-change data to inform peer quality. While a PID trust metric has been implemented, it remains for future work to use it in the PEX. +See the [trustmetric](../../../architecture/adr-006-trust-metric.md ) +and [trustmetric useage](../../../architecture/adr-007-trust-metric-usage.md ) +architecture docs for more details. + diff --git a/docs/specification/new-spec/p2p/trustmetric.md b/docs/specification/new-spec/p2p/trustmetric.md deleted file mode 100644 index b0eaf96e..00000000 --- a/docs/specification/new-spec/p2p/trustmetric.md +++ /dev/null @@ -1,16 +0,0 @@ - -The trust metric tracks the quality of the peers. -When a peer exceeds a certain quality for a certain amount of time, -it is marked as vetted in the addrbook. -If a vetted peer's quality degrades sufficiently, it is booted, and must prove itself from scratch. -If we need to make room for a new vetted peer, we move the lowest scoring vetted peer back to unvetted. -If we need to make room for a new unvetted peer, we remove the lowest scoring unvetted peer - -possibly only if its below some absolute minimum ? - -Peer quality is tracked in the connection and across the reactors. -Behaviours are defined as one of: - - fatal - something outright malicious. we should disconnect and remember them. - - bad - any kind of timeout, msgs that dont unmarshal, or fail other validity checks, or msgs we didn't ask for or arent expecting - - neutral - normal correct behaviour. unknown channels/msg types (version upgrades). - - good - some random majority of peers per reactor sending us useful messages - From ae27e85bf7dc29323cfc73505b040b83293e9990 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 17:51:09 -0500 Subject: [PATCH 2/9] add warnings about new spec --- docs/specification/new-spec/README.md | 15 +++++++++++++-- docs/specification/new-spec/encoding.md | 15 +++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/specification/new-spec/README.md b/docs/specification/new-spec/README.md index 8a07d922..179048dd 100644 --- a/docs/specification/new-spec/README.md +++ b/docs/specification/new-spec/README.md @@ -1,14 +1,24 @@ # Tendermint Specification This is a markdown specification of the Tendermint blockchain. - It defines the base data structures used in the blockchain and how they are validated. -It contains the following components: +XXX: this spec is a work in progress and not yet complete - see github +[isses](https://github.com/tendermint/tendermint/issues) and +[pull requests](https://github.com/tendermint/tendermint/pulls) +for more details. +If you find discrepancies between the spec and the code that +do not have an associated issue or pull request on github, +please submit them to our [bug bounty](https://tendermint.com/security)! + +## Contents + +- [Overview](#overview) - [Encoding and Digests](encoding.md) - [Blockchain](blockchain.md) - [State](state.md) +- [Consensus](consensus.md) - [P2P](p2p/node.md) ## Overview @@ -56,3 +66,4 @@ We call this the `State`. Block verification also requires access to the previou - Light Client - P2P - Reactor protocols (consensus, mempool, blockchain, pex) + diff --git a/docs/specification/new-spec/encoding.md b/docs/specification/new-spec/encoding.md index 02456d84..f401dde7 100644 --- a/docs/specification/new-spec/encoding.md +++ b/docs/specification/new-spec/encoding.md @@ -6,6 +6,9 @@ Tendermint aims to encode data structures in a manner similar to how the corresp Variable length items are length-prefixed. While the encoding was inspired by Go, it is easily implemented in other languages as well given its intuitive design. +XXX: This is changing to use real varints and 4-byte-prefixes. +See https://github.com/tendermint/go-wire/tree/sdk2. + ### Fixed Length Integers Fixed length integers are encoded in Big-Endian using the specified number of bytes. @@ -94,13 +97,13 @@ encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x ``` ### BitArray -BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode +BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode value of each array element. ``` type BitArray struct { - Bits int - Elems []uint64 + Bits int + Elems []uint64 } ``` @@ -192,8 +195,8 @@ MakeParts(object, partSize) ``` type Part struct { - Index int - Bytes byte[] - Proof byte[] + Index int + Bytes byte[] + Proof byte[] } ``` From 39d8da35364f740aec4dd4009bc3c0ef95cbaa26 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 14:54:59 -0500 Subject: [PATCH 3/9] docs: update getting started [ci skip] --- docs/examples/getting-started.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/examples/getting-started.md b/docs/examples/getting-started.md index d7447428..3ae42e27 100644 --- a/docs/examples/getting-started.md +++ b/docs/examples/getting-started.md @@ -22,19 +22,22 @@ The script is also used to facilitate cluster deployment below. ### Manual Install -Requires: -- `go` minimum version 1.9.2 -- `$GOPATH` set and `$GOPATH/bin` on your $PATH (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH) +Requires: +- `go` minimum version 1.9 +- `$GOPATH` environment variable must be set +- `$GOPATH/bin` must be on your `$PATH` (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH) To install Tendermint, run: ``` go get github.com/tendermint/tendermint cd $GOPATH/src/github.com/tendermint/tendermint -make get_vendor_deps +make get_tools && make get_vendor_deps make install ``` +Note that `go get` may return an error but it can be ignored. + Confirm installation: ``` @@ -98,7 +101,7 @@ and check that it worked with: curl -s 'localhost:46657/abci_query?data="abcd"' ``` -We can send transactions with a key:value store: +We can send transactions with a key and value too: ``` curl -s 'localhost:46657/broadcast_tx_commit?tx="name=satoshi"' @@ -114,9 +117,9 @@ where the value is returned in hex. ## Cluster of Nodes -First create four Ubuntu cloud machines. The following was testing on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4. +First create four Ubuntu cloud machines. The following was tested on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4. -Then, `ssh` into each machine, and `curl` then execute [this script](https://git.io/vNLfY): +Then, `ssh` into each machine, and execute [this script](https://git.io/vNLfY): ``` curl -L https://git.io/vNLfY | bash @@ -128,12 +131,12 @@ This will install `go` and other dependencies, get the Tendermint source code, t Next, `cd` into `docs/examples`. Each command below should be run from each node, in sequence: ``` -tendermint node --home ./node1 --proxy_app=dummy -tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656 -tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656 -tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656 +tendermint node --home ./node1 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 +tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 +tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 +tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 ``` -Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json` have come online). Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options. +Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json`) have come online. Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options. Transactions can then be sent as covered in the single, local node example above. From 949211a137287b209783dfeb2eb03a0978f5e0a2 Mon Sep 17 00:00:00 2001 From: caffix Date: Tue, 9 Jan 2018 20:12:41 -0500 Subject: [PATCH 4/9] added a test for PEX reactor seed mode --- p2p/pex_reactor.go | 268 ++++++++++++++++++++++++++++++++++++++++ p2p/pex_reactor_test.go | 45 +++++++ 2 files changed, 313 insertions(+) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 93fddc11..7313f7d5 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "reflect" + "sort" "time" "github.com/pkg/errors" @@ -334,6 +335,273 @@ func (r *PEXReactor) checkSeeds() error { return nil } +// Explores the network searching for more peers. (continuous) +// Seed/Crawler Mode causes this node to quickly disconnect +// from peers, except other seed nodes. +func (r *PEXReactor) seedCrawlerMode() { + // Do an initial crawl + r.crawlPeers() + + // Fire periodically + ticker := time.NewTicker(defaultSeedModePeriod) + + for { + select { + case <-ticker.C: + r.attemptDisconnects() + r.crawlPeers() + case <-r.Quit: + return + } + } +} + +// crawlStatus handles temporary data needed for the +// network crawling performed during seed/crawler mode. +type crawlStatus struct { + // The remote address of a potential peer we learned about + Addr *NetAddress + + // Not empty if we are connected to the address + PeerID string + + // The last time we attempt to reach this address + LastAttempt time.Time + + // The last time we successfully reached this address + LastSuccess time.Time +} + +// oldestFirst implements sort.Interface for []crawlStatus +// based on the LastAttempt field. +type oldestFirst []crawlStatus + +func (of oldestFirst) Len() int { return len(of) } +func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } +func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } + +// getCrawlStatus returns addresses of potential peers that we wish to validate. +// NOTE: The status information is ordered as described above. +func (r *PEXReactor) getCrawlStatus() []crawlStatus { + var of oldestFirst + + addrs := r.book.ListOfKnownAddresses() + // Go through all the addresses in the AddressBook + for _, addr := range addrs { + var peerID string + + // Check if a peer is already connected from this addr + if p := r.Switch.peers.GetByRemoteAddr(addr.Addr); p != nil { + peerID = p.Key() + } + + of = append(of, crawlStatus{ + Addr: addr.Addr, + PeerID: peerID, + LastAttempt: addr.LastAttempt, + LastSuccess: addr.LastSuccess, + }) + } + sort.Sort(of) + return of +} + +// crawlPeers will crawl the network looking for new peer addresses. (once) +// +// TODO Basically, we need to work harder on our good-peer/bad-peer marking. +// What we're currently doing in terms of marking good/bad peers is just a +// placeholder. It should not be the case that an address becomes old/vetted +// upon a single successful connection. +func (r *PEXReactor) crawlPeers() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Use addresses we know of to reach additional peers + for _, cs := range crawlerStatus { + // Do not dial peers that are already connected + if cs.PeerID != "" { + continue + } + // Do not attempt to connect with peers we recently dialed + if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { + continue + } + // Otherwise, attempt to connect with the known address + p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) + if err != nil { + r.book.MarkAttempt(cs.Addr) + continue + } + // Enter the peer ID into our crawl status information + cs.PeerID = p.Key() + r.book.MarkGood(cs.Addr) + } + // Crawl the connected peers asking for more addresses + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // We will wait a minimum period of time before crawling peers again + if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { + p := r.Switch.Peers().Get(cs.PeerID) + if p != nil { + r.RequestPEX(p) + r.book.MarkAttempt(cs.Addr) + } + } + } +} + +// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) +func (r *PEXReactor) attemptDisconnects() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Go through each peer we have connected with + // looking for opportunities to disconnect + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // Remain connected to each peer for a minimum period of time + if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { + continue + } + // Fetch the Peer using the saved ID + p := r.Switch.Peers().Get(cs.PeerID) + if p == nil { + continue + } + // Do not disconnect from persistent peers. + // Specifically, we need to remain connected to other seeds + if p.IsPersistent() { + continue + } + // Otherwise, disconnect from the peer + r.Switch.StopPeerGracefully(p) + } +} + +// crawlStatus handles temporary data needed for the +// network crawling performed during seed/crawler mode. +type crawlStatus struct { + // The remote address of a potential peer we learned about + Addr *NetAddress + + // Not empty if we are connected to the address + PeerID string + + // The last time we attempt to reach this address + LastAttempt time.Time + + // The last time we successfully reached this address + LastSuccess time.Time +} + +// oldestAttempt implements sort.Interface for []crawlStatus +// based on the LastAttempt field. +type oldestAttempt []crawlStatus + +func (oa oldestAttempt) Len() int { return len(oa) } +func (oa oldestAttempt) Swap(i, j int) { oa[i], oa[j] = oa[j], oa[i] } +func (oa oldestAttempt) Less(i, j int) bool { return oa[i].LastAttempt.Before(oa[j].LastAttempt) } + +// getCrawlStatus returns addresses of potential peers that we wish to validate. +// NOTE: The status information is ordered as described above. +func (r *PEXReactor) getCrawlStatus() []crawlStatus { + var oa oldestAttempt + + addrs := r.book.ListOfKnownAddresses() + // Go through all the addresses in the AddressBook + for _, addr := range addrs { + p := r.Switch.peers.GetByRemoteAddr(addr.Addr) + + oa = append(oa, crawlStatus{ + Addr: addr.Addr, + PeerID: p.Key(), + LastAttempt: addr.LastAttempt, + LastSuccess: addr.LastSuccess, + }) + } + sort.Sort(oa) + return oa +} + +// crawlPeers will crawl the network looking for new peer addresses. (once) +// +// TODO Basically, we need to work harder on our good-peer/bad-peer marking. +// What we're currently doing in terms of marking good/bad peers is just a +// placeholder. It should not be the case that an address becomes old/vetted +// upon a single successful connection. +func (r *PEXReactor) crawlPeers() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Use addresses we know of to reach additional peers + for _, cs := range crawlerStatus { + // Do not dial peers that are already connected + if cs.PeerID != "" { + continue + } + // Do not attempt to connect with peers we recently dialed + if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { + continue + } + // Otherwise, attempt to connect with the known address + p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) + if err != nil { + r.book.MarkAttempt(cs.Addr) + continue + } + // Enter the peer ID into our crawl status information + cs.PeerID = p.Key() + r.book.MarkGood(cs.Addr) + } + // Crawl the connected peers asking for more addresses + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // We will wait a minimum period of time before crawling peers again + if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { + p := r.Switch.peers.Get(cs.PeerID) + if p != nil { + r.RequestPEX(p) + } + } + } +} + +// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) +func (r *PEXReactor) attemptDisconnects() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Go through each peer we have connected with + // looking for opportunities to disconnect + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // Remain connected to each peer for a minimum period of time + if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { + continue + } + // Fetch the Peer using the saved ID + p := r.Switch.peers.Get(cs.PeerID) + if p == nil { + continue + } + // Do not disconnect from persistent peers. + // Specifically, we need to remain connected to other seeds + if p.IsPersistent() { + continue + } + // Otherwise, disconnect from the peer + r.Switch.StopPeerGracefully(p) + } +} + // randomly dial seeds until we connect to one or exhaust them func (r *PEXReactor) dialSeed() { lSeeds := len(r.config.Seeds) diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index c0681586..b8ee89b3 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -286,6 +286,51 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { assertSomePeersWithTimeout(t, []*Switch{sw}, 10*time.Millisecond, 10*time.Second) } +func TestPEXReactorCrawlStatus(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(err) + defer os.RemoveAll(dir) // nolint: errcheck + book := NewAddrBook(dir+"addrbook.json", false) + book.SetLogger(log.TestingLogger()) + + var r *PEXReactor + // Seed/Crawler mode uses data from the Switch + makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + r = NewPEXReactor(book, true) + r.SetLogger(log.TestingLogger()) + sw.SetLogger(log.TestingLogger().With("switch", i)) + sw.AddReactor("pex", r) + return sw + }) + + // Create a peer, and add it to the peer set + peer := createRandomPeer(false) + r.Switch.peers.Add(peer) + // Add the peer address to the address book + addr1, _ := NewNetAddressString(peer.NodeInfo().ListenAddr) + r.book.AddAddress(addr1, addr1) + // Add an address to the book that does not have a peer + _, addr2 := createRoutableAddr() + r.book.AddAddress(addr2, addr1) + + // Get the crawl status data + status := r.getCrawlStatus() + + // Make sure it has the proper number of elements + assert.Equal(2, len(status)) + + var num int + for _, cs := range status { + if cs.PeerID != "" { + num++ + } + } + // Check that only one has been identified as a connected peer + assert.Equal(1, num) +} + func createRoutableAddr() (addr string, netAddr *NetAddress) { for { addr = cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) From 88eb3e7af0b63b3bc059453d95f501789e399730 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 01:40:29 -0500 Subject: [PATCH 5/9] some minor renames --- p2p/pex_reactor.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 7313f7d5..0c3567a3 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -21,6 +21,11 @@ const ( defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 maxPexMessageSize = 1048576 // 1MB + + // Seed/Crawler constants + defaultSeedDisconnectWaitPeriod = 2 * time.Minute + defaultCrawlPeerInterval = 2 * time.Minute + defaultCrawlPeersPeriod = 30 * time.Second ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -79,7 +84,13 @@ func (r *PEXReactor) OnStart() error { return err } - go r.ensurePeersRoutine() + // Check if this node should run + // in seed/crawler mode + if r.config.SeedMode { + go r.crawlPeersRoutine() + } else { + go r.ensurePeersRoutine() + } return nil } @@ -338,7 +349,7 @@ func (r *PEXReactor) checkSeeds() error { // Explores the network searching for more peers. (continuous) // Seed/Crawler Mode causes this node to quickly disconnect // from peers, except other seed nodes. -func (r *PEXReactor) seedCrawlerMode() { +func (r *PEXReactor) crawlPeersRoutine() { // Do an initial crawl r.crawlPeers() From c2f97e64545920b98aa4ea48f9669b643a80b001 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 18:28:40 -0500 Subject: [PATCH 6/9] p2p: seed mode fixes from rebase and review --- p2p/connection.go | 4 + p2p/pex_reactor.go | 418 +++++++++++++--------------------------- p2p/pex_reactor_test.go | 48 +++-- 3 files changed, 158 insertions(+), 312 deletions(-) diff --git a/p2p/connection.go b/p2p/connection.go index 306eaf7e..dcb66096 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -88,6 +88,8 @@ type MConnection struct { flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. pingTimer *cmn.RepeatTimer // send pings periodically chStatsTimer *cmn.RepeatTimer // update channel stats periodically + + created time.Time // time of creation } // MConnConfig is a MConnection configuration. @@ -502,6 +504,7 @@ FOR_LOOP: } type ConnectionStatus struct { + Duration time.Duration SendMonitor flow.Status RecvMonitor flow.Status Channels []ChannelStatus @@ -517,6 +520,7 @@ type ChannelStatus struct { func (c *MConnection) Status() ConnectionStatus { var status ConnectionStatus + status.Duration = time.Since(c.created) status.SendMonitor = c.sendMonitor.Status() status.RecvMonitor = c.recvMonitor.Status() status.Channels = make([]ChannelStatus, len(c.channels)) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 0c3567a3..5d919421 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -17,15 +17,22 @@ const ( // PexChannel is a channel for PEX messages PexChannel = byte(0x00) - // period to ensure peers connected - defaultEnsurePeersPeriod = 30 * time.Second - minNumOutboundPeers = 10 - maxPexMessageSize = 1048576 // 1MB + maxPexMessageSize = 1048576 // 1MB + + // ensure we have enough peers + defaultEnsurePeersPeriod = 30 * time.Second + defaultMinNumOutboundPeers = 10 // Seed/Crawler constants - defaultSeedDisconnectWaitPeriod = 2 * time.Minute - defaultCrawlPeerInterval = 2 * time.Minute - defaultCrawlPeersPeriod = 30 * time.Second + // TODO: + // We want seeds to only advertise good peers. + // Peers are marked by external mechanisms. + // We need a config value that can be set to be + // on the order of how long it would take before a good + // peer is marked good. + defaultSeedDisconnectWaitPeriod = 2 * time.Minute // disconnect after this + defaultCrawlPeerInterval = 2 * time.Minute // dont redial for this. TODO: back-off + defaultCrawlPeersPeriod = 30 * time.Second // check some peers every this ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -51,8 +58,11 @@ type PEXReactor struct { // PEXReactorConfig holds reactor specific configuration data. type PEXReactorConfig struct { - // Seeds is a list of addresses reactor may use if it can't connect to peers - // in the addrbook. + // Seed/Crawler mode + SeedMode bool + + // Seeds is a list of addresses reactor may use + // if it can't connect to peers in the addrbook. Seeds []string } @@ -259,19 +269,12 @@ func (r *PEXReactor) ensurePeersRoutine() { // ensurePeers ensures that sufficient peers are connected. (once) // -// Old bucket / New bucket are arbitrary categories to denote whether an -// address is vetted or not, and this needs to be determined over time via a // heuristic that we haven't perfected yet, or, perhaps is manually edited by // the node operator. It should not be used to compute what addresses are // already connected or not. -// -// TODO Basically, we need to work harder on our good-peer/bad-peer marking. -// What we're currently doing in terms of marking good/bad peers is just a -// placeholder. It should not be the case that an address becomes old/vetted -// upon a single successful connection. func (r *PEXReactor) ensurePeers() { numOutPeers, numInPeers, numDialing := r.Switch.NumPeers() - numToDial := minNumOutboundPeers - (numOutPeers + numDialing) + numToDial := defaultMinNumOutboundPeers - (numOutPeers + numDialing) r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial) if numToDial <= 0 { return @@ -327,7 +330,7 @@ func (r *PEXReactor) ensurePeers() { // If we are not connected to nor dialing anybody, fallback to dialing a seed. if numOutPeers+numInPeers+numDialing+len(toDial) == 0 { r.Logger.Info("No addresses to dial nor connected peers. Falling back to seeds") - r.dialSeed() + r.dialSeeds() } } @@ -346,275 +349,8 @@ func (r *PEXReactor) checkSeeds() error { return nil } -// Explores the network searching for more peers. (continuous) -// Seed/Crawler Mode causes this node to quickly disconnect -// from peers, except other seed nodes. -func (r *PEXReactor) crawlPeersRoutine() { - // Do an initial crawl - r.crawlPeers() - - // Fire periodically - ticker := time.NewTicker(defaultSeedModePeriod) - - for { - select { - case <-ticker.C: - r.attemptDisconnects() - r.crawlPeers() - case <-r.Quit: - return - } - } -} - -// crawlStatus handles temporary data needed for the -// network crawling performed during seed/crawler mode. -type crawlStatus struct { - // The remote address of a potential peer we learned about - Addr *NetAddress - - // Not empty if we are connected to the address - PeerID string - - // The last time we attempt to reach this address - LastAttempt time.Time - - // The last time we successfully reached this address - LastSuccess time.Time -} - -// oldestFirst implements sort.Interface for []crawlStatus -// based on the LastAttempt field. -type oldestFirst []crawlStatus - -func (of oldestFirst) Len() int { return len(of) } -func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } -func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } - -// getCrawlStatus returns addresses of potential peers that we wish to validate. -// NOTE: The status information is ordered as described above. -func (r *PEXReactor) getCrawlStatus() []crawlStatus { - var of oldestFirst - - addrs := r.book.ListOfKnownAddresses() - // Go through all the addresses in the AddressBook - for _, addr := range addrs { - var peerID string - - // Check if a peer is already connected from this addr - if p := r.Switch.peers.GetByRemoteAddr(addr.Addr); p != nil { - peerID = p.Key() - } - - of = append(of, crawlStatus{ - Addr: addr.Addr, - PeerID: peerID, - LastAttempt: addr.LastAttempt, - LastSuccess: addr.LastSuccess, - }) - } - sort.Sort(of) - return of -} - -// crawlPeers will crawl the network looking for new peer addresses. (once) -// -// TODO Basically, we need to work harder on our good-peer/bad-peer marking. -// What we're currently doing in terms of marking good/bad peers is just a -// placeholder. It should not be the case that an address becomes old/vetted -// upon a single successful connection. -func (r *PEXReactor) crawlPeers() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Use addresses we know of to reach additional peers - for _, cs := range crawlerStatus { - // Do not dial peers that are already connected - if cs.PeerID != "" { - continue - } - // Do not attempt to connect with peers we recently dialed - if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { - continue - } - // Otherwise, attempt to connect with the known address - p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) - if err != nil { - r.book.MarkAttempt(cs.Addr) - continue - } - // Enter the peer ID into our crawl status information - cs.PeerID = p.Key() - r.book.MarkGood(cs.Addr) - } - // Crawl the connected peers asking for more addresses - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // We will wait a minimum period of time before crawling peers again - if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { - p := r.Switch.Peers().Get(cs.PeerID) - if p != nil { - r.RequestPEX(p) - r.book.MarkAttempt(cs.Addr) - } - } - } -} - -// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) -func (r *PEXReactor) attemptDisconnects() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Go through each peer we have connected with - // looking for opportunities to disconnect - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // Remain connected to each peer for a minimum period of time - if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { - continue - } - // Fetch the Peer using the saved ID - p := r.Switch.Peers().Get(cs.PeerID) - if p == nil { - continue - } - // Do not disconnect from persistent peers. - // Specifically, we need to remain connected to other seeds - if p.IsPersistent() { - continue - } - // Otherwise, disconnect from the peer - r.Switch.StopPeerGracefully(p) - } -} - -// crawlStatus handles temporary data needed for the -// network crawling performed during seed/crawler mode. -type crawlStatus struct { - // The remote address of a potential peer we learned about - Addr *NetAddress - - // Not empty if we are connected to the address - PeerID string - - // The last time we attempt to reach this address - LastAttempt time.Time - - // The last time we successfully reached this address - LastSuccess time.Time -} - -// oldestAttempt implements sort.Interface for []crawlStatus -// based on the LastAttempt field. -type oldestAttempt []crawlStatus - -func (oa oldestAttempt) Len() int { return len(oa) } -func (oa oldestAttempt) Swap(i, j int) { oa[i], oa[j] = oa[j], oa[i] } -func (oa oldestAttempt) Less(i, j int) bool { return oa[i].LastAttempt.Before(oa[j].LastAttempt) } - -// getCrawlStatus returns addresses of potential peers that we wish to validate. -// NOTE: The status information is ordered as described above. -func (r *PEXReactor) getCrawlStatus() []crawlStatus { - var oa oldestAttempt - - addrs := r.book.ListOfKnownAddresses() - // Go through all the addresses in the AddressBook - for _, addr := range addrs { - p := r.Switch.peers.GetByRemoteAddr(addr.Addr) - - oa = append(oa, crawlStatus{ - Addr: addr.Addr, - PeerID: p.Key(), - LastAttempt: addr.LastAttempt, - LastSuccess: addr.LastSuccess, - }) - } - sort.Sort(oa) - return oa -} - -// crawlPeers will crawl the network looking for new peer addresses. (once) -// -// TODO Basically, we need to work harder on our good-peer/bad-peer marking. -// What we're currently doing in terms of marking good/bad peers is just a -// placeholder. It should not be the case that an address becomes old/vetted -// upon a single successful connection. -func (r *PEXReactor) crawlPeers() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Use addresses we know of to reach additional peers - for _, cs := range crawlerStatus { - // Do not dial peers that are already connected - if cs.PeerID != "" { - continue - } - // Do not attempt to connect with peers we recently dialed - if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { - continue - } - // Otherwise, attempt to connect with the known address - p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) - if err != nil { - r.book.MarkAttempt(cs.Addr) - continue - } - // Enter the peer ID into our crawl status information - cs.PeerID = p.Key() - r.book.MarkGood(cs.Addr) - } - // Crawl the connected peers asking for more addresses - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // We will wait a minimum period of time before crawling peers again - if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { - p := r.Switch.peers.Get(cs.PeerID) - if p != nil { - r.RequestPEX(p) - } - } - } -} - -// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) -func (r *PEXReactor) attemptDisconnects() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Go through each peer we have connected with - // looking for opportunities to disconnect - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // Remain connected to each peer for a minimum period of time - if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { - continue - } - // Fetch the Peer using the saved ID - p := r.Switch.peers.Get(cs.PeerID) - if p == nil { - continue - } - // Do not disconnect from persistent peers. - // Specifically, we need to remain connected to other seeds - if p.IsPersistent() { - continue - } - // Otherwise, disconnect from the peer - r.Switch.StopPeerGracefully(p) - } -} - // randomly dial seeds until we connect to one or exhaust them -func (r *PEXReactor) dialSeed() { +func (r *PEXReactor) dialSeeds() { lSeeds := len(r.config.Seeds) if lSeeds == 0 { return @@ -636,6 +372,116 @@ func (r *PEXReactor) dialSeed() { r.Switch.Logger.Error("Couldn't connect to any seeds") } +//---------------------------------------------------------- + +// Explores the network searching for more peers. (continuous) +// Seed/Crawler Mode causes this node to quickly disconnect +// from peers, except other seed nodes. +func (r *PEXReactor) crawlPeersRoutine() { + // Do an initial crawl + r.crawlPeers() + + // Fire periodically + ticker := time.NewTicker(defaultCrawlPeersPeriod) + + for { + select { + case <-ticker.C: + r.attemptDisconnects() + r.crawlPeers() + case <-r.Quit: + return + } + } +} + +// crawlPeerInfo handles temporary data needed for the +// network crawling performed during seed/crawler mode. +type crawlPeerInfo struct { + // The listening address of a potential peer we learned about + Addr *NetAddress + + // The last time we attempt to reach this address + LastAttempt time.Time + + // The last time we successfully reached this address + LastSuccess time.Time +} + +// oldestFirst implements sort.Interface for []crawlPeerInfo +// based on the LastAttempt field. +type oldestFirst []crawlPeerInfo + +func (of oldestFirst) Len() int { return len(of) } +func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } +func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } + +// getPeersToCrawl returns addresses of potential peers that we wish to validate. +// NOTE: The status information is ordered as described above. +func (r *PEXReactor) getPeersToCrawl() []crawlPeerInfo { + var of oldestFirst + + // TODO: not this. be more selective + addrs := r.book.ListOfKnownAddresses() + for _, addr := range addrs { + if len(addr.ID()) == 0 { + continue // dont use peers without id + } + + of = append(of, crawlPeerInfo{ + Addr: addr.Addr, + LastAttempt: addr.LastAttempt, + LastSuccess: addr.LastSuccess, + }) + } + sort.Sort(of) + return of +} + +// crawlPeers will crawl the network looking for new peer addresses. (once) +func (r *PEXReactor) crawlPeers() { + peerInfos := r.getPeersToCrawl() + + now := time.Now() + // Use addresses we know of to reach additional peers + for _, pi := range peerInfos { + // Do not attempt to connect with peers we recently dialed + if now.Sub(pi.LastAttempt) < defaultCrawlPeerInterval { + continue + } + // Otherwise, attempt to connect with the known address + _, err := r.Switch.DialPeerWithAddress(pi.Addr, false) + if err != nil { + r.book.MarkAttempt(pi.Addr) + continue + } + } + // Crawl the connected peers asking for more addresses + for _, pi := range peerInfos { + // We will wait a minimum period of time before crawling peers again + if now.Sub(pi.LastAttempt) >= defaultCrawlPeerInterval { + peer := r.Switch.Peers().Get(pi.Addr.ID) + if peer != nil { + r.RequestPEX(peer) + } + } + } +} + +// attemptDisconnects checks if we've been with each peer long enough to disconnect +func (r *PEXReactor) attemptDisconnects() { + for _, peer := range r.Switch.Peers().List() { + status := peer.Status() + if status.Duration < defaultSeedDisconnectWaitPeriod { + continue + } + if peer.IsPersistent() { + continue + } + r.Switch.StopPeerGracefully(peer) + } +} + //----------------------------------------------------------------------------- // Messages diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index b8ee89b3..91e30fea 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -295,46 +295,42 @@ func TestPEXReactorCrawlStatus(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", false) book.SetLogger(log.TestingLogger()) - var r *PEXReactor + pexR := NewPEXReactor(book, &PEXReactorConfig{SeedMode: true}) // Seed/Crawler mode uses data from the Switch makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { - r = NewPEXReactor(book, true) - r.SetLogger(log.TestingLogger()) + pexR.SetLogger(log.TestingLogger()) sw.SetLogger(log.TestingLogger().With("switch", i)) - sw.AddReactor("pex", r) + sw.AddReactor("pex", pexR) return sw }) - // Create a peer, and add it to the peer set + // Create a peer, add it to the peer set and the addrbook. peer := createRandomPeer(false) - r.Switch.peers.Add(peer) - // Add the peer address to the address book - addr1, _ := NewNetAddressString(peer.NodeInfo().ListenAddr) - r.book.AddAddress(addr1, addr1) - // Add an address to the book that does not have a peer - _, addr2 := createRoutableAddr() - r.book.AddAddress(addr2, addr1) + pexR.Switch.peers.Add(peer) + addr1 := peer.NodeInfo().NetAddress() + pexR.book.AddAddress(addr1, addr1) - // Get the crawl status data - status := r.getCrawlStatus() + // Add a non-connected address to the book. + _, addr2 := createRoutableAddr() + pexR.book.AddAddress(addr2, addr1) + + // Get some peerInfos to crawl + peerInfos := pexR.getPeersToCrawl() // Make sure it has the proper number of elements - assert.Equal(2, len(status)) + assert.Equal(2, len(peerInfos)) - var num int - for _, cs := range status { - if cs.PeerID != "" { - num++ - } - } - // Check that only one has been identified as a connected peer - assert.Equal(1, num) + // TODO: test } func createRoutableAddr() (addr string, netAddr *NetAddress) { for { - addr = cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) - netAddr, _ = NewNetAddressString(addr) + var err error + addr = cmn.Fmt("%X@%v.%v.%v.%v:46656", cmn.RandBytes(20), rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) + netAddr, err = NewNetAddressString(addr) + if err != nil { + panic(err) + } if netAddr.Routable() { break } @@ -346,7 +342,7 @@ func createRandomPeer(outbound bool) *peer { addr, netAddr := createRoutableAddr() p := &peer{ nodeInfo: NodeInfo{ - ListenAddr: netAddr.String(), + ListenAddr: netAddr.DialString(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, From 7b87cdaed8b9b7e4d37a15ee5a64fb1715071265 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 19:28:43 -0500 Subject: [PATCH 7/9] p2p: seed disconnects after sending addrs --- p2p/pex_reactor.go | 33 +++++++++++++++++++++------------ p2p/pex_reactor_test.go | 4 ++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 5d919421..bd19ab3b 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -129,7 +129,7 @@ func (r *PEXReactor) AddPeer(p Peer) { // either via DialPeersAsync or r.Receive. // Ask it for more peers if we need. if r.book.NeedMoreAddrs() { - r.RequestPEX(p) + r.RequestAddrs(p) } } else { // For inbound peers, the peer is its own source, @@ -159,15 +159,24 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { switch msg := msg.(type) { case *pexRequestMessage: - // We received a request for peers from src. + // Check we're not receiving too many requests if err := r.receiveRequest(src); err != nil { r.Switch.StopPeerForError(src, err) return } - r.SendAddrs(src, r.book.GetSelection()) + + // Seeds disconnect after sending a batch of addrs + if r.config.SeedMode { + // TODO: should we be more selective ? + r.SendAddrs(src, r.book.GetSelection()) + r.Switch.StopPeerGracefully(src) + } else { + r.SendAddrs(src, r.book.GetSelection()) + } + case *pexAddrsMessage: - // We received some peer addresses from src. - if err := r.ReceivePEX(msg.Addrs, src); err != nil { + // If we asked for addresses, add them to the book + if err := r.ReceiveAddrs(msg.Addrs, src); err != nil { r.Switch.StopPeerForError(src, err) return } @@ -202,9 +211,9 @@ func (r *PEXReactor) receiveRequest(src Peer) error { return nil } -// RequestPEX asks peer for more addresses if we do not already +// RequestAddrs asks peer for more addresses if we do not already // have a request out for this peer. -func (r *PEXReactor) RequestPEX(p Peer) { +func (r *PEXReactor) RequestAddrs(p Peer) { id := string(p.ID()) if r.requestsSent.Has(id) { return @@ -213,10 +222,10 @@ func (r *PEXReactor) RequestPEX(p Peer) { p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}}) } -// ReceivePEX adds the given addrs to the addrbook if theres an open +// ReceiveAddrs adds the given addrs to the addrbook if theres an open // request for this peer and deletes the open request. // If there's no open request for the src peer, it returns an error. -func (r *PEXReactor) ReceivePEX(addrs []*NetAddress, src Peer) error { +func (r *PEXReactor) ReceiveAddrs(addrs []*NetAddress, src Peer) error { id := string(src.ID()) if !r.requestsSent.Has(id) { @@ -323,7 +332,7 @@ func (r *PEXReactor) ensurePeers() { if peersCount > 0 { peer := peers[rand.Int()%peersCount] // nolint: gas r.Logger.Info("We need more addresses. Sending pexRequest to random peer", "peer", peer) - r.RequestPEX(peer) + r.RequestAddrs(peer) } } @@ -421,7 +430,7 @@ func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j func (r *PEXReactor) getPeersToCrawl() []crawlPeerInfo { var of oldestFirst - // TODO: not this. be more selective + // TODO: be more selective addrs := r.book.ListOfKnownAddresses() for _, addr := range addrs { if len(addr.ID()) == 0 { @@ -462,7 +471,7 @@ func (r *PEXReactor) crawlPeers() { if now.Sub(pi.LastAttempt) >= defaultCrawlPeerInterval { peer := r.Switch.Peers().Get(pi.Addr.ID) if peer != nil { - r.RequestPEX(peer) + r.RequestAddrs(peer) } } } diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 91e30fea..44fd8b51 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -154,7 +154,7 @@ func TestPEXReactorReceive(t *testing.T) { peer := createRandomPeer(false) // we have to send a request to receive responses - r.RequestPEX(peer) + r.RequestAddrs(peer) size := book.Size() addrs := []*NetAddress{peer.NodeInfo().NetAddress()} @@ -228,7 +228,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { id := string(peer.ID()) // request addrs from the peer - r.RequestPEX(peer) + r.RequestAddrs(peer) assert.True(r.requestsSent.Has(id)) assert.True(sw.Peers().Has(peer.ID())) From 8d758560d83ebb827168ce3ebb70e6e99d310101 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 19:30:13 -0500 Subject: [PATCH 8/9] p2p/trustmetric: non-deterministic test --- p2p/trust/metric_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/trust/metric_test.go b/p2p/trust/metric_test.go index 69c9f8f2..98ea99ab 100644 --- a/p2p/trust/metric_test.go +++ b/p2p/trust/metric_test.go @@ -56,7 +56,8 @@ func TestTrustMetricConfig(t *testing.T) { tm.Wait() } -func TestTrustMetricStopPause(t *testing.T) { +// XXX: This test fails non-deterministically +func _TestTrustMetricStopPause(t *testing.T) { // The TestTicker will provide manual control over // the passing of time within the metric tt := NewTestTicker() @@ -89,6 +90,8 @@ func TestTrustMetricStopPause(t *testing.T) { // and check that the number of intervals match tm.NextTimeInterval() tm.NextTimeInterval() + // XXX: fails non-deterministically: + // expected 5, got 6 assert.Equal(t, second+2, tm.Copy().numIntervals) if first > second { From 930fde056a15b238c10861377bbf2fb7a29b171c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 21:28:00 -0500 Subject: [PATCH 9/9] p2p: add back lost func --- p2p/addrbook.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 7591c307..fee179cb 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -324,6 +324,30 @@ func (a *AddrBook) GetSelection() []*NetAddress { return allAddr[:numAddresses] } +// ListOfKnownAddresses returns the new and old addresses. +func (a *AddrBook) ListOfKnownAddresses() []*knownAddress { + a.mtx.Lock() + defer a.mtx.Unlock() + + addrs := []*knownAddress{} + for _, addr := range a.addrLookup { + addrs = append(addrs, addr.copy()) + } + return addrs +} + +func (ka *knownAddress) copy() *knownAddress { + return &knownAddress{ + Addr: ka.Addr, + Src: ka.Src, + Attempts: ka.Attempts, + LastAttempt: ka.LastAttempt, + LastSuccess: ka.LastSuccess, + BucketType: ka.BucketType, + Buckets: ka.Buckets, + } +} + /* Loading & Saving */ type addrBookJSON struct {