go-ethereum/p2p/protocols/protocol.go
Lewis Marshall 91c198778c p2p/protocols: Refactor
Signed-off-by: Lewis Marshall <lewis@lmars.net>
2017-05-16 20:57:31 -07:00

308 lines
9.1 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 (
"context"
"fmt"
"reflect"
"sync"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
)
// error codes used by this protocol scheme
const (
ErrMsgTooLong = iota
ErrDecode
ErrWrite
ErrInvalidMsgCode
ErrInvalidMsgType
ErrHandshake
ErrNoHandler
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",
ErrHandshake: "Handshake error",
ErrNoHandler: "No handler registered 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
}
// Spec is a protocol specification including its name and version as well as
// the types of messages which are exchanged
type Spec struct {
// Name is the name of the protocol, often a three-letter word
Name string
// Version is the version number of the protocol
Version uint
// MaxMsgSize is the maximum accepted length of the message payload
MaxMsgSize uint32
// Messages is a list of message types which this protocol uses, with
// each message type being sent with its array index as the code (so
// [&foo{}, &bar{}, &baz{}] would send foo, bar and baz with codes
// 0, 1 and 2 respectively)
Messages []interface{}
initOnce sync.Once
codes map[reflect.Type]uint64
types map[uint64]reflect.Type
}
func (s *Spec) init() {
s.initOnce.Do(func() {
s.codes = make(map[reflect.Type]uint64, len(s.Messages))
s.types = make(map[uint64]reflect.Type, len(s.Messages))
for i, msg := range s.Messages {
code := uint64(i)
typ := reflect.TypeOf(msg)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
s.codes[typ] = code
s.types[code] = typ
}
})
}
func (s *Spec) Length() uint64 {
return uint64(len(s.Messages))
}
func (s *Spec) GetCode(msg interface{}) (uint64, bool) {
s.init()
typ := reflect.TypeOf(msg)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
code, ok := s.codes[typ]
return code, ok
}
func (s *Spec) NewMsg(code uint64) (interface{}, bool) {
s.init()
typ, ok := s.types[code]
if !ok {
return nil, false
}
return reflect.New(typ).Interface(), true
}
// A Peer represents a remote peer or protocol instance that is running on a peer connection with
// a remote peer
type Peer struct {
*p2p.Peer // the p2p.Peer object representing the remote
rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from
spec *Spec
Errc chan error
wErrc chan error // write error channel
}
// 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, spec *Spec) *Peer {
return &Peer{
Peer: p,
rw: rw,
spec: spec,
Errc: make(chan error),
wErrc: make(chan error),
}
}
// Run starts the forever loop that handles incoming messages
// called within the p2p.Protocol#Run function
func (self *Peer) Run(handler func(msg interface{}) error) error {
go func() {
for {
if err := self.handleIncoming(handler); err != nil {
self.Errc <- err
return
}
}
}()
return <-self.Errc
}
// 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(err error) {
self.Errc <- err
}
// 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 {
code, found := self.spec.GetCode(msg)
if !found {
return errorf(ErrInvalidMsgType, "%v", code)
}
log.Trace(fmt.Sprintf("=> msg #%d TO %v : %v", code, self.ID(), msg))
return p2p.Send(self.rw, code, msg)
}
// 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(handle func(msg interface{}) error) error {
msg, err := self.rw.ReadMsg()
if err != nil {
return err
}
log.Trace(fmt.Sprintf("<= %v", msg))
// make sure that the payload has been fully consumed
defer msg.Discard()
if msg.Size > self.spec.MaxMsgSize {
return errorf(ErrMsgTooLong, "%v > %v", msg.Size, self.spec.MaxMsgSize)
}
val, ok := self.spec.NewMsg(msg.Code)
if !ok {
return errorf(ErrInvalidMsgCode, "%v", msg.Code)
}
if err := msg.Decode(val); err != nil {
return errorf(ErrDecode, "<= %v: %v", msg, err)
}
log.Trace(fmt.Sprintf("<= %v FROM %v %T %v", msg, self.ID(), val, val))
// 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
if err := handle(val); err != nil {
return errorf(ErrHandler, "(msg code %v): %v", msg.Code, err)
}
return 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(ctx context.Context, hs interface{}) (interface{}, error) {
if _, ok := self.spec.GetCode(hs); !ok {
return nil, errorf(ErrHandshake, "unknown handshake message type: %T", hs)
}
errc := make(chan error, 2)
go func() {
if err := self.Send(hs); err != nil {
errc <- errorf(ErrHandshake, "cannot send: %v", err)
}
}()
hsc := make(chan interface{})
go func() {
var rhs interface{}
err := self.handleIncoming(func(msg interface{}) error {
rhs = msg
return nil
})
if err != nil {
errc <- err
return
}
hsc <- rhs
}()
select {
case rhs := <-hsc:
return rhs, nil
case <-ctx.Done():
return nil, ctx.Err()
case err := <-errc:
return nil, err
}
}