@cryptotaxi247 / kubo / commits / 0e55ca937

feat: add experimental optimistic provide

This adds the ability to enable "optimistic provide" to the default DHT client, which enables faster provides and reprovides. For more information about optimistic provide, see: https://protocollabs.notion.site/Optimistic-Provide-2c79745820fa45649d48de038516b814 Note that this feature only works when using non-custom router types. This does not include the ability to enable optimistic provide on custom routers for now, to minimize the footprint of this experimental feature. We intend on continuing to test this and improve the UX, which may or may not involve adding configuration for it to custom routers. We also plan on refactoring/redesigning custom routers more broadly so I don't want this to add more effort for maintainers and confusion for users.

Gus Eggert committed Mar 24, 2023 at 21:09 UTC 0e55ca9377515ef2a78816ee78db33a0fa66bf3b
10 files changed +111 -92
config/experiments.go
+10 -8
@@ -1,12 +1,14 @@
1 package config
2
3 type Experiments struct {
4 - FilestoreEnabled bool
5 - UrlstoreEnabled bool
6 - ShardingEnabled bool `json:",omitempty"` // deprecated by autosharding: https://github.com/ipfs/kubo/pull/8527
7 - GraphsyncEnabled bool
8 - Libp2pStreamMounting bool
9 - P2pHttpProxy bool //nolint
10 - StrategicProviding bool
11 - AcceleratedDHTClient bool
4 + FilestoreEnabled bool
5 + UrlstoreEnabled bool
6 + ShardingEnabled bool `json:",omitempty"` // deprecated by autosharding: https://github.com/ipfs/kubo/pull/8527
7 + GraphsyncEnabled bool
8 + Libp2pStreamMounting bool
9 + P2pHttpProxy bool //nolint
10 + StrategicProviding bool
11 + AcceleratedDHTClient bool
12 + OptimisticProvide bool
13 + OptimisticProvideJobsPoolSize int
14 }
core/node/libp2p/host.go
+14 -7
@@ -53,13 +53,18 @@ func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (out P2PHo
53 return out, err
54 }
55
56 + routingOptArgs := RoutingOptionArgs{
57 + Ctx: ctx,
58 + Datastore: params.Repo.Datastore(),
59 + Validator: params.Validator,
60 + BootstrapPeers: bootstrappers,
61 + OptimisticProvide: cfg.Experimental.OptimisticProvide,
62 + OptimisticProvideJobsPoolSize: cfg.Experimental.OptimisticProvideJobsPoolSize,
63 + }
64 opts = append(opts, libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
57 - r, err := params.RoutingOption(
58 - ctx, h,
59 - params.Repo.Datastore(),
60 - params.Validator,
61 - bootstrappers...,
62 - )
65 + args := routingOptArgs
66 + args.Host = h
67 + r, err := params.RoutingOption(args)
68 out.Routing = r
69 return r, err
70 }))
@@ -69,10 +74,12 @@ func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (out P2PHo
74 return P2PHostOut{}, err
75 }
76
77 + routingOptArgs.Host = out.Host
78 +
79 // this code is necessary just for tests: mock network constructions
80 // ignore the libp2p constructor options that actually construct the routing!
81 if out.Routing == nil {
75 - r, err := params.RoutingOption(ctx, out.Host, params.Repo.Datastore(), params.Validator, bootstrappers...)
82 + r, err := params.RoutingOption(routingOptArgs)
83 if err != nil {
84 return P2PHostOut{}, err
85 }
core/node/libp2p/routingopt.go
+39 -69
@@ -18,13 +18,17 @@ import (
18 routing "github.com/libp2p/go-libp2p/core/routing"
19 )
20
21 -type RoutingOption func(
22 - context.Context,
23 - host.Host,
24 - datastore.Batching,
25 - record.Validator,
26 - ...peer.AddrInfo,
27 -) (routing.Routing, error)
21 +type RoutingOptionArgs struct {
22 + Ctx context.Context
23 + Host host.Host
24 + Datastore datastore.Batching
25 + Validator record.Validator
26 + BootstrapPeers []peer.AddrInfo
27 + OptimisticProvide bool
28 + OptimisticProvideJobsPoolSize int
29 +}
30 +
31 +type RoutingOption func(args RoutingOptionArgs) (routing.Routing, error)
32
33 // Default HTTP routers used in parallel to DHT when Routing.Type = "auto"
34 var defaultHTTPRouters = []string{
@@ -40,25 +44,13 @@ func init() {
44 }
45
46 // ConstructDefaultRouting returns routers used when Routing.Type is unset or set to "auto"
43 -func ConstructDefaultRouting(peerID string, addrs []string, privKey string, routingOpt RoutingOption) func(
44 - ctx context.Context,
45 - host host.Host,
46 - dstore datastore.Batching,
47 - validator record.Validator,
48 - bootstrapPeers ...peer.AddrInfo,
49 -) (routing.Routing, error) {
50 - return func(
51 - ctx context.Context,
52 - host host.Host,
53 - dstore datastore.Batching,
54 - validator record.Validator,
55 - bootstrapPeers ...peer.AddrInfo,
56 - ) (routing.Routing, error) {
47 +func ConstructDefaultRouting(peerID string, addrs []string, privKey string, routingOpt RoutingOption) RoutingOption {
48 + return func(args RoutingOptionArgs) (routing.Routing, error) {
49 // Defined routers will be queried in parallel (optimizing for response speed)
50 // Different trade-offs can be made by setting Routing.Type = "custom" with own Routing.Routers
51 var routers []*routinghelpers.ParallelRouter
52
61 - dhtRouting, err := routingOpt(ctx, host, dstore, validator, bootstrapPeers...)
53 + dhtRouting, err := routingOpt(args)
54 if err != nil {
55 return nil, err
56 }
@@ -97,54 +89,38 @@ func ConstructDefaultRouting(peerID string, addrs []string, privKey string, rout
89 }
90
91 // constructDHTRouting is used when Routing.Type = "dht"
100 -func constructDHTRouting(mode dht.ModeOpt) func(
101 - ctx context.Context,
102 - host host.Host,
103 - dstore datastore.Batching,
104 - validator record.Validator,
105 - bootstrapPeers ...peer.AddrInfo,
106 -) (routing.Routing, error) {
107 - return func(
108 - ctx context.Context,
109 - host host.Host,
110 - dstore datastore.Batching,
111 - validator record.Validator,
112 - bootstrapPeers ...peer.AddrInfo,
113 - ) (routing.Routing, error) {
92 +func constructDHTRouting(mode dht.ModeOpt) RoutingOption {
93 + return func(args RoutingOptionArgs) (routing.Routing, error) {
94 + dhtOpts := []dht.Option{
95 + dht.Concurrency(10),
96 + dht.Mode(mode),
97 + dht.Datastore(args.Datastore),
98 + dht.Validator(args.Validator),
99 + }
100 + if args.OptimisticProvide {
101 + dhtOpts = append(dhtOpts, dht.EnableOptimisticProvide())
102 + }
103 + if args.OptimisticProvideJobsPoolSize != 0 {
104 + dhtOpts = append(dhtOpts, dht.OptimisticProvideJobsPoolSize(args.OptimisticProvideJobsPoolSize))
105 + }
106 return dual.New(
115 - ctx, host,
116 - dual.DHTOption(
117 - dht.Concurrency(10),
118 - dht.Mode(mode),
119 - dht.Datastore(dstore),
120 - dht.Validator(validator)),
121 - dual.WanDHTOption(dht.BootstrapPeers(bootstrapPeers...)),
107 + args.Ctx, args.Host,
108 + dual.DHTOption(dhtOpts...),
109 + dual.WanDHTOption(dht.BootstrapPeers(args.BootstrapPeers...)),
110 )
111 }
112 }
113
114 // ConstructDelegatedRouting is used when Routing.Type = "custom"
127 -func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, peerID string, addrs []string, privKey string) func(
128 - ctx context.Context,
129 - host host.Host,
130 - dstore datastore.Batching,
131 - validator record.Validator,
132 - bootstrapPeers ...peer.AddrInfo,
133 -) (routing.Routing, error) {
134 - return func(
135 - ctx context.Context,
136 - host host.Host,
137 - dstore datastore.Batching,
138 - validator record.Validator,
139 - bootstrapPeers ...peer.AddrInfo,
140 - ) (routing.Routing, error) {
115 +func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, peerID string, addrs []string, privKey string) RoutingOption {
116 + return func(args RoutingOptionArgs) (routing.Routing, error) {
117 return irouting.Parse(routers, methods,
118 &irouting.ExtraDHTParams{
143 - BootstrapPeers: bootstrapPeers,
144 - Host: host,
145 - Validator: validator,
146 - Datastore: dstore,
147 - Context: ctx,
119 + BootstrapPeers: args.BootstrapPeers,
120 + Host: args.Host,
121 + Validator: args.Validator,
122 + Datastore: args.Datastore,
123 + Context: args.Ctx,
124 },
125 &irouting.ExtraHTTPParams{
126 PeerID: peerID,
@@ -154,13 +130,7 @@ func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, p
130 }
131 }
132
157 -func constructNilRouting(
158 - ctx context.Context,
159 - host host.Host,
160 - dstore datastore.Batching,
161 - validator record.Validator,
162 - bootstrapPeers ...peer.AddrInfo,
163 -) (routing.Routing, error) {
133 +func constructNilRouting(_ RoutingOptionArgs) (routing.Routing, error) {
134 return routinghelpers.Null{}, nil
135 }
136
docs/examples/kubo-as-a-library/go.mod
+2 -1
@@ -103,7 +103,7 @@ require (
103 github.com/libp2p/go-doh-resolver v0.4.0 // indirect
104 github.com/libp2p/go-flow-metrics v0.1.0 // indirect
105 github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect
106 - github.com/libp2p/go-libp2p-kad-dht v0.22.0 // indirect
106 + github.com/libp2p/go-libp2p-kad-dht v0.23.0 // indirect
107 github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect
108 github.com/libp2p/go-libp2p-pubsub v0.9.3 // indirect
109 github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect
@@ -188,6 +188,7 @@ require (
188 golang.org/x/text v0.8.0 // indirect
189 golang.org/x/tools v0.6.0 // indirect
190 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
191 + gonum.org/v1/gonum v0.11.0 // indirect
192 google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
193 google.golang.org/grpc v1.53.0 // indirect
194 google.golang.org/protobuf v1.30.0 // indirect
docs/examples/kubo-as-a-library/go.sum
+4 -2
@@ -518,8 +518,8 @@ github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLE
518 github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w=
519 github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g=
520 github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw=
521 -github.com/libp2p/go-libp2p-kad-dht v0.22.0 h1:cW2nGgG0hztDM42tOPyC5cVflD7EzLaHM0/Kjol6Wio=
522 -github.com/libp2p/go-libp2p-kad-dht v0.22.0/go.mod h1:hareSo3Z/GJ7nUWPMj7XhD/56a7+rRltYCWwCuy3FQk=
521 +github.com/libp2p/go-libp2p-kad-dht v0.23.0 h1:sxE6LxLopp79eLeV695n7+c77V/Vn4AMF28AdM/XFqM=
522 +github.com/libp2p/go-libp2p-kad-dht v0.23.0/go.mod h1:oO5N308VT2msnQI6qi5M61wzPmJYg7Tr9e16m5n7uDU=
523 github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio=
524 github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA=
525 github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U=
@@ -1191,6 +1191,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
1191 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1192 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
1193 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
1194 +gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
1195 +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
1196 google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1197 google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1198 google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
go.mod
+2 -1
@@ -47,7 +47,7 @@ require (
47 github.com/libp2p/go-doh-resolver v0.4.0
48 github.com/libp2p/go-libp2p v0.26.4
49 github.com/libp2p/go-libp2p-http v0.5.0
50 - github.com/libp2p/go-libp2p-kad-dht v0.22.0
50 + github.com/libp2p/go-libp2p-kad-dht v0.23.0
51 github.com/libp2p/go-libp2p-kbucket v0.5.0
52 github.com/libp2p/go-libp2p-pubsub v0.9.3
53 github.com/libp2p/go-libp2p-pubsub-router v0.6.0
@@ -222,6 +222,7 @@ require (
222 golang.org/x/text v0.8.0 // indirect
223 golang.org/x/tools v0.6.0 // indirect
224 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
225 + gonum.org/v1/gonum v0.11.0 // indirect
226 google.golang.org/appengine v1.6.7 // indirect
227 google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
228 google.golang.org/grpc v1.53.0 // indirect
go.sum
+4 -2
@@ -549,8 +549,8 @@ github.com/libp2p/go-libp2p-gostream v0.6.0 h1:QfAiWeQRce6pqnYfmIVWJFXNdDyfiR/qk
549 github.com/libp2p/go-libp2p-gostream v0.6.0/go.mod h1:Nywu0gYZwfj7Jc91PQvbGU8dIpqbQQkjWgDuOrFaRdA=
550 github.com/libp2p/go-libp2p-http v0.5.0 h1:+x0AbLaUuLBArHubbbNRTsgWz0RjNTy6DJLOxQ3/QBc=
551 github.com/libp2p/go-libp2p-http v0.5.0/go.mod h1:glh87nZ35XCQyFsdzZps6+F4HYI6DctVFY5u1fehwSg=
552 -github.com/libp2p/go-libp2p-kad-dht v0.22.0 h1:cW2nGgG0hztDM42tOPyC5cVflD7EzLaHM0/Kjol6Wio=
553 -github.com/libp2p/go-libp2p-kad-dht v0.22.0/go.mod h1:hareSo3Z/GJ7nUWPMj7XhD/56a7+rRltYCWwCuy3FQk=
552 +github.com/libp2p/go-libp2p-kad-dht v0.23.0 h1:sxE6LxLopp79eLeV695n7+c77V/Vn4AMF28AdM/XFqM=
553 +github.com/libp2p/go-libp2p-kad-dht v0.23.0/go.mod h1:oO5N308VT2msnQI6qi5M61wzPmJYg7Tr9e16m5n7uDU=
554 github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio=
555 github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA=
556 github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U=
@@ -1282,6 +1282,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
1282 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1283 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
1284 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
1285 +gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
1286 +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
1287 google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1288 google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1289 google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
test/cli/dht_opt_prov_test.go new
+30
@@ -0,0 +1,30 @@
1 +package cli
2 +
3 +import (
4 + "testing"
5 +
6 + "github.com/ipfs/kubo/config"
7 + "github.com/ipfs/kubo/test/cli/harness"
8 + "github.com/ipfs/kubo/test/cli/testutils"
9 + "github.com/stretchr/testify/assert"
10 +)
11 +
12 +func TestDHTOptimisticProvide(t *testing.T) {
13 + t.Parallel()
14 +
15 + t.Run("optimistic provide smoke test", func(t *testing.T) {
16 + nodes := harness.NewT(t).NewNodes(2).Init()
17 +
18 + nodes[0].UpdateConfig(func(cfg *config.Config) {
19 + cfg.Experimental.OptimisticProvide = true
20 + })
21 +
22 + nodes.StartDaemons().Connect()
23 +
24 + hash := nodes[0].IPFSAddStr(testutils.RandomStr(100))
25 + nodes[0].IPFS("dht", "provide", hash)
26 +
27 + res := nodes[1].IPFS("routing", "findprovs", "--num-providers=1", hash)
28 + assert.Equal(t, nodes[0].PeerID().String(), res.Stdout.Trimmed())
29 + })
30 +}
test/cli/harness/node.go
+2 -2
@@ -218,8 +218,8 @@ func (n *Node) Init(ipfsArgs ...string) *Node {
218 //
219 // node.StartDaemonWithReq(harness.RunRequest{
220 // CmdOpts: []harness.CmdOpt{
221 -// harness.RunWithStderr(os.Stdout),
222 -// harness.RunWithStdout(os.Stdout),
221 +// harness.RunWithStderr(os.Stdout),
222 +// harness.RunWithStdout(os.Stdout),
223 // },
224 // })
225 func (n *Node) StartDaemonWithReq(req RunRequest) *Node {
test/cli/testutils/random.go
+4
@@ -10,3 +10,7 @@ func RandomBytes(n int) []byte {
10 }
11 return bytes
12 }
13 +
14 +func RandomStr(n int) string {
15 + return string(RandomBytes(n))
16 +}