feat:make portal wire out of p2p package

Signed-off-by: Chen Kai <281165273grape@gmail.com>
This commit is contained in:
Chen Kai 2024-11-20 22:19:37 +08:00
parent 8bb4364475
commit 71d8f7a842
27 changed files with 5138 additions and 258 deletions

View file

@ -163,7 +163,7 @@ func discv4Ping(ctx *cli.Context) error {
defer disc.Close()
start := time.Now()
if err := disc.Ping(n); err != nil {
if err := disc.PingWithoutResp(n); err != nil {
return fmt.Errorf("node didn't respond: %v", err)
}
fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start))

View file

@ -84,7 +84,7 @@ func discv5Ping(ctx *cli.Context) error {
disc, _ := startV5(ctx)
defer disc.Close()
fmt.Println(disc.Ping(n))
fmt.Println(disc.PingWithoutResp(n))
return nil
}

View file

@ -114,7 +114,7 @@ func (d *DiscV5API) GetEnr(nodeId string) (bool, error) {
if err != nil {
return false, err
}
n := d.DiscV5.tab.getNode(id)
n := d.DiscV5.tab.GetNode(id)
if n == nil {
return false, errors.New("record not in local routing table")
}
@ -128,7 +128,7 @@ func (d *DiscV5API) DeleteEnr(nodeId string) (bool, error) {
return false, err
}
n := d.DiscV5.tab.getNode(id)
n := d.DiscV5.tab.GetNode(id)
if n == nil {
return false, errors.New("record not in local routing table")
}
@ -161,7 +161,7 @@ func (d *DiscV5API) Ping(enr string) (*DiscV5PongResp, error) {
return nil, err
}
pong, err := d.DiscV5.pingInner(n)
pong, err := d.DiscV5.PingWithResp(n)
if err != nil {
return nil, err
}
@ -178,7 +178,7 @@ func (d *DiscV5API) FindNodes(enr string, distances []uint) ([]string, error) {
if err != nil {
return nil, err
}
findNodes, err := d.DiscV5.findnode(n, distances)
findNodes, err := d.DiscV5.Findnode(n, distances)
if err != nil {
return nil, err
}
@ -283,7 +283,7 @@ func (p *PortalProtocolAPI) GetEnr(nodeId string) (string, error) {
return p.portalProtocol.localNode.Node().String(), nil
}
n := p.portalProtocol.table.getNode(id)
n := p.portalProtocol.table.GetNode(id)
if n == nil {
return "", errors.New("record not in local routing table")
}
@ -297,7 +297,7 @@ func (p *PortalProtocolAPI) DeleteEnr(nodeId string) (bool, error) {
return false, err
}
n := p.portalProtocol.table.getNode(id)
n := p.portalProtocol.table.GetNode(id)
if n == nil {
return false, nil
}

View file

@ -24,30 +24,30 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
)
// lookup performs a network search for nodes close to the given target. It approaches the
// Lookup performs a network search for nodes close to the given target. It approaches the
// target by querying nodes that are closer to it on each iteration. The given target does
// not need to be an actual node identifier.
type lookup struct {
type Lookup struct {
tab *Table
queryfunc queryFunc
replyCh chan []*enode.Node
cancelCh <-chan struct{}
asked, seen map[enode.ID]bool
result nodesByDistance
result NodesByDistance
replyBuffer []*enode.Node
queries int
}
type queryFunc func(*enode.Node) ([]*enode.Node, error)
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
it := &lookup{
func NewLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *Lookup {
it := &Lookup{
tab: tab,
queryfunc: q,
asked: make(map[enode.ID]bool),
seen: make(map[enode.ID]bool),
result: nodesByDistance{target: target},
replyCh: make(chan []*enode.Node, alpha),
result: NodesByDistance{Target: target},
replyCh: make(chan []*enode.Node, Alpha),
cancelCh: ctx.Done(),
queries: -1,
}
@ -57,16 +57,16 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
return it
}
// run runs the lookup to completion and returns the closest nodes found.
func (it *lookup) run() []*enode.Node {
// Run runs the lookup to completion and returns the closest nodes found.
func (it *Lookup) Run() []*enode.Node {
for it.advance() {
}
return it.result.entries
return it.result.Entries
}
// advance advances the lookup until any new nodes have been found.
// It returns false when the lookup has ended.
func (it *lookup) advance() bool {
func (it *Lookup) advance() bool {
for it.startQueries() {
select {
case nodes := <-it.replyCh:
@ -74,7 +74,7 @@ func (it *lookup) advance() bool {
for _, n := range nodes {
if n != nil && !it.seen[n.ID()] {
it.seen[n.ID()] = true
it.result.push(n, bucketSize)
it.result.Push(n, BucketSize)
it.replyBuffer = append(it.replyBuffer, n)
}
}
@ -89,7 +89,7 @@ func (it *lookup) advance() bool {
return false
}
func (it *lookup) shutdown() {
func (it *Lookup) shutdown() {
for it.queries > 0 {
<-it.replyCh
it.queries--
@ -98,28 +98,28 @@ func (it *lookup) shutdown() {
it.replyBuffer = nil
}
func (it *lookup) startQueries() bool {
func (it *Lookup) startQueries() bool {
if it.queryfunc == nil {
return false
}
// The first query returns nodes from the local table.
if it.queries == -1 {
closest := it.tab.findnodeByID(it.result.target, bucketSize, false)
closest := it.tab.FindnodeByID(it.result.Target, BucketSize, false)
// Avoid finishing the lookup too quickly if table is empty. It'd be better to wait
// for the table to fill in this case, but there is no good mechanism for that
// yet.
if len(closest.entries) == 0 {
if len(closest.Entries) == 0 {
it.slowdown()
}
it.queries = 1
it.replyCh <- closest.entries
it.replyCh <- closest.Entries
return true
}
// Ask the closest nodes that we haven't asked yet.
for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ {
n := it.result.entries[i]
for i := 0; i < len(it.result.Entries) && it.queries < Alpha; i++ {
n := it.result.Entries[i]
if !it.asked[n.ID()] {
it.asked[n.ID()] = true
it.queries++
@ -130,7 +130,7 @@ func (it *lookup) startQueries() bool {
return it.queries > 0
}
func (it *lookup) slowdown() {
func (it *Lookup) slowdown() {
sleep := time.NewTimer(1 * time.Second)
defer sleep.Stop()
select {
@ -139,9 +139,9 @@ func (it *lookup) slowdown() {
}
}
func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) {
func (it *Lookup) query(n *enode.Node, reply chan<- []*enode.Node) {
r, err := it.queryfunc(n)
if !errors.Is(err, errClosed) { // avoid recording failures on shutdown.
if !errors.Is(err, ErrClosed) { // avoid recording failures on shutdown.
success := len(r) > 0
it.tab.trackRequest(n, success, r)
if err != nil {
@ -158,10 +158,10 @@ type lookupIterator struct {
nextLookup lookupFunc
ctx context.Context
cancel func()
lookup *lookup
lookup *Lookup
}
type lookupFunc func(ctx context.Context) *lookup
type lookupFunc func(ctx context.Context) *Lookup
func newLookupIterator(ctx context.Context, next lookupFunc) *lookupIterator {
ctx, cancel := context.WithCancel(ctx)

View file

@ -54,27 +54,27 @@ func (n *tableNode) String() string {
return n.Node.String()
}
// nodesByDistance is a list of nodes, ordered by distance to target.
type nodesByDistance struct {
entries []*enode.Node
target enode.ID
// NodesByDistance is a list of nodes, ordered by distance to target.
type NodesByDistance struct {
Entries []*enode.Node
Target enode.ID
}
// push adds the given node to the list, keeping the total size below maxElems.
func (h *nodesByDistance) push(n *enode.Node, maxElems int) {
ix := sort.Search(len(h.entries), func(i int) bool {
return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
// Push adds the given node to the list, keeping the total size below maxElems.
func (h *NodesByDistance) Push(n *enode.Node, maxElems int) {
ix := sort.Search(len(h.Entries), func(i int) bool {
return enode.DistCmp(h.Target, h.Entries[i].ID(), n.ID()) > 0
})
end := len(h.entries)
if len(h.entries) < maxElems {
h.entries = append(h.entries, n)
end := len(h.Entries)
if len(h.Entries) < maxElems {
h.Entries = append(h.Entries, n)
}
if ix < end {
// Slide existing entries down to make room.
// This will overwrite the entry we just appended.
copy(h.entries[ix+1:], h.entries[ix:])
h.entries[ix] = n
copy(h.Entries[ix+1:], h.Entries[ix:])
h.Entries[ix] = n
}
}

View file

@ -255,7 +255,7 @@ func (p *PortalProtocol) Start() error {
return err
}
go p.table.loop()
go p.table.Loop()
for i := 0; i < concurrentOffers; i++ {
go p.offerWorker()
@ -269,7 +269,7 @@ func (p *PortalProtocol) Start() error {
func (p *PortalProtocol) Stop() {
p.cancelCloseCtx()
p.table.close()
p.table.Close()
p.DiscV5.Close()
if p.Utp != nil {
p.Utp.Stop()
@ -335,7 +335,7 @@ func (p *PortalProtocol) setupDiscV5AndTable() error {
Log: p.Log,
}
p.table, err = newTable(p, p.localNode.Database(), cfg)
p.table, err = NewTable(p, p.localNode.Database(), cfg)
if err != nil {
return err
}
@ -343,7 +343,7 @@ func (p *PortalProtocol) setupDiscV5AndTable() error {
return nil
}
func (p *PortalProtocol) ping(node *enode.Node) (uint64, error) {
func (p *PortalProtocol) Ping(node *enode.Node) (uint64, error) {
pong, err := p.pingInner(node)
if err != nil {
return 0, err
@ -515,7 +515,7 @@ func (p *PortalProtocol) processOffer(target *enode.Node, resp []byte, request *
if metrics.Enabled {
p.portalMetrics.messagesReceivedAccept.Mark(1)
}
isAdded := p.table.addFoundNode(target, true)
isAdded := p.table.AddFoundNode(target, true)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
} else {
@ -651,7 +651,7 @@ func (p *PortalProtocol) processContent(target *enode.Node, resp []byte) (byte,
if metrics.Enabled {
p.portalMetrics.messagesReceivedContent.Mark(1)
}
isAdded := p.table.addFoundNode(target, true)
isAdded := p.table.AddFoundNode(target, true)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
} else {
@ -669,7 +669,7 @@ func (p *PortalProtocol) processContent(target *enode.Node, resp []byte) (byte,
if metrics.Enabled {
p.portalMetrics.messagesReceivedContent.Mark(1)
}
isAdded := p.table.addFoundNode(target, true)
isAdded := p.table.AddFoundNode(target, true)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
} else {
@ -729,7 +729,7 @@ func (p *PortalProtocol) processContent(target *enode.Node, resp []byte) (byte,
if metrics.Enabled {
p.portalMetrics.messagesReceivedContent.Mark(1)
}
isAdded := p.table.addFoundNode(target, true)
isAdded := p.table.AddFoundNode(target, true)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
} else {
@ -757,7 +757,7 @@ func (p *PortalProtocol) processNodes(target *enode.Node, resp []byte, distances
return nil, err
}
isAdded := p.table.addFoundNode(target, true)
isAdded := p.table.AddFoundNode(target, true)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
} else {
@ -828,7 +828,7 @@ func (p *PortalProtocol) processPong(target *enode.Node, resp []byte) (*portalwi
if metrics.Enabled {
p.portalMetrics.messagesReceivedPong.Mark(1)
}
isAdded := p.table.addFoundNode(target, true)
isAdded := p.table.AddFoundNode(target, true)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
} else {
@ -840,8 +840,8 @@ func (p *PortalProtocol) processPong(target *enode.Node, resp []byte) (*portalwi
}
func (p *PortalProtocol) handleTalkRequest(id enode.ID, addr *net.UDPAddr, msg []byte) []byte {
if n := p.DiscV5.getNode(id); n != nil {
p.table.addInboundNode(n)
if n := p.DiscV5.GetNode(id); n != nil {
p.table.AddInboundNode(n)
}
msgCode := msg[0]
@ -1377,7 +1377,7 @@ func (p *PortalProtocol) verifyResponseNode(sender *enode.Node, r *enr.Record, d
return nil, errors.New("not contained in netrestrict list")
}
if n.UDP() <= 1024 {
return nil, errLowPort
return nil, ErrLowPort
}
if distances != nil {
nd := enode.LogDist(sender.ID(), n.ID())
@ -1394,24 +1394,24 @@ func (p *PortalProtocol) verifyResponseNode(sender *enode.Node, r *enr.Record, d
// 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()
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) LookupSelf() []*enode.Node {
return p.newLookup(p.closeCtx, p.Self().ID()).Run()
}
func (p *PortalProtocol) newRandomLookup(ctx context.Context) *lookup {
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 *enode.Node) ([]*enode.Node, error) {
func (p *PortalProtocol) newLookup(ctx context.Context, target enode.ID) *Lookup {
return NewLookup(ctx, p.table, target, func(n *enode.Node) ([]*enode.Node, error) {
return p.lookupWorker(n, target)
})
}
@ -1419,28 +1419,28 @@ func (p *PortalProtocol) newLookup(ctx context.Context, target enode.ID) *lookup
// lookupWorker performs FINDNODE calls against a single node during lookup.
func (p *PortalProtocol) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.Node, error) {
var (
dists = lookupDistances(target, destNode.ID())
nodes = nodesByDistance{target: target}
dists = LookupDistances(target, destNode.ID())
nodes = NodesByDistance{Target: target}
err error
)
var r []*enode.Node
r, err = p.findNodes(destNode, dists)
if errors.Is(err, errClosed) {
if errors.Is(err, ErrClosed) {
return nil, err
}
for _, n := range r {
if n.ID() != p.Self().ID() {
isAdded := p.table.addFoundNode(n, false)
isAdded := p.table.AddFoundNode(n, false)
if isAdded {
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", n.IP(), "port", n.UDP())
} else {
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", n.IP(), "port", n.UDP())
}
nodes.push(n, portalFindnodesResultLimit)
nodes.Push(n, portalFindnodesResultLimit)
}
}
return nodes.entries, err
return nodes.Entries, err
}
func (p *PortalProtocol) offerWorker() {
@ -1496,13 +1496,13 @@ func (p *PortalProtocol) findNodesCloseToContent(contentId []byte, limit int) []
// Lookup performs a recursive lookup for the given target.
// It returns the closest nodes to target.
func (p *PortalProtocol) Lookup(target enode.ID) []*enode.Node {
return p.newLookup(p.closeCtx, target).run()
return p.newLookup(p.closeCtx, target).Run()
}
// Resolve searches for a specific Node with the given ID and tries to get the most recent
// version of the Node record for it. It returns n if the Node could not be resolved.
func (p *PortalProtocol) Resolve(n *enode.Node) *enode.Node {
if intable := p.table.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
if intable := p.table.GetNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
n = intable
}
// Try asking directly. This works if the Node is still responding on the endpoint we have.
@ -1527,7 +1527,7 @@ func (p *PortalProtocol) ResolveNodeId(id enode.ID) *enode.Node {
return p.Self()
}
n := p.table.getNode(id)
n := p.table.GetNode(id)
if n != nil {
p.Log.Debug("found Id in table and will request enr from the node", "id", id.String())
// Try asking directly. This works if the Node is still responding on the endpoint we have.
@ -1564,7 +1564,7 @@ func (p *PortalProtocol) collectTableNodes(rip net.IP, distances []uint, limit i
processed[dist] = struct{}{}
checkLive := !p.table.cfg.NoFindnodeLivenessCheck
for _, n := range p.table.appendBucketNodes(dist, bn[:0], checkLive) {
for _, n := range p.table.AppendBucketNodes(dist, bn[:0], checkLive) {
// Apply some pre-checks to avoid sending invalid nodes.
// Note liveness is checked by appendLiveNodes.
if netutil.CheckRelayIP(rip, n.IP()) != nil {
@ -1582,7 +1582,7 @@ func (p *PortalProtocol) collectTableNodes(rip net.IP, distances []uint, limit i
func (p *PortalProtocol) ContentLookup(contentKey, contentId []byte) ([]byte, bool, error) {
lookupContext, cancel := context.WithCancel(context.Background())
resChan := make(chan *traceContentInfoResp, alpha)
resChan := make(chan *traceContentInfoResp, Alpha)
hasResult := int32(0)
result := ContentInfoResp{}
@ -1600,9 +1600,9 @@ func (p *PortalProtocol) ContentLookup(contentKey, contentId []byte) ([]byte, bo
}
}()
newLookup(lookupContext, p.table, enode.ID(contentId), func(n *enode.Node) ([]*enode.Node, error) {
NewLookup(lookupContext, p.table, enode.ID(contentId), func(n *enode.Node) ([]*enode.Node, error) {
return p.contentLookupWorker(n, contentKey, resChan, cancel, &hasResult)
}).run()
}).Run()
close(resChan)
wg.Wait()
@ -1616,7 +1616,7 @@ func (p *PortalProtocol) ContentLookup(contentKey, contentId []byte) ([]byte, bo
func (p *PortalProtocol) TraceContentLookup(contentKey, contentId []byte) (*TraceContentResult, error) {
lookupContext, cancel := context.WithCancel(context.Background())
// resp channel
resChan := make(chan *traceContentInfoResp, alpha)
resChan := make(chan *traceContentInfoResp, Alpha)
hasResult := int32(0)
@ -1633,10 +1633,10 @@ func (p *PortalProtocol) TraceContentLookup(contentKey, contentId []byte) (*Trac
Cancelled: make([]string, 0),
}
nodes := p.table.findnodeByID(enode.ID(contentId), bucketSize, false)
nodes := p.table.FindnodeByID(enode.ID(contentId), BucketSize, false)
localResponse := make([]string, 0, len(nodes.entries))
for _, node := range nodes.entries {
localResponse := make([]string, 0, len(nodes.Entries))
for _, node := range nodes.Entries {
id := "0x" + node.ID().String()
localResponse = append(localResponse, id)
}
@ -1698,10 +1698,10 @@ func (p *PortalProtocol) TraceContentLookup(contentKey, contentId []byte) (*Trac
}
}()
lookup := newLookup(lookupContext, p.table, enode.ID(contentId), func(n *enode.Node) ([]*enode.Node, error) {
lookup := NewLookup(lookupContext, p.table, enode.ID(contentId), func(n *enode.Node) ([]*enode.Node, error) {
return p.contentLookupWorker(n, contentKey, resChan, cancel, &hasResult)
})
lookup.run()
lookup.Run()
close(resChan)
wg.Wait()

View file

@ -122,7 +122,7 @@ func (p *PortalUtp) packetRouterFunc(buf []byte, id enode.ID, addr *net.UDPAddr)
if n, ok := p.discV5.GetCachedNode(addr.String()); ok {
//_, err := p.DiscV5.TalkRequestToID(id, addr, string(portalwire.UTPNetwork), buf)
req := &v5wire.TalkRequest{Protocol: string(portalwire.Utp), Message: buf}
p.discV5.sendFromAnotherThreadWithNode(n, netip.AddrPortFrom(netutil.IPToAddr(addr.IP), uint16(addr.Port)), req)
p.discV5.SendFromAnotherThreadWithNode(n, netip.AddrPortFrom(netutil.IPToAddr(addr.IP), uint16(addr.Port)), req)
return len(buf), nil
} else {

View file

@ -39,8 +39,8 @@ import (
)
const (
alpha = 3 // Kademlia concurrency factor
bucketSize = 16 // Kademlia bucket size
Alpha = 3 // Kademlia concurrency factor
BucketSize = 16 // Kademlia bucket size
maxReplacements = 10 // Size of per-bucket replacement list
// We keep buckets for the upper 1/15 of distances because
@ -92,9 +92,9 @@ type Table struct {
type transport interface {
Self() *enode.Node
RequestENR(*enode.Node) (*enode.Node, error)
lookupRandom() []*enode.Node
lookupSelf() []*enode.Node
ping(*enode.Node) (seq uint64, err error)
LookupRandom() []*enode.Node
LookupSelf() []*enode.Node
Ping(*enode.Node) (seq uint64, err error)
}
// bucket contains nodes, ordered by their last activity. the entry
@ -118,7 +118,7 @@ type trackRequestOp struct {
success bool
}
func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
func NewTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
cfg = cfg.withDefaults()
tab := &Table{
net: t,
@ -196,8 +196,8 @@ func (tab *Table) self() *enode.Node {
return tab.net.Self()
}
// getNode returns the node with the given ID or nil if it isn't in the table.
func (tab *Table) getNode(id enode.ID) *enode.Node {
// GetNode returns the node with the given ID or nil if it isn't in the table.
func (tab *Table) GetNode(id enode.ID) *enode.Node {
tab.mutex.Lock()
defer tab.mutex.Unlock()
@ -210,8 +210,8 @@ func (tab *Table) getNode(id enode.ID) *enode.Node {
return nil
}
// close terminates the network listener and flushes the node database.
func (tab *Table) close() {
// Close terminates the network listener and flushes the node database.
func (tab *Table) Close() {
close(tab.closeReq)
<-tab.closed
}
@ -255,40 +255,40 @@ func (tab *Table) refresh() <-chan struct{} {
return done
}
// findnodeByID returns the n nodes in the table that are closest to the given id.
// FindnodeByID returns the n nodes in the table that are closest to the given id.
// This is used by the FINDNODE/v4 handler.
//
// The preferLive parameter says whether the caller wants liveness-checked results. If
// preferLive is true and the table contains any verified nodes, the result will not
// contain unverified nodes. However, if there are no verified nodes at all, the result
// will contain unverified nodes.
func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance {
func (tab *Table) FindnodeByID(target enode.ID, nresults int, preferLive bool) *NodesByDistance {
tab.mutex.Lock()
defer tab.mutex.Unlock()
// Scan all buckets. There might be a better way to do this, but there aren't that many
// buckets, so this solution should be fine. The worst-case complexity of this loop
// is O(tab.len() * nresults).
nodes := &nodesByDistance{target: target}
liveNodes := &nodesByDistance{target: target}
nodes := &NodesByDistance{Target: target}
liveNodes := &NodesByDistance{Target: target}
for _, b := range &tab.buckets {
for _, n := range b.entries {
nodes.push(n.Node, nresults)
nodes.Push(n.Node, nresults)
if preferLive && n.isValidatedLive {
liveNodes.push(n.Node, nresults)
liveNodes.Push(n.Node, nresults)
}
}
}
if preferLive && len(liveNodes.entries) > 0 {
if preferLive && len(liveNodes.Entries) > 0 {
return liveNodes
}
return nodes
}
// appendBucketNodes adds nodes at the given distance to the result slice.
// AppendBucketNodes adds nodes at the given distance to the result slice.
// This is used by the FINDNODE/v5 handler.
func (tab *Table) appendBucketNodes(dist uint, result []*enode.Node, checkLive bool) []*enode.Node {
func (tab *Table) AppendBucketNodes(dist uint, result []*enode.Node, checkLive bool) []*enode.Node {
if dist > 256 {
return result
}
@ -322,12 +322,12 @@ func (tab *Table) len() (n int) {
return n
}
// addFoundNode adds a node which may not be live. If the bucket has space available,
// AddFoundNode adds a node which may not be live. If the bucket has space available,
// adding the node succeeds immediately. Otherwise, the node is added to the replacements
// list.
//
// The caller must not hold tab.mutex.
func (tab *Table) addFoundNode(n *enode.Node, forceSetLive bool) bool {
func (tab *Table) AddFoundNode(n *enode.Node, forceSetLive bool) bool {
op := addNodeOp{node: n, isInbound: false, forceSetLive: forceSetLive}
select {
case tab.addNodeCh <- op:
@ -337,7 +337,7 @@ func (tab *Table) addFoundNode(n *enode.Node, forceSetLive bool) bool {
}
}
// addInboundNode adds a node from an inbound contact. If the bucket has no space, the
// AddInboundNode adds a node from an inbound contact. If the bucket has no space, the
// node is added to the replacements list.
//
// There is an additional safety measure: if the table is still initializing the node is
@ -345,7 +345,7 @@ func (tab *Table) addFoundNode(n *enode.Node, forceSetLive bool) bool {
// repeatedly.
//
// The caller must not hold tab.mutex.
func (tab *Table) addInboundNode(n *enode.Node) bool {
func (tab *Table) AddInboundNode(n *enode.Node) bool {
op := addNodeOp{node: n, isInbound: true}
select {
case tab.addNodeCh <- op:
@ -363,8 +363,8 @@ func (tab *Table) trackRequest(n *enode.Node, success bool, foundNodes []*enode.
}
}
// loop is the main loop of Table.
func (tab *Table) loop() {
// Loop is the main loop of Table.
func (tab *Table) Loop() {
var (
refresh = time.NewTimer(tab.nextRefreshTime())
refreshDone = make(chan struct{}) // where doRefresh reports completion
@ -447,7 +447,7 @@ func (tab *Table) doRefresh(done chan struct{}) {
tab.loadSeedNodes()
// Run self lookup to discover new neighbor nodes.
tab.net.lookupSelf()
tab.net.LookupSelf()
// The Kademlia paper specifies that the bucket refresh should
// perform a lookup in the least recently used bucket. We cannot
@ -456,7 +456,7 @@ func (tab *Table) doRefresh(done chan struct{}) {
// sha3 preimage that falls into a chosen bucket.
// We perform a few lookups with a random target instead.
for i := 0; i < 3; i++ {
tab.net.lookupRandom()
tab.net.LookupRandom()
}
}
@ -542,7 +542,7 @@ func (tab *Table) handleAddNode(req addNodeOp) bool {
tab.log.Debug("the node is already in table", "id", req.node.ID())
return false
}
if len(b.entries) >= bucketSize {
if len(b.entries) >= BucketSize {
// Bucket full, maybe add as replacement.
tab.log.Debug("the bucket is full and will add in replacement", "id", req.node.ID())
tab.addReplacement(b, req.node)
@ -697,7 +697,7 @@ func (tab *Table) handleTrackRequest(op trackRequestOp) {
// many times, but only if there are enough other nodes in the bucket. This latter
// condition specifically exists to make bootstrapping in smaller test networks more
// reliable.
if fails >= maxFindnodeFailures && len(b.entries) >= bucketSize/4 {
if fails >= maxFindnodeFailures && len(b.entries) >= BucketSize/4 {
tab.deleteInBucket(b, op.node.ID())
}
@ -717,3 +717,32 @@ func pushNode(list []*tableNode, n *tableNode, max int) ([]*tableNode, *tableNod
list[0] = n
return list, removed
}
func (tab *Table) WaitInit() {
<-tab.initDone
}
func (tab *Table) NodeIds() [][]string {
tab.mutex.Lock()
defer tab.mutex.Unlock()
nodes := make([][]string, 0)
for _, b := range &tab.buckets {
bucketNodes := make([]string, 0)
for _, n := range b.entries {
bucketNodes = append(bucketNodes, "0x"+n.ID().String())
}
nodes = append(nodes, bucketNodes)
}
return nodes
}
func (tab *Table) Config() Config {
return tab.cfg
}
func (tab *Table) DeleteNode(n *enode.Node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.bucket(n.ID())
tab.deleteInBucket(b, n.ID())
}

View file

@ -111,7 +111,7 @@ func (tr *tableRevalidation) startRequest(tab *Table, n *tableNode) {
func (tab *Table) doRevalidate(resp revalidationResponse, node *enode.Node) {
// Ping the selected node and wait for a pong response.
remoteSeq, err := tab.net.ping(node)
remoteSeq, err := tab.net.Ping(node)
resp.didRespond = err == nil
// Also fetch record if the node replied and returned a higher sequence number.

View file

@ -63,7 +63,7 @@ func TestRevalidation_nodeRemoved(t *testing.T) {
tr.handleResponse(tab, resp)
// Ensure the node was not re-added to the table.
if tab.getNode(node.ID()) != nil {
if tab.GetNode(node.ID()) != nil {
t.Fatal("node was re-added to Table")
}
if tr.fast.contains(node.ID()) || tr.slow.contains(node.ID()) {

View file

@ -59,7 +59,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
Log: testlog.Logger(t, log.LevelTrace),
})
defer db.Close()
defer tab.close()
defer tab.Close()
<-tab.initDone
@ -79,7 +79,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
transport.dead[replacementNode.ID()] = !newNodeIsResponding
// Add replacement node to table.
tab.addFoundNode(replacementNode, false)
tab.AddFoundNode(replacementNode, false)
t.Log("last:", last.ID())
t.Log("replacement:", replacementNode.ID())
@ -108,7 +108,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
// Check bucket content.
tab.mutex.Lock()
defer tab.mutex.Unlock()
wantSize := bucketSize
wantSize := BucketSize
if !lastInBucketIsResponding && !newNodeIsResponding {
wantSize--
}
@ -150,11 +150,11 @@ func TestTable_IPLimit(t *testing.T) {
transport := newPingRecorder()
tab, db := newTestTable(transport, Config{})
defer db.Close()
defer tab.close()
defer tab.Close()
for i := 0; i < tableIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
tab.addFoundNode(n, false)
tab.AddFoundNode(n, false)
}
if tab.len() > tableIPLimit {
t.Errorf("too many nodes in table")
@ -167,12 +167,12 @@ func TestTable_BucketIPLimit(t *testing.T) {
transport := newPingRecorder()
tab, db := newTestTable(transport, Config{})
defer db.Close()
defer tab.close()
defer tab.Close()
d := 3
for i := 0; i < bucketIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
tab.addFoundNode(n, false)
tab.AddFoundNode(n, false)
}
if tab.len() > bucketIPLimit {
t.Errorf("too many nodes in table")
@ -204,11 +204,11 @@ func TestTable_findnodeByID(t *testing.T) {
transport := newPingRecorder()
tab, db := newTestTable(transport, Config{})
defer db.Close()
defer tab.close()
defer tab.Close()
fillTable(tab, test.All, true)
// check that closest(Target, N) returns nodes
result := tab.findnodeByID(test.Target, test.N, false).entries
result := tab.FindnodeByID(test.Target, test.N, false).Entries
if hasDuplicates(result) {
t.Errorf("result contains duplicates")
return false
@ -264,7 +264,7 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
t := &closeTest{
Self: gen(enode.ID{}, rand).(enode.ID),
Target: gen(enode.ID{}, rand).(enode.ID),
N: rand.Intn(bucketSize),
N: rand.Intn(BucketSize),
}
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
r := new(enr.Record)
@ -279,20 +279,20 @@ func TestTable_addInboundNode(t *testing.T) {
tab, db := newTestTable(newPingRecorder(), Config{})
<-tab.initDone
defer db.Close()
defer tab.close()
defer tab.Close()
// Insert two nodes.
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
tab.addFoundNode(n1, false)
tab.addFoundNode(n2, false)
tab.AddFoundNode(n1, false)
tab.AddFoundNode(n2, false)
checkBucketContent(t, tab, []*enode.Node{n1, n2})
// Add a changed version of n2. The bucket should be updated.
newrec := n2.Record()
newrec.Set(enr.IP{99, 99, 99, 99})
n2v2 := enode.SignNull(newrec, n2.ID())
tab.addInboundNode(n2v2)
tab.AddInboundNode(n2v2)
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
// Try updating n2 without sequence number change. The update is accepted
@ -301,7 +301,7 @@ func TestTable_addInboundNode(t *testing.T) {
newrec.Set(enr.IP{100, 100, 100, 100})
newrec.SetSeq(n2.Seq())
n2v3 := enode.SignNull(newrec, n2.ID())
tab.addInboundNode(n2v3)
tab.AddInboundNode(n2v3)
checkBucketContent(t, tab, []*enode.Node{n1, n2v3})
}
@ -309,20 +309,20 @@ func TestTable_addFoundNode(t *testing.T) {
tab, db := newTestTable(newPingRecorder(), Config{})
<-tab.initDone
defer db.Close()
defer tab.close()
defer tab.Close()
// Insert two nodes.
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
tab.addFoundNode(n1, false)
tab.addFoundNode(n2, false)
tab.AddFoundNode(n1, false)
tab.AddFoundNode(n2, false)
checkBucketContent(t, tab, []*enode.Node{n1, n2})
// Add a changed version of n2. The bucket should be updated.
newrec := n2.Record()
newrec.Set(enr.IP{99, 99, 99, 99})
n2v2 := enode.SignNull(newrec, n2.ID())
tab.addFoundNode(n2v2, false)
tab.AddFoundNode(n2v2, false)
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
// Try updating n2 without a sequence number change.
@ -331,7 +331,7 @@ func TestTable_addFoundNode(t *testing.T) {
newrec.Set(enr.IP{100, 100, 100, 100})
newrec.SetSeq(n2.Seq())
n2v3 := enode.SignNull(newrec, n2.ID())
tab.addFoundNode(n2v3, false)
tab.AddFoundNode(n2v3, false)
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
}
@ -340,18 +340,18 @@ func TestTable_addInboundNodeUpdateV4Accept(t *testing.T) {
tab, db := newTestTable(newPingRecorder(), Config{})
<-tab.initDone
defer db.Close()
defer tab.close()
defer tab.Close()
// Add a v4 node.
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
tab.addInboundNode(n1)
tab.AddInboundNode(n1)
checkBucketContent(t, tab, []*enode.Node{n1})
// Add an updated version with changed IP.
// The update will be accepted because it is inbound.
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
tab.addInboundNode(n1v2)
tab.AddInboundNode(n1v2)
checkBucketContent(t, tab, []*enode.Node{n1v2})
}
@ -361,18 +361,18 @@ func TestTable_addFoundNodeV4UpdateReject(t *testing.T) {
tab, db := newTestTable(newPingRecorder(), Config{})
<-tab.initDone
defer db.Close()
defer tab.close()
defer tab.Close()
// Add a v4 node.
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
tab.addFoundNode(n1, false)
tab.AddFoundNode(n1, false)
checkBucketContent(t, tab, []*enode.Node{n1})
// Add an updated version with changed IP.
// The update won't be accepted because it isn't inbound.
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
tab.addFoundNode(n1v2, false)
tab.AddFoundNode(n1v2, false)
checkBucketContent(t, tab, []*enode.Node{n1})
}
@ -407,14 +407,14 @@ func TestTable_revalidateSyncRecord(t *testing.T) {
})
<-tab.initDone
defer db.Close()
defer tab.close()
defer tab.Close()
// Insert a node.
var r enr.Record
r.Set(enr.IP(net.IP{127, 0, 0, 1}))
id := enode.ID{1}
n1 := enode.SignNull(&r, id)
tab.addFoundNode(n1, false)
tab.AddFoundNode(n1, false)
// Update the node record.
r.Set(enr.WithEntry("foo", "bar"))
@ -426,7 +426,7 @@ func TestTable_revalidateSyncRecord(t *testing.T) {
waitForRevalidationPing(t, transport, tab, n2.ID())
waitForRevalidationPing(t, transport, tab, n2.ID())
intable := tab.getNode(id)
intable := tab.GetNode(id)
if !reflect.DeepEqual(intable, n2) {
t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq())
}
@ -448,22 +448,22 @@ func TestNodesPush(t *testing.T) {
// Insert all permutations into lists with size limit 3.
for _, nodes := range perm {
list := nodesByDistance{target: target}
list := NodesByDistance{Target: target}
for _, n := range nodes {
list.push(n, 3)
list.Push(n, 3)
}
if !slices.EqualFunc(list.entries, perm[0], nodeIDEqual) {
if !slices.EqualFunc(list.Entries, perm[0], nodeIDEqual) {
t.Fatal("not equal")
}
}
// Insert all permutations into lists with size limit 2.
for _, nodes := range perm {
list := nodesByDistance{target: target}
list := NodesByDistance{Target: target}
for _, n := range nodes {
list.push(n, 2)
list.Push(n, 2)
}
if !slices.EqualFunc(list.entries, perm[0][:2], nodeIDEqual) {
if !slices.EqualFunc(list.Entries, perm[0][:2], nodeIDEqual) {
t.Fatal("not equal")
}
}

View file

@ -45,14 +45,14 @@ func init() {
func newTestTable(t transport, cfg Config) (*Table, *enode.DB) {
tab, db := newInactiveTestTable(t, cfg)
go tab.loop()
go tab.Loop()
return tab, db
}
// newInactiveTestTable creates a Table without running the main loop.
func newInactiveTestTable(t transport, cfg Config) (*Table, *enode.DB) {
db, _ := enode.OpenDB("")
tab, _ := newTable(t, db, cfg)
tab, _ := NewTable(t, db, cfg)
return tab, db
}
@ -110,20 +110,20 @@ func intIP(i int) net.IP {
func fillBucket(tab *Table, id enode.ID) (last *tableNode) {
ld := enode.LogDist(tab.self().ID(), id)
b := tab.bucket(id)
for len(b.entries) < bucketSize {
for len(b.entries) < BucketSize {
node := nodeAtDistance(tab.self().ID(), ld, intIP(ld))
if !tab.addFoundNode(node, false) {
if !tab.AddFoundNode(node, false) {
panic("node not added")
}
}
return b.entries[bucketSize-1]
return b.entries[BucketSize-1]
}
// fillTable adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex.
func fillTable(tab *Table, nodes []*enode.Node, setLive bool) {
for _, n := range nodes {
tab.addFoundNode(n, setLive)
tab.AddFoundNode(n, setLive)
}
}
@ -160,8 +160,8 @@ func (t *pingRecorder) updateRecord(n *enode.Node) {
// Stubs to satisfy the transport interface.
func (t *pingRecorder) Self() *enode.Node { return nullNode }
func (t *pingRecorder) lookupSelf() []*enode.Node { return nil }
func (t *pingRecorder) lookupRandom() []*enode.Node { return nil }
func (t *pingRecorder) LookupSelf() []*enode.Node { return nil }
func (t *pingRecorder) LookupRandom() []*enode.Node { return nil }
func (t *pingRecorder) waitPing(timeout time.Duration) *enode.Node {
t.mu.Lock()
@ -190,7 +190,7 @@ func (t *pingRecorder) waitPing(timeout time.Duration) *enode.Node {
}
// ping simulates a ping request.
func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) {
func (t *pingRecorder) Ping(n *enode.Node) (seq uint64, err error) {
t.mu.Lock()
defer t.mu.Unlock()

View file

@ -59,8 +59,8 @@ func TestUDPv4_Lookup(t *testing.T) {
for _, e := range results {
t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.target.ID(), e.ID()), e.ID().Bytes())
}
if len(results) != bucketSize {
t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
if len(results) != BucketSize {
t.Errorf("wrong number of results: got %d, want %d", len(results), BucketSize)
}
checkLookupResults(t, lookupTestnet, results)
}

View file

@ -43,8 +43,8 @@ var (
errUnknownNode = errors.New("unknown node")
errTimeout = errors.New("RPC timeout")
errClockWarp = errors.New("reply deadline too far in the future")
errClosed = errors.New("socket closed")
errLowPort = errors.New("low port")
ErrClosed = errors.New("socket closed")
ErrLowPort = errors.New("low port")
errNoUDPEndpoint = errors.New("node has no UDP endpoint")
)
@ -143,12 +143,12 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
log: cfg.Log,
}
tab, err := newTable(t, ln.Database(), cfg)
tab, err := NewTable(t, ln.Database(), cfg)
if err != nil {
return nil, err
}
t.tab = tab
go tab.loop()
go tab.Loop()
t.wg.Add(2)
go t.loop()
@ -167,7 +167,7 @@ func (t *UDPv4) Close() {
t.cancelCloseCtx()
t.conn.Close()
t.wg.Wait()
t.tab.close()
t.tab.Close()
})
}
@ -179,7 +179,7 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
return rn
}
// Check table for the ID, we might have a newer version there.
if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
if intable := t.tab.GetNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
n = intable
if rn, err := t.RequestENR(n); err == nil {
return rn
@ -210,14 +210,14 @@ func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
return v4wire.NewEndpoint(addr, uint16(node.TCP()))
}
// Ping sends a ping message to the given node.
func (t *UDPv4) Ping(n *enode.Node) error {
_, err := t.ping(n)
// PingWithoutResp sends a ping message to the given node.
func (t *UDPv4) PingWithoutResp(n *enode.Node) error {
_, err := t.Ping(n)
return err
}
// ping sends a ping message to the given node and waits for a reply.
func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
func (t *UDPv4) Ping(n *enode.Node) (seq uint64, err error) {
addr, ok := n.UDPEndpoint()
if !ok {
return 0, errNoUDPEndpoint
@ -271,7 +271,7 @@ func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node {
// case and run the bootstrapping logic.
<-t.tab.refresh()
}
return t.newLookup(t.closeCtx, v4wire.EncodePubkey(key)).run()
return t.newLookup(t.closeCtx, v4wire.EncodePubkey(key)).Run()
}
// RandomNodes is an iterator yielding nodes from a random walk of the DHT.
@ -280,25 +280,25 @@ func (t *UDPv4) RandomNodes() enode.Iterator {
}
// lookupRandom implements transport.
func (t *UDPv4) lookupRandom() []*enode.Node {
return t.newRandomLookup(t.closeCtx).run()
func (t *UDPv4) LookupRandom() []*enode.Node {
return t.newRandomLookup(t.closeCtx).Run()
}
// lookupSelf implements transport.
func (t *UDPv4) lookupSelf() []*enode.Node {
func (t *UDPv4) LookupSelf() []*enode.Node {
pubkey := v4wire.EncodePubkey(&t.priv.PublicKey)
return t.newLookup(t.closeCtx, pubkey).run()
return t.newLookup(t.closeCtx, pubkey).Run()
}
func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
func (t *UDPv4) newRandomLookup(ctx context.Context) *Lookup {
var target v4wire.Pubkey
crand.Read(target[:])
return t.newLookup(ctx, target)
}
func (t *UDPv4) newLookup(ctx context.Context, targetKey v4wire.Pubkey) *lookup {
func (t *UDPv4) newLookup(ctx context.Context, targetKey v4wire.Pubkey) *Lookup {
target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
it := newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
it := NewLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
addr, ok := n.UDPEndpoint()
if !ok {
return nil, errNoUDPEndpoint
@ -315,7 +315,7 @@ func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire
// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
// active until enough nodes have been received.
nodes := make([]*enode.Node, 0, bucketSize)
nodes := make([]*enode.Node, 0, BucketSize)
nreceived := 0
rm := t.pending(toid, toAddrPort.Addr(), v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
reply := r.(*v4wire.Neighbors)
@ -328,7 +328,7 @@ func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire
}
nodes = append(nodes, n)
}
return true, nreceived >= bucketSize
return true, nreceived >= BucketSize
})
t.send(toAddrPort, toid, &v4wire.Findnode{
Target: target,
@ -400,7 +400,7 @@ func (t *UDPv4) pending(id enode.ID, ip netip.Addr, ptype byte, callback replyMa
case t.addReplyMatcher <- p:
// loop will handle it
case <-t.closeCtx.Done():
ch <- errClosed
ch <- ErrClosed
}
return p
}
@ -461,7 +461,7 @@ func (t *UDPv4) loop() {
select {
case <-t.closeCtx.Done():
for el := plist.Front(); el != nil; el = el.Next() {
el.Value.(*replyMatcher).errc <- errClosed
el.Value.(*replyMatcher).errc <- ErrClosed
}
return
@ -599,7 +599,7 @@ func (t *UDPv4) ensureBond(toid enode.ID, toaddr netip.AddrPort) {
func (t *UDPv4) nodeFromRPC(sender netip.AddrPort, rn v4wire.Node) (*enode.Node, error) {
if rn.UDP <= 1024 {
return nil, errLowPort
return nil, ErrLowPort
}
if err := netutil.CheckRelayIP(sender.Addr().AsSlice(), rn.IP); err != nil {
return nil, err
@ -692,10 +692,10 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode
n := enode.NewV4(h.senderKey, fromIP, int(req.From.TCP), int(from.Port()))
if time.Since(t.db.LastPongReceived(n.ID(), from.Addr())) > bondExpiration {
t.sendPing(fromID, from, func() {
t.tab.addInboundNode(n)
t.tab.AddInboundNode(n)
})
} else {
t.tab.addInboundNode(n)
t.tab.AddInboundNode(n)
}
// Update node database and endpoint predictor.
@ -747,7 +747,7 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from netip.AddrPort, fromID e
// Determine closest nodes.
target := enode.ID(crypto.Keccak256Hash(req.Target[:]))
preferLive := !t.tab.cfg.NoFindnodeLivenessCheck
closest := t.tab.findnodeByID(target, bucketSize, preferLive).entries
closest := t.tab.FindnodeByID(target, BucketSize, preferLive).Entries
// Send neighbors in chunks with at most maxNeighbors per packet
// to stay below the packet size limit.

View file

@ -112,7 +112,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
test.t.Helper()
dgram, err := test.pipe.receive()
if err == errClosed {
if err == ErrClosed {
return true
} else if err != nil {
test.t.Error("packet receive error:", err)
@ -151,7 +151,7 @@ func TestUDPv4_pingTimeout(t *testing.T) {
key := newkey()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port)
if _, err := test.udp.ping(node); err != errTimeout {
if _, err := test.udp.Ping(node); err != errTimeout {
t.Error("expected timeout error, got", err)
}
}
@ -256,9 +256,9 @@ func TestUDPv4_findnode(t *testing.T) {
// put a few nodes into the table. their exact
// distribution shouldn't matter much, although we need to
// take care not to overflow any bucket.
nodes := &nodesByDistance{target: testTarget.ID()}
nodes := &NodesByDistance{Target: testTarget.ID()}
live := make(map[enode.ID]bool)
numCandidates := 2 * bucketSize
numCandidates := 2 * BucketSize
for i := 0; i < numCandidates; i++ {
key := newkey()
ip := net.IP{10, 13, 0, byte(i)}
@ -267,8 +267,8 @@ func TestUDPv4_findnode(t *testing.T) {
if i > numCandidates/2 {
live[n.ID()] = true
}
test.table.addFoundNode(n, live[n.ID()])
nodes.push(n, numCandidates)
test.table.AddFoundNode(n, live[n.ID()])
nodes.Push(n, numCandidates)
}
// ensure there's a bond with the test node,
@ -277,7 +277,7 @@ func TestUDPv4_findnode(t *testing.T) {
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.Addr(), time.Now())
// check that closest neighbors are returned.
expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
expected := test.table.FindnodeByID(testTarget.ID(), BucketSize, true)
test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
waitNeighbors := func(want []*enode.Node) {
test.waitPacketOut(func(p *v4wire.Neighbors, to netip.AddrPort, hash []byte) {
@ -287,7 +287,7 @@ func TestUDPv4_findnode(t *testing.T) {
}
for i, n := range p.Nodes {
if n.ID.ID() != want[i].ID() {
t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i])
t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.Entries[i])
}
if !live[n.ID.ID()] {
t.Errorf("result includes dead node %v", n.ID.ID())
@ -296,7 +296,7 @@ func TestUDPv4_findnode(t *testing.T) {
})
}
// Receive replies.
want := expected.entries
want := expected.Entries
if len(want) > v4wire.MaxNeighbors {
waitNeighbors(want[:v4wire.MaxNeighbors])
want = want[v4wire.MaxNeighbors:]
@ -644,7 +644,7 @@ func (c *dgramPipe) receive() (dgram, error) {
c.cond.Wait()
}
if c.closed {
return dgram{}, errClosed
return dgram{}, ErrClosed
}
if timedOut {
return dgram{}, errTimeout

View file

@ -140,7 +140,7 @@ func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
if err != nil {
return nil, err
}
go t.tab.loop()
go t.tab.Loop()
t.wg.Add(2)
go t.readLoop()
go t.dispatch()
@ -180,7 +180,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
cancelCloseCtx: cancelCloseCtx,
}
t.talk = newTalkSystem(t)
tab, err := newTable(t, t.db, cfg)
tab, err := NewTable(t, t.db, cfg)
if err != nil {
return nil, err
}
@ -200,20 +200,20 @@ func (t *UDPv5) Close() {
t.conn.Close()
t.talk.wait()
t.wg.Wait()
t.tab.close()
t.tab.Close()
})
}
// Ping sends a ping message to the given node.
func (t *UDPv5) Ping(n *enode.Node) error {
_, err := t.ping(n)
// PingWithoutResp sends a ping message to the given node.
func (t *UDPv5) PingWithoutResp(n *enode.Node) error {
_, err := t.Ping(n)
return err
}
// Resolve searches for a specific node with the given ID and tries to get the most recent
// version of the node record for it. It returns n if the node could not be resolved.
func (t *UDPv5) Resolve(n *enode.Node) *enode.Node {
if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
if intable := t.tab.GetNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
n = intable
}
// Try asking directly. This works if the node is still responding on the endpoint we have.
@ -237,7 +237,7 @@ func (t *UDPv5) ResolveNodeId(id enode.ID) *enode.Node {
return t.Self()
}
n := t.tab.getNode(id)
n := t.tab.GetNode(id)
if n != nil {
// Try asking directly. This works if the Node is still responding on the endpoint we have.
if resp, err := t.RequestENR(n); err == nil {
@ -341,29 +341,29 @@ func (t *UDPv5) RandomNodes() enode.Iterator {
// Lookup performs a recursive lookup for the given target.
// It returns the closest nodes to target.
func (t *UDPv5) Lookup(target enode.ID) []*enode.Node {
return t.newLookup(t.closeCtx, target).run()
return t.newLookup(t.closeCtx, target).Run()
}
// lookupRandom looks up a random target.
// This is needed to satisfy the transport interface.
func (t *UDPv5) lookupRandom() []*enode.Node {
return t.newRandomLookup(t.closeCtx).run()
func (t *UDPv5) LookupRandom() []*enode.Node {
return t.newRandomLookup(t.closeCtx).Run()
}
// lookupSelf looks up our own node ID.
// This is needed to satisfy the transport interface.
func (t *UDPv5) lookupSelf() []*enode.Node {
return t.newLookup(t.closeCtx, t.Self().ID()).run()
func (t *UDPv5) LookupSelf() []*enode.Node {
return t.newLookup(t.closeCtx, t.Self().ID()).Run()
}
func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
func (t *UDPv5) newRandomLookup(ctx context.Context) *Lookup {
var target enode.ID
crand.Read(target[:])
return t.newLookup(ctx, target)
}
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
return newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *Lookup {
return NewLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
return t.lookupWorker(n, target)
})
}
@ -371,27 +371,27 @@ func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
// lookupWorker performs FINDNODE calls against a single node during lookup.
func (t *UDPv5) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.Node, error) {
var (
dists = lookupDistances(target, destNode.ID())
nodes = nodesByDistance{target: target}
dists = LookupDistances(target, destNode.ID())
nodes = NodesByDistance{Target: target}
err error
)
var r []*enode.Node
r, err = t.findnode(destNode, dists)
if errors.Is(err, errClosed) {
r, err = t.Findnode(destNode, dists)
if errors.Is(err, ErrClosed) {
return nil, err
}
for _, n := range r {
if n.ID() != t.Self().ID() {
nodes.push(n, findnodeResultLimit)
nodes.Push(n, findnodeResultLimit)
}
}
return nodes.entries, err
return nodes.Entries, err
}
// lookupDistances computes the distance parameter for FINDNODE calls to dest.
// LookupDistances computes the distance parameter for FINDNODE calls to dest.
// It chooses distances adjacent to logdist(target, dest), e.g. for a target
// with logdist(target, dest) = 255 the result is [255, 256, 254].
func lookupDistances(target, dest enode.ID) (dists []uint) {
func LookupDistances(target, dest enode.ID) (dists []uint) {
td := enode.LogDist(target, dest)
dists = append(dists, uint(td))
for i := 1; len(dists) < lookupRequestLimit; i++ {
@ -406,8 +406,8 @@ func lookupDistances(target, dest enode.ID) (dists []uint) {
}
// ping calls PING on a node and waits for a PONG response.
func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
pong, err := t.pingInner(n)
func (t *UDPv5) Ping(n *enode.Node) (uint64, error) {
pong, err := t.PingWithResp(n)
if err != nil {
return 0, err
}
@ -415,8 +415,8 @@ func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
return pong.ENRSeq, nil
}
// pingInner calls PING on a node and waits for a PONG response.
func (t *UDPv5) pingInner(n *enode.Node) (*v5wire.Pong, error) {
// PingWithResp calls PING on a node and waits for a PONG response.
func (t *UDPv5) PingWithResp(n *enode.Node) (*v5wire.Pong, error) {
req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()}
resp := t.callToNode(n, v5wire.PongMsg, req)
defer t.callDone(resp)
@ -431,7 +431,7 @@ func (t *UDPv5) pingInner(n *enode.Node) (*v5wire.Pong, error) {
// RequestENR requests n's record.
func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
nodes, err := t.findnode(n, []uint{0})
nodes, err := t.Findnode(n, []uint{0})
if err != nil {
return nil, err
}
@ -441,8 +441,8 @@ func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
return nodes[0], nil
}
// findnode calls FINDNODE on a node and waits for responses.
func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
// Findnode calls FINDNODE on a node and waits for responses.
func (t *UDPv5) Findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
resp := t.callToNode(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances})
return t.waitForNodes(resp, distances)
}
@ -493,7 +493,7 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
return nil, errors.New("not contained in netrestrict list")
}
if node.UDP() <= 1024 {
return nil, errLowPort
return nil, ErrLowPort
}
if distances != nil {
nd := enode.LogDist(c.id, node.ID())
@ -537,7 +537,7 @@ func (t *UDPv5) initCall(c *callV5, responseType byte, packet v5wire.Packet) {
select {
case t.callCh <- c:
case <-t.closeCtx.Done():
c.err <- errClosed
c.err <- ErrClosed
}
}
@ -630,12 +630,12 @@ func (t *UDPv5) dispatch() {
close(t.readNextCh)
for id, queue := range t.callQueue {
for _, c := range queue {
c.err <- errClosed
c.err <- ErrClosed
}
delete(t.callQueue, id)
}
for id, c := range t.activeCallByNode {
c.err <- errClosed
c.err <- ErrClosed
delete(t.activeCallByNode, id)
delete(t.activeCallByAuth, c.nonce)
}
@ -709,7 +709,7 @@ func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr netip.AddrPort, pack
}
}
func (t *UDPv5) sendFromAnotherThreadWithNode(node *enode.Node, toAddr netip.AddrPort, packet v5wire.Packet) {
func (t *UDPv5) SendFromAnotherThreadWithNode(node *enode.Node, toAddr netip.AddrPort, packet v5wire.Packet) {
select {
case t.sendCh <- sendRequest{node.ID(), node, toAddr, packet}:
case <-t.closeCtx.Done():
@ -792,7 +792,7 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr netip.AddrPort) error {
}
if fromNode != nil {
// Handshake succeeded, add to table.
t.tab.addInboundNode(fromNode)
t.tab.AddInboundNode(fromNode)
t.putCache(fromAddr.String(), fromNode)
}
if packet.Kind() != v5wire.WhoareyouPacket {
@ -825,9 +825,9 @@ func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr netip.AddrPort, p v
return true
}
// getNode looks for a node record in table and database.
func (t *UDPv5) getNode(id enode.ID) *enode.Node {
if n := t.tab.getNode(id); n != nil {
// GetNode looks for a node record in table and database.
func (t *UDPv5) GetNode(id enode.ID) *enode.Node {
if n := t.tab.GetNode(id); n != nil {
return n
}
if n := t.localNode.Database().Node(id); n != nil {
@ -865,7 +865,7 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) {
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
crand.Read(challenge.IDNonce[:])
if n := t.getNode(fromID); n != nil {
if n := t.GetNode(fromID); n != nil {
challenge.Node = n
challenge.RecordSeq = n.Seq()
}
@ -952,7 +952,7 @@ func (t *UDPv5) collectTableNodes(rip netip.Addr, distances []uint, limit int) [
processed[dist] = struct{}{}
checkLive := !t.tab.cfg.NoFindnodeLivenessCheck
for _, n := range t.tab.appendBucketNodes(dist, bn[:0], checkLive) {
for _, n := range t.tab.AppendBucketNodes(dist, bn[:0], checkLive) {
// Apply some pre-checks to avoid sending invalid nodes.
// Note liveness is checked by appendLiveNodes.
if netutil.CheckRelayAddr(rip, n.IPAddr()) != nil {
@ -1014,3 +1014,7 @@ func (t *UDPv5) GetCachedNode(addr string) (*enode.Node, bool) {
n, ok := t.cachedAddrNode[addr]
return n, ok
}
func (t *UDPv5) Table() *Table {
return t.tab
}

View file

@ -143,7 +143,7 @@ func TestUDPv5_unknownPacket(t *testing.T) {
// Make Node known.
n := test.getNode(test.remotekey, test.remoteaddr).Node()
test.table.addFoundNode(n, false)
test.table.AddFoundNode(n, false)
test.packetIn(&v5wire.Unknown{Nonce: nonce})
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
@ -237,7 +237,7 @@ func TestUDPv5_pingCall(t *testing.T) {
// This ping times out.
go func() {
_, err := test.udp.ping(remote)
_, err := test.udp.Ping(remote)
done <- err
}()
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {})
@ -247,7 +247,7 @@ func TestUDPv5_pingCall(t *testing.T) {
// This ping works.
go func() {
_, err := test.udp.ping(remote)
_, err := test.udp.Ping(remote)
done <- err
}()
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
@ -259,7 +259,7 @@ func TestUDPv5_pingCall(t *testing.T) {
// This ping gets a reply from the wrong endpoint.
go func() {
_, err := test.udp.ping(remote)
_, err := test.udp.Ping(remote)
done <- err
}()
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
@ -288,7 +288,7 @@ func TestUDPv5_findnodeCall(t *testing.T) {
)
go func() {
var err error
response, err = test.udp.findnode(remote, distances)
response, err = test.udp.Findnode(remote, distances)
done <- err
}()
@ -330,11 +330,11 @@ func TestUDPv5_callResend(t *testing.T) {
remote := test.getNode(test.remotekey, test.remoteaddr).Node()
done := make(chan error, 2)
go func() {
_, err := test.udp.ping(remote)
_, err := test.udp.Ping(remote)
done <- err
}()
go func() {
_, err := test.udp.ping(remote)
_, err := test.udp.Ping(remote)
done <- err
}()
@ -367,7 +367,7 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) {
remote := test.getNode(test.remotekey, test.remoteaddr).Node()
done := make(chan error, 1)
go func() {
_, err := test.udp.ping(remote)
_, err := test.udp.Ping(remote)
done <- err
}()
@ -398,7 +398,7 @@ func TestUDPv5_callTimeoutReset(t *testing.T) {
done = make(chan error, 1)
)
go func() {
_, err := test.udp.findnode(remote, []uint{distance})
_, err := test.udp.Findnode(remote, []uint{distance})
done <- err
}()
@ -535,38 +535,38 @@ func TestUDPv5_talkRequest(t *testing.T) {
}
}
// This test checks that lookupDistances works.
// This test checks that LookupDistances works.
func TestUDPv5_lookupDistances(t *testing.T) {
test := newUDPV5Test(t)
lnID := test.table.self().ID()
t.Run("target distance of 1", func(t *testing.T) {
node := nodeAtDistance(lnID, 1, intIP(0))
dists := lookupDistances(lnID, node.ID())
dists := LookupDistances(lnID, node.ID())
require.Equal(t, []uint{1, 2, 3}, dists)
})
t.Run("target distance of 2", func(t *testing.T) {
node := nodeAtDistance(lnID, 2, intIP(0))
dists := lookupDistances(lnID, node.ID())
dists := LookupDistances(lnID, node.ID())
require.Equal(t, []uint{2, 3, 1}, dists)
})
t.Run("target distance of 128", func(t *testing.T) {
node := nodeAtDistance(lnID, 128, intIP(0))
dists := lookupDistances(lnID, node.ID())
dists := LookupDistances(lnID, node.ID())
require.Equal(t, []uint{128, 129, 127}, dists)
})
t.Run("target distance of 255", func(t *testing.T) {
node := nodeAtDistance(lnID, 255, intIP(0))
dists := lookupDistances(lnID, node.ID())
dists := LookupDistances(lnID, node.ID())
require.Equal(t, []uint{255, 256, 254}, dists)
})
t.Run("target distance of 256", func(t *testing.T) {
node := nodeAtDistance(lnID, 256, intIP(0))
dists := lookupDistances(lnID, node.ID())
dists := LookupDistances(lnID, node.ID())
require.Equal(t, []uint{256, 255, 254}, dists)
})
}
@ -817,7 +817,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
exptype := fn.Type().In(0)
dgram, err := test.pipe.receive()
if err == errClosed {
if err == ErrClosed {
return true
}
if err == errTimeout {

View file

@ -94,7 +94,7 @@ const (
// Should reject packets smaller than minPacketSize.
minPacketSize = 63
maxPacketSize = 1280
MaxPacketSize = 1280
minMessageSize = 48 // this refers to data after static headers
randomPacketMsgSize = 20
@ -169,7 +169,7 @@ func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock, pr
privkey: key,
sc: NewSessionCache(1024, clock),
protocolID: DefaultProtocolID,
decbuf: make([]byte, maxPacketSize),
decbuf: make([]byte, MaxPacketSize),
}
if protocolID != nil {
c.protocolID = *protocolID

543
portalnetwork/api.go Normal file
View file

@ -0,0 +1,543 @@
package portalnetwork
import (
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/holiman/uint256"
)
// DiscV5API json-rpc spec
// https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/portal-network-specs/assembled-spec/jsonrpc/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false
type DiscV5API struct {
DiscV5 *discover.UDPv5
}
func NewDiscV5API(discV5 *discover.UDPv5) *DiscV5API {
return &DiscV5API{discV5}
}
type NodeInfo struct {
NodeId string `json:"nodeId"`
Enr string `json:"enr"`
Ip string `json:"ip"`
}
type RoutingTableInfo struct {
Buckets [][]string `json:"buckets"`
LocalNodeId string `json:"localNodeId"`
}
type DiscV5PongResp struct {
EnrSeq uint64 `json:"enrSeq"`
RecipientIP string `json:"recipientIP"`
RecipientPort uint16 `json:"recipientPort"`
}
type PortalPongResp struct {
EnrSeq uint32 `json:"enrSeq"`
DataRadius string `json:"dataRadius"`
}
type ContentInfo struct {
Content string `json:"content"`
UtpTransfer bool `json:"utpTransfer"`
}
type TraceContentResult struct {
Content string `json:"content"`
UtpTransfer bool `json:"utpTransfer"`
Trace Trace `json:"trace"`
}
type Trace struct {
Origin string `json:"origin"` // local node id
TargetId string `json:"targetId"` // target content id
ReceivedFrom string `json:"receivedFrom"` // the node id of which content from
Responses map[string]RespByNode `json:"responses"` // the node id and there response nodeIds
Metadata map[string]*NodeMetadata `json:"metadata"` // node id and there metadata object
StartedAtMs int `json:"startedAtMs"` // timestamp of the beginning of this request in milliseconds
Cancelled []string `json:"cancelled"` // the node ids which are send but cancelled
}
type NodeMetadata struct {
Enr string `json:"enr"`
Distance string `json:"distance"`
}
type RespByNode struct {
DurationMs int32 `json:"durationMs"`
RespondedWith []string `json:"respondedWith"`
}
type Enrs struct {
Enrs []string `json:"enrs"`
}
func (d *DiscV5API) NodeInfo() *NodeInfo {
n := d.DiscV5.LocalNode().Node()
return &NodeInfo{
NodeId: "0x" + n.ID().String(),
Enr: n.String(),
Ip: n.IP().String(),
}
}
func (d *DiscV5API) RoutingTableInfo() *RoutingTableInfo {
n := d.DiscV5.LocalNode().Node()
bucketNodes := d.DiscV5.RoutingTableInfo()
return &RoutingTableInfo{
Buckets: bucketNodes,
LocalNodeId: "0x" + n.ID().String(),
}
}
func (d *DiscV5API) AddEnr(enr string) (bool, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return false, err
}
// immediately add the node to the routing table
d.DiscV5.Table().AddInboundNode(n)
return true, nil
}
func (d *DiscV5API) GetEnr(nodeId string) (bool, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return false, err
}
n := d.DiscV5.Table().GetNode(id)
if n == nil {
return false, errors.New("record not in local routing table")
}
return true, nil
}
func (d *DiscV5API) DeleteEnr(nodeId string) (bool, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return false, err
}
n := d.DiscV5.Table().GetNode(id)
if n == nil {
return false, errors.New("record not in local routing table")
}
d.DiscV5.Table().DeleteNode(n)
return true, nil
}
func (d *DiscV5API) LookupEnr(nodeId string) (string, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return "", err
}
enr := d.DiscV5.ResolveNodeId(id)
if enr == nil {
return "", errors.New("record not found in DHT lookup")
}
return enr.String(), nil
}
func (d *DiscV5API) Ping(enr string) (*DiscV5PongResp, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
pong, err := d.DiscV5.PingWithResp(n)
if err != nil {
return nil, err
}
return &DiscV5PongResp{
EnrSeq: pong.ENRSeq,
RecipientIP: pong.ToIP.String(),
RecipientPort: pong.ToPort,
}, nil
}
func (d *DiscV5API) FindNodes(enr string, distances []uint) ([]string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
findNodes, err := d.DiscV5.Findnode(n, distances)
if err != nil {
return nil, err
}
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
func (d *DiscV5API) TalkReq(enr string, protocol string, payload string) (string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return "", err
}
req, err := hexutil.Decode(payload)
if err != nil {
return "", err
}
talkResp, err := d.DiscV5.TalkRequest(n, protocol, req)
if err != nil {
return "", err
}
return hexutil.Encode(talkResp), nil
}
func (d *DiscV5API) RecursiveFindNodes(nodeId string) ([]string, error) {
findNodes := d.DiscV5.Lookup(enode.HexID(nodeId))
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
type PortalProtocolAPI struct {
portalProtocol *PortalProtocol
}
func NewPortalAPI(portalProtocol *PortalProtocol) *PortalProtocolAPI {
return &PortalProtocolAPI{
portalProtocol: portalProtocol,
}
}
func (p *PortalProtocolAPI) NodeInfo() *NodeInfo {
n := p.portalProtocol.localNode.Node()
return &NodeInfo{
NodeId: n.ID().String(),
Enr: n.String(),
Ip: n.IP().String(),
}
}
func (p *PortalProtocolAPI) RoutingTableInfo() *RoutingTableInfo {
n := p.portalProtocol.localNode.Node()
bucketNodes := p.portalProtocol.RoutingTableInfo()
return &RoutingTableInfo{
Buckets: bucketNodes,
LocalNodeId: "0x" + n.ID().String(),
}
}
func (p *PortalProtocolAPI) AddEnr(enr string) (bool, error) {
p.portalProtocol.Log.Debug("serving AddEnr", "enr", enr)
n, err := enode.ParseForAddEnr(enode.ValidSchemes, enr)
if err != nil {
return false, err
}
p.portalProtocol.AddEnr(n)
return true, nil
}
func (p *PortalProtocolAPI) AddEnrs(enrs []string) bool {
// Note: unspecified RPC, but useful for our local testnet test
for _, enr := range enrs {
n, err := enode.ParseForAddEnr(enode.ValidSchemes, enr)
if err != nil {
continue
}
p.portalProtocol.AddEnr(n)
}
return true
}
func (p *PortalProtocolAPI) GetEnr(nodeId string) (string, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return "", err
}
if id == p.portalProtocol.localNode.Node().ID() {
return p.portalProtocol.localNode.Node().String(), nil
}
n := p.portalProtocol.table.GetNode(id)
if n == nil {
return "", errors.New("record not in local routing table")
}
return n.String(), nil
}
func (p *PortalProtocolAPI) DeleteEnr(nodeId string) (bool, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return false, err
}
n := p.portalProtocol.table.GetNode(id)
if n == nil {
return false, nil
}
p.portalProtocol.table.DeleteNode(n)
return true, nil
}
func (p *PortalProtocolAPI) LookupEnr(nodeId string) (string, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return "", err
}
enr := p.portalProtocol.ResolveNodeId(id)
if enr == nil {
return "", errors.New("record not found in DHT lookup")
}
return enr.String(), nil
}
func (p *PortalProtocolAPI) Ping(enr string) (*PortalPongResp, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
pong, err := p.portalProtocol.pingInner(n)
if err != nil {
return nil, err
}
customPayload := &portalwire.PingPongCustomData{}
err = customPayload.UnmarshalSSZ(pong.CustomPayload)
if err != nil {
return nil, err
}
nodeRadius := new(uint256.Int)
err = nodeRadius.UnmarshalSSZ(customPayload.Radius)
if err != nil {
return nil, err
}
return &PortalPongResp{
EnrSeq: uint32(pong.EnrSeq),
DataRadius: nodeRadius.Hex(),
}, nil
}
func (p *PortalProtocolAPI) FindNodes(enr string, distances []uint) ([]string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
findNodes, err := p.portalProtocol.findNodes(n, distances)
if err != nil {
return nil, err
}
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
func (p *PortalProtocolAPI) FindContent(enr string, contentKey string) (interface{}, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
contentKeyBytes, err := hexutil.Decode(contentKey)
if err != nil {
return nil, err
}
flag, findContent, err := p.portalProtocol.findContent(n, contentKeyBytes)
if err != nil {
return nil, err
}
switch flag {
case portalwire.ContentRawSelector:
contentInfo := &ContentInfo{
Content: hexutil.Encode(findContent.([]byte)),
UtpTransfer: false,
}
p.portalProtocol.Log.Trace("FindContent", "contentInfo", contentInfo)
return contentInfo, nil
case portalwire.ContentConnIdSelector:
contentInfo := &ContentInfo{
Content: hexutil.Encode(findContent.([]byte)),
UtpTransfer: true,
}
p.portalProtocol.Log.Trace("FindContent", "contentInfo", contentInfo)
return contentInfo, nil
default:
enrs := make([]string, 0)
for _, r := range findContent.([]*enode.Node) {
enrs = append(enrs, r.String())
}
p.portalProtocol.Log.Trace("FindContent", "enrs", enrs)
return &Enrs{
Enrs: enrs,
}, nil
}
}
func (p *PortalProtocolAPI) Offer(enr string, contentItems [][2]string) (string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return "", err
}
entries := make([]*ContentEntry, 0, len(contentItems))
for _, contentItem := range contentItems {
contentKey, err := hexutil.Decode(contentItem[0])
if err != nil {
return "", err
}
contentValue, err := hexutil.Decode(contentItem[1])
if err != nil {
return "", err
}
contentEntry := &ContentEntry{
ContentKey: contentKey,
Content: contentValue,
}
entries = append(entries, contentEntry)
}
transientOfferRequest := &TransientOfferRequest{
Contents: entries,
}
offerReq := &OfferRequest{
Kind: TransientOfferRequestKind,
Request: transientOfferRequest,
}
accept, err := p.portalProtocol.offer(n, offerReq)
if err != nil {
return "", err
}
return hexutil.Encode(accept), nil
}
func (p *PortalProtocolAPI) RecursiveFindNodes(nodeId string) ([]string, error) {
findNodes := p.portalProtocol.Lookup(enode.HexID(nodeId))
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
func (p *PortalProtocolAPI) RecursiveFindContent(contentKeyHex string) (*ContentInfo, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return nil, err
}
contentId := p.portalProtocol.toContentId(contentKey)
data, err := p.portalProtocol.Get(contentKey, contentId)
if err == nil {
return &ContentInfo{
Content: hexutil.Encode(data),
UtpTransfer: false,
}, err
}
p.portalProtocol.Log.Warn("find content err", "contextKey", hexutil.Encode(contentKey), "err", err)
content, utpTransfer, err := p.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
return nil, err
}
return &ContentInfo{
Content: hexutil.Encode(content),
UtpTransfer: utpTransfer,
}, err
}
func (p *PortalProtocolAPI) LocalContent(contentKeyHex string) (string, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return "", err
}
contentId := p.portalProtocol.ToContentId(contentKey)
content, err := p.portalProtocol.Get(contentKey, contentId)
if err != nil {
return "", err
}
return hexutil.Encode(content), nil
}
func (p *PortalProtocolAPI) Store(contentKeyHex string, contextHex string) (bool, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return false, err
}
contentId := p.portalProtocol.ToContentId(contentKey)
if !p.portalProtocol.InRange(contentId) {
return false, nil
}
content, err := hexutil.Decode(contextHex)
if err != nil {
return false, err
}
err = p.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
return false, err
}
return true, nil
}
func (p *PortalProtocolAPI) Gossip(contentKeyHex, contentHex string) (int, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return 0, err
}
content, err := hexutil.Decode(contentHex)
if err != nil {
return 0, err
}
id := p.portalProtocol.Self().ID()
return p.portalProtocol.Gossip(&id, [][]byte{contentKey}, [][]byte{content})
}
func (p *PortalProtocolAPI) TraceRecursiveFindContent(contentKeyHex string) (*TraceContentResult, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return nil, err
}
contentId := p.portalProtocol.toContentId(contentKey)
return p.portalProtocol.TraceContentLookup(contentKey, contentId)
}

172
portalnetwork/nat.go Normal file
View file

@ -0,0 +1,172 @@
package portalnetwork
import (
"net"
"time"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/nat"
)
const (
portMapDuration = 10 * time.Minute
portMapRefreshInterval = 8 * time.Minute
portMapRetryInterval = 5 * time.Minute
extipRetryInterval = 2 * time.Minute
)
type portMapping struct {
protocol string
name string
port int
// for use by the portMappingLoop goroutine:
extPort int // the mapped port returned by the NAT interface
nextTime mclock.AbsTime
}
// setupPortMapping starts the port mapping loop if necessary.
// Note: this needs to be called after the LocalNode instance has been set on the server.
func (p *PortalProtocol) setupPortMapping() {
// portMappingRegister will receive up to two values: one for the TCP port if
// listening is enabled, and one more for enabling UDP port mapping if discovery is
// enabled. We make it buffered to avoid blocking setup while a mapping request is in
// progress.
p.portMappingRegister = make(chan *portMapping, 2)
switch p.NAT.(type) {
case nil:
// No NAT interface configured.
go p.consumePortMappingRequests()
case nat.ExtIP:
// ExtIP doesn't block, set the IP right away.
ip, _ := p.NAT.ExternalIP()
p.localNode.SetStaticIP(ip)
go p.consumePortMappingRequests()
case nat.STUN:
// STUN doesn't block, set the IP right away.
ip, _ := p.NAT.ExternalIP()
p.localNode.SetStaticIP(ip)
go p.consumePortMappingRequests()
default:
go p.portMappingLoop()
}
}
func (p *PortalProtocol) consumePortMappingRequests() {
for {
select {
case <-p.closeCtx.Done():
return
case <-p.portMappingRegister:
}
}
}
// portMappingLoop manages port mappings for UDP and TCP.
func (p *PortalProtocol) portMappingLoop() {
newLogger := func(proto string, e int, i int) log.Logger {
return log.New("proto", proto, "extport", e, "intport", i, "interface", p.NAT)
}
var (
mappings = make(map[string]*portMapping, 2)
refresh = mclock.NewAlarm(p.clock)
extip = mclock.NewAlarm(p.clock)
lastExtIP net.IP
)
extip.Schedule(p.clock.Now())
defer func() {
refresh.Stop()
extip.Stop()
for _, m := range mappings {
if m.extPort != 0 {
log := newLogger(m.protocol, m.extPort, m.port)
log.Debug("Deleting port mapping")
p.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
}
}
}()
for {
// Schedule refresh of existing mappings.
for _, m := range mappings {
refresh.Schedule(m.nextTime)
}
select {
case <-p.closeCtx.Done():
return
case <-extip.C():
extip.Schedule(p.clock.Now().Add(extipRetryInterval))
ip, err := p.NAT.ExternalIP()
if err != nil {
log.Debug("Couldn't get external IP", "err", err, "interface", p.NAT)
} else if !ip.Equal(lastExtIP) {
log.Debug("External IP changed", "ip", extip, "interface", p.NAT)
} else {
continue
}
// Here, we either failed to get the external IP, or it has changed.
lastExtIP = ip
p.localNode.SetStaticIP(ip)
p.Log.Debug("set static ip in nat", "ip", p.localNode.Node().IP().String())
// Ensure port mappings are refreshed in case we have moved to a new network.
for _, m := range mappings {
m.nextTime = p.clock.Now()
}
case m := <-p.portMappingRegister:
if m.protocol != "TCP" && m.protocol != "UDP" {
panic("unknown NAT protocol name: " + m.protocol)
}
mappings[m.protocol] = m
m.nextTime = p.clock.Now()
case <-refresh.C():
for _, m := range mappings {
if p.clock.Now() < m.nextTime {
continue
}
external := m.port
if m.extPort != 0 {
external = m.extPort
}
log := newLogger(m.protocol, external, m.port)
log.Trace("Attempting port mapping")
port, err := p.NAT.AddMapping(m.protocol, external, m.port, m.name, portMapDuration)
if err != nil {
log.Debug("Couldn't add port mapping", "err", err)
m.extPort = 0
m.nextTime = p.clock.Now().Add(portMapRetryInterval)
continue
}
// It was mapped!
m.extPort = int(port)
m.nextTime = p.clock.Now().Add(portMapRefreshInterval)
if external != m.extPort {
log = newLogger(m.protocol, m.extPort, m.port)
log.Info("NAT mapped alternative port")
} else {
log.Info("NAT mapped port")
}
// Update port in local ENR.
switch m.protocol {
case "TCP":
p.localNode.Set(enr.TCP(m.extPort))
case "UDP":
p.localNode.SetFallbackUDP(m.extPort)
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
package portalnetwork
import "github.com/ethereum/go-ethereum/metrics"
type portalMetrics struct {
messagesReceivedAccept metrics.Meter
messagesReceivedNodes metrics.Meter
messagesReceivedFindNodes metrics.Meter
messagesReceivedFindContent metrics.Meter
messagesReceivedContent metrics.Meter
messagesReceivedOffer metrics.Meter
messagesReceivedPing metrics.Meter
messagesReceivedPong metrics.Meter
messagesSentAccept metrics.Meter
messagesSentNodes metrics.Meter
messagesSentFindNodes metrics.Meter
messagesSentFindContent metrics.Meter
messagesSentContent metrics.Meter
messagesSentOffer metrics.Meter
messagesSentPing metrics.Meter
messagesSentPong metrics.Meter
utpInFailConn metrics.Counter
utpInFailRead metrics.Counter
utpInFailDeadline metrics.Counter
utpInSuccess metrics.Counter
utpOutFailConn metrics.Counter
utpOutFailWrite metrics.Counter
utpOutFailDeadline metrics.Counter
utpOutSuccess metrics.Counter
contentDecodedTrue metrics.Counter
contentDecodedFalse metrics.Counter
}
func newPortalMetrics(protocolName string) *portalMetrics {
return &portalMetrics{
messagesReceivedAccept: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/accept", nil),
messagesReceivedNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/nodes", nil),
messagesReceivedFindNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/find_nodes", nil),
messagesReceivedFindContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/find_content", nil),
messagesReceivedContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/content", nil),
messagesReceivedOffer: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/offer", nil),
messagesReceivedPing: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/ping", nil),
messagesReceivedPong: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/pong", nil),
messagesSentAccept: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/accept", nil),
messagesSentNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/nodes", nil),
messagesSentFindNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/find_nodes", nil),
messagesSentFindContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/find_content", nil),
messagesSentContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/content", nil),
messagesSentOffer: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/offer", nil),
messagesSentPing: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/ping", nil),
messagesSentPong: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/pong", nil),
utpInFailConn: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/fail_conn", nil),
utpInFailRead: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/fail_read", nil),
utpInFailDeadline: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/fail_deadline", nil),
utpInSuccess: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/success", nil),
utpOutFailConn: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/fail_conn", nil),
utpOutFailWrite: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/fail_write", nil),
utpOutFailDeadline: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/fail_deadline", nil),
utpOutSuccess: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/success", nil),
contentDecodedTrue: metrics.NewRegisteredCounter("portal/"+protocolName+"/content/decoded/true", nil),
contentDecodedFalse: metrics.NewRegisteredCounter("portal/"+protocolName+"/content/decoded/false", nil),
}
}

View file

@ -0,0 +1,503 @@
package portalnetwork
import (
"context"
"crypto/rand"
"errors"
"fmt"
"io"
"net"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/optimism-java/utp-go"
"github.com/optimism-java/utp-go/libutp"
"github.com/prysmaticlabs/go-bitfield"
"golang.org/x/exp/slices"
"github.com/ethereum/go-ethereum/common/hexutil"
"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"
assert "github.com/stretchr/testify/require"
)
func setupLocalPortalNode(addr string, bootNodes []*enode.Node) (*PortalProtocol, error) {
conf := DefaultPortalProtocolConfig()
conf.NAT = nil
if addr != "" {
conf.ListenAddr = addr
}
if bootNodes != nil {
conf.BootstrapNodes = bootNodes
}
addr1, err := net.ResolveUDPAddr("udp", conf.ListenAddr)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", addr1)
if err != nil {
return nil, err
}
privKey := newkey()
discCfg := Config{
PrivateKey: privKey,
NetRestrict: conf.NetRestrict,
Bootnodes: conf.BootstrapNodes,
}
nodeDB, err := enode.OpenDB(conf.NodeDBPath)
if err != nil {
return nil, err
}
localNode := enode.NewLocalNode(nodeDB, privKey)
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(Tag)
if conf.NAT == nil {
var addrs []net.Addr
addrs, err = net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, address := range addrs {
// check ip addr is loopback addr
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
localNode.SetStaticIP(ipnet.IP)
break
}
}
}
}
discV5, err := ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, err
}
utpSocket := NewPortalUtp(context.Background(), conf, discV5, conn)
contentQueue := make(chan *ContentElement, 50)
portalProtocol, err := NewPortalProtocol(
conf,
portalwire.History,
privKey,
conn,
localNode,
discV5,
utpSocket,
&storage.MockStorage{Db: make(map[string][]byte)},
contentQueue)
if err != nil {
return nil, err
}
return portalProtocol, nil
}
func TestPortalWireProtocolUdp(t *testing.T) {
node1, err := setupLocalPortalNode(":8777", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":8778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LvlTrace)
err = node2.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
node3, err := setupLocalPortalNode(":8779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
cid1 := libutp.ReceConnId(12)
cid2 := libutp.ReceConnId(116)
cliSendMsgWithCid1 := "there are connection id : 12!"
cliSendMsgWithCid2 := "there are connection id: 116!"
serverEchoWithCid := "accept connection sends back msg: echo"
largeTestContent := make([]byte, 1199)
_, err = rand.Read(largeTestContent)
assert.NoError(t, err)
var workGroup sync.WaitGroup
var acceptGroup sync.WaitGroup
workGroup.Add(4)
acceptGroup.Add(1)
go func() {
var acceptConn *utp.Conn
defer func() {
workGroup.Done()
_ = acceptConn.Close()
}()
acceptConn, err := node1.Utp.AcceptWithCid(context.Background(), node2.localNode.ID(), cid1)
if err != nil {
panic(err)
}
acceptGroup.Done()
buf := make([]byte, 100)
n, err := acceptConn.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
assert.Equal(t, cliSendMsgWithCid1, string(buf[:n]))
_, err = acceptConn.Write([]byte(serverEchoWithCid))
if err != nil {
panic(err)
}
}()
go func() {
var connId2Conn net.Conn
defer func() {
workGroup.Done()
_ = connId2Conn.Close()
}()
connId2Conn, err := node1.Utp.AcceptWithCid(context.Background(), node2.localNode.ID(), cid2)
if err != nil {
panic(err)
}
buf := make([]byte, 100)
n, err := connId2Conn.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
assert.Equal(t, cliSendMsgWithCid2, string(buf[:n]))
_, err = connId2Conn.Write(largeTestContent)
if err != nil {
panic(err)
}
}()
go func() {
var connWithConnId net.Conn
defer func() {
workGroup.Done()
if connWithConnId != nil {
_ = connWithConnId.Close()
}
}()
connWithConnId, err = node2.Utp.DialWithCid(context.Background(), node1.localNode.Node(), cid1.SendId())
if err != nil {
panic(err)
}
_, err = connWithConnId.Write([]byte(cliSendMsgWithCid1))
if err != nil && err != io.EOF {
panic(err)
}
buf := make([]byte, 100)
n, err := connWithConnId.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
assert.Equal(t, serverEchoWithCid, string(buf[:n]))
}()
go func() {
var ConnId2Conn net.Conn
defer func() {
workGroup.Done()
if ConnId2Conn != nil {
_ = ConnId2Conn.Close()
}
}()
ConnId2Conn, err = node2.Utp.DialWithCid(context.Background(), node1.localNode.Node(), cid2.SendId())
if err != nil && err != io.EOF {
panic(err)
}
_, err = ConnId2Conn.Write([]byte(cliSendMsgWithCid2))
if err != nil {
panic(err)
}
data := make([]byte, 0)
buf := make([]byte, 1024)
for {
var n int
n, err = ConnId2Conn.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
}
data = append(data, buf[:n]...)
}
assert.Equal(t, largeTestContent, data)
}()
workGroup.Wait()
node1.Stop()
node2.Stop()
node3.Stop()
}
func TestPortalWireProtocol(t *testing.T) {
node1, err := setupLocalPortalNode(":7777", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LevelDebug)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":7778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LevelDebug)
err = node2.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
node3, err := setupLocalPortalNode(":7779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LevelDebug)
err = node3.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
slices.ContainsFunc(node1.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
})
slices.ContainsFunc(node1.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node3.localNode.Node().ID()
})
slices.ContainsFunc(node2.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node1.localNode.Node().ID()
})
slices.ContainsFunc(node2.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node3.localNode.Node().ID()
})
slices.ContainsFunc(node3.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node1.localNode.Node().ID()
})
slices.ContainsFunc(node3.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
})
err = node1.storage.Put(nil, node1.toContentId([]byte("test_key")), []byte("test_value"))
assert.NoError(t, err)
flag, content, err := node2.findContent(node1.localNode.Node(), []byte("test_key"))
assert.NoError(t, err)
assert.Equal(t, portalwire.ContentRawSelector, flag)
assert.Equal(t, []byte("test_value"), content)
flag, content, err = node2.findContent(node3.localNode.Node(), []byte("test_key"))
assert.NoError(t, err)
assert.Equal(t, portalwire.ContentEnrsSelector, flag)
assert.Equal(t, 1, len(content.([]*enode.Node)))
assert.Equal(t, node1.localNode.Node().ID(), content.([]*enode.Node)[0].ID())
// create a byte slice of length 1199 and fill it with random data
// this will be used as a test content
largeTestContent := make([]byte, 2000)
_, err = rand.Read(largeTestContent)
assert.NoError(t, err)
err = node1.storage.Put(nil, node1.toContentId([]byte("large_test_key")), largeTestContent)
assert.NoError(t, err)
flag, content, err = node2.findContent(node1.localNode.Node(), []byte("large_test_key"))
assert.NoError(t, err)
assert.Equal(t, largeTestContent, content)
assert.Equal(t, portalwire.ContentConnIdSelector, flag)
testEntry1 := &ContentEntry{
ContentKey: []byte("test_entry1"),
Content: []byte("test_entry1_content"),
}
testEntry2 := &ContentEntry{
ContentKey: []byte("test_entry2"),
Content: []byte("test_entry2_content"),
}
testTransientOfferRequest := &TransientOfferRequest{
Contents: []*ContentEntry{testEntry1, testEntry2},
}
offerRequest := &OfferRequest{
Kind: TransientOfferRequestKind,
Request: testTransientOfferRequest,
}
contentKeys, err := node1.offer(node3.localNode.Node(), offerRequest)
assert.Equal(t, uint64(2), bitfield.Bitlist(contentKeys).Count())
assert.NoError(t, err)
contentElement := <-node3.contentQueue
assert.Equal(t, node1.localNode.Node().ID(), contentElement.Node)
assert.Equal(t, testEntry1.ContentKey, contentElement.ContentKeys[0])
assert.Equal(t, testEntry1.Content, contentElement.Contents[0])
assert.Equal(t, testEntry2.ContentKey, contentElement.ContentKeys[1])
assert.Equal(t, testEntry2.Content, contentElement.Contents[1])
testGossipContentKeys := [][]byte{[]byte("test_gossip_content_keys"), []byte("test_gossip_content_keys2")}
testGossipContent := [][]byte{[]byte("test_gossip_content"), []byte("test_gossip_content2")}
id := node1.Self().ID()
gossip, err := node1.Gossip(&id, testGossipContentKeys, testGossipContent)
assert.NoError(t, err)
assert.Equal(t, 2, gossip)
contentElement = <-node2.contentQueue
assert.Equal(t, node1.localNode.Node().ID(), contentElement.Node)
assert.Equal(t, testGossipContentKeys[0], contentElement.ContentKeys[0])
assert.Equal(t, testGossipContent[0], contentElement.Contents[0])
assert.Equal(t, testGossipContentKeys[1], contentElement.ContentKeys[1])
assert.Equal(t, testGossipContent[1], contentElement.Contents[1])
contentElement = <-node3.contentQueue
assert.Equal(t, node1.localNode.Node().ID(), contentElement.Node)
assert.Equal(t, testGossipContentKeys[0], contentElement.ContentKeys[0])
assert.Equal(t, testGossipContent[0], contentElement.Contents[0])
assert.Equal(t, testGossipContentKeys[1], contentElement.ContentKeys[1])
assert.Equal(t, testGossipContent[1], contentElement.Contents[1])
node1.Stop()
node2.Stop()
node3.Stop()
}
func TestCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
defer func() {
t.Log("goroutine cancel")
}()
time.Sleep(time.Second * 5)
}(ctx)
cancel()
t.Log("after main cancel")
time.Sleep(time.Second * 3)
}
func TestContentLookup(t *testing.T) {
node1, err := setupLocalPortalNode(":17777", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":17778", []*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(":17779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
defer func() {
node1.Stop()
node2.Stop()
node3.Stop()
}()
contentKey := []byte{0x3, 0x4}
content := []byte{0x1, 0x2}
contentId := node1.toContentId(contentKey)
err = node3.storage.Put(nil, contentId, content)
assert.NoError(t, err)
res, _, err := node1.ContentLookup(contentKey, contentId)
assert.NoError(t, err)
assert.Equal(t, res, content)
nonExist := []byte{0x2, 0x4}
res, _, err = node1.ContentLookup(nonExist, node1.toContentId(nonExist))
assert.Equal(t, ContentNotFound, err)
assert.Nil(t, res)
}
func TestTraceContentLookup(t *testing.T) {
node1, err := setupLocalPortalNode(":17787", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":17788", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LvlTrace)
err = node2.Start()
assert.NoError(t, err)
node3, err := setupLocalPortalNode(":17789", []*enode.Node{node2.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
defer node1.Stop()
defer node2.Stop()
defer node3.Stop()
contentKey := []byte{0x3, 0x4}
content := []byte{0x1, 0x2}
contentId := node1.toContentId(contentKey)
err = node1.storage.Put(nil, contentId, content)
assert.NoError(t, err)
node1Id := hexutil.Encode(node1.Self().ID().Bytes())
node2Id := hexutil.Encode(node2.Self().ID().Bytes())
node3Id := hexutil.Encode(node3.Self().ID().Bytes())
res, err := node3.TraceContentLookup(contentKey, contentId)
assert.NoError(t, err)
assert.Equal(t, res.Content, hexutil.Encode(content))
assert.Equal(t, res.UtpTransfer, false)
assert.Equal(t, res.Trace.Origin, node3Id)
assert.Equal(t, res.Trace.TargetId, hexutil.Encode(contentId))
assert.Equal(t, res.Trace.ReceivedFrom, node1Id)
// check nodeMeta
node1Meta := res.Trace.Metadata[node1Id]
assert.Equal(t, node1Meta.Enr, node1.Self().String())
dis := node1.Distance(node1.Self().ID(), enode.ID(contentId))
assert.Equal(t, node1Meta.Distance, hexutil.Encode(dis[:]))
node2Meta := res.Trace.Metadata[node2Id]
assert.Equal(t, node2Meta.Enr, node2.Self().String())
dis = node2.Distance(node2.Self().ID(), enode.ID(contentId))
assert.Equal(t, node2Meta.Distance, hexutil.Encode(dis[:]))
node3Meta := res.Trace.Metadata[node3Id]
assert.Equal(t, node3Meta.Enr, node3.Self().String())
dis = node3.Distance(node3.Self().ID(), enode.ID(contentId))
assert.Equal(t, node3Meta.Distance, hexutil.Encode(dis[:]))
// check response
node3Response := res.Trace.Responses[node3Id]
assert.Equal(t, node3Response.RespondedWith, []string{node2Id})
node2Response := res.Trace.Responses[node2Id]
assert.Equal(t, node2Response.RespondedWith, []string{node1Id})
node1Response := res.Trace.Responses[node1Id]
assert.Equal(t, node1Response.RespondedWith, ([]string)(nil))
}

139
portalnetwork/portal_utp.go Normal file
View file

@ -0,0 +1,139 @@
package portalnetwork
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/optimism-java/utp-go"
"github.com/optimism-java/utp-go/libutp"
"go.uber.org/zap"
)
type PortalUtp struct {
ctx context.Context
log log.Logger
discV5 *discover.UDPv5
conn discover.UDPConn
ListenAddr string
listener *utp.Listener
utpSm *utp.SocketManager
packetRouter *utp.PacketRouter
lAddr *utp.Addr
startOnce sync.Once
}
func NewPortalUtp(ctx context.Context, config *PortalProtocolConfig, discV5 *discover.UDPv5, conn discover.UDPConn) *PortalUtp {
return &PortalUtp{
ctx: ctx,
log: log.New("protocol", "utp", "local", conn.LocalAddr().String()),
discV5: discV5,
conn: conn,
ListenAddr: config.ListenAddr,
}
}
func (p *PortalUtp) Start() error {
var err error
go p.startOnce.Do(func() {
var logger *zap.Logger
if p.log.Enabled(p.ctx, log.LevelDebug) || p.log.Enabled(p.ctx, log.LevelTrace) {
logger, err = zap.NewDevelopmentConfig().Build()
} else {
logger, err = zap.NewProductionConfig().Build()
}
if err != nil {
return
}
laddr := p.getLocalAddr()
p.packetRouter = utp.NewPacketRouter(p.packetRouterFunc)
p.utpSm, err = utp.NewSocketManagerWithOptions(
"utp",
laddr,
utp.WithContext(p.ctx),
utp.WithLogger(logger.Named(p.ListenAddr)),
utp.WithPacketRouter(p.packetRouter),
utp.WithMaxPacketSize(1145))
if err != nil {
return
}
p.listener, err = utp.ListenUTPOptions("utp", (*utp.Addr)(laddr), utp.WithSocketManager(p.utpSm))
if err != nil {
return
}
p.lAddr = p.listener.Addr().(*utp.Addr)
// register discv5 listener
p.discV5.RegisterTalkHandler(string(portalwire.Utp), p.handleUtpTalkRequest)
})
return err
}
func (p *PortalUtp) Stop() {
err := p.listener.Close()
if err != nil {
p.log.Error("close utp listener has error", "error", err)
}
p.discV5.Close()
}
func (p *PortalUtp) DialWithCid(ctx context.Context, dest *enode.Node, connId uint16) (net.Conn, error) {
raddr := &utp.Addr{IP: dest.IP(), Port: dest.UDP()}
p.log.Debug("will connect to: ", "nodeId", dest.ID().String(), "connId", connId)
conn, err := utp.DialUTPOptions("utp", p.lAddr, raddr, utp.WithContext(ctx), utp.WithSocketManager(p.utpSm), utp.WithConnId(connId))
return conn, err
}
func (p *PortalUtp) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
raddr := &utp.Addr{IP: dest.IP(), Port: dest.UDP()}
p.log.Info("will connect to: ", "addr", raddr.String())
conn, err := utp.DialUTPOptions("utp", p.lAddr, raddr, utp.WithContext(ctx), utp.WithSocketManager(p.utpSm))
return conn, err
}
func (p *PortalUtp) AcceptWithCid(ctx context.Context, nodeId enode.ID, cid *libutp.ConnId) (*utp.Conn, error) {
p.log.Debug("will accept from: ", "nodeId", nodeId.String(), "sendId", cid.SendId(), "recvId", cid.RecvId())
return p.listener.AcceptUTPContext(ctx, nodeId, cid)
}
func (p *PortalUtp) Accept(ctx context.Context) (*utp.Conn, error) {
return p.listener.AcceptUTPContext(ctx, enode.ID{}, nil)
}
func (p *PortalUtp) getLocalAddr() *net.UDPAddr {
laddr := p.conn.LocalAddr().(*net.UDPAddr)
p.log.Debug("UDP listener up", "addr", laddr)
return laddr
}
func (p *PortalUtp) packetRouterFunc(buf []byte, id enode.ID, addr *net.UDPAddr) (int, error) {
p.log.Info("will send to target data", "nodeId", id.String(), "ip", addr.IP.To4().String(), "port", addr.Port, "bufLength", len(buf))
if n, ok := p.discV5.GetCachedNode(addr.String()); ok {
//_, err := p.DiscV5.TalkRequestToID(id, addr, string(portalwire.UTPNetwork), buf)
req := &v5wire.TalkRequest{Protocol: string(portalwire.Utp), Message: buf}
p.discV5.SendFromAnotherThreadWithNode(n, netip.AddrPortFrom(netutil.IPToAddr(addr.IP), uint16(addr.Port)), req)
return len(buf), nil
} else {
p.log.Warn("not found target node info", "ip", addr.IP.To4().String(), "port", addr.Port, "bufLength", len(buf))
return 0, fmt.Errorf("not found target node id")
}
}
func (p *PortalUtp) handleUtpTalkRequest(id enode.ID, addr *net.UDPAddr, msg []byte) []byte {
p.log.Trace("receive utp data", "nodeId", id.String(), "addr", addr, "msg-length", len(msg))
p.packetRouter.ReceiveMessage(msg, &utp.NodeInfo{Id: id, Addr: addr})
return []byte("")
}

View file

@ -0,0 +1,336 @@
package portalwire
import (
ssz "github.com/ferranbt/fastssz"
)
// note: We changed the generated file since fastssz issues which can't be passed by the CI, so we commented the go:generate line
///go:generate sszgen --path messages.go --exclude-objs Content,Enrs,ContentKV
// 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
)
// Content selectors for the portal protocol.
const (
ContentConnIdSelector byte = 0x00
ContentRawSelector byte = 0x01
ContentEnrsSelector byte = 0x02
)
const (
ContentKeysLimit = 64
// OfferMessageOverhead overhead of content message is a result of 1byte for kind enum, and
// 4 bytes for offset in ssz serialization
OfferMessageOverhead = 5
// PerContentKeyOverhead each key in ContentKeysList has uint32 offset which results in 4 bytes per
// key overhead when serialized
PerContentKeyOverhead = 4
)
// Protocol IDs for the portal protocol.
// var (
// StateNetwork = []byte{0x50, 0x0a}
// HistoryNetwork = []byte{0x50, 0x0b}
// TxGossipNetwork = []byte{0x50, 0x0c}
// HeaderGossipNetwork = []byte{0x50, 0x0d}
// CanonicalIndicesNetwork = []byte{0x50, 0x0e}
// BeaconLightClientNetwork = []byte{0x50, 0x1a}
// UTPNetwork = []byte{0x75, 0x74, 0x70}
// Rendezvous = []byte{0x72, 0x65, 0x6e}
// )
type ProtocolId []byte
var (
State ProtocolId = []byte{0x50, 0x0A}
History ProtocolId = []byte{0x50, 0x0B}
Beacon ProtocolId = []byte{0x50, 0x0C}
CanonicalIndices ProtocolId = []byte{0x50, 0x0D}
VerkleState ProtocolId = []byte{0x50, 0x0E}
TransactionGossip ProtocolId = []byte{0x50, 0x0F}
Utp ProtocolId = []byte{0x75, 0x74, 0x70}
)
var protocalName = map[string]string{
string(State): "state",
string(History): "history",
string(Beacon): "beacon",
string(CanonicalIndices): "canonical indices",
string(VerkleState): "verkle state",
string(TransactionGossip): "transaction gossip",
}
func (p ProtocolId) Name() string {
return protocalName[string(p)]
}
// const (
// HistoryNetworkName = "history"
// BeaconNetworkName = "beacon"
// StateNetworkName = "state"
// )
// var NetworkNameMap = map[string]string{
// string(StateNetwork): StateNetworkName,
// string(HistoryNetwork): HistoryNetworkName,
// string(BeaconLightClientNetwork): BeaconNetworkName,
// }
type ContentKV struct {
ContentKey []byte
Content []byte
}
// 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"`
}
)
// 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
// 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
tail := buf
// Field (0) 'Content'
{
buf = tail[:]
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) {
// Field (0) 'Content'
return len(c.Content)
}
// 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(0)
// 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
tail := buf
// Field (0) 'Enrs'
{
buf = tail[:]
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 = 0
// 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)
}

View file

@ -0,0 +1,957 @@
// Code generated by fastssz. DO NOT EDIT.
// Hash: 26a61b12807ff78c64a029acdd5bcb580dfe35b7bfbf8bf04ceebae1a3d5cac1
// 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 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)
}

View file

@ -0,0 +1,212 @@
package portalwire
import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
ssz "github.com/ferranbt/fastssz"
"github.com/holiman/uint256"
"github.com/prysmaticlabs/go-bitfield"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var maxUint256 = uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
// https://github.com/ethereum/portal-network-specs/blob/master/portal-wire-test-vectors.md
// we remove the message type here
func TestPingMessage(t *testing.T) {
dataRadius := maxUint256.Sub(maxUint256, uint256.NewInt(1))
reverseBytes, err := dataRadius.MarshalSSZ()
require.NoError(t, err)
customData := &PingPongCustomData{
Radius: reverseBytes,
}
dataBytes, err := customData.MarshalSSZ()
assert.NoError(t, err)
ping := &Ping{
EnrSeq: 1,
CustomPayload: dataBytes,
}
expected := "0x01000000000000000c000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
data, err := ping.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}
func TestPongMessage(t *testing.T) {
dataRadius := maxUint256.Div(maxUint256, uint256.NewInt(2))
reverseBytes, err := dataRadius.MarshalSSZ()
require.NoError(t, err)
customData := &PingPongCustomData{
Radius: reverseBytes,
}
dataBytes, err := customData.MarshalSSZ()
assert.NoError(t, err)
pong := &Pong{
EnrSeq: 1,
CustomPayload: dataBytes,
}
expected := "0x01000000000000000c000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
data, err := pong.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}
func TestFindNodesMessage(t *testing.T) {
distances := []uint16{256, 255}
distancesBytes := make([][2]byte, len(distances))
for i, distance := range distances {
copy(distancesBytes[i][:], ssz.MarshalUint16(make([]byte, 0), distance))
}
findNode := &FindNodes{
Distances: distancesBytes,
}
data, err := findNode.MarshalSSZ()
expected := "0x040000000001ff00"
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}
func TestNodes(t *testing.T) {
enrs := []string{
"enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg",
"enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU",
}
enrsBytes := make([][]byte, 0)
for _, enr := range enrs {
n, err := enode.Parse(enode.ValidSchemes, enr)
assert.NoError(t, err)
enrBytes, err := rlp.EncodeToBytes(n.Record())
assert.NoError(t, err)
enrsBytes = append(enrsBytes, enrBytes)
}
testCases := []struct {
name string
input [][]byte
expected string
}{
{
name: "empty nodes",
input: make([][]byte, 0),
expected: "0x0105000000",
},
{
name: "two nodes",
input: enrsBytes,
expected: "0x0105000000080000007f000000f875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235",
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
nodes := &Nodes{
Total: 1,
Enrs: test.input,
}
data, err := nodes.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, test.expected, fmt.Sprintf("0x%x", data))
})
}
}
func TestContent(t *testing.T) {
contentKey := "0x706f7274616c"
content := &FindContent{
ContentKey: hexutil.MustDecode(contentKey),
}
expected := "0x04000000706f7274616c"
data, err := content.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
expected = "0x7468652063616b652069732061206c6965"
contentRes := &Content{
Content: hexutil.MustDecode("0x7468652063616b652069732061206c6965"),
}
data, err = contentRes.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
expectData := &Content{}
err = expectData.UnmarshalSSZ(data)
assert.NoError(t, err)
assert.Equal(t, contentRes.Content, expectData.Content)
enrs := []string{
"enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg",
"enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU",
}
enrsBytes := make([][]byte, 0)
for _, enr := range enrs {
n, err := enode.Parse(enode.ValidSchemes, enr)
assert.NoError(t, err)
enrBytes, err := rlp.EncodeToBytes(n.Record())
assert.NoError(t, err)
enrsBytes = append(enrsBytes, enrBytes)
}
enrsRes := &Enrs{
Enrs: enrsBytes,
}
expected = "0x080000007f000000f875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
data, err = enrsRes.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
expectEnrs := &Enrs{}
err = expectEnrs.UnmarshalSSZ(data)
assert.NoError(t, err)
assert.Equal(t, expectEnrs.Enrs, enrsRes.Enrs)
}
func TestOfferAndAcceptMessage(t *testing.T) {
contentKey := "0x010203"
contentBytes := hexutil.MustDecode(contentKey)
contentKeys := [][]byte{contentBytes}
offer := &Offer{
ContentKeys: contentKeys,
}
expected := "0x0400000004000000010203"
data, err := offer.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
contentKeyBitlist := bitfield.NewBitlist(8)
contentKeyBitlist.SetBitAt(0, true)
accept := &Accept{
ConnectionId: []byte{0x01, 0x02},
ContentKeys: contentKeyBitlist,
}
expected = "0x0102060000000101"
data, err = accept.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}