mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
p2p/discv5: implement UDP TALK request in discv5
This commit is contained in:
parent
01d92531ee
commit
9907044bf5
4 changed files with 204 additions and 15 deletions
|
|
@ -22,12 +22,14 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
|
|
@ -64,12 +66,17 @@ type Network struct {
|
||||||
refreshResp chan (<-chan struct{}) // ...and get the channel to block on from this one
|
refreshResp chan (<-chan struct{}) // ...and get the channel to block on from this one
|
||||||
read chan ingressPacket // ingress packets arrive here
|
read chan ingressPacket // ingress packets arrive here
|
||||||
timeout chan timeoutEvent
|
timeout chan timeoutEvent
|
||||||
queryReq chan *findnodeQuery // lookups submit findnode queries on this channel
|
queryReq chan deferredQuery // lookups submit findnode queries on this channel
|
||||||
tableOpReq chan func()
|
tableOpReq chan func()
|
||||||
tableOpResp chan struct{}
|
tableOpResp chan struct{}
|
||||||
topicRegisterReq chan topicRegisterReq
|
topicRegisterReq chan topicRegisterReq
|
||||||
topicSearchReq chan topicSearchReq
|
topicSearchReq chan topicSearchReq
|
||||||
|
|
||||||
|
talkRequestSubLock sync.RWMutex
|
||||||
|
talkResponseSubLock sync.Mutex
|
||||||
|
talkRequestSubs map[string]TalkRequestHandler
|
||||||
|
talkResponseSubs map[string]TalkResponseHandler
|
||||||
|
|
||||||
// State of the main loop.
|
// State of the main loop.
|
||||||
tab *Table
|
tab *Table
|
||||||
topictab *topicTable
|
topictab *topicTable
|
||||||
|
|
@ -79,6 +86,21 @@ type Network struct {
|
||||||
timeoutTimers map[timeoutEvent]*time.Timer
|
timeoutTimers map[timeoutEvent]*time.Timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// TalkRequestHandler processes an incoming talkRequest. If ok is true then response is
|
||||||
|
// sent back, along with the recommended delay feedback. If there is no urgent need to
|
||||||
|
// communicate (for example, in case of regular polling) then the sender should wait the
|
||||||
|
// given amount of seconds before sending the next request. If delay recommendation is
|
||||||
|
// disobeyed too many times by a sender then the handler can stop responding until the
|
||||||
|
// last recommended delay has elapsed.
|
||||||
|
TalkRequestHandler func(id enode.ID, addr *net.UDPAddr, request interface{}) (response interface{}, delay uint, ok bool)
|
||||||
|
// TalkResponseHandler is registered by the sender of each talkRequest. If the request
|
||||||
|
// is canceled the handler is still called with a nil parameter. If too many responses
|
||||||
|
// from a peer are considered invalid by their handlers then the peer goes into
|
||||||
|
// contested state.
|
||||||
|
TalkResponseHandler func(response interface{}, delay uint) (valid bool)
|
||||||
|
)
|
||||||
|
|
||||||
// transport is implemented by the UDP transport.
|
// transport is implemented by the UDP transport.
|
||||||
// it is an interface so we can test without opening lots of UDP
|
// it is an interface so we can test without opening lots of UDP
|
||||||
// sockets and without generating a private key.
|
// sockets and without generating a private key.
|
||||||
|
|
@ -101,6 +123,14 @@ type findnodeQuery struct {
|
||||||
reply chan<- []*Node
|
reply chan<- []*Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type talkQuery struct {
|
||||||
|
remote *Node
|
||||||
|
talkID string
|
||||||
|
payload interface{}
|
||||||
|
key string
|
||||||
|
handler TalkResponseHandler
|
||||||
|
}
|
||||||
|
|
||||||
type topicRegisterReq struct {
|
type topicRegisterReq struct {
|
||||||
add bool
|
add bool
|
||||||
topic Topic
|
topic Topic
|
||||||
|
|
@ -151,10 +181,12 @@ func newNetwork(conn transport, ourPubkey ecdsa.PublicKey, dbPath string, netres
|
||||||
timeoutTimers: make(map[timeoutEvent]*time.Timer),
|
timeoutTimers: make(map[timeoutEvent]*time.Timer),
|
||||||
tableOpReq: make(chan func()),
|
tableOpReq: make(chan func()),
|
||||||
tableOpResp: make(chan struct{}),
|
tableOpResp: make(chan struct{}),
|
||||||
queryReq: make(chan *findnodeQuery),
|
queryReq: make(chan deferredQuery),
|
||||||
topicRegisterReq: make(chan topicRegisterReq),
|
topicRegisterReq: make(chan topicRegisterReq),
|
||||||
topicSearchReq: make(chan topicSearchReq),
|
topicSearchReq: make(chan topicSearchReq),
|
||||||
nodes: make(map[NodeID]*Node),
|
nodes: make(map[NodeID]*Node),
|
||||||
|
talkRequestSubs: make(map[string]TalkRequestHandler),
|
||||||
|
talkResponseSubs: make(map[string]TalkResponseHandler),
|
||||||
}
|
}
|
||||||
go net.loop()
|
go net.loop()
|
||||||
return net, nil
|
return net, nil
|
||||||
|
|
@ -410,7 +442,6 @@ loop:
|
||||||
|
|
||||||
// Ingress packet handling.
|
// Ingress packet handling.
|
||||||
case pkt := <-net.read:
|
case pkt := <-net.read:
|
||||||
//fmt.Println("read", pkt.ev)
|
|
||||||
log.Trace("<-net.read")
|
log.Trace("<-net.read")
|
||||||
n := net.internNode(&pkt)
|
n := net.internNode(&pkt)
|
||||||
prestate := n.state
|
prestate := n.state
|
||||||
|
|
@ -446,7 +477,7 @@ loop:
|
||||||
case q := <-net.queryReq:
|
case q := <-net.queryReq:
|
||||||
log.Trace("<-net.queryReq")
|
log.Trace("<-net.queryReq")
|
||||||
if !q.start(net) {
|
if !q.start(net) {
|
||||||
q.remote.deferQuery(q)
|
q.deferQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interacting with the table.
|
// Interacting with the table.
|
||||||
|
|
@ -769,12 +800,19 @@ type nodeNetGuts struct {
|
||||||
state *nodeState
|
state *nodeState
|
||||||
pingEcho []byte // hash of last ping sent by us
|
pingEcho []byte // hash of last ping sent by us
|
||||||
pingTopics []Topic // topic set sent by us in last ping
|
pingTopics []Topic // topic set sent by us in last ping
|
||||||
deferredQueries []*findnodeQuery // queries that can't be sent yet
|
deferredQueries []deferredQuery // queries that can't be sent yet
|
||||||
pendingNeighbours *findnodeQuery // current query, waiting for reply
|
pendingNeighbours *findnodeQuery // current query, waiting for reply
|
||||||
queryTimeouts int
|
queryTimeouts int
|
||||||
|
talkFailures int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nodeNetGuts) deferQuery(q *findnodeQuery) {
|
type deferredQuery interface {
|
||||||
|
start(net *Network) bool
|
||||||
|
cancel()
|
||||||
|
deferQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nodeNetGuts) deferQuery(q deferredQuery) {
|
||||||
n.deferredQueries = append(n.deferredQueries, q)
|
n.deferredQueries = append(n.deferredQueries, q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -810,6 +848,14 @@ func (q *findnodeQuery) start(net *Network) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *findnodeQuery) cancel() {
|
||||||
|
q.reply <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *findnodeQuery) deferQuery() {
|
||||||
|
q.remote.deferQuery(q)
|
||||||
|
}
|
||||||
|
|
||||||
// Node Events (the input to the state machine).
|
// Node Events (the input to the state machine).
|
||||||
|
|
||||||
type nodeEvent uint
|
type nodeEvent uint
|
||||||
|
|
@ -828,6 +874,8 @@ const (
|
||||||
topicRegisterPacket
|
topicRegisterPacket
|
||||||
topicQueryPacket
|
topicQueryPacket
|
||||||
topicNodesPacket
|
topicNodesPacket
|
||||||
|
talkRequestPacket
|
||||||
|
talkResponsePacket
|
||||||
|
|
||||||
// Non-packet events.
|
// Non-packet events.
|
||||||
// Event values in this category are allocated outside
|
// Event values in this category are allocated outside
|
||||||
|
|
@ -835,6 +883,7 @@ const (
|
||||||
pongTimeout nodeEvent = iota + 256
|
pongTimeout nodeEvent = iota + 256
|
||||||
pingTimeout
|
pingTimeout
|
||||||
neighboursTimeout
|
neighboursTimeout
|
||||||
|
talkTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node State Machine.
|
// Node State Machine.
|
||||||
|
|
@ -868,7 +917,7 @@ func init() {
|
||||||
n.pingEcho = nil
|
n.pingEcho = nil
|
||||||
// Abort active queries.
|
// Abort active queries.
|
||||||
for _, q := range n.deferredQueries {
|
for _, q := range n.deferredQueries {
|
||||||
q.reply <- nil
|
q.cancel()
|
||||||
}
|
}
|
||||||
n.deferredQueries = nil
|
n.deferredQueries = nil
|
||||||
if n.pendingNeighbours != nil {
|
if n.pendingNeighbours != nil {
|
||||||
|
|
@ -1021,7 +1070,7 @@ func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error {
|
||||||
//fmt.Println("handle", n.addr().String(), n.state, ev)
|
//fmt.Println("handle", n.addr().String(), n.state, ev)
|
||||||
if pkt != nil {
|
if pkt != nil {
|
||||||
if err := net.checkPacket(n, ev, pkt); err != nil {
|
if err := net.checkPacket(n, ev, pkt); err != nil {
|
||||||
//fmt.Println("check err:", err)
|
//fmt.Println("check err:", err, pkt)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Start the background expiration goroutine after the first
|
// Start the background expiration goroutine after the first
|
||||||
|
|
@ -1036,6 +1085,7 @@ func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error {
|
||||||
if n.state == nil {
|
if n.state == nil {
|
||||||
n.state = unknown //???
|
n.state = unknown //???
|
||||||
}
|
}
|
||||||
|
//fmt.Println("old state:", n.state)
|
||||||
next, err := n.state.handle(net, n, ev, pkt)
|
next, err := n.state.handle(net, n, ev, pkt)
|
||||||
net.transition(n, next)
|
net.transition(n, next)
|
||||||
//fmt.Println("new state:", n.state)
|
//fmt.Println("new state:", n.state)
|
||||||
|
|
@ -1196,7 +1246,51 @@ func (net *Network) handleQueryEvent(n *Node, ev nodeEvent, pkt *ingressPacket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n.state, nil
|
return n.state, nil
|
||||||
|
case talkRequestPacket:
|
||||||
|
p := pkt.data.(*talkRequest)
|
||||||
|
net.talkRequestSubLock.RLock()
|
||||||
|
subFn := net.talkRequestSubs[string(p.TalkID)]
|
||||||
|
net.talkRequestSubLock.RUnlock()
|
||||||
|
if subFn != nil {
|
||||||
|
resp, delay, ok := subFn(enode.ID(n.sha), n.addr(), p.Payload)
|
||||||
|
if ok {
|
||||||
|
net.conn.send(n, talkResponsePacket, talkResponse{ReplyTok: pkt.hash, Delay: delay, Payload: resp})
|
||||||
|
} else {
|
||||||
|
n.talkFailures++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n.talkFailures++
|
||||||
|
}
|
||||||
|
if n.talkFailures > maxTalkFailures && n.state == known {
|
||||||
|
return contested, errors.New("too many talk failures")
|
||||||
|
}
|
||||||
|
return n.state, nil
|
||||||
|
case talkResponsePacket:
|
||||||
|
p := pkt.data.(*talkResponse)
|
||||||
|
net.talkResponseSubLock.Lock()
|
||||||
|
key := string(n.sha[:]) + string(p.ReplyTok)
|
||||||
|
subFn := net.talkResponseSubs[key]
|
||||||
|
if subFn != nil {
|
||||||
|
delete(net.talkResponseSubs, key)
|
||||||
|
}
|
||||||
|
net.talkResponseSubLock.Unlock()
|
||||||
|
if subFn == nil || !subFn(p.Payload, p.Delay) {
|
||||||
|
n.talkFailures++
|
||||||
|
if n.talkFailures > maxTalkFailures && n.state == known {
|
||||||
|
return contested, errors.New("too many talk failures")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n.state, nil
|
||||||
|
case talkTimeout:
|
||||||
|
if n.pendingNeighbours != nil {
|
||||||
|
n.pendingNeighbours.reply <- nil
|
||||||
|
n.pendingNeighbours = nil
|
||||||
|
}
|
||||||
|
n.queryTimeouts++
|
||||||
|
if n.queryTimeouts > maxFindnodeFailures && n.state == known {
|
||||||
|
return contested, errors.New("too many timeouts")
|
||||||
|
}
|
||||||
|
return n.state, nil
|
||||||
default:
|
default:
|
||||||
return n.state, errInvalidEvent
|
return n.state, errInvalidEvent
|
||||||
}
|
}
|
||||||
|
|
@ -1260,3 +1354,82 @@ func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error {
|
||||||
n.startNextQuery(net)
|
n.startNextQuery(net)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterTalkHandler assigns a handler callback to the given talk ID
|
||||||
|
func (net *Network) RegisterTalkHandler(talkID string, handler TalkRequestHandler) {
|
||||||
|
net.talkRequestSubLock.Lock()
|
||||||
|
net.talkRequestSubs[talkID] = handler
|
||||||
|
net.talkRequestSubLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTalkHandler removes the handler assigned to the given talk ID
|
||||||
|
func (net *Network) RemoveTalkHandler(talkID string) {
|
||||||
|
net.talkRequestSubLock.Lock()
|
||||||
|
delete(net.talkRequestSubs, talkID)
|
||||||
|
net.talkRequestSubLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *talkQuery) start(net *Network) bool {
|
||||||
|
if q.remote == net.tab.self {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if q.remote.state == known {
|
||||||
|
net.talkResponseSubLock.Lock()
|
||||||
|
hash := net.conn.send(q.remote, talkRequestPacket, talkRequest{TalkID: []byte(q.talkID), Payload: q.payload})
|
||||||
|
q.key = string(q.remote.sha[:]) + string(hash[:])
|
||||||
|
net.talkResponseSubs[q.key] = q.handler
|
||||||
|
net.talkResponseSubLock.Unlock()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// If the node is not known yet, it won't accept queries.
|
||||||
|
// Initiate the transition to known.
|
||||||
|
// The request will be sent later when the node reaches known state.
|
||||||
|
if q.remote.state == unknown {
|
||||||
|
net.transition(q.remote, verifyinit)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *talkQuery) cancel() {
|
||||||
|
q.handler(nil, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *talkQuery) deferQuery() {
|
||||||
|
q.remote.deferQuery(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTalkRequest sends a talRequest and registers a response handler. It returns
|
||||||
|
// a cancel function that removes the response handler and calls it with a nil parameter
|
||||||
|
// if the response has not arrived yet.
|
||||||
|
func (net *Network) SendTalkRequest(to *enode.Node, talkID string, payload interface{}, handler TalkResponseHandler) (cancel func() bool) {
|
||||||
|
var nodeID NodeID
|
||||||
|
copy(nodeID[:], crypto.FromECDSAPub(to.Pubkey())[1:])
|
||||||
|
node := net.nodes[nodeID]
|
||||||
|
if node == nil {
|
||||||
|
node = NewNode(nodeID, to.IP(), uint16(to.UDP()), uint16(to.TCP()))
|
||||||
|
node.state = unknown
|
||||||
|
net.nodes[nodeID] = node
|
||||||
|
}
|
||||||
|
q := &talkQuery{remote: node, talkID: talkID, payload: payload, handler: handler}
|
||||||
|
if node.state == nil || !node.state.canQuery {
|
||||||
|
net.ping(node, node.addr())
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case net.queryReq <- q:
|
||||||
|
case <-net.closed:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() bool {
|
||||||
|
net.talkResponseSubLock.Lock()
|
||||||
|
cancel := q.key != "" && net.talkResponseSubs[q.key] != nil
|
||||||
|
if cancel {
|
||||||
|
delete(net.talkResponseSubs, q.key)
|
||||||
|
}
|
||||||
|
net.talkResponseSubLock.Unlock()
|
||||||
|
if cancel {
|
||||||
|
handler(nil, 0)
|
||||||
|
}
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ const (
|
||||||
nBuckets = hashBits + 1 // Number of buckets
|
nBuckets = hashBits + 1 // Number of buckets
|
||||||
|
|
||||||
maxFindnodeFailures = 5
|
maxFindnodeFailures = 5
|
||||||
|
maxTalkFailures = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
type Table struct {
|
type Table struct {
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,17 @@ type (
|
||||||
Nodes []rpcNode
|
Nodes []rpcNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
talkRequest struct {
|
||||||
|
TalkID []byte
|
||||||
|
Payload interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
talkResponse struct {
|
||||||
|
ReplyTok []byte
|
||||||
|
Delay uint
|
||||||
|
Payload interface{}
|
||||||
|
}
|
||||||
|
|
||||||
rpcNode struct {
|
rpcNode struct {
|
||||||
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
||||||
UDP uint16 // for discovery protocol
|
UDP uint16 // for discovery protocol
|
||||||
|
|
@ -420,6 +431,10 @@ func decodePacket(buffer []byte, pkt *ingressPacket) error {
|
||||||
pkt.data = new(topicQuery)
|
pkt.data = new(topicQuery)
|
||||||
case topicNodesPacket:
|
case topicNodesPacket:
|
||||||
pkt.data = new(topicNodes)
|
pkt.data = new(topicNodes)
|
||||||
|
case talkRequestPacket:
|
||||||
|
pkt.data = new(talkRequest)
|
||||||
|
case talkResponsePacket:
|
||||||
|
pkt.data = new(talkResponse)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown packet type: %d", sigdata[0])
|
return fmt.Errorf("unknown packet type: %d", sigdata[0])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ func (n ID) MarshalText() ([]byte, error) {
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
func (n *ID) UnmarshalText(text []byte) error {
|
func (n *ID) UnmarshalText(text []byte) error {
|
||||||
id, err := parseID(string(text))
|
id, err := ParseID(string(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -229,14 +229,14 @@ func (n *ID) UnmarshalText(text []byte) error {
|
||||||
// The string may be prefixed with 0x.
|
// The string may be prefixed with 0x.
|
||||||
// It panics if the string is not a valid ID.
|
// It panics if the string is not a valid ID.
|
||||||
func HexID(in string) ID {
|
func HexID(in string) ID {
|
||||||
id, err := parseID(in)
|
id, err := ParseID(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseID(in string) (ID, error) {
|
func ParseID(in string) (ID, error) {
|
||||||
var id ID
|
var id ID
|
||||||
b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
|
b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue