go-ethereum/p2p/protocols/protocol.go
zelig 23973c994d p2p/protocols: devp2p subprotocol abstraction
Package protocols is an extension to p2p. It offers a user friendly simple way to define
devp2p subprotocols by abstracting away code that implementations would typically use.

The package provides a standard protocol peer object of type protocols.Peer initialised from
a p2p.Peer, a p2p.MsgReadWriter (the arguments passed to p2p.Protocol#Run
and a protocols.CodeMap.

Allowing the p2p.Protocol#Run function to construct this peer allows passing it to arbitrary
service instances sitting on peer connections. These service instances can encapsulate vertical slices
of business logic without duplicating code related to protocol communication.

*    registering multiple handler callbacks for incoming messages
*    automate assigments of code indexes to messages
*    automate RLP decoding/encoding based on reflecting
*    provide the forever loop to read incoming messages
*    standardise error handling related to communication
*    TODO: automatic generation of wire protocol specification for peers

The test demonstrates the use of protocols/testing package for testing protocols

see the possibly obsolete #2254 for the peer management/connectivity related aspect)
2016-10-02 19:53:33 +02:00

320 lines
11 KiB
Go

/*
Package protocols is an extension to p2p. It offers a user friendly simple way to define
devp2p subprotocols by abstracting away code standardly shared by protocols.
The subprotocol architecture is inspired by the node package. Similar to a node
the standard protocol (class) registers service contructors that are instantiated as service
instances on the protocol isntance that is launched on a p2p peer connection.
By mounting various protocol modules protocols can encapsulate vertical slices of business logic
without duplicating code related to protocol communication.
Standard protocol supports:
* mounting services instantiated with the remote peer when a protocol instance is launched on a newly
established peer connection
* registering module-specific handshakes and offers validation and renegotiation of handshakes
* registering multiple handlers for incoming messages
* automate assigments of code indexes to messages
* automate RLP decoding/encoding based on reflecting
* provide the forever loop to read incoming messages
* standardise error handling related to communication
* enables access to sister services of the same peer connection analogous to node.Service
* TODO: automatic generation of wire protocol specification for peers
* peerPool abstracting out peer management by defining a peerPool that is called to register/unregister
peers as they connect and drop (ideally the peerPool also implements the peerPool interface that the
p2p server needs to suggest peers to connect to in server-as-initiator mode of operation
see https://github.com/ethereum/go-ethereum/issues/2254 for the peer management/connectivity related
aspect
*/
package protocols
import (
"fmt"
"reflect"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
)
// error codes used by this protocol scheme
const (
ErrMsgTooLong = iota
ErrDecode
ErrWrite
ErrInvalidMsgCode
ErrInvalidMsgType
ErrLocalHandshake
ErrRemoteHandshake
ErrHandler
)
// error description strings associated with the codes
var errorToString = map[int]string{
ErrMsgTooLong: "Message too long",
ErrDecode: "Invalid message (RLP error)",
ErrWrite: "Error sending message",
ErrInvalidMsgCode: "Invalid message code",
ErrInvalidMsgType: "Invalid message type",
ErrLocalHandshake: "Local handshake error",
ErrRemoteHandshake: "Remote handshake error",
ErrHandler: "Message handler error",
}
/*
Error implements the standard go error interface.
Use:
errorf(code, format, params ...interface{})
Prints as:
<description>: <details>
where description is given by code in errorToString
and details is fmt.Sprintf(format, params...)
exported field Code can be checked
*/
type Error struct {
Code int
message string
format string
params []interface{}
}
func (self Error) Error() (message string) {
if len(message) == 0 {
name, ok := errorToString[self.Code]
if !ok {
panic("invalid message code")
}
self.message = name
if self.format != "" {
self.message += ": " + fmt.Sprintf(self.format, self.params...)
}
}
return self.message
}
func errorf(code int, format string, params ...interface{}) *Error {
self := &Error{
Code: code,
format: format,
params: params,
}
return self
}
// implements the code table spec
// listing the message codes and types etc
// and further metadata about the protocol
type CodeMap struct {
Name string // name of the protocol
Version uint // version
MaxMsgSize int // max length of message payload size
codes []reflect.Type // index of codes to msg types - to create zero values
messages map[reflect.Type]uint // index of types to codes, for sending by type
}
func NewCodeMap(name string, version uint, maxMsgSize int, msgs ...interface{}) *CodeMap {
self := &CodeMap{
Name: name,
Version: version,
MaxMsgSize: maxMsgSize,
messages: make(map[reflect.Type]uint),
}
self.Register(msgs...)
return self
}
func (self *CodeMap) Length() uint64 {
return uint64(len(self.codes))
}
func (self *CodeMap) Register(msgs ...interface{}) {
code := uint(len(self.codes))
for _, msg := range msgs {
typ := reflect.TypeOf(msg)
_, found := self.messages[typ]
if found {
// ignore duplicates
continue
}
// next code assigned to message type typ
self.messages[typ] = code
self.codes = append(self.codes, typ)
code++
}
}
// A Peer represents a remote peer or protocol instance that is running on a peer connection with
// a remote peer
type Peer struct {
ct *CodeMap // CodeMap for the protocol
*p2p.Peer // the p2p.Peer object representing the remote
rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from
handlers map[reflect.Type][]func(interface{}) error // message type -> message handler callback(s) map
disconnect func() // Disconnect function set differently for testing
// lastActive time.Time // tracking last active state
}
// NewPeer returns a new peer
// this constructor is called by the p2p.Protocol#Run function
// the first two arguments are comming the arguments passed to p2p.Protocol.Run function
// the third argument is the CodeMap describing the protocol messages and options
func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap) *Peer {
return newPeer(p, rw, ct, func() {
p.Disconnect(p2p.DiscSubprotocolError)
})
}
// the disconnect function needs to be set differently for testing
func NewTestPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap) *Peer {
return newPeer(p, rw, ct, func() {
rw.(*p2p.MsgPipeRW).Close()
})
}
func newPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap, disconn func()) *Peer {
return &Peer{
ct: ct,
Peer: p,
rw: rw,
handlers: make(map[reflect.Type][]func(interface{}) error),
disconnect: disconn,
}
}
// Register is called on the peer typically within the constructor of service instances running on peer connections
// These constructors are called by the p2p.Protocol#Run function
// It ties handler callbackss for specific message types
// A message type can have several handlers registered by the same or different protocol services
// Register is meant to be called once, deregistering is not currently supported therefore
// handlers are assumed to be static across handshake renegotiations
// i.e., a service instance either handles a message or not (irrespective of the handshake)
// it panics if the message type is not defined in the CodeMap
func (self *Peer) Register(msg interface{}, handler func(interface{}) error) uint {
typ := reflect.TypeOf(msg)
code, found := self.ct.messages[typ]
if !found {
panic(fmt.Sprintf("message type '%v' unknown ", typ))
}
glog.V(logger.Debug).Infof("registered handle for %v %v", msg, typ)
self.handlers[typ] = append(self.handlers[typ], handler)
return code
}
// Run starts the forever loop that handles incoming messages
// called within the p2p.Protocol#Run function
func (self *Peer) Run() error {
var err error
for {
_, err = self.handleIncoming()
if err != nil {
return err
}
}
}
// Drop disconnects a peer.
// falls back to self.disconnect which is set as p2p.Peer#Disconnect except
// for test peers where it calls p2p.MsgPipe#Close so that the readloop can terminate
// TODO: may need to implement protocol drop only? don't want to kick off the peer
// if they are useful for other protocols
// overwrite Disconnect for testing, so that protocol readloop quits
func (self *Peer) Drop() {
self.disconnect()
}
// Send takes a message, encodes it in RLP, finds the right message code and sends the
// message off to the peer
// this low level call will be wrapped by libraries providing routed or broadcast sends
// but often just used to forward and push messages to directly connected peers
func (self *Peer) Send(msg interface{}) error {
typ := reflect.TypeOf(msg)
code, found := self.ct.messages[typ]
if !found {
return errorf(ErrInvalidMsgType, "%v", typ)
}
glog.V(logger.Debug).Infof("=> %v %v (%d)", msg, typ, code)
err := p2p.Send(self.rw, uint64(code), msg)
if err != nil {
self.Drop()
return errorf(ErrWrite, "(msg code: %v): %v", code, err)
}
return nil
}
// handleIncoming(code)
// is called each cycle of the main forever loop that handles and dispatches incoming messages
// if this returns an error the loop returns and the peer is disconnected with the error
// checks message size, out-of-range message codes, handles decoding with reflection,
// call handlers as callback onside
func (self *Peer) handleIncoming() (interface{}, error) {
msg, err := self.rw.ReadMsg()
glog.V(logger.Debug).Infof("<= %v", msg)
if err != nil {
return nil, err
}
// make sure that the payload has been fully consumed
defer msg.Discard()
if msg.Size > uint32(self.ct.MaxMsgSize) {
return nil, errorf(ErrMsgTooLong, "%v > %v", msg.Size, self.ct.MaxMsgSize)
}
// check if the message code is correct
maxMsgCode := uint(len(self.ct.messages))
if msg.Code >= uint64(maxMsgCode) {
return nil, errorf(ErrInvalidMsgCode, "%v (>=%v)", msg.Code, maxMsgCode)
}
// it is safe to be unsafe here
typ := self.ct.codes[msg.Code]
val := reflect.New(typ)
req := val.Elem()
req.Set(reflect.Zero(typ))
if err := msg.Decode(val.Interface()); err != nil {
return nil, errorf(ErrDecode, "<= %v: %v", msg, err)
}
glog.V(logger.Debug).Infof("<= %v %v (%d)", req, typ, msg.Code)
// call the registered handler callbacks
// a registered callback take the decoded message as argument as an interface
// which the handler is supposed to cast to the appropriate type
// it is entirely safe not to check the cast in the handler since the handler is
// chosen based on the proper type in the first place
for _, f := range self.handlers[typ] {
glog.V(6).Infof("handler for %v", typ)
err = f(req.Interface())
if err != nil {
return nil, errorf(ErrHandler, "(msg code %v): %v", msg.Code, err)
}
}
return req.Interface(), nil
}
// Handshake initiates a handshake on the peer connection
// * the argument is the local handshake to be sent to the remote peer
// * expects a remote handshake back of the same type
// returns the remote hs and an error
func (self *Peer) Handshake(hs interface{}) (interface{}, error) {
typ := reflect.TypeOf(hs)
_, found := self.ct.messages[typ]
if !found {
return nil, errorf(ErrLocalHandshake, "unknown handshake message type: %v", typ)
}
err := self.Send(hs)
if err != nil {
return nil, errorf(ErrLocalHandshake, "cannot send: %v", err)
}
// receiving and validating remote handshake, expect code
rhs, err := self.handleIncoming()
if err != nil {
return nil, errorf(ErrRemoteHandshake, "'%v': %v", self.ct.Name, err)
}
return rhs, nil
}