p2p: tighten up and test PeerManager (#6034)

This tightens up the `PeerManager` and related code, adds a ton of tests, and fixes a bunch of inconsistencies and bugs.
This commit is contained in:
Erik Grinaker
2021-02-03 06:15:23 +00:00
committed by GitHub
parent fd597dc726
commit 2aad26e2f1
21 changed files with 3066 additions and 1281 deletions
+1 -2
View File
@@ -106,10 +106,9 @@ func ParseNodeAddress(urlString string) (NodeAddress, error) {
Protocol: Protocol(strings.ToLower(url.Scheme)),
}
// Opaque URLs are expected to contain only a node ID, also used as path.
// Opaque URLs are expected to contain only a node ID.
if url.Opaque != "" {
address.NodeID = NodeID(url.Opaque)
address.Path = url.Opaque
return address, address.Validate()
}
+1 -1
View File
@@ -158,7 +158,7 @@ func TestParseNodeAddress(t *testing.T) {
},
{
"memory:" + user,
p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: user},
p2p.NodeAddress{Protocol: "memory", NodeID: id},
true,
},
+15
View File
@@ -27,6 +27,21 @@ func (e Envelope) Strip() Envelope {
return e
}
// PeerError is a peer error reported via the Error channel.
//
// FIXME: This currently just disconnects the peer, which is too simplistic.
// For example, some errors should be logged, some should cause disconnects,
// and some should ban the peer.
//
// FIXME: This should probably be replaced by a more general PeerBehavior
// concept that can mark good and bad behavior and contributes to peer scoring.
// It should possibly also allow reactors to request explicit actions, e.g.
// disconnection or banning, in addition to doing this based on aggregates.
type PeerError struct {
NodeID NodeID
Err error
}
// Channel is a bidirectional channel for Protobuf message exchange with peers.
// A Channel is safe for concurrent use by multiple goroutines.
type Channel struct {
-1157
View File
File diff suppressed because it is too large Load Diff
+1275
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -7
View File
@@ -30,7 +30,7 @@ type ReactorV2 struct {
peerManager *p2p.PeerManager
pexCh *p2p.Channel
peerUpdates *p2p.PeerUpdatesCh
peerUpdates *p2p.PeerUpdates
closeCh chan struct{}
}
@@ -39,7 +39,7 @@ func NewReactorV2(
logger log.Logger,
peerManager *p2p.PeerManager,
pexCh *p2p.Channel,
peerUpdates *p2p.PeerUpdatesCh,
peerUpdates *p2p.PeerUpdates,
) *ReactorV2 {
r := &ReactorV2{
peerManager: peerManager,
@@ -181,9 +181,8 @@ func (r *ReactorV2) processPexCh() {
if err := r.handleMessage(r.pexCh.ID(), envelope); err != nil {
r.Logger.Error("failed to process message", "ch_id", r.pexCh.ID(), "envelope", envelope, "err", err)
r.pexCh.Error() <- p2p.PeerError{
PeerID: envelope.From,
Err: err,
Severity: p2p.PeerErrorSeverityLow,
NodeID: envelope.From,
Err: err,
}
}
@@ -197,11 +196,11 @@ func (r *ReactorV2) processPexCh() {
// processPeerUpdate processes a PeerUpdate. For added peers, PeerStatusUp, we
// send a request for addresses.
func (r *ReactorV2) processPeerUpdate(peerUpdate p2p.PeerUpdate) {
r.Logger.Debug("received peer update", "peer", peerUpdate.PeerID, "status", peerUpdate.Status)
r.Logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status)
if peerUpdate.Status == p2p.PeerStatusUp {
r.pexCh.Out() <- p2p.Envelope{
To: peerUpdate.PeerID,
To: peerUpdate.NodeID,
Message: &protop2p.PexRequest{},
}
}
+19 -13
View File
@@ -255,13 +255,10 @@ func (r *Router) routeChannel(channel *Channel) {
if !ok {
return
}
// FIXME: We just disconnect the peer for now
r.logger.Error("peer error, disconnecting", "peer", peerError.PeerID, "err", peerError.Err)
r.peerMtx.RLock()
peerQueue, ok := r.peerQueues[peerError.PeerID]
r.peerMtx.RUnlock()
if ok {
peerQueue.close()
// FIXME: We just evict the peer for now.
r.logger.Error("peer error, evicting", "peer", peerError.NodeID, "err", peerError.Err)
if err := r.peerManager.Errored(peerError.NodeID, peerError.Err); err != nil {
r.logger.Error("failed to report peer error", "peer", peerError.NodeID, "err", err)
}
case <-channel.Done():
@@ -338,7 +335,6 @@ func (r *Router) acceptPeers(transport Transport) {
r.peerMtx.Lock()
r.peerQueues[peerInfo.NodeID] = queue
r.peerMtx.Unlock()
r.peerManager.Ready(peerInfo.NodeID)
defer func() {
r.peerMtx.Lock()
@@ -350,6 +346,11 @@ func (r *Router) acceptPeers(transport Transport) {
}
}()
if err := r.peerManager.Ready(peerInfo.NodeID); err != nil {
r.logger.Error("failed to mark peer as ready", "peer", peerInfo.NodeID, "err", err)
return
}
r.routePeer(peerInfo.NodeID, conn, queue)
}()
}
@@ -359,7 +360,7 @@ func (r *Router) acceptPeers(transport Transport) {
func (r *Router) dialPeers() {
ctx := r.stopCtx()
for {
peerID, address, err := r.peerManager.DialNext(ctx)
address, err := r.peerManager.DialNext(ctx)
switch err {
case nil:
case context.Canceled:
@@ -371,12 +372,13 @@ func (r *Router) dialPeers() {
}
go func() {
peerID := address.NodeID
conn, err := r.dialPeer(ctx, address)
if errors.Is(err, context.Canceled) {
return
} else if err != nil {
r.logger.Error("failed to dial peer", "peer", peerID, "err", err)
if err = r.peerManager.DialFailed(peerID, address); err != nil {
if err = r.peerManager.DialFailed(address); err != nil {
r.logger.Error("failed to report dial failure", "peer", peerID, "err", err)
}
return
@@ -388,13 +390,13 @@ func (r *Router) dialPeers() {
return
} else if err != nil {
r.logger.Error("failed to handshake with peer", "peer", peerID, "err", err)
if err = r.peerManager.DialFailed(peerID, address); err != nil {
if err = r.peerManager.DialFailed(address); err != nil {
r.logger.Error("failed to report dial failure", "peer", peerID, "err", err)
}
return
}
if err = r.peerManager.Dialed(peerID, address); err != nil {
if err = r.peerManager.Dialed(address); err != nil {
r.logger.Error("failed to dial peer", "peer", peerID, "err", err)
return
}
@@ -403,7 +405,6 @@ func (r *Router) dialPeers() {
r.peerMtx.Lock()
r.peerQueues[peerID] = queue
r.peerMtx.Unlock()
r.peerManager.Ready(peerID)
defer func() {
r.peerMtx.Lock()
@@ -415,6 +416,11 @@ func (r *Router) dialPeers() {
}
}()
if err := r.peerManager.Ready(peerID); err != nil {
r.logger.Error("failed to mark peer as ready", "peer", peerID, "err", err)
return
}
r.routePeer(peerID, conn, queue)
}()
}
+6 -7
View File
@@ -115,9 +115,9 @@ func TestRouter(t *testing.T) {
// Wait for peers to come online, and ping them as they do.
for i := 0; i < len(peers); i++ {
peerUpdate := <-peerUpdates.Updates()
peerID := peerUpdate.PeerID
peerID := peerUpdate.NodeID
require.Equal(t, p2p.PeerUpdate{
PeerID: peerID,
NodeID: peerID,
Status: p2p.PeerStatusUp,
}, peerUpdate)
@@ -140,13 +140,12 @@ func TestRouter(t *testing.T) {
// We then submit an error for a peer, and watch it get disconnected.
channel.Error() <- p2p.PeerError{
PeerID: peers[0].NodeID,
Err: errors.New("test error"),
Severity: p2p.PeerErrorSeverityCritical,
NodeID: peers[0].NodeID,
Err: errors.New("test error"),
}
peerUpdate := <-peerUpdates.Updates()
require.Equal(t, p2p.PeerUpdate{
PeerID: peers[0].NodeID,
NodeID: peers[0].NodeID,
Status: p2p.PeerStatusDown,
}, peerUpdate)
@@ -154,7 +153,7 @@ func TestRouter(t *testing.T) {
// for that to happen.
peerUpdate = <-peerUpdates.Updates()
require.Equal(t, p2p.PeerUpdate{
PeerID: peers[0].NodeID,
NodeID: peers[0].NodeID,
Status: p2p.PeerStatusUp,
}, peerUpdate)
}
+6 -6
View File
@@ -29,7 +29,7 @@ type (
BaseReactor
Name string
PeerUpdates *PeerUpdatesCh
PeerUpdates *PeerUpdates
Channels map[ChannelID]*ChannelShim
}
@@ -162,10 +162,10 @@ func (rs *ReactorShim) handlePeerErrors() {
for _, cs := range rs.Channels {
go func(cs *ChannelShim) {
for pErr := range cs.Channel.errCh {
if pErr.PeerID != "" {
peer := rs.Switch.peers.Get(pErr.PeerID)
if pErr.NodeID != "" {
peer := rs.Switch.peers.Get(pErr.NodeID)
if peer == nil {
rs.Logger.Error("failed to handle peer error; failed to find peer", "peer", pErr.PeerID)
rs.Logger.Error("failed to handle peer error; failed to find peer", "peer", pErr.NodeID)
continue
}
@@ -225,7 +225,7 @@ func (rs *ReactorShim) GetChannels() []*ChannelDescriptor {
// handle adding a peer.
func (rs *ReactorShim) AddPeer(peer Peer) {
select {
case rs.PeerUpdates.updatesCh <- PeerUpdate{PeerID: peer.ID(), Status: PeerStatusUp}:
case rs.PeerUpdates.updatesCh <- PeerUpdate{NodeID: peer.ID(), Status: PeerStatusUp}:
rs.Logger.Debug("sent peer update", "reactor", rs.Name, "peer", peer.ID(), "status", PeerStatusUp)
case <-rs.PeerUpdates.Done():
@@ -244,7 +244,7 @@ func (rs *ReactorShim) AddPeer(peer Peer) {
// handle removing a peer.
func (rs *ReactorShim) RemovePeer(peer Peer, reason interface{}) {
select {
case rs.PeerUpdates.updatesCh <- PeerUpdate{PeerID: peer.ID(), Status: PeerStatusDown}:
case rs.PeerUpdates.updatesCh <- PeerUpdate{NodeID: peer.ID(), Status: PeerStatusDown}:
rs.Logger.Debug(
"sent peer update",
"reactor", rs.Name,
+2 -2
View File
@@ -123,7 +123,7 @@ func TestReactorShim_AddPeer(t *testing.T) {
rts.shim.AddPeer(peerA)
wg.Wait()
require.Equal(t, peerIDA, peerUpdate.PeerID)
require.Equal(t, peerIDA, peerUpdate.NodeID)
require.Equal(t, p2p.PeerStatusUp, peerUpdate.Status)
}
@@ -143,7 +143,7 @@ func TestReactorShim_RemovePeer(t *testing.T) {
rts.shim.RemovePeer(peerA, "test reason")
wg.Wait()
require.Equal(t, peerIDA, peerUpdate.PeerID)
require.Equal(t, peerIDA, peerUpdate.NodeID)
require.Equal(t, p2p.PeerStatusDown, peerUpdate.Status)
}