2019-04-15 16:38:45 +02:00
# ADR 039: Peer Behaviour Interface
2019-04-12 12:32:00 +02:00
## Changelog
* 07-03-2019: Initial draft
2019-04-15 16:38:45 +02:00
* 14-03-2019: Updates from feedback
2019-04-12 12:32:00 +02:00
## Context
The responsibility for signaling and acting upon peer behaviour lacks a single
owning component and is heavily coupled with the network stack[<sup>1</sup> ](#references ). 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
2019-04-15 16:38:45 +02:00
coupling `p2p.Switch` . Introduce a ErrorBehaviourPeer to provide
concrete reasons for stopping peers. Introduce GoodBehaviourPeer to provide
concrete ways in which a peer contributes.
2019-04-12 12:32:00 +02:00
### Implementation Changes
PeerBehaviour then becomes an interface for signaling peer errors as well
as for marking peers as `good` .
```go
type PeerBehaviour interface {
2019-04-15 16:38:45 +02:00
Behaved(peer Peer, reason GoodBehaviourPeer)
Errored(peer Peer, reason ErrorBehaviourPeer)
2019-04-12 12:32:00 +02:00
}
```
Instead of signaling peers to stop with arbitrary reasons:
`reason interface{}`
2019-04-15 16:38:45 +02:00
We introduce a concrete error type ErrorBehaviourPeer:
2019-04-12 12:32:00 +02:00
```go
2019-04-15 16:38:45 +02:00
type ErrorBehaviourPeer int
2019-04-12 12:32:00 +02:00
const (
2019-04-15 16:38:45 +02:00
ErrorBehaviourUnknown = iota
ErrorBehaviourBadMessage
ErrorBehaviourMessageOutofOrder
...
)
```
To provide additional information on the ways a peer contributed, we introduce
the GoodBehaviourPeer type.
```go
type GoodBehaviourPeer int
const (
GoodBehaviourVote = iota
GoodBehaviourBlockPart
2019-04-12 12:32:00 +02:00
...
)
```
As a first iteration we provide a concrete implementation which wraps
the switch:
```go
type SwitchedPeerBehaviour struct {
sw *Switch
}
2019-04-15 16:38:45 +02:00
func (spb *SwitchedPeerBehaviour) Errored(peer Peer, reason ErrorBehaviourPeer) {
2019-04-12 12:32:00 +02:00
spb.sw.StopPeerForError(peer, reason)
}
2019-04-15 16:38:45 +02:00
func (spb *SwitchedPeerBehaviour) Behaved(peer Peer, reason GoodBehaviourPeer) {
2019-04-12 12:32:00 +02:00
spb.sw.MarkPeerAsGood(peer)
}
func NewSwitchedPeerBehaviour(sw *Switch) *SwitchedPeerBehaviour {
return & SwitchedPeerBehaviour{
sw: sw,
}
}
```
2019-04-15 16:38:45 +02:00
Reactors, which are often difficult to unit test[<sup>2</sup> ](#references ) could use an implementation which exposes the signals produced by the reactor in
2019-04-12 12:32:00 +02:00
manufactured scenarios:
```go
2019-04-15 16:38:45 +02:00
type ErrorBehaviours map[Peer][]ErrorBehaviourPeer
type GoodBehaviours map[Peer][]GoodBehaviourPeer
2019-04-12 12:32:00 +02:00
type StorePeerBehaviour struct {
2019-04-15 16:38:45 +02:00
eb ErrorBehaviours
gb GoodBehaviours
2019-04-12 12:32:00 +02:00
}
func NewStorePeerBehaviour() *StorePeerBehaviour{
return & StorePeerBehaviour{
2019-04-15 16:38:45 +02:00
eb: make(ErrorBehaviours),
gb: make(GoodBehaviours),
2019-04-12 12:32:00 +02:00
}
}
2019-04-15 16:38:45 +02:00
func (spb StorePeerBehaviour) Errored(peer Peer, reason ErrorBehaviourPeer) {
if _, ok := spb.eb[peer]; !ok {
spb.eb[peer] = []ErrorBehaviours{reason}
2019-04-12 12:32:00 +02:00
} else {
2019-04-15 16:38:45 +02:00
spb.eb[peer] = append(spb.eb[peer], reason)
2019-04-12 12:32:00 +02:00
}
}
2019-04-15 16:38:45 +02:00
func (mpb *StorePeerBehaviour) GetErrored() ErrorBehaviours {
return mpb.eb
2019-04-12 12:32:00 +02:00
}
2019-04-15 16:38:45 +02:00
func (spb StorePeerBehaviour) Behaved(peer Peer, reason GoodBehaviourPeer) {
if _, ok := spb.gb[peer]; !ok {
spb.gb[peer] = []GoodBehaviourPeer{reason}
} else {
spb.gb[peer] = append(spb.gb[peer], reason)
2019-04-12 12:32:00 +02:00
}
}
2019-04-15 16:38:45 +02:00
func (spb *StorePeerBehaviour) GetBehaved() GoodBehaviours {
return spb.gb
2019-04-12 12:32:00 +02:00
}
```
## Status
2019-04-15 16:38:45 +02:00
Accepted
2019-04-12 12:32:00 +02:00
## 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 ](https://github.com/tendermint/tendermint/issues/2067 ): P2P Refactor
2. PR: [#3506 ](https://github.com/tendermint/tendermint/pull/3506 ): ADR 036: Blockchain Reactor Refactor