ADR 039: Peer Behaviour Interface

Changelog

  • 07-03-2019: Initial draft
  • 14-03-2019: Updates from feedback

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 ErrorBehaviourPeer to provide concrete reasons for stopping peers. Introduce GoodBehaviourPeer to provide concrete ways in which a peer contributes.

Implementation Changes

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

  1. type PeerBehaviour interface {
  2. Behaved(peer Peer, reason GoodBehaviourPeer)
  3. Errored(peer Peer, reason ErrorBehaviourPeer)
  4. }

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

We introduce a concrete error type ErrorBehaviourPeer:

  1. type ErrorBehaviourPeer int
  2. const (
  3. ErrorBehaviourUnknown = iota
  4. ErrorBehaviourBadMessage
  5. ErrorBehaviourMessageOutofOrder
  6. ...
  7. )

To provide additional information on the ways a peer contributed, we introduce the GoodBehaviourPeer type.

  1. type GoodBehaviourPeer int
  2. const (
  3. GoodBehaviourVote = iota
  4. GoodBehaviourBlockPart
  5. ...
  6. )

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

  1. type SwitchedPeerBehaviour struct {
  2. sw *Switch
  3. }
  4. func (spb *SwitchedPeerBehaviour) Errored(peer Peer, reason ErrorBehaviourPeer) {
  5. spb.sw.StopPeerForError(peer, reason)
  6. }
  7. func (spb *SwitchedPeerBehaviour) Behaved(peer Peer, reason GoodBehaviourPeer) {
  8. spb.sw.MarkPeerAsGood(peer)
  9. }
  10. func NewSwitchedPeerBehaviour(sw *Switch) *SwitchedPeerBehaviour {
  11. return &SwitchedPeerBehaviour{
  12. sw: sw,
  13. }
  14. }

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

  1. type ErrorBehaviours map[Peer][]ErrorBehaviourPeer
  2. type GoodBehaviours map[Peer][]GoodBehaviourPeer
  3. type StorePeerBehaviour struct {
  4. eb ErrorBehaviours
  5. gb GoodBehaviours
  6. }
  7. func NewStorePeerBehaviour() *StorePeerBehaviour{
  8. return &StorePeerBehaviour{
  9. eb: make(ErrorBehaviours),
  10. gb: make(GoodBehaviours),
  11. }
  12. }
  13. func (spb StorePeerBehaviour) Errored(peer Peer, reason ErrorBehaviourPeer) {
  14. if _, ok := spb.eb[peer]; !ok {
  15. spb.eb[peer] = []ErrorBehaviours{reason}
  16. } else {
  17. spb.eb[peer] = append(spb.eb[peer], reason)
  18. }
  19. }
  20. func (mpb *StorePeerBehaviour) GetErrored() ErrorBehaviours {
  21. return mpb.eb
  22. }
  23. func (spb StorePeerBehaviour) Behaved(peer Peer, reason GoodBehaviourPeer) {
  24. if _, ok := spb.gb[peer]; !ok {
  25. spb.gb[peer] = []GoodBehaviourPeer{reason}
  26. } else {
  27. spb.gb[peer] = append(spb.gb[peer], reason)
  28. }
  29. }
  30. func (spb *StorePeerBehaviour) GetBehaved() GoodBehaviours {
  31. return spb.gb
  32. }

Status

Accepted

Consequences

Positive

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

Negative

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

Neutral

References

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