From 499017009a3d0bda7910edb97dfcbb767376b7f6 Mon Sep 17 00:00:00 2001 From: grapebaba <281165273@qq.com> Date: Thu, 19 Oct 2023 13:57:50 +0800 Subject: [PATCH] feat:make portal wire ping/pong findnodes/nodes works Signed-off-by: grapebaba <281165273@qq.com> --- go.mod | 3 + go.sum | 9 + p2p/discover/portal_protocol.go | 541 ++++++++ p2p/discover/portal_protocol_test.go | 79 ++ p2p/discover/portalwire/messages.go | 97 ++ p2p/discover/portalwire/messages_encoding.go | 1194 ++++++++++++++++++ 6 files changed, 1923 insertions(+) create mode 100644 p2p/discover/portal_protocol.go create mode 100644 p2p/discover/portal_protocol_test.go create mode 100644 p2p/discover/portalwire/messages.go create mode 100644 p2p/discover/portalwire/messages_encoding.go diff --git a/go.mod b/go.mod index 4901030311..3f8b2194df 100644 --- a/go.mod +++ b/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 @@ -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 diff --git a/go.sum b/go.sum index 6017c9f77e..ef3b839b0d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/p2p/discover/portal_protocol.go b/p2p/discover/portal_protocol.go new file mode 100644 index 0000000000..d0682cc2f4 --- /dev/null +++ b/p2p/discover/portal_protocol.go @@ -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 +} diff --git a/p2p/discover/portal_protocol_test.go b/p2p/discover/portal_protocol_test.go new file mode 100644 index 0000000000..6bfa29e1d3 --- /dev/null +++ b/p2p/discover/portal_protocol_test.go @@ -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() + }) +} diff --git a/p2p/discover/portalwire/messages.go b/p2p/discover/portalwire/messages.go new file mode 100644 index 0000000000..7773998d92 --- /dev/null +++ b/p2p/discover/portalwire/messages.go @@ -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)) +} diff --git a/p2p/discover/portalwire/messages_encoding.go b/p2p/discover/portalwire/messages_encoding.go new file mode 100644 index 0000000000..f3dd00645b --- /dev/null +++ b/p2p/discover/portalwire/messages_encoding.go @@ -0,0 +1,1194 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 1f7cf716b197089d098199d33ce79d7b9d61b272e3d573561109f635d370d026 +// Version: 0.1.3 +package portalwire + +import ( + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the PingPongCustomData object +func (p *PingPongCustomData) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the PingPongCustomData object to a target array +func (p *PingPongCustomData) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Radius' + if size := len(p.Radius); size != 32 { + err = ssz.ErrBytesLengthFn("PingPongCustomData.Radius", size, 32) + return + } + dst = append(dst, p.Radius...) + + return +} + +// UnmarshalSSZ ssz unmarshals the PingPongCustomData object +func (p *PingPongCustomData) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 32 { + return ssz.ErrSize + } + + // Field (0) 'Radius' + if cap(p.Radius) == 0 { + p.Radius = make([]byte, 0, len(buf[0:32])) + } + p.Radius = append(p.Radius, buf[0:32]...) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the PingPongCustomData object +func (p *PingPongCustomData) SizeSSZ() (size int) { + size = 32 + return +} + +// HashTreeRoot ssz hashes the PingPongCustomData object +func (p *PingPongCustomData) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the PingPongCustomData object with a hasher +func (p *PingPongCustomData) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Radius' + if size := len(p.Radius); size != 32 { + err = ssz.ErrBytesLengthFn("PingPongCustomData.Radius", size, 32) + return + } + hh.PutBytes(p.Radius) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the PingPongCustomData object +func (p *PingPongCustomData) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(p) +} + +// MarshalSSZ ssz marshals the Ping object +func (p *Ping) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the Ping object to a target array +func (p *Ping) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(12) + + // Field (0) 'EnrSeq' + dst = ssz.MarshalUint64(dst, p.EnrSeq) + + // Offset (1) 'CustomPayload' + dst = ssz.WriteOffset(dst, offset) + offset += len(p.CustomPayload) + + // Field (1) 'CustomPayload' + if size := len(p.CustomPayload); size > 2048 { + err = ssz.ErrBytesLengthFn("Ping.CustomPayload", size, 2048) + return + } + dst = append(dst, p.CustomPayload...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Ping object +func (p *Ping) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 12 { + return ssz.ErrSize + } + + tail := buf + var o1 uint64 + + // Field (0) 'EnrSeq' + p.EnrSeq = ssz.UnmarshallUint64(buf[0:8]) + + // Offset (1) 'CustomPayload' + if o1 = ssz.ReadOffset(buf[8:12]); o1 > size { + return ssz.ErrOffset + } + + if o1 < 12 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'CustomPayload' + { + buf = tail[o1:] + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(p.CustomPayload) == 0 { + p.CustomPayload = make([]byte, 0, len(buf)) + } + p.CustomPayload = append(p.CustomPayload, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Ping object +func (p *Ping) SizeSSZ() (size int) { + size = 12 + + // Field (1) 'CustomPayload' + size += len(p.CustomPayload) + + return +} + +// HashTreeRoot ssz hashes the Ping object +func (p *Ping) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the Ping object with a hasher +func (p *Ping) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'EnrSeq' + hh.PutUint64(p.EnrSeq) + + // Field (1) 'CustomPayload' + { + elemIndx := hh.Index() + byteLen := uint64(len(p.CustomPayload)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.Append(p.CustomPayload) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Ping object +func (p *Ping) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(p) +} + +// MarshalSSZ ssz marshals the FindNodes object +func (f *FindNodes) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(f) +} + +// MarshalSSZTo ssz marshals the FindNodes object to a target array +func (f *FindNodes) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'Distances' + dst = ssz.WriteOffset(dst, offset) + offset += len(f.Distances) * 2 + + // Field (0) 'Distances' + if size := len(f.Distances); size > 256 { + err = ssz.ErrListTooBigFn("FindNodes.Distances", size, 256) + return + } + for ii := 0; ii < len(f.Distances); ii++ { + dst = append(dst, f.Distances[ii][:]...) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the FindNodes object +func (f *FindNodes) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'Distances' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'Distances' + { + buf = tail[o0:] + num, err := ssz.DivideInt2(len(buf), 2, 256) + if err != nil { + return err + } + f.Distances = make([][2]byte, num) + for ii := 0; ii < num; ii++ { + copy(f.Distances[ii][:], buf[ii*2:(ii+1)*2]) + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the FindNodes object +func (f *FindNodes) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'Distances' + size += len(f.Distances) * 2 + + return +} + +// HashTreeRoot ssz hashes the FindNodes object +func (f *FindNodes) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(f) +} + +// HashTreeRootWith ssz hashes the FindNodes object with a hasher +func (f *FindNodes) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Distances' + { + if size := len(f.Distances); size > 256 { + err = ssz.ErrListTooBigFn("FindNodes.Distances", size, 256) + return + } + subIndx := hh.Index() + for _, i := range f.Distances { + hh.PutBytes(i[:]) + } + numItems := uint64(len(f.Distances)) + hh.MerkleizeWithMixin(subIndx, numItems, 256) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the FindNodes object +func (f *FindNodes) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(f) +} + +// MarshalSSZ ssz marshals the FindContent object +func (f *FindContent) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(f) +} + +// MarshalSSZTo ssz marshals the FindContent object to a target array +func (f *FindContent) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'ContentKey' + dst = ssz.WriteOffset(dst, offset) + offset += len(f.ContentKey) + + // Field (0) 'ContentKey' + if size := len(f.ContentKey); size > 2048 { + err = ssz.ErrBytesLengthFn("FindContent.ContentKey", size, 2048) + return + } + dst = append(dst, f.ContentKey...) + + return +} + +// UnmarshalSSZ ssz unmarshals the FindContent object +func (f *FindContent) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'ContentKey' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'ContentKey' + { + buf = tail[o0:] + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(f.ContentKey) == 0 { + f.ContentKey = make([]byte, 0, len(buf)) + } + f.ContentKey = append(f.ContentKey, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the FindContent object +func (f *FindContent) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'ContentKey' + size += len(f.ContentKey) + + return +} + +// HashTreeRoot ssz hashes the FindContent object +func (f *FindContent) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(f) +} + +// HashTreeRootWith ssz hashes the FindContent object with a hasher +func (f *FindContent) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'ContentKey' + { + elemIndx := hh.Index() + byteLen := uint64(len(f.ContentKey)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.Append(f.ContentKey) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the FindContent object +func (f *FindContent) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(f) +} + +// MarshalSSZ ssz marshals the Offer object +func (o *Offer) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(o) +} + +// MarshalSSZTo ssz marshals the Offer object to a target array +func (o *Offer) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'ContentKeys' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(o.ContentKeys); ii++ { + offset += 4 + offset += len(o.ContentKeys[ii]) + } + + // Field (0) 'ContentKeys' + if size := len(o.ContentKeys); size > 64 { + err = ssz.ErrListTooBigFn("Offer.ContentKeys", size, 64) + return + } + { + offset = 4 * len(o.ContentKeys) + for ii := 0; ii < len(o.ContentKeys); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += len(o.ContentKeys[ii]) + } + } + for ii := 0; ii < len(o.ContentKeys); ii++ { + if size := len(o.ContentKeys[ii]); size > 2048 { + err = ssz.ErrBytesLengthFn("Offer.ContentKeys[ii]", size, 2048) + return + } + dst = append(dst, o.ContentKeys[ii]...) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Offer object +func (o *Offer) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'ContentKeys' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'ContentKeys' + { + buf = tail[o0:] + num, err := ssz.DecodeDynamicLength(buf, 64) + if err != nil { + return err + } + o.ContentKeys = make([][]byte, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(o.ContentKeys[indx]) == 0 { + o.ContentKeys[indx] = make([]byte, 0, len(buf)) + } + o.ContentKeys[indx] = append(o.ContentKeys[indx], buf...) + return nil + }) + if err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Offer object +func (o *Offer) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'ContentKeys' + for ii := 0; ii < len(o.ContentKeys); ii++ { + size += 4 + size += len(o.ContentKeys[ii]) + } + + return +} + +// HashTreeRoot ssz hashes the Offer object +func (o *Offer) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(o) +} + +// HashTreeRootWith ssz hashes the Offer object with a hasher +func (o *Offer) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'ContentKeys' + { + subIndx := hh.Index() + num := uint64(len(o.ContentKeys)) + if num > 64 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range o.ContentKeys { + { + elemIndx := hh.Index() + byteLen := uint64(len(elem)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.AppendBytes32(elem) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + } + hh.MerkleizeWithMixin(subIndx, num, 64) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Offer object +func (o *Offer) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(o) +} + +// MarshalSSZ ssz marshals the Pong object +func (p *Pong) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the Pong object to a target array +func (p *Pong) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(12) + + // Field (0) 'EnrSeq' + dst = ssz.MarshalUint64(dst, p.EnrSeq) + + // Offset (1) 'CustomPayload' + dst = ssz.WriteOffset(dst, offset) + offset += len(p.CustomPayload) + + // Field (1) 'CustomPayload' + if size := len(p.CustomPayload); size > 2048 { + err = ssz.ErrBytesLengthFn("Pong.CustomPayload", size, 2048) + return + } + dst = append(dst, p.CustomPayload...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Pong object +func (p *Pong) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 12 { + return ssz.ErrSize + } + + tail := buf + var o1 uint64 + + // Field (0) 'EnrSeq' + p.EnrSeq = ssz.UnmarshallUint64(buf[0:8]) + + // Offset (1) 'CustomPayload' + if o1 = ssz.ReadOffset(buf[8:12]); o1 > size { + return ssz.ErrOffset + } + + if o1 < 12 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'CustomPayload' + { + buf = tail[o1:] + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(p.CustomPayload) == 0 { + p.CustomPayload = make([]byte, 0, len(buf)) + } + p.CustomPayload = append(p.CustomPayload, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Pong object +func (p *Pong) SizeSSZ() (size int) { + size = 12 + + // Field (1) 'CustomPayload' + size += len(p.CustomPayload) + + return +} + +// HashTreeRoot ssz hashes the Pong object +func (p *Pong) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the Pong object with a hasher +func (p *Pong) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'EnrSeq' + hh.PutUint64(p.EnrSeq) + + // Field (1) 'CustomPayload' + { + elemIndx := hh.Index() + byteLen := uint64(len(p.CustomPayload)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.Append(p.CustomPayload) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Pong object +func (p *Pong) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(p) +} + +// MarshalSSZ ssz marshals the Nodes object +func (n *Nodes) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(n) +} + +// MarshalSSZTo ssz marshals the Nodes object to a target array +func (n *Nodes) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(5) + + // Field (0) 'Total' + dst = ssz.MarshalUint8(dst, n.Total) + + // Offset (1) 'Enrs' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(n.Enrs); ii++ { + offset += 4 + offset += len(n.Enrs[ii]) + } + + // Field (1) 'Enrs' + if size := len(n.Enrs); size > 32 { + err = ssz.ErrListTooBigFn("Nodes.Enrs", size, 32) + return + } + { + offset = 4 * len(n.Enrs) + for ii := 0; ii < len(n.Enrs); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += len(n.Enrs[ii]) + } + } + for ii := 0; ii < len(n.Enrs); ii++ { + if size := len(n.Enrs[ii]); size > 2048 { + err = ssz.ErrBytesLengthFn("Nodes.Enrs[ii]", size, 2048) + return + } + dst = append(dst, n.Enrs[ii]...) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Nodes object +func (n *Nodes) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 5 { + return ssz.ErrSize + } + + tail := buf + var o1 uint64 + + // Field (0) 'Total' + n.Total = ssz.UnmarshallUint8(buf[0:1]) + + // Offset (1) 'Enrs' + if o1 = ssz.ReadOffset(buf[1:5]); o1 > size { + return ssz.ErrOffset + } + + if o1 < 5 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Enrs' + { + buf = tail[o1:] + num, err := ssz.DecodeDynamicLength(buf, 32) + if err != nil { + return err + } + n.Enrs = make([][]byte, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(n.Enrs[indx]) == 0 { + n.Enrs[indx] = make([]byte, 0, len(buf)) + } + n.Enrs[indx] = append(n.Enrs[indx], buf...) + return nil + }) + if err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Nodes object +func (n *Nodes) SizeSSZ() (size int) { + size = 5 + + // Field (1) 'Enrs' + for ii := 0; ii < len(n.Enrs); ii++ { + size += 4 + size += len(n.Enrs[ii]) + } + + return +} + +// HashTreeRoot ssz hashes the Nodes object +func (n *Nodes) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(n) +} + +// HashTreeRootWith ssz hashes the Nodes object with a hasher +func (n *Nodes) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Total' + hh.PutUint8(n.Total) + + // Field (1) 'Enrs' + { + subIndx := hh.Index() + num := uint64(len(n.Enrs)) + if num > 32 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range n.Enrs { + { + elemIndx := hh.Index() + byteLen := uint64(len(elem)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.AppendBytes32(elem) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + } + hh.MerkleizeWithMixin(subIndx, num, 32) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Nodes object +func (n *Nodes) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(n) +} + +// MarshalSSZ ssz marshals the ConnectionId object +func (c *ConnectionId) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(c) +} + +// MarshalSSZTo ssz marshals the ConnectionId object to a target array +func (c *ConnectionId) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Id' + if size := len(c.Id); size != 2 { + err = ssz.ErrBytesLengthFn("ConnectionId.Id", size, 2) + return + } + dst = append(dst, c.Id...) + + return +} + +// UnmarshalSSZ ssz unmarshals the ConnectionId object +func (c *ConnectionId) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 2 { + return ssz.ErrSize + } + + // Field (0) 'Id' + if cap(c.Id) == 0 { + c.Id = make([]byte, 0, len(buf[0:2])) + } + c.Id = append(c.Id, buf[0:2]...) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ConnectionId object +func (c *ConnectionId) SizeSSZ() (size int) { + size = 2 + return +} + +// HashTreeRoot ssz hashes the ConnectionId object +func (c *ConnectionId) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(c) +} + +// HashTreeRootWith ssz hashes the ConnectionId object with a hasher +func (c *ConnectionId) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Id' + if size := len(c.Id); size != 2 { + err = ssz.ErrBytesLengthFn("ConnectionId.Id", size, 2) + return + } + hh.PutBytes(c.Id) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the ConnectionId object +func (c *ConnectionId) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(c) +} + +// MarshalSSZ ssz marshals the Content object +func (c *Content) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(c) +} + +// MarshalSSZTo ssz marshals the Content object to a target array +func (c *Content) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'Content' + dst = ssz.WriteOffset(dst, offset) + offset += len(c.Content) + + // Field (0) 'Content' + if size := len(c.Content); size > 2048 { + err = ssz.ErrBytesLengthFn("Content.Content", size, 2048) + return + } + dst = append(dst, c.Content...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Content object +func (c *Content) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'Content' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'Content' + { + buf = tail[o0:] + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(c.Content) == 0 { + c.Content = make([]byte, 0, len(buf)) + } + c.Content = append(c.Content, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Content object +func (c *Content) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'Content' + size += len(c.Content) + + return +} + +// HashTreeRoot ssz hashes the Content object +func (c *Content) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(c) +} + +// HashTreeRootWith ssz hashes the Content object with a hasher +func (c *Content) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Content' + { + elemIndx := hh.Index() + byteLen := uint64(len(c.Content)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.Append(c.Content) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Content object +func (c *Content) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(c) +} + +// MarshalSSZ ssz marshals the Enrs object +func (e *Enrs) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(e) +} + +// MarshalSSZTo ssz marshals the Enrs object to a target array +func (e *Enrs) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'Enrs' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(e.Enrs); ii++ { + offset += 4 + offset += len(e.Enrs[ii]) + } + + // Field (0) 'Enrs' + if size := len(e.Enrs); size > 32 { + err = ssz.ErrListTooBigFn("Enrs.Enrs", size, 32) + return + } + { + offset = 4 * len(e.Enrs) + for ii := 0; ii < len(e.Enrs); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += len(e.Enrs[ii]) + } + } + for ii := 0; ii < len(e.Enrs); ii++ { + if size := len(e.Enrs[ii]); size > 2048 { + err = ssz.ErrBytesLengthFn("Enrs.Enrs[ii]", size, 2048) + return + } + dst = append(dst, e.Enrs[ii]...) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the Enrs object +func (e *Enrs) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'Enrs' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'Enrs' + { + buf = tail[o0:] + num, err := ssz.DecodeDynamicLength(buf, 32) + if err != nil { + return err + } + e.Enrs = make([][]byte, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if len(buf) > 2048 { + return ssz.ErrBytesLength + } + if cap(e.Enrs[indx]) == 0 { + e.Enrs[indx] = make([]byte, 0, len(buf)) + } + e.Enrs[indx] = append(e.Enrs[indx], buf...) + return nil + }) + if err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Enrs object +func (e *Enrs) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'Enrs' + for ii := 0; ii < len(e.Enrs); ii++ { + size += 4 + size += len(e.Enrs[ii]) + } + + return +} + +// HashTreeRoot ssz hashes the Enrs object +func (e *Enrs) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(e) +} + +// HashTreeRootWith ssz hashes the Enrs object with a hasher +func (e *Enrs) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Enrs' + { + subIndx := hh.Index() + num := uint64(len(e.Enrs)) + if num > 32 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range e.Enrs { + { + elemIndx := hh.Index() + byteLen := uint64(len(elem)) + if byteLen > 2048 { + err = ssz.ErrIncorrectListSize + return + } + hh.AppendBytes32(elem) + hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32) + } + } + hh.MerkleizeWithMixin(subIndx, num, 32) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Enrs object +func (e *Enrs) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(e) +} + +// MarshalSSZ ssz marshals the Accept object +func (a *Accept) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(a) +} + +// MarshalSSZTo ssz marshals the Accept object to a target array +func (a *Accept) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(6) + + // Field (0) 'ConnectionId' + if size := len(a.ConnectionId); size != 2 { + err = ssz.ErrBytesLengthFn("Accept.ConnectionId", size, 2) + return + } + dst = append(dst, a.ConnectionId...) + + // Offset (1) 'ContentKeys' + dst = ssz.WriteOffset(dst, offset) + offset += len(a.ContentKeys) + + // Field (1) 'ContentKeys' + if size := len(a.ContentKeys); size > 64 { + err = ssz.ErrBytesLengthFn("Accept.ContentKeys", size, 64) + return + } + dst = append(dst, a.ContentKeys...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Accept object +func (a *Accept) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 6 { + return ssz.ErrSize + } + + tail := buf + var o1 uint64 + + // Field (0) 'ConnectionId' + if cap(a.ConnectionId) == 0 { + a.ConnectionId = make([]byte, 0, len(buf[0:2])) + } + a.ConnectionId = append(a.ConnectionId, buf[0:2]...) + + // Offset (1) 'ContentKeys' + if o1 = ssz.ReadOffset(buf[2:6]); o1 > size { + return ssz.ErrOffset + } + + if o1 < 6 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'ContentKeys' + { + buf = tail[o1:] + if err = ssz.ValidateBitlist(buf, 64); err != nil { + return err + } + if cap(a.ContentKeys) == 0 { + a.ContentKeys = make([]byte, 0, len(buf)) + } + a.ContentKeys = append(a.ContentKeys, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Accept object +func (a *Accept) SizeSSZ() (size int) { + size = 6 + + // Field (1) 'ContentKeys' + size += len(a.ContentKeys) + + return +} + +// HashTreeRoot ssz hashes the Accept object +func (a *Accept) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(a) +} + +// HashTreeRootWith ssz hashes the Accept object with a hasher +func (a *Accept) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'ConnectionId' + if size := len(a.ConnectionId); size != 2 { + err = ssz.ErrBytesLengthFn("Accept.ConnectionId", size, 2) + return + } + hh.PutBytes(a.ConnectionId) + + // Field (1) 'ContentKeys' + if len(a.ContentKeys) == 0 { + err = ssz.ErrEmptyBitlist + return + } + hh.PutBitlist(a.ContentKeys, 64) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Accept object +func (a *Accept) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(a) +}