p2p/discv5: implement talkRequest

This commit is contained in:
Zsolt Felfoldi 2019-11-14 21:51:08 +01:00
parent 3bb6815fc1
commit 4bb4cb0fd4
3 changed files with 102 additions and 1 deletions

View file

@ -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"
@ -70,6 +72,11 @@ type Network 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,11 @@ type Network struct {
timeoutTimers map[timeoutEvent]*time.Timer timeoutTimers map[timeoutEvent]*time.Timer
} }
type (
TalkRequestHandler func(enode.ID, rlp.RawValue) (rlp.RawValue, bool)
TalkResponseHandler func(rlp.RawValue) 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.
@ -155,6 +167,8 @@ func newNetwork(conn transport, ourPubkey ecdsa.PublicKey, dbPath string, netres
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
@ -772,6 +786,7 @@ type nodeNetGuts struct {
deferredQueries []*findnodeQuery // queries that can't be sent yet deferredQueries []*findnodeQuery // 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) { func (n *nodeNetGuts) deferQuery(q *findnodeQuery) {
@ -828,6 +843,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 +852,7 @@ const (
pongTimeout nodeEvent = iota + 256 pongTimeout nodeEvent = iota + 256
pingTimeout pingTimeout
neighboursTimeout neighboursTimeout
talkTimeout
) )
// Node State Machine. // Node State Machine.
@ -1196,7 +1214,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, ok := subFn(enode.ID(n.sha), p.Payload)
if ok {
net.conn.send(n, talkResponsePacket, talkResponse{ReplyTok: pkt.hash, 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) {
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 +1322,27 @@ func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error {
n.startNextQuery(net) n.startNextQuery(net)
return nil return nil
} }
func (net *Network) RegisterTalkHandler(talkID string, handler TalkRequestHandler) {
net.talkRequestSubLock.Lock()
net.talkRequestSubs[talkID] = handler
net.talkRequestSubLock.Unlock()
}
func (net *Network) SendTalkRequest(to *enode.Node, talkID string, payload rlp.RawValue, handler TalkResponseHandler) func() bool {
node := NewNode(to.ID(), to.IP(), to.UDP(), to.TCP())
net.talkResponseSubLock.Lock()
hash := net.conn.send(node, talkRequestPacket, talkRequest{TalkID: []byte(talkID), Payload: payload})
key := string(to.sha[:]) + string(hash[:])
net.talkResponseSubs[key] = handler
net.talkResponseSubLock.Unlock()
return func() bool {
net.talkResponseSubLock.Lock()
cancel := net.talkResponseSubs[key] != nil
if cancel {
delete(net.talkResponseSubs, key)
}
net.talkResponseSubLock.Unlock()
return cancel
}
}

View file

@ -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 {

View file

@ -119,6 +119,16 @@ type (
Nodes []rpcNode Nodes []rpcNode
} }
talkRequest struct {
TalkID []byte
Payload rlp.RawValue
}
talkResponse struct {
ReplyTok []byte
Payload rlp.RawValue
}
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 +430,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])
} }