Skip to content
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

Swarm discovery on the gateway (readonly) interface #3540

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions core/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ var rootROSubcommands = map[string]*cmds.Command{
},
"cat": CatCmd,
"commands": CommandsDaemonROCmd,
"discover": DiscoverCmd,
"dns": DNSCmd,
"get": GetCmd,
"ls": LsCmd,
Expand Down
148 changes: 97 additions & 51 deletions core/commands/swarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"path"
"sort"

cmds "github.com/ipfs/go-ipfs/commands"
repo "github.com/ipfs/go-ipfs/repo"
config "github.com/ipfs/go-ipfs/repo/config"
"github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/repo"
"github.com/ipfs/go-ipfs/repo/config"
"github.com/ipfs/go-ipfs/repo/fsrepo"
iaddr "github.com/ipfs/go-ipfs/thirdparty/ipfsaddr"
swarm "gx/ipfs/QmWfxnAiQ5TnnCgiX9ikVUKFNHRgGhbgKdx5DoKPELD7P4/go-libp2p-swarm"
pstore "gx/ipfs/QmeXj9VAjmYQZxpmVz7VzccbJrpmr8qkCDSjfVNsPTWTYU/go-libp2p-peerstore"
"github.com/ipfs/go-ipfs/thirdparty/ipfsaddr"

mafilter "gx/ipfs/QmSMZwvs3n4GBikZ7hKzT17c3bk65FmyZo2JqtJ16swqCv/multiaddr-filter"
ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
swarm "gx/ipfs/QmWfxnAiQ5TnnCgiX9ikVUKFNHRgGhbgKdx5DoKPELD7P4/go-libp2p-swarm"
pstore "gx/ipfs/QmeXj9VAjmYQZxpmVz7VzccbJrpmr8qkCDSjfVNsPTWTYU/go-libp2p-peerstore"
)

type stringList struct {
Expand Down Expand Up @@ -46,6 +48,67 @@ ipfs peers in the internet.
},
}

var DiscoverCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Get random peers of the IPFS node.",
ShortDescription: `
'ipfs discover' is useful for discovering the whole IPFS network without
being a peer on the network. The client starts discovering from some
bootstrap nodes and gets some random nodes. Recursively continuing to
crawl they can reach each node without putting too much strain on any
of the nodes.
`,
},
Run: func(req cmds.Request, res cmds.Response) {
log.Debug("ipfs discover")
n := getNode(req, res)
if n == nil {
return
}

conns := n.PeerHost.Network().Conns()
var sample []int = shuffle(len(conns), 5)

var addrs []string
for _, i := range sample {
pid := conns[i].RemotePeer()
addr := conns[i].RemoteMultiaddr()

saddr := path.Join(addr.String(), "ipfs", pid.Pretty())
addrs = append(addrs, saddr)
}
sort.Sort(sort.StringSlice(addrs))

res.SetOutput(&stringList{addrs})
},
Type: stringList{},
Marshalers: cmds.MarshalerMap{
cmds.Text: stringListMarshaler,
},
}

func shuffle(input, output int) []int {
indexes := make([]int, input)
for i := range indexes {
indexes[i] = i
}
done := 0
unsorted := indexes
for done < output {
left := len(unsorted)
if left <= 0 {
break
}
picked := rand.Intn(left)
if picked > 0 {
unsorted[0], unsorted[picked] = unsorted[picked], unsorted[0]
}
unsorted = unsorted[1:]
done += 1
}
return indexes[:done]
}

var swarmPeersCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List peers with open connections.",
Expand All @@ -61,14 +124,8 @@ var swarmPeersCmd = &cmds.Command{
Run: func(req cmds.Request, res cmds.Response) {

log.Debug("ipfs swarm peers")
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

if n.PeerHost == nil {
res.SetError(errNotOnline, cmds.ErrClient)
n := getNode(req, res)
if n == nil {
return
}

Expand All @@ -77,7 +134,6 @@ var swarmPeersCmd = &cmds.Command{
streams, _, _ := req.Option("streams").Bool()

conns := n.PeerHost.Network().Conns()

var out connInfos
for _, c := range conns {
pid := c.RemotePeer()
Expand Down Expand Up @@ -201,14 +257,8 @@ var swarmAddrsCmd = &cmds.Command{
},
Run: func(req cmds.Request, res cmds.Response) {

n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

if n.PeerHost == nil {
res.SetError(errNotOnline, cmds.ErrClient)
n := getNode(req, res)
if n == nil {
return
}

Expand Down Expand Up @@ -264,14 +314,8 @@ var swarmAddrsLocalCmd = &cmds.Command{
},
Run: func(req cmds.Request, res cmds.Response) {

n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

if n.PeerHost == nil {
res.SetError(errNotOnline, cmds.ErrClient)
n := getNode(req, res)
if n == nil {
return
}

Expand Down Expand Up @@ -311,21 +355,15 @@ ipfs swarm connect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3
cmds.StringArg("address", true, true, "Address of peer to connect to.").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
ctx := req.Context()

n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
n := getNode(req, res)
if n == nil {
return
}

ctx := req.Context()
addrs := req.Arguments()

if n.PeerHost == nil {
res.SetError(errNotOnline, cmds.ErrClient)
return
}

snet, ok := n.PeerHost.Network().(*swarm.Network)
if !ok {
res.SetError(fmt.Errorf("peerhost network was not swarm"), cmds.ErrNormal)
Expand Down Expand Up @@ -379,19 +417,12 @@ it will reconnect.
cmds.StringArg("address", true, true, "Address of peer to disconnect from.").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
n := getNode(req, res)
if n == nil {
return
}

addrs := req.Arguments()

if n.PeerHost == nil {
res.SetError(errNotOnline, cmds.ErrClient)
return
}

iaddrs, err := parseAddresses(addrs)
if err != nil {
res.SetError(err, cmds.ErrNormal)
Expand Down Expand Up @@ -448,10 +479,10 @@ func stringListMarshaler(res cmds.Response) (io.Reader, error) {

// parseAddresses is a function that takes in a slice of string peer addresses
// (multiaddr + peerid) and returns slices of multiaddrs and peerids.
func parseAddresses(addrs []string) (iaddrs []iaddr.IPFSAddr, err error) {
iaddrs = make([]iaddr.IPFSAddr, len(addrs))
func parseAddresses(addrs []string) (iaddrs []ipfsaddr.IPFSAddr, err error) {
iaddrs = make([]ipfsaddr.IPFSAddr, len(addrs))
for i, saddr := range addrs {
iaddrs[i], err = iaddr.ParseString(saddr)
iaddrs[i], err = ipfsaddr.ParseString(saddr)
if err != nil {
return nil, cmds.ClientError("invalid peer address: " + err.Error())
}
Expand All @@ -476,6 +507,21 @@ func peersWithAddresses(addrs []string) (pis []pstore.PeerInfo, err error) {
return pis, nil
}

func getNode(req cmds.Request, res cmds.Response) *core.IpfsNode {
Copy link
Member

Choose a reason for hiding this comment

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

this should probably be getOnlineNode or something, and lets go ahead and return the errors and handle them in the caller.

Copy link
Author

Choose a reason for hiding this comment

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

When I see copy-paste, I have the urge to extract and name the thing. Since all commands in this file wanted an online node and handled the error the same way, this seemed to be the logical step to reduce coupling. I even played with the idea to look further into other commands and introduce a middle-man on the req itself, but I stopped myself.

I can get rid of the res parameter, but then I would return (*core.IpfsNode, *commands.Error) and add a func (*commands.response) SetError(*Error) if that is fine with you.

Copy link
Member

Choose a reason for hiding this comment

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

just use a regular error for that.

Copy link
Author

Choose a reason for hiding this comment

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

I cannot. The 2 errors need different commands.ErrorType set. So I either

  • create my own error type,
  • reuse the commands.Error or
  • have 3 return arguments (*core.IpfsNode, commands.ErrorType, error).

Alternatively I inline this method back and wash my hands in innocence.

n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return nil
}

if n.PeerHost == nil {
res.SetError(errNotOnline, cmds.ErrClient)
return nil
}

return n
}

var swarmFiltersCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Manipulate address filters.",
Expand Down
37 changes: 37 additions & 0 deletions core/commands/swarm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package commands

import (
"testing"
"sort"
)

func TestShuffleEmpty(t *testing.T) {
actual := shuffle(0, 3)
assertLen(t, actual, 0)
}

func TestShuffleSingle(t *testing.T) {
actual := shuffle(1, 3)
assertLen(t, actual, 1)
if actual[0] != 0 {
t.Errorf("Expected 0 as the only value, but got %d", actual[0])
}
}

func TestShuffleUnique(t *testing.T) {
actual := shuffle(100, 100)
assertLen(t, actual, 100)
sort.Ints(actual)
for i, v := range actual {
if i != v {
t.Fatalf("Not each value is present exactly once")
}
}
}

func assertLen(t *testing.T, actual []int, expected int) {
actualLen := len(actual)
if actualLen != expected {
t.Error("Expected a slice with %d items, but got %d", expected, actualLen)
}
}
2 changes: 1 addition & 1 deletion exchange/bitswap/network/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type BitSwapNetwork interface {
peer.ID,
bsmsg.BitSwapMessage) error

// SetDelegate registers the Reciver to handle messages received from the
// SetDelegate registers the Receiver to handle messages received from the
// network.
SetDelegate(Receiver)

Expand Down