feat:make portal wire ping/pong findnodes/nodes works

Signed-off-by: grapebaba <281165273@qq.com>
This commit is contained in:
grapebaba 2023-10-19 13:57:50 +08:00
parent 58ae1df684
commit 499017009a
6 changed files with 1923 additions and 0 deletions

3
go.mod
View file

@ -23,6 +23,7 @@ require (
github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127
github.com/ethereum/c-kzg-4844 v0.4.0
github.com/fatih/color v1.13.0
github.com/ferranbt/fastssz v0.1.3
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/fsnotify/fsnotify v1.6.0
@ -116,10 +117,12 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect

9
go.sum
View file

@ -189,6 +189,8 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo=
github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE=
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY=
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
@ -402,6 +404,9 @@ github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -450,6 +455,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
@ -578,6 +585,8 @@ github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2n
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/umbracle/gohashtree v0.0.2-alpha.0.20230207094856-5b775a815c10 h1:CQh33pStIp/E30b7TxDlXfM0145bn2e8boI30IxAhTg=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=

View file

@ -0,0 +1,541 @@
package discover
import (
"context"
"crypto/ecdsa"
crand "crypto/rand"
"errors"
"fmt"
"net"
"time"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover/portalwire"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/rlp"
ssz "github.com/ferranbt/fastssz"
"github.com/holiman/uint256"
)
const (
// This is the fairness knob for the discovery mixer. When looking for peers, we'll
// wait this long for a single source of candidates before moving on and trying other
// sources.
discmixTimeout = 5 * time.Second
// TalkResp message is a response message so the session is established and a
// regular discv5 packet is assumed for size calculation.
// Regular message = IV + header + message
// talkResp message = rlp: [request-id, response]
talkRespOverhead = 16 + // IV size
55 + // header size
1 + // talkResp msg id
3 + // rlp encoding outer list, max length will be encoded in 2 bytes
9 + // request id (max = 8) + 1 byte from rlp encoding byte string
3 + // rlp encoding response byte string, max length in 2 bytes
16 // HMAC
)
type PortalProtocolConfig struct {
BootstrapNodes []*enode.Node
ListenAddr string
NetRestrict *netutil.Netlist
NodeRadius *uint256.Int
RadiusCacheSize int
NodeDBPath string
}
func DefaultPortalProtocolConfig() *PortalProtocolConfig {
nodeRadius, _ := uint256.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
return &PortalProtocolConfig{
BootstrapNodes: make([]*enode.Node, 0),
ListenAddr: ":9000",
NetRestrict: nil,
NodeRadius: nodeRadius,
RadiusCacheSize: 32 * 1024 * 1024,
NodeDBPath: "",
}
}
type PortalProtocol struct {
table *Table
protocolId string
nodeRadius *uint256.Int
DiscV5 *UDPv5
ListenAddr string
localNode *enode.LocalNode
log log.Logger
discmix *enode.FairMix
PrivateKey *ecdsa.PrivateKey
NetRestrict *netutil.Netlist
BootstrapNodes []*enode.Node
validSchemes enr.IdentityScheme
radiusCache *fastcache.Cache
closeCtx context.Context
cancelCloseCtx context.CancelFunc
}
func NewPortalProtocol(config *PortalProtocolConfig, protocolId string, privateKey *ecdsa.PrivateKey) (*PortalProtocol, error) {
nodeDB, err := enode.OpenDB(config.NodeDBPath)
if err != nil {
return nil, err
}
localNode := enode.NewLocalNode(nodeDB, privateKey)
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
closeCtx, cancelCloseCtx := context.WithCancel(context.Background())
protocol := &PortalProtocol{
protocolId: protocolId,
ListenAddr: config.ListenAddr,
log: log.New("protocol", protocolId),
PrivateKey: privateKey,
NetRestrict: config.NetRestrict,
BootstrapNodes: config.BootstrapNodes,
nodeRadius: config.NodeRadius,
radiusCache: fastcache.New(config.RadiusCacheSize),
closeCtx: closeCtx,
cancelCloseCtx: cancelCloseCtx,
localNode: localNode,
validSchemes: enode.ValidSchemes,
}
return protocol, nil
}
func (p *PortalProtocol) Start() error {
err := p.setupDiscV5AndTable()
if err != nil {
return err
}
p.DiscV5.RegisterTalkHandler(p.protocolId, p.handleTalkRequest)
go p.table.loop()
return nil
}
func (p *PortalProtocol) setupUDPListening() (*net.UDPConn, error) {
listenAddr := p.ListenAddr
addr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return nil, err
}
laddr := conn.LocalAddr().(*net.UDPAddr)
p.localNode.SetFallbackUDP(laddr.Port)
p.log.Debug("UDP listener up", "addr", laddr)
// TODO: NAT
//if !laddr.IP.IsLoopback() && !laddr.IP.IsPrivate() {
// srv.portMappingRegister <- &portMapping{
// protocol: "UDP",
// name: "ethereum peer discovery",
// port: laddr.Port,
// }
//}
return conn, nil
}
func (p *PortalProtocol) setupDiscV5AndTable() error {
p.discmix = enode.NewFairMix(discmixTimeout)
conn, err := p.setupUDPListening()
if err != nil {
return err
}
cfg := Config{
PrivateKey: p.PrivateKey,
NetRestrict: p.NetRestrict,
Bootnodes: p.BootstrapNodes,
Log: p.log,
}
p.DiscV5, err = ListenV5(conn, p.localNode, cfg)
if err != nil {
return err
}
p.table, err = newMeteredTable(p, p.localNode.Database(), cfg)
return nil
}
func (p *PortalProtocol) findNodes(node *enode.Node, distances []uint) ([]*enode.Node, error) {
distancesBytes := make([][2]byte, len(distances))
for i, distance := range distances {
copy(distancesBytes[i][:], ssz.MarshalUint16(make([]byte, 0), uint16(distance)))
}
findNodes := &portalwire.FindNodes{
Distances: distancesBytes,
}
p.log.Trace("Sending find nodes request", "id", node.ID(), "findNodes", findNodes)
findNodesBytes, err := findNodes.MarshalSSZ()
if err != nil {
p.log.Error("failed to marshal find nodes request", "err", err)
return nil, err
}
talkRequestBytes := make([]byte, 0, len(findNodesBytes)+1)
talkRequestBytes = append(talkRequestBytes, portalwire.FINDNODES)
talkRequestBytes = append(talkRequestBytes, findNodesBytes...)
talkResp, err := p.DiscV5.TalkRequest(node, p.protocolId, talkRequestBytes)
if err != nil {
p.log.Error("failed to send find nodes request", "err", err)
return nil, err
}
return p.processNodes(node, talkResp, distances)
}
func (p *PortalProtocol) processNodes(target *enode.Node, resp []byte, distances []uint) ([]*enode.Node, error) {
var (
nodes []*enode.Node
seen = make(map[enode.ID]struct{})
err error
verified = 0
)
if resp[0] != portalwire.NODES {
return nil, fmt.Errorf("invalid nodes response")
}
nodesResp := &portalwire.Nodes{}
err = nodesResp.UnmarshalSSZ(resp[1:])
if err != nil {
return nil, err
}
p.table.addVerifiedNode(wrapNode(target))
var n *enode.Node
for _, b := range nodesResp.Enrs {
record := &enr.Record{}
err = rlp.DecodeBytes(b, record)
if err != nil {
p.log.Debug("Invalid record in nodes response", "id", target.ID(), "err", err)
continue
}
n, err = p.verifyResponseNode(target, record, distances, seen)
if err != nil {
p.log.Debug("Invalid record in nodes response", "id", target.ID(), "err", err)
continue
}
verified++
nodes = append(nodes, n)
}
p.log.Trace("Received nodes response", "id", target.ID(), "total", nodesResp.Total, "verified", verified, "nodes", nodes)
return nodes, nil
}
func (p *PortalProtocol) processPong(target *enode.Node, resp []byte) (uint64, error) {
if resp[0] != portalwire.PONG {
return 0, fmt.Errorf("invalid pong response")
}
pong := &portalwire.Pong{}
err := pong.UnmarshalSSZ(resp[1:])
if err != nil {
p.replaceNode(target)
return 0, err
}
p.log.Trace("Received pong response", "id", target.ID(), "pong", pong)
customPayload := &portalwire.PingPongCustomData{}
err = customPayload.UnmarshalSSZ(pong.CustomPayload)
if err != nil {
p.replaceNode(target)
return 0, err
}
p.log.Trace("Received pong response", "id", target.ID(), "pong", pong, "customPayload", customPayload)
p.radiusCache.Set([]byte(target.ID().String()), customPayload.Radius)
p.table.addVerifiedNode(wrapNode(target))
return pong.EnrSeq, nil
}
func (p *PortalProtocol) handleTalkRequest(id enode.ID, addr *net.UDPAddr, msg []byte) []byte {
if node := p.DiscV5.getNode(id); node != nil {
p.table.addSeenNode(wrapNode(node))
}
msgCode := msg[0]
switch msgCode {
case portalwire.PING:
pingRequest := &portalwire.Ping{}
err := pingRequest.UnmarshalSSZ(msg[1:])
if err != nil {
p.log.Error("failed to unmarshal ping request", "err", err)
return nil
}
p.log.Trace("received ping request", "protocol", p.protocolId, "source", id, "pingRequest", pingRequest)
resp, err := p.handlePing(id, pingRequest)
if err != nil {
p.log.Error("failed to handle ping request", "err", err)
return nil
}
return resp
case portalwire.FINDNODES:
findNodesRequest := &portalwire.FindNodes{}
err := findNodesRequest.UnmarshalSSZ(msg[1:])
if err != nil {
p.log.Error("failed to unmarshal find nodes request", "err", err)
return nil
}
p.log.Trace("received find nodes request", "protocol", p.protocolId, "source", id, "findNodesRequest", findNodesRequest)
resp, err := p.handleFindNodes(addr, findNodesRequest)
if err != nil {
p.log.Error("failed to handle find nodes request", "err", err)
return nil
}
return resp
}
return nil
}
func (p *PortalProtocol) handlePing(id enode.ID, ping *portalwire.Ping) ([]byte, error) {
pingCustomPayload := &portalwire.PingPongCustomData{}
err := pingCustomPayload.UnmarshalSSZ(ping.CustomPayload)
if err != nil {
return nil, err
}
p.radiusCache.Set([]byte(id.String()), pingCustomPayload.Radius)
enrSeq := p.DiscV5.LocalNode().Seq()
radiusBytes, err := p.nodeRadius.MarshalSSZ()
if err != nil {
return nil, err
}
pongCustomPayload := &portalwire.PingPongCustomData{
Radius: radiusBytes,
}
pongCustomPayloadBytes, err := pongCustomPayload.MarshalSSZ()
if err != nil {
return nil, err
}
pong := &portalwire.Pong{
EnrSeq: enrSeq,
CustomPayload: pongCustomPayloadBytes,
}
p.log.Trace("Sending pong response", "protocol", p.protocolId, "source", id, "pong", pong)
pongBytes, err := pong.MarshalSSZ()
if err != nil {
return nil, err
}
talkRespBytes := make([]byte, 0, len(pongBytes)+1)
talkRespBytes = append(talkRespBytes, portalwire.PONG)
talkRespBytes = append(talkRespBytes, pongBytes...)
return talkRespBytes, nil
}
func (p *PortalProtocol) handleFindNodes(fromAddr *net.UDPAddr, request *portalwire.FindNodes) ([]byte, error) {
distances := make([]uint, len(request.Distances))
for i, distance := range request.Distances {
distances[i] = uint(ssz.UnmarshallUint16(distance[:]))
}
nodes := p.DiscV5.collectTableNodes(fromAddr.IP, distances, 64)
nodesOverhead := 1 + 1 + 4 // msg id + total + container offset
maxPayloadSize := maxPacketSize - talkRespOverhead - nodesOverhead
enrOverhead := 4 //per added ENR, 4 bytes offset overhead
enrs := p.truncateNodes(nodes, maxPayloadSize, enrOverhead)
nodesMsg := &portalwire.Nodes{
Total: 1,
Enrs: enrs,
}
p.log.Trace("Sending nodes response", "protocol", p.protocolId, "source", fromAddr, "nodes", nodesMsg)
nodesMsgBytes, err := nodesMsg.MarshalSSZ()
if err != nil {
return nil, err
}
talkRespBytes := make([]byte, 0, len(nodesMsgBytes)+1)
talkRespBytes = append(talkRespBytes, portalwire.NODES)
talkRespBytes = append(talkRespBytes, nodesMsgBytes...)
return talkRespBytes, nil
}
func (p *PortalProtocol) Self() *enode.Node {
return p.DiscV5.LocalNode().Node()
}
func (p *PortalProtocol) RequestENR(n *enode.Node) (*enode.Node, error) {
nodes, err := p.findNodes(n, []uint{0})
if err != nil {
return nil, err
}
if len(nodes) != 1 {
return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes))
}
return nodes[0], nil
}
func (p *PortalProtocol) verifyResponseNode(sender *enode.Node, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) {
n, err := enode.New(p.validSchemes, r)
if err != nil {
return nil, err
}
if err = netutil.CheckRelayIP(sender.IP(), n.IP()); err != nil {
return nil, err
}
if p.NetRestrict != nil && !p.NetRestrict.Contains(n.IP()) {
return nil, errors.New("not contained in netrestrict list")
}
if n.UDP() <= 1024 {
return nil, errLowPort
}
if distances != nil {
nd := enode.LogDist(sender.ID(), n.ID())
if !containsUint(uint(nd), distances) {
return nil, errors.New("does not match any requested distance")
}
}
if _, ok := seen[n.ID()]; ok {
return nil, fmt.Errorf("duplicate record")
}
seen[n.ID()] = struct{}{}
return n, nil
}
func (p *PortalProtocol) ping(node *enode.Node) (uint64, error) {
enrSeq := p.DiscV5.LocalNode().Seq()
radiusBytes, err := p.nodeRadius.MarshalSSZ()
if err != nil {
return 0, err
}
customPayload := &portalwire.PingPongCustomData{
Radius: radiusBytes,
}
customPayloadBytes, err := customPayload.MarshalSSZ()
if err != nil {
return 0, err
}
pingRequest := &portalwire.Ping{
EnrSeq: enrSeq,
CustomPayload: customPayloadBytes,
}
p.log.Trace("Sending ping request", "protocol", p.protocolId, "source", p.Self().ID(), "target", node.ID(), "ping", pingRequest)
pingRequestBytes, err := pingRequest.MarshalSSZ()
if err != nil {
return 0, err
}
talkRequestBytes := make([]byte, 0, len(pingRequestBytes)+1)
talkRequestBytes = append(talkRequestBytes, portalwire.PING)
talkRequestBytes = append(talkRequestBytes, pingRequestBytes...)
talkResp, err := p.DiscV5.TalkRequest(node, p.protocolId, talkRequestBytes)
if err != nil {
p.replaceNode(node)
}
return p.processPong(node, talkResp)
}
func (p *PortalProtocol) replaceNode(node *enode.Node) {
p.table.mutex.Lock()
defer p.table.mutex.Unlock()
b := p.table.bucket(node.ID())
p.table.replace(b, wrapNode(node))
}
// lookupRandom looks up a random target.
// This is needed to satisfy the transport interface.
func (p *PortalProtocol) lookupRandom() []*enode.Node {
return p.newRandomLookup(p.closeCtx).run()
}
// lookupSelf looks up our own node ID.
// This is needed to satisfy the transport interface.
func (p *PortalProtocol) lookupSelf() []*enode.Node {
return p.newLookup(p.closeCtx, p.Self().ID()).run()
}
func (p *PortalProtocol) newRandomLookup(ctx context.Context) *lookup {
var target enode.ID
crand.Read(target[:])
return p.newLookup(ctx, target)
}
func (p *PortalProtocol) newLookup(ctx context.Context, target enode.ID) *lookup {
return newLookup(ctx, p.table, target, func(n *node) ([]*node, error) {
return p.lookupWorker(n, target)
})
}
// lookupWorker performs FINDNODE calls against a single node during lookup.
func (p *PortalProtocol) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
var (
dists = lookupDistances(target, destNode.ID())
nodes = nodesByDistance{target: target}
err error
)
var r []*enode.Node
r, err = p.findNodes(unwrapNode(destNode), dists)
if errors.Is(err, errClosed) {
return nil, err
}
for _, n := range r {
if n.ID() != p.Self().ID() {
nodes.push(wrapNode(n), findnodeResultLimit)
}
}
return nodes.entries, err
}
func (p *PortalProtocol) truncateNodes(nodes []*enode.Node, maxSize int, enrOverhead int) [][]byte {
res := make([][]byte, 0)
totalSize := 0
for _, n := range nodes {
enrBytes, err := rlp.EncodeToBytes(n.Record())
if err != nil {
p.log.Error("failed to encode n", "err", err)
continue
}
if totalSize+len(enrBytes)+enrOverhead > maxSize {
break
} else {
res = append(res, enrBytes)
totalSize += len(enrBytes)
}
}
return res
}

View file

@ -0,0 +1,79 @@
package discover
import (
"fmt"
"testing"
"time"
"github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover/portalwire"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
)
func setupLocalPortalNode(addr string, bootNodes []*enode.Node) (*PortalProtocol, error) {
conf := DefaultPortalProtocolConfig()
if addr != "" {
conf.ListenAddr = addr
}
if bootNodes != nil {
conf.BootstrapNodes = bootNodes
}
portalProtocol, err := NewPortalProtocol(conf, portalwire.HistoryNetwork, newkey())
if err != nil {
return nil, err
}
return portalProtocol, nil
}
func TestPortalWireProtocol(t *testing.T) {
node1, err := setupLocalPortalNode(":7777", nil)
assert.NoError(t, err)
node1.log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
fmt.Println(node1.localNode.Node().String())
node2, err := setupLocalPortalNode(":7778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.log = testlog.Logger(t, log.LvlTrace)
err = node2.Start()
assert.NoError(t, err)
fmt.Println(node2.localNode.Node().String())
node3, err := setupLocalPortalNode(":7779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
fmt.Println(node3.localNode.Node().String())
time.Sleep(10 * time.Second)
assert.Equal(t, 2, len(node1.table.Nodes()))
assert.Equal(t, 2, len(node2.table.Nodes()))
assert.Equal(t, 2, len(node3.table.Nodes()))
slices.ContainsFunc(node1.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
})
slices.ContainsFunc(node1.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node3.localNode.Node().ID()
})
slices.ContainsFunc(node2.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node1.localNode.Node().ID()
})
slices.ContainsFunc(node2.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node3.localNode.Node().ID()
})
slices.ContainsFunc(node3.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node1.localNode.Node().ID()
})
slices.ContainsFunc(node3.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
})
}

View file

@ -0,0 +1,97 @@
package portalwire
import "github.com/ethereum/go-ethereum/common/hexutil"
// Protocol IDs for the portal protocol.
const (
StateNetwork = "0x500a"
HistoryNetwork = "0x500b"
TxGossipNetwork = "0x500c"
HeaderGossipNetwork = "0x500d"
CanonicalIndicesNetwork = "0x500e"
BeaconLightClientNetwork = "0x501a"
UTPNetwork = "0x757470"
Rendezvous = "0x72656e"
)
// Message codes for the portal protocol.
const (
PING byte = 0x00
PONG byte = 0x01
FINDNODES byte = 0x02
NODES byte = 0x03
FINDCONTENT byte = 0x04
CONTENT byte = 0x05
OFFER byte = 0x06
ACCEPT byte = 0x07
)
// Request messages for the portal protocol.
type (
PingPongCustomData struct {
Radius []byte `ssz-size:"32"`
}
Ping struct {
EnrSeq uint64
CustomPayload []byte `ssz-max:"2048"`
}
FindNodes struct {
Distances [][2]byte `ssz-max:"256,2" ssz-size:"?,2"`
}
FindContent struct {
ContentKey []byte `ssz-max:"2048"`
}
Offer struct {
ContentKeys [][]byte `ssz-max:"64,2048"`
}
)
// Response messages for the portal protocol.
type (
Pong struct {
EnrSeq uint64
CustomPayload []byte `ssz-max:"2048"`
}
Nodes struct {
Total uint8
Enrs [][]byte `ssz-max:"32,2048"`
}
ConnectionId struct {
Id []byte `ssz-size:"2"`
}
Content struct {
Content []byte `ssz-max:"2048"`
}
Enrs struct {
Enrs [][]byte `ssz-max:"32,2048"`
}
Accept struct {
ConnectionId []byte `ssz-size:"2"`
ContentKeys []byte `ssz:"bitlist" ssz-max:"64"`
}
)
func getTalkReqOverheadByLen(protocolIdLen int) int {
return 16 + // IV size
55 + // header size
1 + // talkReq msg id
3 + // rlp encoding outer list, max length will be encoded in 2 bytes
9 + // request id (max = 8) + 1 byte from rlp encoding byte string
protocolIdLen + 1 + // + 1 is necessary due to rlp encoding of byte string
3 + // rlp encoding response byte string, max length in 2 bytes
16 // HMAC
}
func getTalkReqOverhead(protocolId string) int {
protocolIdBytes, _ := hexutil.Decode(protocolId)
return getTalkReqOverheadByLen(len(protocolIdBytes))
}

File diff suppressed because it is too large Load diff