mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
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)
320 lines
11 KiB
Go
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
|
|
}
|