Skip to content

Commit 23973c9

Browse files
committed
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)
1 parent b07124f commit 23973c9

3 files changed

Lines changed: 747 additions & 43 deletions

File tree

p2p/protocols/protocol.go

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
/*
2+
Package protocols is an extension to p2p. It offers a user friendly simple way to define
3+
devp2p subprotocols by abstracting away code standardly shared by protocols.
4+
The subprotocol architecture is inspired by the node package. Similar to a node
5+
the standard protocol (class) registers service contructors that are instantiated as service
6+
instances on the protocol isntance that is launched on a p2p peer connection.
7+
8+
By mounting various protocol modules protocols can encapsulate vertical slices of business logic
9+
without duplicating code related to protocol communication.
10+
Standard protocol supports:
11+
12+
* mounting services instantiated with the remote peer when a protocol instance is launched on a newly
13+
established peer connection
14+
* registering module-specific handshakes and offers validation and renegotiation of handshakes
15+
* registering multiple handlers for incoming messages
16+
* automate assigments of code indexes to messages
17+
* automate RLP decoding/encoding based on reflecting
18+
* provide the forever loop to read incoming messages
19+
* standardise error handling related to communication
20+
* enables access to sister services of the same peer connection analogous to node.Service
21+
* TODO: automatic generation of wire protocol specification for peers
22+
* peerPool abstracting out peer management by defining a peerPool that is called to register/unregister
23+
peers as they connect and drop (ideally the peerPool also implements the peerPool interface that the
24+
p2p server needs to suggest peers to connect to in server-as-initiator mode of operation
25+
see https://github.com/ethereum/go-ethereum/issues/2254 for the peer management/connectivity related
26+
aspect
27+
28+
*/
29+
30+
package protocols
31+
32+
import (
33+
"fmt"
34+
"reflect"
35+
36+
"github.com/ethereum/go-ethereum/logger"
37+
"github.com/ethereum/go-ethereum/logger/glog"
38+
"github.com/ethereum/go-ethereum/p2p"
39+
)
40+
41+
// error codes used by this protocol scheme
42+
const (
43+
ErrMsgTooLong = iota
44+
ErrDecode
45+
ErrWrite
46+
ErrInvalidMsgCode
47+
ErrInvalidMsgType
48+
ErrLocalHandshake
49+
ErrRemoteHandshake
50+
ErrHandler
51+
)
52+
53+
// error description strings associated with the codes
54+
var errorToString = map[int]string{
55+
ErrMsgTooLong: "Message too long",
56+
ErrDecode: "Invalid message (RLP error)",
57+
ErrWrite: "Error sending message",
58+
ErrInvalidMsgCode: "Invalid message code",
59+
ErrInvalidMsgType: "Invalid message type",
60+
ErrLocalHandshake: "Local handshake error",
61+
ErrRemoteHandshake: "Remote handshake error",
62+
ErrHandler: "Message handler error",
63+
}
64+
65+
/*
66+
Error implements the standard go error interface.
67+
Use:
68+
69+
errorf(code, format, params ...interface{})
70+
71+
Prints as:
72+
73+
<description>: <details>
74+
75+
where description is given by code in errorToString
76+
and details is fmt.Sprintf(format, params...)
77+
78+
exported field Code can be checked
79+
*/
80+
type Error struct {
81+
Code int
82+
message string
83+
format string
84+
params []interface{}
85+
}
86+
87+
func (self Error) Error() (message string) {
88+
if len(message) == 0 {
89+
name, ok := errorToString[self.Code]
90+
if !ok {
91+
panic("invalid message code")
92+
}
93+
self.message = name
94+
if self.format != "" {
95+
self.message += ": " + fmt.Sprintf(self.format, self.params...)
96+
}
97+
}
98+
return self.message
99+
}
100+
101+
func errorf(code int, format string, params ...interface{}) *Error {
102+
self := &Error{
103+
Code: code,
104+
format: format,
105+
params: params,
106+
}
107+
108+
return self
109+
}
110+
111+
// implements the code table spec
112+
// listing the message codes and types etc
113+
// and further metadata about the protocol
114+
type CodeMap struct {
115+
Name string // name of the protocol
116+
Version uint // version
117+
MaxMsgSize int // max length of message payload size
118+
codes []reflect.Type // index of codes to msg types - to create zero values
119+
messages map[reflect.Type]uint // index of types to codes, for sending by type
120+
}
121+
122+
func NewCodeMap(name string, version uint, maxMsgSize int, msgs ...interface{}) *CodeMap {
123+
self := &CodeMap{
124+
Name: name,
125+
Version: version,
126+
MaxMsgSize: maxMsgSize,
127+
messages: make(map[reflect.Type]uint),
128+
}
129+
self.Register(msgs...)
130+
return self
131+
}
132+
133+
func (self *CodeMap) Length() uint64 {
134+
return uint64(len(self.codes))
135+
}
136+
137+
func (self *CodeMap) Register(msgs ...interface{}) {
138+
code := uint(len(self.codes))
139+
for _, msg := range msgs {
140+
typ := reflect.TypeOf(msg)
141+
_, found := self.messages[typ]
142+
if found {
143+
// ignore duplicates
144+
continue
145+
}
146+
// next code assigned to message type typ
147+
self.messages[typ] = code
148+
self.codes = append(self.codes, typ)
149+
code++
150+
}
151+
}
152+
153+
// A Peer represents a remote peer or protocol instance that is running on a peer connection with
154+
// a remote peer
155+
type Peer struct {
156+
ct *CodeMap // CodeMap for the protocol
157+
*p2p.Peer // the p2p.Peer object representing the remote
158+
rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from
159+
handlers map[reflect.Type][]func(interface{}) error // message type -> message handler callback(s) map
160+
disconnect func() // Disconnect function set differently for testing
161+
// lastActive time.Time // tracking last active state
162+
}
163+
164+
// NewPeer returns a new peer
165+
// this constructor is called by the p2p.Protocol#Run function
166+
// the first two arguments are comming the arguments passed to p2p.Protocol.Run function
167+
// the third argument is the CodeMap describing the protocol messages and options
168+
func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap) *Peer {
169+
return newPeer(p, rw, ct, func() {
170+
p.Disconnect(p2p.DiscSubprotocolError)
171+
})
172+
}
173+
174+
// the disconnect function needs to be set differently for testing
175+
func NewTestPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap) *Peer {
176+
return newPeer(p, rw, ct, func() {
177+
rw.(*p2p.MsgPipeRW).Close()
178+
})
179+
}
180+
181+
func newPeer(p *p2p.Peer, rw p2p.MsgReadWriter, ct *CodeMap, disconn func()) *Peer {
182+
return &Peer{
183+
ct: ct,
184+
Peer: p,
185+
rw: rw,
186+
handlers: make(map[reflect.Type][]func(interface{}) error),
187+
disconnect: disconn,
188+
}
189+
}
190+
191+
// Register is called on the peer typically within the constructor of service instances running on peer connections
192+
// These constructors are called by the p2p.Protocol#Run function
193+
// It ties handler callbackss for specific message types
194+
// A message type can have several handlers registered by the same or different protocol services
195+
// Register is meant to be called once, deregistering is not currently supported therefore
196+
// handlers are assumed to be static across handshake renegotiations
197+
// i.e., a service instance either handles a message or not (irrespective of the handshake)
198+
// it panics if the message type is not defined in the CodeMap
199+
func (self *Peer) Register(msg interface{}, handler func(interface{}) error) uint {
200+
typ := reflect.TypeOf(msg)
201+
code, found := self.ct.messages[typ]
202+
if !found {
203+
panic(fmt.Sprintf("message type '%v' unknown ", typ))
204+
}
205+
glog.V(logger.Debug).Infof("registered handle for %v %v", msg, typ)
206+
self.handlers[typ] = append(self.handlers[typ], handler)
207+
return code
208+
}
209+
210+
// Run starts the forever loop that handles incoming messages
211+
// called within the p2p.Protocol#Run function
212+
func (self *Peer) Run() error {
213+
var err error
214+
for {
215+
_, err = self.handleIncoming()
216+
if err != nil {
217+
return err
218+
}
219+
}
220+
}
221+
222+
// Drop disconnects a peer.
223+
// falls back to self.disconnect which is set as p2p.Peer#Disconnect except
224+
// for test peers where it calls p2p.MsgPipe#Close so that the readloop can terminate
225+
// TODO: may need to implement protocol drop only? don't want to kick off the peer
226+
// if they are useful for other protocols
227+
// overwrite Disconnect for testing, so that protocol readloop quits
228+
func (self *Peer) Drop() {
229+
self.disconnect()
230+
}
231+
232+
// Send takes a message, encodes it in RLP, finds the right message code and sends the
233+
// message off to the peer
234+
// this low level call will be wrapped by libraries providing routed or broadcast sends
235+
// but often just used to forward and push messages to directly connected peers
236+
func (self *Peer) Send(msg interface{}) error {
237+
typ := reflect.TypeOf(msg)
238+
code, found := self.ct.messages[typ]
239+
if !found {
240+
return errorf(ErrInvalidMsgType, "%v", typ)
241+
}
242+
glog.V(logger.Debug).Infof("=> %v %v (%d)", msg, typ, code)
243+
err := p2p.Send(self.rw, uint64(code), msg)
244+
if err != nil {
245+
self.Drop()
246+
return errorf(ErrWrite, "(msg code: %v): %v", code, err)
247+
}
248+
return nil
249+
}
250+
251+
// handleIncoming(code)
252+
// is called each cycle of the main forever loop that handles and dispatches incoming messages
253+
// if this returns an error the loop returns and the peer is disconnected with the error
254+
// checks message size, out-of-range message codes, handles decoding with reflection,
255+
// call handlers as callback onside
256+
func (self *Peer) handleIncoming() (interface{}, error) {
257+
msg, err := self.rw.ReadMsg()
258+
glog.V(logger.Debug).Infof("<= %v", msg)
259+
if err != nil {
260+
return nil, err
261+
}
262+
// make sure that the payload has been fully consumed
263+
defer msg.Discard()
264+
265+
if msg.Size > uint32(self.ct.MaxMsgSize) {
266+
return nil, errorf(ErrMsgTooLong, "%v > %v", msg.Size, self.ct.MaxMsgSize)
267+
}
268+
269+
// check if the message code is correct
270+
maxMsgCode := uint(len(self.ct.messages))
271+
if msg.Code >= uint64(maxMsgCode) {
272+
return nil, errorf(ErrInvalidMsgCode, "%v (>=%v)", msg.Code, maxMsgCode)
273+
}
274+
275+
// it is safe to be unsafe here
276+
typ := self.ct.codes[msg.Code]
277+
val := reflect.New(typ)
278+
req := val.Elem()
279+
req.Set(reflect.Zero(typ))
280+
if err := msg.Decode(val.Interface()); err != nil {
281+
return nil, errorf(ErrDecode, "<= %v: %v", msg, err)
282+
}
283+
glog.V(logger.Debug).Infof("<= %v %v (%d)", req, typ, msg.Code)
284+
285+
// call the registered handler callbacks
286+
// a registered callback take the decoded message as argument as an interface
287+
// which the handler is supposed to cast to the appropriate type
288+
// it is entirely safe not to check the cast in the handler since the handler is
289+
// chosen based on the proper type in the first place
290+
for _, f := range self.handlers[typ] {
291+
glog.V(6).Infof("handler for %v", typ)
292+
err = f(req.Interface())
293+
if err != nil {
294+
return nil, errorf(ErrHandler, "(msg code %v): %v", msg.Code, err)
295+
}
296+
}
297+
return req.Interface(), nil
298+
}
299+
300+
// Handshake initiates a handshake on the peer connection
301+
// * the argument is the local handshake to be sent to the remote peer
302+
// * expects a remote handshake back of the same type
303+
// returns the remote hs and an error
304+
func (self *Peer) Handshake(hs interface{}) (interface{}, error) {
305+
typ := reflect.TypeOf(hs)
306+
_, found := self.ct.messages[typ]
307+
if !found {
308+
return nil, errorf(ErrLocalHandshake, "unknown handshake message type: %v", typ)
309+
}
310+
err := self.Send(hs)
311+
if err != nil {
312+
return nil, errorf(ErrLocalHandshake, "cannot send: %v", err)
313+
}
314+
// receiving and validating remote handshake, expect code
315+
rhs, err := self.handleIncoming()
316+
if err != nil {
317+
return nil, errorf(ErrRemoteHandshake, "'%v': %v", self.ct.Name, err)
318+
}
319+
return rhs, nil
320+
}

0 commit comments

Comments
 (0)