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

feat: add "autoclient" routing type #9708

Merged
merged 3 commits into from
Mar 9, 2023
Merged
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
61 changes: 35 additions & 26 deletions cmd/ipfs/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,33 @@ import (
)

const (
adjustFDLimitKwd = "manage-fdlimit"
enableGCKwd = "enable-gc"
initOptionKwd = "init"
initConfigOptionKwd = "init-config"
initProfileOptionKwd = "init-profile"
ipfsMountKwd = "mount-ipfs"
ipnsMountKwd = "mount-ipns"
migrateKwd = "migrate"
mountKwd = "mount"
offlineKwd = "offline" // global option
routingOptionKwd = "routing"
routingOptionSupernodeKwd = "supernode"
routingOptionDHTClientKwd = "dhtclient"
routingOptionDHTKwd = "dht"
routingOptionDHTServerKwd = "dhtserver"
routingOptionNoneKwd = "none"
routingOptionCustomKwd = "custom"
routingOptionDefaultKwd = "default"
routingOptionAutoKwd = "auto"
unencryptTransportKwd = "disable-transport-encryption"
unrestrictedAPIAccessKwd = "unrestricted-api"
writableKwd = "writable"
enablePubSubKwd = "enable-pubsub-experiment"
enableIPNSPubSubKwd = "enable-namesys-pubsub"
enableMultiplexKwd = "enable-mplex-experiment"
agentVersionSuffix = "agent-version-suffix"
adjustFDLimitKwd = "manage-fdlimit"
enableGCKwd = "enable-gc"
initOptionKwd = "init"
initConfigOptionKwd = "init-config"
initProfileOptionKwd = "init-profile"
ipfsMountKwd = "mount-ipfs"
ipnsMountKwd = "mount-ipns"
migrateKwd = "migrate"
mountKwd = "mount"
offlineKwd = "offline" // global option
routingOptionKwd = "routing"
routingOptionSupernodeKwd = "supernode"
routingOptionDHTClientKwd = "dhtclient"
routingOptionDHTKwd = "dht"
routingOptionDHTServerKwd = "dhtserver"
routingOptionNoneKwd = "none"
routingOptionCustomKwd = "custom"
routingOptionDefaultKwd = "default"
routingOptionAutoKwd = "auto"
routingOptionAutoClientKwd = "autoclient"
unencryptTransportKwd = "disable-transport-encryption"
unrestrictedAPIAccessKwd = "unrestricted-api"
writableKwd = "writable"
enablePubSubKwd = "enable-pubsub-experiment"
enableIPNSPubSubKwd = "enable-namesys-pubsub"
enableMultiplexKwd = "enable-mplex-experiment"
agentVersionSuffix = "agent-version-suffix"
// apiAddrKwd = "address-api"
// swarmAddrKwd = "address-swarm"
)
Expand Down Expand Up @@ -416,6 +417,14 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
cfg.Identity.PeerID,
cfg.Addresses.Swarm,
cfg.Identity.PrivKey,
libp2p.DHTOption,
)
case routingOptionAutoClientKwd:
ncfg.Routing = libp2p.ConstructDefaultRouting(
cfg.Identity.PeerID,
cfg.Addresses.Swarm,
cfg.Identity.PrivKey,
libp2p.DHTClientOption,
)
case routingOptionDHTClientKwd:
ncfg.Routing = libp2p.DHTClientOption
Expand Down
2 changes: 1 addition & 1 deletion config/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type Routing struct {
// Type sets default daemon routing mode.
//
// Can be one of "auto", "dht", "dhtclient", "dhtserver", "none", or "custom".
// Can be one of "auto", "autoclient", "dht", "dhtclient", "dhtserver", "none", or "custom".
// When unset or set to "auto", DHT and implicit routers are used.
// When "custom" is set, user-provided Routing.Routers is used.
Type *OptionalString `json:",omitempty"`
Expand Down
5 changes: 2 additions & 3 deletions core/node/libp2p/routingopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func init() {
}

// ConstructDefaultRouting returns routers used when Routing.Type is unset or set to "auto"
func ConstructDefaultRouting(peerID string, addrs []string, privKey string) func(
func ConstructDefaultRouting(peerID string, addrs []string, privKey string, routingOpt RoutingOption) func(
ctx context.Context,
host host.Host,
dstore datastore.Batching,
Expand All @@ -58,8 +58,7 @@ func ConstructDefaultRouting(peerID string, addrs []string, privKey string) func
// Different trade-offs can be made by setting Routing.Type = "custom" with own Routing.Routers
var routers []*routinghelpers.ParallelRouter

// Run the default DHT routing (same as Routing.Type = "dht")
dhtRouting, err := DHTOption(ctx, host, dstore, validator, bootstrapPeers...)
dhtRouting, err := routingOpt(ctx, host, dstore, validator, bootstrapPeers...)
if err != nil {
return nil, err
}
Expand Down
6 changes: 6 additions & 0 deletions docs/changelogs/v0.19.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Overview](#overview)
- [🔦 Highlights](#-highlights)
- [Improving the libp2p resource management integration](#improving-the-libp2p-resource-management-integration)
- [Addition of "autoclient" router type](#addition-of-autoclient-router-type)
- [📝 Changelog](#-changelog)
- [👨‍👩‍👧‍👦 Contributors](#-contributors)

Expand All @@ -22,6 +23,11 @@ and [0.18.1](https://github.com/ipfs/kubo/blob/master/docs/changelogs/v0.18.md#i
- Note: we don't expect most users to need these capablities, but they are there if so.
1. [Doc updates](https://github.com/ipfs/kubo/blob/master/docs/libp2p-resource-management.md).

#### Addition of "autoclient" router type
A new routing type "autoclient" has been added. This mode is similar to "auto", in that it is a hybrid of content routers (including Kademlia and HTTP routers), but it does not run a DHT server. This is similar to the difference between "dhtclient" and "dht" router types.

See the [Routing.Type documentation](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingtype) for more information.

### 📝 Changelog

### 👨‍👩‍👧‍👦 Contributors
6 changes: 4 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -1349,11 +1349,13 @@ Contains options for content, peer, and IPNS routing mechanisms.

### `Routing.Type`

There are multiple routing options: "auto", "none", "dht" and "custom".
There are multiple routing options: "auto", "autoclient", "none", "dht", "dhtclient", and "custom".

* **DEFAULT:** If unset, or set to "auto", your node will use the IPFS DHT
and parallel HTTP routers listed below for additional speed.

* If set to "autoclient", your node will behave as in "auto" but without running a DHT server.

* If set to "none", your node will use _no_ routing system. You'll have to
explicitly connect to peers that have the content you're looking for.

Expand All @@ -1379,7 +1381,7 @@ To force a specific DHT-only mode, client or server, set `Routing.Type` to
`dhtclient` or `dhtserver` respectively. Please do not set this to `dhtserver`
unless you're sure your node is reachable from the public network.

When `Routing.Type` is set to `auto` your node will accelerate some types of routing
When `Routing.Type` is set to `auto` or `autoclient` your node will accelerate some types of routing
by leveraging HTTP endpoints compatible with [IPIP-337](https://github.com/ipfs/specs/pull/337)
in addition to the IPFS DHT.
By default, an instance of [IPNI](https://github.com/ipni/specs/blob/main/IPNI.md#readme)
Expand Down
2 changes: 1 addition & 1 deletion test/cli/delegated_routing_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestHTTPDelegatedRouting(t *testing.T) {
}))
t.Cleanup(server.Close)

node.IPFS("config", "Routing.Type", "--json", `"custom"`)
node.IPFS("config", "Routing.Type", "custom")
node.IPFS("config", "Routing.Routers.TestDelegatedRouter", "--json", ToJSONStr(JSONObj{
"Type": "http",
"Parameters": JSONObj{
Expand Down
39 changes: 39 additions & 0 deletions test/cli/dht_autoclient_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cli

import (
"bytes"
"testing"

"github.com/ipfs/kubo/test/cli/harness"
"github.com/ipfs/kubo/test/cli/testutils"
"github.com/stretchr/testify/assert"
)

func TestDHTAutoclient(t *testing.T) {
t.Parallel()
nodes := harness.NewT(t).NewNodes(10).Init()
harness.Nodes(nodes[8:]).ForEachPar(func(node *harness.Node) {
node.IPFS("config", "Routing.Type", "autoclient")
})
nodes.StartDaemons().Connect()

t.Run("file added on node in client mode is retrievable from node in client mode", func(t *testing.T) {
t.Parallel()
randomBytes := testutils.RandomBytes(1000)
hash := nodes[8].IPFSAdd(bytes.NewReader(randomBytes))

res := nodes[9].IPFS("cat", hash)
assert.Equal(t, randomBytes, []byte(res.Stdout.Trimmed()))
})

t.Run("file added on node in server mode is retrievable from all nodes", func(t *testing.T) {
t.Parallel()
randomBytes := testutils.RandomBytes(1000)
hash := nodes[0].IPFSAdd(bytes.NewReader(randomBytes))

for i := 0; i < 10; i++ {
res := nodes[i].IPFS("cat", hash)
assert.Equal(t, randomBytes, []byte(res.Stdout.Trimmed()))
}
})
}
137 changes: 137 additions & 0 deletions test/cli/dht_legacy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package cli

import (
"sort"
"sync"
"testing"

"github.com/ipfs/kubo/test/cli/harness"
"github.com/ipfs/kubo/test/cli/testutils"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLegacyDHT(t *testing.T) {
nodes := harness.NewT(t).NewNodes(5).Init()
nodes.ForEachPar(func(node *harness.Node) {
node.IPFS("config", "Routing.Type", "dht")
})
nodes.StartDaemons().Connect()

t.Run("ipfs dht findpeer", func(t *testing.T) {
t.Parallel()
res := nodes[1].RunIPFS("dht", "findpeer", nodes[0].PeerID().String())
assert.Equal(t, 0, res.ExitCode())

swarmAddr := nodes[0].SwarmAddrsWithoutPeerIDs()[0]
require.Equal(t, swarmAddr.String(), res.Stdout.Trimmed())
})

t.Run("ipfs dht get <key>", func(t *testing.T) {
t.Parallel()
hash := nodes[2].IPFSAddStr("hello world")
nodes[2].IPFS("name", "publish", "/ipfs/"+hash)

res := nodes[1].IPFS("dht", "get", "/ipns/"+nodes[2].PeerID().String())
assert.Contains(t, res.Stdout.String(), "/ipfs/"+hash)

t.Run("put round trips (#3124)", func(t *testing.T) {
t.Parallel()
nodes[0].WriteBytes("get_result", res.Stdout.Bytes())
res := nodes[0].IPFS("dht", "put", "/ipns/"+nodes[2].PeerID().String(), "get_result")
assert.Greater(t, len(res.Stdout.Lines()), 0, "should put to at least one node")
})

t.Run("put with bad keys fails (issue #5113, #4611)", func(t *testing.T) {
t.Parallel()
keys := []string{"foo", "/pk/foo", "/ipns/foo"}
for _, key := range keys {
key := key
t.Run(key, func(t *testing.T) {
t.Parallel()
res := nodes[0].RunIPFS("dht", "put", key)
assert.Equal(t, 1, res.ExitCode())
assert.Contains(t, res.Stderr.String(), "invalid")
assert.Empty(t, res.Stdout.String())
})
}
})

t.Run("get with bad keys (issue #4611)", func(t *testing.T) {
for _, key := range []string{"foo", "/pk/foo"} {
key := key
t.Run(key, func(t *testing.T) {
t.Parallel()
res := nodes[0].RunIPFS("dht", "get", key)
assert.Equal(t, 1, res.ExitCode())
assert.Contains(t, res.Stderr.String(), "invalid")
assert.Empty(t, res.Stdout.String())
})
}
})
})

t.Run("ipfs dht findprovs", func(t *testing.T) {
t.Parallel()
hash := nodes[3].IPFSAddStr("some stuff")
res := nodes[4].IPFS("dht", "findprovs", hash)
assert.Equal(t, nodes[3].PeerID().String(), res.Stdout.Trimmed())
})

t.Run("ipfs dht query <peerID>", func(t *testing.T) {
t.Parallel()
t.Run("normal DHT configuration", func(t *testing.T) {
t.Parallel()
hash := nodes[0].IPFSAddStr("some other stuff")
peerCounts := map[string]int{}
peerCountsMut := sync.Mutex{}
harness.Nodes(nodes).ForEachPar(func(node *harness.Node) {
res := node.IPFS("dht", "query", hash)
closestPeer := res.Stdout.Lines()[0]
// check that it's a valid peer ID
_, err := peer.Decode(closestPeer)
require.NoError(t, err)

peerCountsMut.Lock()
peerCounts[closestPeer]++
peerCountsMut.Unlock()
})
// 4 nodes should see the same peer ID
// 1 node (the closest) should see a different one
var counts []int
for _, count := range peerCounts {
counts = append(counts, count)
}
sort.IntSlice(counts).Sort()
assert.Equal(t, []int{1, 4}, counts)
})

})

t.Run("dht commands fail when offline", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()

// these cannot be run in parallel due to repo locking (seems like a bug)

t.Run("dht findprovs", func(t *testing.T) {
res := node.RunIPFS("dht", "findprovs", testutils.CIDEmptyDir)
assert.Equal(t, 1, res.ExitCode())
assert.Contains(t, res.Stderr.String(), "this command must be run in online mode")
})

t.Run("dht findpeer", func(t *testing.T) {
res := node.RunIPFS("dht", "findpeer", testutils.CIDEmptyDir)
assert.Equal(t, 1, res.ExitCode())
assert.Contains(t, res.Stderr.String(), "this command must be run in online mode")
})

t.Run("dht put", func(t *testing.T) {
node.WriteBytes("foo", []byte("foo"))
res := node.RunIPFS("dht", "put", "/ipns/"+node.PeerID().String(), "foo")
assert.Equal(t, 1, res.ExitCode())
assert.Contains(t, res.Stderr.String(), "this action must be run in online mode")
})
})
}
2 changes: 1 addition & 1 deletion test/cli/harness/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func (h *Harness) Mkdirs(paths ...string) {
}
}

func (h *Harness) Sh(expr string) RunResult {
func (h *Harness) Sh(expr string) *RunResult {
return h.Runner.Run(RunRequest{
Path: "bash",
Args: []string{"-c", expr},
Expand Down
Loading