Skip to content

p2p/discover: expose discv5 functions for portal JSON-RPC interface #31117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat:expose discv5 function for portal discv5 json rpc
Signed-off-by: Chen Kai <[email protected]>
  • Loading branch information
GrapeBaBa committed Feb 3, 2025
commit b61b1053f08256dfd2e069de3a6950a2a561e059
2 changes: 1 addition & 1 deletion cmd/devp2p/discv4cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion cmd/devp2p/discv5cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
46 changes: 46 additions & 0 deletions p2p/discover/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,49 @@ func pushNode(list []*tableNode, n *tableNode, max int) ([]*tableNode, *tableNod
list[0] = n
return list, removed
}

// waitInit waits until the table is initialized.
func (tab *Table) waitInit() {
<-tab.initDone
}

// nodeList returns all nodes contained in the table.
func (tab *Table) nodeList() []*enode.Node {
if !tab.isInitDone() {
return nil
}

tab.mutex.Lock()
defer tab.mutex.Unlock()

var nodes []*enode.Node
for _, b := range &tab.buckets {
for _, n := range b.entries {
nodes = append(nodes, n.Node)
}
}
return nodes
}

// nodeIds returns the node IDs hex representation in the table.
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, fmt.Sprintf("0x%s", n.ID().String()))
}
nodes = append(nodes, bucketNodes)
}
return nodes
}

// deleteNode removes a node from the table.
func (tab *Table) deleteNode(n *enode.Node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.bucket(n.ID())
tab.deleteInBucket(b, n.ID())
}
4 changes: 2 additions & 2 deletions p2p/discover/v4_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ 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 {
// PingWithoutResp sends a ping message to the given node.
func (t *UDPv4) PingWithoutResp(n *enode.Node) error {
_, err := t.ping(n)
return err
}
Expand Down
93 changes: 72 additions & 21 deletions p2p/discover/v5_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ func (t *UDPv5) Close() {
})
}

// Ping sends a ping message to the given node.
func (t *UDPv5) Ping(n *enode.Node) error {
// PingWithoutResp sends a ping message to the given node.
func (t *UDPv5) PingWithoutResp(n *enode.Node) error {
_, err := t.ping(n)
return err
}
Expand All @@ -226,21 +226,62 @@ func (t *UDPv5) Resolve(n *enode.Node) *enode.Node {
return n
}

// AllNodes returns all the nodes stored in the local table.
func (t *UDPv5) AllNodes() []*enode.Node {
t.tab.mutex.Lock()
defer t.tab.mutex.Unlock()
nodes := make([]*enode.Node, 0)
// ResolveNodeId searches for a specific Node with the given ID.
// It returns nil if the nodeId could not be resolved.
func (t *UDPv5) ResolveNodeId(id enode.ID) *enode.Node {
if id == t.Self().ID() {
return t.Self()
}

for _, b := range &t.tab.buckets {
for _, n := range b.entries {
nodes = append(nodes, n.Node)
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 {
return resp
}
}
return nodes

// Otherwise do a network lookup.
result := t.Lookup(id)
for _, rn := range result {
if rn.ID() == id {
if n != nil && rn.Seq() <= n.Seq() {
return n
} else {
return rn
}
}
}

return n
}

// AllNodes returns all the nodes stored in the local table.
func (t *UDPv5) AllNodes() []*enode.Node {
return t.tab.nodeList()
}

// LocalNode returns the current local node running the
// RoutingTableInfo returns the routing table information. Used for Portal discv5 RoutingTableInfo API.
func (t *UDPv5) RoutingTableInfo() [][]string {
return t.tab.nodeIds()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should expose Table.Nodes method instead because it has more information.

}

// AddKnownNode adds a node to the routing table. Used for Portal discv5 AddEnr API.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have a comment that says it's only for testing.

func (t *UDPv5) AddKnownNode(n *enode.Node) bool {
return t.tab.addFoundNode(n, true)
}

// DeleteNode removes a node from the routing table. Used for Portal discv5 DeleteEnr API.
func (t *UDPv5) DeleteNode(n *enode.Node) {
t.tab.deleteNode(n)
}

// WaitInit waits for the routing table to be initialized.
func (t *UDPv5) WaitInit() {
t.tab.waitInit()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to add a config option (in discover/common.go) that disables init check in table.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change will be made in another PR.

}

// LocalNode returns the current local Node running the
// protocol.
func (t *UDPv5) LocalNode() *enode.LocalNode {
return t.localNode
Expand Down Expand Up @@ -328,7 +369,7 @@ func (t *UDPv5) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.No
err error
)
var r []*enode.Node
r, err = t.findnode(destNode, dists)
r, err = t.Findnode(destNode, dists)
if errors.Is(err, errClosed) {
return nil, err
}
Expand Down Expand Up @@ -359,21 +400,31 @@ 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.PingWithResp(n)
if err != nil {
return 0, err
}

return pong.ENRSeq, nil
}

// PingWithResp calls PING on a node and waits for a PONG response.
func (t *UDPv5) PingWithResp(n *enode.Node) (*v5wire.Pong, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just change Ping signature to return the response object. No need to add WithoutResp method.

req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()}
resp := t.callToNode(n, v5wire.PongMsg, req)
defer t.callDone(resp)

select {
case pong := <-resp.ch:
return pong.(*v5wire.Pong).ENRSeq, nil
return pong.(*v5wire.Pong), nil
case err := <-resp.err:
return 0, err
return nil, err
}
}

// 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
}
Expand All @@ -383,8 +434,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)
}
Expand Down Expand Up @@ -736,8 +787,8 @@ 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 {
// 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
}
Expand Down Expand Up @@ -776,7 +827,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()
}
Expand Down
4 changes: 2 additions & 2 deletions p2p/discover/v5_udp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}()

Expand Down Expand Up @@ -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
}()

Expand Down