@cryptotaxi247 / kubo / commits / e89cce63f

feat: add identify option to swarm peers command

Fixes #9578

Arthur Gavazza committed Mar 30, 2023 at 01:34 UTC e89cce63fd1428c40031edb30b407cbb468abb08
4 files changed +210 -50
core/commands/swarm.go
+64 -7
@@ -2,6 +2,7 @@ package commands
2
3 import (
4 "context"
5 + "encoding/base64"
6 "encoding/json"
7 "errors"
8 "fmt"
@@ -21,8 +22,10 @@ import (
22 "github.com/ipfs/kubo/repo/fsrepo"
23
24 cmds "github.com/ipfs/go-ipfs-cmds"
25 + ic "github.com/libp2p/go-libp2p/core/crypto"
26 inet "github.com/libp2p/go-libp2p/core/network"
27 "github.com/libp2p/go-libp2p/core/peer"
28 + pstore "github.com/libp2p/go-libp2p/core/peerstore"
29 rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
30 ma "github.com/multiformats/go-multiaddr"
31 madns "github.com/multiformats/go-multiaddr-dns"
@@ -69,6 +72,7 @@ const (
72 swarmDirectionOptionName = "direction"
73 swarmResetLimitsOptionName = "reset"
74 swarmUsedResourcesPercentageName = "min-used-limit-perc"
75 + swarmIdentifyOptionName = "identify"
76 )
77
78 type peeringResult struct {
@@ -236,17 +240,18 @@ var swarmPeersCmd = &cmds.Command{
240 cmds.BoolOption(swarmStreamsOptionName, "Also list information about open streams for each peer"),
241 cmds.BoolOption(swarmLatencyOptionName, "Also list information about latency to each peer"),
242 cmds.BoolOption(swarmDirectionOptionName, "Also list information about the direction of connection"),
243 + cmds.BoolOption(swarmIdentifyOptionName, "Also list information about peers identify"),
244 },
245 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
246 api, err := cmdenv.GetApi(env, req)
247 if err != nil {
248 return err
249 }
245 -
250 verbose, _ := req.Options[swarmVerboseOptionName].(bool)
251 latency, _ := req.Options[swarmLatencyOptionName].(bool)
252 streams, _ := req.Options[swarmStreamsOptionName].(bool)
253 direction, _ := req.Options[swarmDirectionOptionName].(bool)
254 + identify, _ := req.Options[swarmIdentifyOptionName].(bool)
255
256 conns, err := api.Swarm().Peers(req.Context)
257 if err != nil {
@@ -287,6 +292,15 @@ var swarmPeersCmd = &cmds.Command{
292 ci.Streams = append(ci.Streams, streamInfo{Protocol: string(s)})
293 }
294 }
295 +
296 + if verbose || identify {
297 + n, err := cmdenv.GetNode(env)
298 + if err != nil {
299 + return err
300 + }
301 + identifyResult, _ := ci.identifyPeer(n.Peerstore, c.ID())
302 + ci.Identify = identifyResult
303 + }
304 sort.Sort(&ci)
305 out.Peers = append(out.Peers, ci)
306 }
@@ -411,12 +425,13 @@ type streamInfo struct {
425 }
426
427 type connInfo struct {
414 - Addr string
415 - Peer string
416 - Latency string
417 - Muxer string
418 - Direction inet.Direction
419 - Streams []streamInfo
428 + Addr string `json:",omitempty"`
429 + Peer string `json:",omitempty"`
430 + Latency string `json:",omitempty"`
431 + Muxer string `json:",omitempty"`
432 + Direction inet.Direction `json:",omitempty"`
433 + Streams []streamInfo `json:",omitempty"`
434 + Identify IdOutput `json:",omitempty"`
435 }
436
437 func (ci *connInfo) Less(i, j int) bool {
@@ -447,6 +462,48 @@ func (ci connInfos) Swap(i, j int) {
462 ci.Peers[i], ci.Peers[j] = ci.Peers[j], ci.Peers[i]
463 }
464
465 +func (ci *connInfo) identifyPeer(ps pstore.Peerstore, p peer.ID) (IdOutput, error) {
466 + var info IdOutput
467 + info.ID = p.String()
468 +
469 + if pk := ps.PubKey(p); pk != nil {
470 + pkb, err := ic.MarshalPublicKey(pk)
471 + if err != nil {
472 + return IdOutput{}, err
473 + }
474 + info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
475 + }
476 +
477 + addrInfo := ps.PeerInfo(p)
478 + addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
479 + if err != nil {
480 + return IdOutput{}, err
481 + }
482 +
483 + for _, a := range addrs {
484 + info.Addresses = append(info.Addresses, a.String())
485 + }
486 + sort.Strings(info.Addresses)
487 +
488 + if protocols, err := ps.GetProtocols(p); err == nil {
489 + info.Protocols = append(info.Protocols, protocols...)
490 + sort.Slice(info.Protocols, func(i, j int) bool { return info.Protocols[i] < info.Protocols[j] })
491 + }
492 +
493 + if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
494 + if vs, ok := v.(string); ok {
495 + info.ProtocolVersion = vs
496 + }
497 + }
498 + if v, err := ps.Get(p, "AgentVersion"); err == nil {
499 + if vs, ok := v.(string); ok {
500 + info.AgentVersion = vs
501 + }
502 + }
503 +
504 + return info, nil
505 +}
506 +
507 // directionString transfers to string
508 func directionString(d inet.Direction) string {
509 switch d {
test/cli/harness/peering.go new
+37
@@ -0,0 +1,37 @@
1 +package harness
2 +
3 +import (
4 + "fmt"
5 + "math/rand"
6 + "testing"
7 +
8 + "github.com/ipfs/kubo/config"
9 +)
10 +
11 +type Peering struct {
12 + From int
13 + To int
14 +}
15 +
16 +func newRandPort() int {
17 + n := rand.Int()
18 + return 3000 + (n % 1000)
19 +}
20 +
21 +func CreatePeerNodes(t *testing.T, n int, peerings []Peering) (*Harness, Nodes) {
22 + h := NewT(t)
23 + nodes := h.NewNodes(n).Init()
24 + nodes.ForEachPar(func(node *Node) {
25 + node.UpdateConfig(func(cfg *config.Config) {
26 + cfg.Routing.Type = config.NewOptionalString("none")
27 + cfg.Addresses.Swarm = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", newRandPort())}
28 + })
29 +
30 + })
31 +
32 + for _, peering := range peerings {
33 + nodes[peering.From].PeerWith(nodes[peering.To])
34 + }
35 +
36 + return h, nodes
37 +}
test/cli/peering_test.go
+12 -43
@@ -1,12 +1,9 @@
1 package cli
2
3 import (
4 - "fmt"
5 - "math/rand"
4 "testing"
5 "time"
6
9 - "github.com/ipfs/kubo/config"
7 "github.com/ipfs/kubo/test/cli/harness"
8 . "github.com/ipfs/kubo/test/cli/testutils"
9 "github.com/libp2p/go-libp2p/core/peer"
@@ -16,16 +13,6 @@ import (
13 func TestPeering(t *testing.T) {
14 t.Parallel()
15
19 - type peering struct {
20 - from int
21 - to int
22 - }
23 -
24 - newRandPort := func() int {
25 - n := rand.Int()
26 - return 3000 + (n % 1000)
27 - }
28 -
16 containsPeerID := func(p peer.ID, peers []peer.ID) bool {
17 for _, peerID := range peers {
18 if p == peerID {
@@ -63,34 +50,16 @@ func TestPeering(t *testing.T) {
50 }, 20*time.Second, 10*time.Millisecond, "%d -> %d peered", from.ID, to.ID)
51 }
52
66 - assertPeerings := func(h *harness.Harness, nodes []*harness.Node, peerings []peering) {
67 - ForEachPar(peerings, func(peering peering) {
68 - assertPeered(h, nodes[peering.from], nodes[peering.to])
53 + assertPeerings := func(h *harness.Harness, nodes []*harness.Node, peerings []harness.Peering) {
54 + ForEachPar(peerings, func(peering harness.Peering) {
55 + assertPeered(h, nodes[peering.From], nodes[peering.To])
56 })
57 }
58
72 - createNodes := func(t *testing.T, n int, peerings []peering) (*harness.Harness, harness.Nodes) {
73 - h := harness.NewT(t)
74 - nodes := h.NewNodes(n).Init()
75 - nodes.ForEachPar(func(node *harness.Node) {
76 - node.UpdateConfig(func(cfg *config.Config) {
77 - cfg.Routing.Type = config.NewOptionalString("none")
78 - cfg.Addresses.Swarm = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", newRandPort())}
79 - })
80 -
81 - })
82 -
83 - for _, peering := range peerings {
84 - nodes[peering.from].PeerWith(nodes[peering.to])
85 - }
86 -
87 - return h, nodes
88 - }
89 -
59 t.Run("bidirectional peering should work (simultaneous connect)", func(t *testing.T) {
60 t.Parallel()
92 - peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
93 - h, nodes := createNodes(t, 3, peerings)
61 + peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
62 + h, nodes := harness.CreatePeerNodes(t, 3, peerings)
63
64 nodes.StartDaemons()
65 assertPeerings(h, nodes, peerings)
@@ -101,8 +70,8 @@ func TestPeering(t *testing.T) {
70
71 t.Run("1 should reconnect to 2 when 2 disconnects from 1", func(t *testing.T) {
72 t.Parallel()
104 - peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
105 - h, nodes := createNodes(t, 3, peerings)
73 + peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
74 + h, nodes := harness.CreatePeerNodes(t, 3, peerings)
75
76 nodes.StartDaemons()
77 assertPeerings(h, nodes, peerings)
@@ -113,12 +82,12 @@ func TestPeering(t *testing.T) {
82
83 t.Run("1 will peer with 2 when it comes online", func(t *testing.T) {
84 t.Parallel()
116 - peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
117 - h, nodes := createNodes(t, 3, peerings)
85 + peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
86 + h, nodes := harness.CreatePeerNodes(t, 3, peerings)
87
88 nodes[0].StartDaemon()
89 nodes[1].StartDaemon()
121 - assertPeerings(h, nodes, []peering{{from: 0, to: 1}, {from: 1, to: 0}})
90 + assertPeerings(h, nodes, []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}})
91
92 nodes[2].StartDaemon()
93 assertPeerings(h, nodes, peerings)
@@ -126,8 +95,8 @@ func TestPeering(t *testing.T) {
95
96 t.Run("1 will re-peer with 2 when it disconnects and then comes back online", func(t *testing.T) {
97 t.Parallel()
129 - peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
130 - h, nodes := createNodes(t, 3, peerings)
98 + peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
99 + h, nodes := harness.CreatePeerNodes(t, 3, peerings)
100
101 nodes.StartDaemons()
102 assertPeerings(h, nodes, peerings)
test/cli/swarm_test.go new
+97
@@ -0,0 +1,97 @@
1 +package cli
2 +
3 +import (
4 + "encoding/json"
5 + "fmt"
6 + "testing"
7 +
8 + "github.com/ipfs/kubo/test/cli/harness"
9 +
10 + "github.com/stretchr/testify/assert"
11 +)
12 +
13 +// TODO: Migrate the rest of the sharness swarm test.
14 +func TestSwarm(t *testing.T) {
15 + type identifyType struct {
16 + ID string
17 + PublicKey string
18 + Addresses []string
19 + AgentVersion string
20 + ProtocolVersion string
21 + Protocols []string
22 + }
23 + type peer struct {
24 + Identify identifyType
25 + }
26 + type expectedOutputType struct {
27 + Peers []peer
28 + }
29 +
30 + t.Parallel()
31 +
32 + t.Run("ipfs swarm peers returns empty peers when a node is not connected to any peers", func(t *testing.T) {
33 + t.Parallel()
34 + node := harness.NewT(t).NewNode().Init().StartDaemon()
35 + res := node.RunIPFS("swarm", "peers", "--enc=json", "--identify")
36 + var output expectedOutputType
37 + err := json.Unmarshal(res.Stdout.Bytes(), &output)
38 + assert.Nil(t, err)
39 + assert.Equal(t, 0, len(output.Peers))
40 +
41 + })
42 + t.Run("ipfs swarm peers with flag identify outputs expected identify information about connected peers", func(t *testing.T) {
43 + t.Parallel()
44 + node := harness.NewT(t).NewNode().Init().StartDaemon()
45 + otherNode := harness.NewT(t).NewNode().Init().StartDaemon()
46 + node.Connect(otherNode)
47 +
48 + res := node.RunIPFS("swarm", "peers", "--enc=json", "--identify")
49 + var output expectedOutputType
50 + err := json.Unmarshal(res.Stdout.Bytes(), &output)
51 + assert.Nil(t, err)
52 + actualID := output.Peers[0].Identify.ID
53 + actualPublicKey := output.Peers[0].Identify.PublicKey
54 + actualAgentVersion := output.Peers[0].Identify.AgentVersion
55 + actualAdresses := output.Peers[0].Identify.Addresses
56 + actualProtocolVersion := output.Peers[0].Identify.ProtocolVersion
57 + actualProtocols := output.Peers[0].Identify.Protocols
58 +
59 + expectedID := otherNode.PeerID().String()
60 + expectedAddresses := []string{fmt.Sprintf("%s/p2p/%s", otherNode.SwarmAddrs()[0], actualID)}
61 +
62 + assert.Equal(t, actualID, expectedID)
63 + assert.NotNil(t, actualPublicKey)
64 + assert.NotNil(t, actualAgentVersion)
65 + assert.NotNil(t, actualProtocolVersion)
66 + assert.Len(t, actualAdresses, 1)
67 + assert.Equal(t, expectedAddresses[0], actualAdresses[0])
68 + assert.Greater(t, len(actualProtocols), 0)
69 +
70 + })
71 +
72 + t.Run("ipfs swarm peers with flag identify outputs Identify field with data that matches calling ipfs id on a peer", func(t *testing.T) {
73 + t.Parallel()
74 + node := harness.NewT(t).NewNode().Init().StartDaemon()
75 + otherNode := harness.NewT(t).NewNode().Init().StartDaemon()
76 + node.Connect(otherNode)
77 +
78 + otherNodeIDResponse := otherNode.RunIPFS("id", "--enc=json")
79 + var otherNodeIDOutput identifyType
80 + err := json.Unmarshal(otherNodeIDResponse.Stdout.Bytes(), &otherNodeIDOutput)
81 + assert.Nil(t, err)
82 + res := node.RunIPFS("swarm", "peers", "--enc=json", "--identify")
83 +
84 + var output expectedOutputType
85 + err = json.Unmarshal(res.Stdout.Bytes(), &output)
86 + assert.Nil(t, err)
87 + outputIdentify := output.Peers[0].Identify
88 +
89 + assert.Equal(t, outputIdentify.ID, otherNodeIDOutput.ID)
90 + assert.Equal(t, outputIdentify.PublicKey, otherNodeIDOutput.PublicKey)
91 + assert.Equal(t, outputIdentify.AgentVersion, otherNodeIDOutput.AgentVersion)
92 + assert.Equal(t, outputIdentify.ProtocolVersion, otherNodeIDOutput.ProtocolVersion)
93 + assert.ElementsMatch(t, outputIdentify.Addresses, otherNodeIDOutput.Addresses)
94 + assert.ElementsMatch(t, outputIdentify.Protocols, otherNodeIDOutput.Protocols)
95 +
96 + })
97 +}