You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3.7 KiB

ADR 037: Peer Behaviour Interface

Changelog

  • 07-03-2019: Initial draft

Context

The responsibility for signaling and acting upon peer behaviour lacks a single owning component and is heavily coupled with the network stack1. Reactors maintain a reference to the p2p.Switch which they use to call switch.StopPeerForError(...) when a peer misbehaves and switch.MarkAsGood(...) when a peer contributes in some meaningful way. While the switch handles StopPeerForError internally, the MarkAsGood method delegates to another component, p2p.AddrBook. This scheme of delegation across Switch obscures the responsibility for handling peer behaviour and ties up the reactors in a larger dependency graph when testing.

Decision

Introduce a PeerBehaviour interface and concrete implementations which provide methods for reactors to signal peer behaviour without direct coupling p2p.Switch. Introduce a ErrPeer to provide concrete reasons for stopping peers.

Implementation Changes

PeerBehaviour then becomes an interface for signaling peer errors as well as for marking peers as good.

XXX: It might be better to pass p2p.ID instead of the whole peer but as a first draft maintain the underlying implementation as much as possible.

type PeerBehaviour interface {
    Errored(peer Peer, reason ErrPeer)
    MarkPeerAsGood(peer Peer)
}

Instead of signaling peers to stop with arbitrary reasons: reason interface{}

We introduce a concrete error type ErrPeer:

type ErrPeer int

const (
    ErrPeerUnknown = iota
    ErrPeerBadMessage
    ErrPeerMessageOutofOrder
    ...
)

As a first iteration we provide a concrete implementation which wraps the switch:

type SwitchedPeerBehaviour struct {
    sw *Switch
}

func (spb *SwitchedPeerBehaviour) Errored(peer Peer, reason ErrPeer) {
    spb.sw.StopPeerForError(peer, reason)
}

func (spb *SwitchedPeerBehaviour) MarkPeerAsGood(peer Peer) {
    spb.sw.MarkPeerAsGood(peer)
}

func NewSwitchedPeerBehaviour(sw *Switch) *SwitchedPeerBehaviour {
    return &SwitchedPeerBehaviour{
        sw: sw,
    }
}

Reactors, which are often difficult to unit test2. could use an implementation which exposes the signals produced by the reactor in manufactured scenarios:

type PeerErrors map[Peer][]ErrPeer
type GoodPeers map[Peer]bool

type StorePeerBehaviour struct {
    pe PeerErrors
    gp GoodPeers
}

func NewStorePeerBehaviour() *StorePeerBehaviour{
    return &StorePeerBehaviour{
        pe: make(PeerErrors),
        gp: GoodPeers{},
    }
}

func (spb StorePeerBehaviour) Errored(peer Peer, reason ErrPeer) {
    if _, ok := spb.pe[peer]; !ok {
        spb.pe[peer] = []ErrPeer{reason}
    } else {
        spb.pe[peer] = append(spb.pe[peer], reason)
    }
}

func (mpb *StorePeerBehaviour) GetPeerErrors() PeerErrors {
    return mpb.pe
}

func (spb *StorePeerBehaviour) MarkPeerAsGood(peer Peer) {
    if _, ok := spb.gp[peer]; !ok {
        spb.gp[peer] = true
    }
}

func (spb *StorePeerBehaviour) GetGoodPeers() GoodPeers {
    return spb.gp
}

Status

Proposed

Consequences

Positive

* De-couple signaling from acting upon peer behaviour.
* Reduce the coupling of reactors and the Switch and the network
  stack
* The responsibility of managing peer behaviour can be migrated to
  a single component instead of split between the switch and the
  address book.

Negative

* The first iteration will simply wrap the Switch and introduce a
  level of indirection.

Neutral

References

  1. Issue #2067: P2P Refactor
  2. PR: #3506: ADR 036: Blockchain Reactor Refactor