mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-28 15:46:43 +00:00
feat:make portal wire ping/pong findnodes/nodes works
Signed-off-by: grapebaba <281165273@qq.com>
This commit is contained in:
parent
333dd956bf
commit
dd4e8ff2cb
6 changed files with 1923 additions and 0 deletions
3
go.mod
3
go.mod
|
|
@ -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
|
||||
|
|
@ -115,10 +116,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
9
go.sum
|
|
@ -191,6 +191,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=
|
||||
|
|
@ -406,6 +408,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=
|
||||
|
|
@ -453,6 +458,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=
|
||||
|
|
@ -581,6 +588,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=
|
||||
|
|
|
|||
541
p2p/discover/portal_protocol.go
Normal file
541
p2p/discover/portal_protocol.go
Normal 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
|
||||
}
|
||||
79
p2p/discover/portal_protocol_test.go
Normal file
79
p2p/discover/portal_protocol_test.go
Normal 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()
|
||||
})
|
||||
}
|
||||
97
p2p/discover/portalwire/messages.go
Normal file
97
p2p/discover/portalwire/messages.go
Normal 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))
|
||||
}
|
||||
1194
p2p/discover/portalwire/messages_encoding.go
Normal file
1194
p2p/discover/portalwire/messages_encoding.go
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue