@cryptotaxi247 / kubo / commits / 804634d59

namesys: switch to async code

License: MIT Signed-off-by: Łukasz Magiera <magik6k@gmail.com>

Łukasz Magiera committed Aug 28, 2018 at 15:37 UTC 804634d59b249a00768390e479bec558035ef5f3
7 files changed +74 -256
namesys/base.go
+24 -42
@@ -17,45 +17,33 @@ type onceResult struct {
17 }
18
19 type resolver interface {
20 - // resolveOnce looks up a name once (without recursion).
21 - resolveOnce(ctx context.Context, name string, options opts.ResolveOpts) (value path.Path, ttl time.Duration, err error)
22 -
20 resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult
21 }
22
23 // resolve is a helper for implementing Resolver.ResolveN using resolveOnce.
24 func resolve(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) (path.Path, error) {
28 - depth := options.Depth
29 - for {
30 - p, _, err := r.resolveOnce(ctx, name, options)
31 - if err != nil {
32 - return "", err
33 - }
34 - log.Debugf("resolved %s to %s", name, p.String())
25 + ctx, cancel := context.WithCancel(ctx)
26 + defer cancel()
27
36 - if strings.HasPrefix(p.String(), "/ipfs/") {
37 - // we've bottomed out with an IPFS path
38 - return p, nil
39 - }
28 + err := ErrResolveFailed
29 + var p path.Path
30
41 - if depth == 1 {
42 - return p, ErrResolveRecursion
43 - }
31 + resCh := resolveAsync(ctx, r, name, options, prefix)
32
45 - if !strings.HasPrefix(p.String(), prefix) {
46 - return p, nil
47 - }
48 - name = strings.TrimPrefix(p.String(), prefix)
49 -
50 - if depth > 1 {
51 - depth--
33 + for res := range resCh {
34 + p, err = res.Path, res.Err
35 + if err != nil {
36 + break
37 }
38 }
39 +
40 + return p, err
41 }
42
43 //TODO:
44 // - better error handling
58 -func resolveAsyncDo(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) <-chan Result {
45 +// - select on writes
46 +func resolveAsync(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) <-chan Result {
47 resCh := r.resolveOnceAsync(ctx, name, options)
48 depth := options.Depth
49 outCh := make(chan Result)
@@ -70,7 +58,7 @@ func resolveAsyncDo(ctx context.Context, r resolver, name string, options opts.R
58 case res, ok := <-resCh:
59 if !ok {
60 resCh = nil
73 - continue
61 + break
62 }
63
64 if res.err != nil {
@@ -79,14 +67,13 @@ func resolveAsyncDo(ctx context.Context, r resolver, name string, options opts.R
67 }
68 log.Debugf("resolved %s to %s", name, res.value.String())
69 if strings.HasPrefix(res.value.String(), "/ipfs/") {
82 - outCh <- Result{Err: res.err}
83 - continue
70 + outCh <- Result{Path: res.value}
71 + break
72 }
85 - p := strings.TrimPrefix(res.value.String(), prefix)
73
74 if depth == 1 {
88 - outCh <- Result{Err: ErrResolveRecursion}
89 - continue
75 + outCh <- Result{Path: res.value, Err: ErrResolveRecursion}
76 + break
77 }
78
79 subopts := options
@@ -102,26 +89,21 @@ func resolveAsyncDo(ctx context.Context, r resolver, name string, options opts.R
89 subCtx, cancelSub = context.WithCancel(ctx)
90 defer cancelSub()
91
105 - subCh = resolveAsyncDo(subCtx, r, p, subopts, prefix)
92 + p := strings.TrimPrefix(res.value.String(), prefix)
93 + subCh = resolveAsync(subCtx, r, p, subopts, prefix)
94 case res, ok := <-subCh:
95 if !ok {
96 subCh = nil
109 - continue
110 - }
111 -
112 - if res.Err != nil {
113 - outCh <- Result{Err: res.Err}
114 - return
97 + break
98 }
99
100 outCh <- res
101 case <-ctx.Done():
102 }
103 + if resCh == nil && subCh == nil {
104 + return
105 + }
106 }
107 }()
108 return outCh
109 }
124 -
125 -func resolveAsync(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) <-chan Result {
126 - return resolveAsyncDo(ctx, r, name, options, prefix)
127 -}
namesys/dns.go
+17 -50
@@ -5,7 +5,6 @@ import (
5 "errors"
6 "net"
7 "strings"
8 - "time"
8
9 opts "github.com/ipfs/go-ipfs/namesys/opts"
10 isd "gx/ipfs/QmZmmuAXgX73UQmX1jRKjTGmjzq24Jinqkq8vzkBtno4uX/go-is-domain"
@@ -31,6 +30,7 @@ func (r *DNSResolver) Resolve(ctx context.Context, name string, options ...opts.
30 return resolve(ctx, r, name, opts.ProcessOpts(options), "/ipns/")
31 }
32
33 +// ResolveAsync implements Resolver.
34 func (r *DNSResolver) ResolveAsync(ctx context.Context, name string, options ...opts.ResolveOpt) <-chan Result {
35 return resolveAsync(ctx, r, name, opts.ProcessOpts(options), "/ipns/")
36 }
@@ -43,51 +43,6 @@ type lookupRes struct {
43 // resolveOnce implements resolver.
44 // TXT records for a given domain name should contain a b58
45 // encoded multihash.
46 -func (r *DNSResolver) resolveOnce(ctx context.Context, name string, options opts.ResolveOpts) (path.Path, time.Duration, error) {
47 - segments := strings.SplitN(name, "/", 2)
48 - domain := segments[0]
49 -
50 - if !isd.IsDomain(domain) {
51 - return "", 0, errors.New("not a valid domain name")
52 - }
53 - log.Debugf("DNSResolver resolving %s", domain)
54 -
55 - rootChan := make(chan lookupRes, 1)
56 - go workDomain(r, domain, rootChan)
57 -
58 - subChan := make(chan lookupRes, 1)
59 - go workDomain(r, "_dnslink."+domain, subChan)
60 -
61 - var subRes lookupRes
62 - select {
63 - case subRes = <-subChan:
64 - case <-ctx.Done():
65 - return "", 0, ctx.Err()
66 - }
67 -
68 - var p path.Path
69 - if subRes.error == nil {
70 - p = subRes.path
71 - } else {
72 - var rootRes lookupRes
73 - select {
74 - case rootRes = <-rootChan:
75 - case <-ctx.Done():
76 - return "", 0, ctx.Err()
77 - }
78 - if rootRes.error == nil {
79 - p = rootRes.path
80 - } else {
81 - return "", 0, ErrResolveFailed
82 - }
83 - }
84 - var err error
85 - if len(segments) > 1 {
86 - p, err = path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[1])
87 - }
88 - return p, 0, err
89 -}
90 -
46 func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult {
47 out := make(chan onceResult, 1)
48 segments := strings.SplitN(name, "/", 2)
@@ -106,6 +61,13 @@ func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options
61 subChan := make(chan lookupRes, 1)
62 go workDomain(r, "_dnslink."+domain, subChan)
63
64 + appendPath := func(p path.Path) (path.Path, error) {
65 + if len(segments) > 1 {
66 + return path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[1])
67 + }
68 + return p, nil
69 + }
70 +
71 go func() {
72 defer close(out)
73 for {
@@ -113,21 +75,25 @@ func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options
75 case subRes, ok := <-subChan:
76 if !ok {
77 subChan = nil
78 + break
79 }
80 if subRes.error == nil {
81 + p, err := appendPath(subRes.path)
82 select {
119 - case out <- onceResult{value: subRes.path}:
83 + case out <- onceResult{value: p, err: err}:
84 case <-ctx.Done():
85 }
86 return
87 }
88 case rootRes, ok := <-rootChan:
89 if !ok {
126 - subChan = nil
90 + rootChan = nil
91 + break
92 }
93 if rootRes.error == nil {
94 + p, err := appendPath(rootRes.path)
95 select {
130 - case out <- onceResult{value: rootRes.path}:
96 + case out <- onceResult{value: p, err: err}:
97 case <-ctx.Done():
98 }
99 }
@@ -144,8 +110,9 @@ func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options
110 }
111
112 func workDomain(r *DNSResolver, name string, res chan lookupRes) {
147 - txt, err := r.lookupTXT(name)
113 + defer close(res)
114
115 + txt, err := r.lookupTXT(name)
116 if err != nil {
117 // Error is != nil
118 res <- lookupRes{"", err}
namesys/ipns_resolver_validation_test.go
+5 -6
@@ -57,14 +57,13 @@ func TestResolverValidation(t *testing.T) {
57 }
58
59 // Resolve entry
60 - resp, _, err := resolver.resolveOnce(ctx, id.Pretty(), opts.DefaultResolveOpts())
60 + resp, err := resolve(ctx, resolver, id.Pretty(), opts.DefaultResolveOpts(), "/ipns/")
61 if err != nil {
62 t.Fatal(err)
63 }
64 if resp != path.Path(p) {
65 t.Fatalf("Mismatch between published path %s and resolved path %s", p, resp)
66 }
67 -
67 // Create expired entry
68 expiredEntry, err := ipns.Create(priv, p, 1, ts.Add(-1*time.Hour))
69 if err != nil {
@@ -78,7 +77,7 @@ func TestResolverValidation(t *testing.T) {
77 }
78
79 // Record should fail validation because entry is expired
81 - _, _, err = resolver.resolveOnce(ctx, id.Pretty(), opts.DefaultResolveOpts())
80 + _, err = resolve(ctx, resolver, id.Pretty(), opts.DefaultResolveOpts(), "/ipns/")
81 if err == nil {
82 t.Fatal("ValidateIpnsRecord should have returned error")
83 }
@@ -100,7 +99,7 @@ func TestResolverValidation(t *testing.T) {
99
100 // Record should fail validation because public key defined by
101 // ipns path doesn't match record signature
103 - _, _, err = resolver.resolveOnce(ctx, id2.Pretty(), opts.DefaultResolveOpts())
102 + _, err = resolve(ctx, resolver, id2.Pretty(), opts.DefaultResolveOpts(), "/ipns/")
103 if err == nil {
104 t.Fatal("ValidateIpnsRecord should have failed signature verification")
105 }
@@ -118,7 +117,7 @@ func TestResolverValidation(t *testing.T) {
117
118 // Record should fail validation because public key is not available
119 // in peer store or on network
121 - _, _, err = resolver.resolveOnce(ctx, id3.Pretty(), opts.DefaultResolveOpts())
120 + _, err = resolve(ctx, resolver, id3.Pretty(), opts.DefaultResolveOpts(), "/ipns/")
121 if err == nil {
122 t.Fatal("ValidateIpnsRecord should have failed because public key was not found")
123 }
@@ -133,7 +132,7 @@ func TestResolverValidation(t *testing.T) {
132 // public key is available in the peer store by looking it up in
133 // the DHT, which causes the DHT to fetch it and cache it in the
134 // peer store
136 - _, _, err = resolver.resolveOnce(ctx, id3.Pretty(), opts.DefaultResolveOpts())
135 + _, err = resolve(ctx, resolver, id3.Pretty(), opts.DefaultResolveOpts(), "/ipns/")
136 if err != nil {
137 t.Fatal(err)
138 }
namesys/namesys.go
-42
@@ -82,48 +82,6 @@ func (ns *mpns) ResolveAsync(ctx context.Context, name string, options ...opts.R
82 }
83
84 // resolveOnce implements resolver.
85 -func (ns *mpns) resolveOnce(ctx context.Context, name string, options opts.ResolveOpts) (path.Path, time.Duration, error) {
86 - if !strings.HasPrefix(name, "/ipns/") {
87 - name = "/ipns/" + name
88 - }
89 - segments := strings.SplitN(name, "/", 4)
90 - if len(segments) < 3 || segments[0] != "" {
91 - log.Debugf("invalid name syntax for %s", name)
92 - return "", 0, ErrResolveFailed
93 - }
94 -
95 - key := segments[2]
96 -
97 - p, ok := ns.cacheGet(key)
98 - var err error
99 - if !ok {
100 - // Resolver selection:
101 - // 1. if it is a multihash resolve through "ipns".
102 - // 2. if it is a domain name, resolve through "dns"
103 - // 3. otherwise resolve through the "proquint" resolver
104 - var res resolver
105 - if _, err := mh.FromB58String(key); err == nil {
106 - res = ns.ipnsResolver
107 - } else if isd.IsDomain(key) {
108 - res = ns.dnsResolver
109 - } else {
110 - res = ns.proquintResolver
111 - }
112 -
113 - var ttl time.Duration
114 - p, ttl, err = res.resolveOnce(ctx, key, options)
115 - if err != nil {
116 - return "", 0, ErrResolveFailed
117 - }
118 - ns.cacheSet(key, p, ttl)
119 - }
120 -
121 - if len(segments) > 3 {
122 - p, err = path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[3])
123 - }
124 - return p, 0, err
125 -}
126 -
85 func (ns *mpns) resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult {
86 out := make(chan onceResult, 1)
87
namesys/namesys_test.go
+6 -8
@@ -4,12 +4,11 @@ import (
4 "context"
5 "fmt"
6 "testing"
7 - "time"
7
8 opts "github.com/ipfs/go-ipfs/namesys/opts"
9 +
10 "gx/ipfs/QmU4x3742bvgfxJsByEDpBnifJqjJdV6x528co4hwKCn46/go-unixfs"
11 path "gx/ipfs/QmcjwUb36Z16NJkvDX6ccXPqsFswo6AsRXynyXcLLCphV2/go-path"
12 -
12 ci "gx/ipfs/QmPvyPwuCgJ7pDmrKDxRtsScJgBaM5h4EpRL2qQJsmXf4n/go-libp2p-crypto"
13 offroute "gx/ipfs/QmScZySgru9jaoDa12sSfvh21sWbqF5eXkieTmJzAHJXkQ/go-ipfs-routing/offline"
14 ds "gx/ipfs/QmUyz7JTJzgegC6tiJrfby3mPhzcdswVtG4x58TQ6pq8jV/go-datastore"
@@ -38,13 +37,12 @@ func testResolution(t *testing.T, resolver Resolver, name string, depth uint, ex
37 }
38 }
39
41 -func (r *mockResolver) resolveOnce(ctx context.Context, name string, opts opts.ResolveOpts) (path.Path, time.Duration, error) {
42 - p, err := path.ParsePath(r.entries[name])
43 - return p, 0, err
44 -}
45 -
40 func (r *mockResolver) resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult {
47 - panic("stub")
41 + p, err := path.ParsePath(r.entries[name])
42 + out := make(chan onceResult, 1)
43 + out <- onceResult{value: p, err: err}
44 + close(out)
45 + return out
46 }
47
48 func mockResolverOne() *mockResolver {
namesys/proquint.go
+1 -12
@@ -1,10 +1,8 @@
1 package namesys
2
3 import (
4 + "context"
5 "errors"
5 - "time"
6 -
7 - context "context"
6
7 opts "github.com/ipfs/go-ipfs/namesys/opts"
8 proquint "gx/ipfs/QmYnf27kzqR2cxt6LFZdrAFJuQd6785fTkBvMuEj9EeRxM/proquint"
@@ -19,15 +17,6 @@ func (r *ProquintResolver) Resolve(ctx context.Context, name string, options ...
17 }
18
19 // resolveOnce implements resolver. Decodes the proquint string.
22 -func (r *ProquintResolver) resolveOnce(ctx context.Context, name string, options opts.ResolveOpts) (path.Path, time.Duration, error) {
23 - ok, err := proquint.IsProquint(name)
24 - if err != nil || !ok {
25 - return "", 0, errors.New("not a valid proquint string")
26 - }
27 - // Return a 0 TTL as caching this result is pointless.
28 - return path.FromString(string(proquint.Decode(name))), 0, nil
29 -}
30 -
20 func (r *ProquintResolver) resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult {
21 out := make(chan onceResult, 1)
22 defer close(out)
namesys/routing.go
+21 -96
@@ -11,6 +11,7 @@ import (
11 cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
12 mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
13 routing "gx/ipfs/QmVBnJDKhtFXTRVjXKinqpwGu8t1DyNqPKan2iGX8PR8xG/go-libp2p-routing"
14 + ropts "gx/ipfs/QmVBnJDKhtFXTRVjXKinqpwGu8t1DyNqPKan2iGX8PR8xG/go-libp2p-routing/options"
15 logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
16 dht "gx/ipfs/QmZVakpN44VAUxs9eXAuUGLFYTCGmSyqSy6hyEKfMv68ME/go-libp2p-kad-dht"
17 ipns "gx/ipfs/QmZrmn2BPZbSviQAWeyY2iXkCukmJHv9n7zrLgWU5KgbTb/go-ipns"
@@ -42,120 +43,30 @@ func (r *IpnsResolver) Resolve(ctx context.Context, name string, options ...opts
43 return resolve(ctx, r, name, opts.ProcessOpts(options), "/ipns/")
44 }
45
46 +// ResolveAsync implements Resolver.
47 func (r *IpnsResolver) ResolveAsync(ctx context.Context, name string, options ...opts.ResolveOpt) <-chan Result {
48 return resolveAsync(ctx, r, name, opts.ProcessOpts(options), "/ipns/")
49 }
50
51 // resolveOnce implements resolver. Uses the IPFS routing system to
52 // resolve SFS-like names.
51 -func (r *IpnsResolver) resolveOnce(ctx context.Context, name string, options opts.ResolveOpts) (path.Path, time.Duration, error) {
52 - log.Debugf("RoutingResolver resolving %s", name)
53 -
54 - if options.DhtTimeout != 0 {
55 - // Resolution must complete within the timeout
56 - var cancel context.CancelFunc
57 - ctx, cancel = context.WithTimeout(ctx, options.DhtTimeout)
58 - defer cancel()
59 - }
60 -
61 - name = strings.TrimPrefix(name, "/ipns/")
62 - pid, err := peer.IDB58Decode(name)
63 - if err != nil {
64 - // name should be a multihash. if it isn't, error out here.
65 - log.Debugf("RoutingResolver: IPNS address not a valid peer ID: [%s]\n", name)
66 - return "", 0, err
67 - }
68 -
69 - // Name should be the hash of a public key retrievable from ipfs.
70 - // We retrieve the public key here to make certain that it's in the peer
71 - // store before calling GetValue() on the DHT - the DHT will call the
72 - // ipns validator, which in turn will get the public key from the peer
73 - // store to verify the record signature
74 - _, err = routing.GetPublicKey(r.routing, ctx, pid)
75 - if err != nil {
76 - log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
77 - return "", 0, err
78 - }
79 -
80 - // Use the routing system to get the name.
81 - // Note that the DHT will call the ipns validator when retrieving
82 - // the value, which in turn verifies the ipns record signature
83 - ipnsKey := ipns.RecordKey(pid)
84 - val, err := r.routing.GetValue(ctx, ipnsKey, dht.Quorum(int(options.DhtRecordCount)))
85 - if err != nil {
86 - log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
87 - return "", 0, err
88 - }
89 -
90 - entry := new(pb.IpnsEntry)
91 - err = proto.Unmarshal(val, entry)
92 - if err != nil {
93 - log.Debugf("RoutingResolver: could not unmarshal value for name %s: %s", name, err)
94 - return "", 0, err
95 - }
96 -
97 - var p path.Path
98 - // check for old style record:
99 - if valh, err := mh.Cast(entry.GetValue()); err == nil {
100 - // Its an old style multihash record
101 - log.Debugf("encountered CIDv0 ipns entry: %s", valh)
102 - p = path.FromCid(cid.NewCidV0(valh))
103 - } else {
104 - // Not a multihash, probably a new record
105 - p, err = path.ParsePath(string(entry.GetValue()))
106 - if err != nil {
107 - return "", 0, err
108 - }
109 - }
110 -
111 - ttl := DefaultResolverCacheTTL
112 - if entry.Ttl != nil {
113 - ttl = time.Duration(*entry.Ttl)
114 - }
115 - switch eol, err := ipns.GetEOL(entry); err {
116 - case ipns.ErrUnrecognizedValidity:
117 - // No EOL.
118 - case nil:
119 - ttEol := eol.Sub(time.Now())
120 - if ttEol < 0 {
121 - // It *was* valid when we first resolved it.
122 - ttl = 0
123 - } else if ttEol < ttl {
124 - ttl = ttEol
125 - }
126 - default:
127 - log.Errorf("encountered error when parsing EOL: %s", err)
128 - return "", 0, err
129 - }
130 -
131 - return p, ttl, nil
132 -}
133 -
53 func (r *IpnsResolver) resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult {
54 out := make(chan onceResult, 1)
55 log.Debugf("RoutingResolver resolving %s", name)
56 + cancel := func() {}
57 +
58 if options.DhtTimeout != 0 {
59 // Resolution must complete within the timeout
139 - var cancel context.CancelFunc
60 ctx, cancel = context.WithTimeout(ctx, options.DhtTimeout)
141 - defer cancel()
61 }
62
63 name = strings.TrimPrefix(name, "/ipns/")
145 - hash, err := mh.FromB58String(name)
146 - if err != nil {
147 - // name should be a multihash. if it isn't, error out here.
148 - log.Debugf("RoutingResolver: bad input hash: [%s]\n", name)
149 - out <- onceResult{err: err}
150 - close(out)
151 - return out
152 - }
153 -
154 - pid, err := peer.IDFromBytes(hash)
64 + pid, err := peer.IDB58Decode(name)
65 if err != nil {
66 log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
67 out <- onceResult{err: err}
68 close(out)
69 + cancel()
70 return out
71 }
72
@@ -169,6 +80,7 @@ func (r *IpnsResolver) resolveOnceAsync(ctx context.Context, name string, option
80 log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
81 out <- onceResult{err: err}
82 close(out)
83 + cancel()
84 return out
85 }
86
@@ -177,15 +89,17 @@ func (r *IpnsResolver) resolveOnceAsync(ctx context.Context, name string, option
89 // the value, which in turn verifies the ipns record signature
90 ipnsKey := ipns.RecordKey(pid)
91
180 - vals, err := r.routing.(*dht.IpfsDHT).SearchValue(ctx, ipnsKey, dht.Quorum(int(options.DhtRecordCount)))
92 + vals, err := r.searchValue(ctx, ipnsKey, dht.Quorum(int(options.DhtRecordCount)))
93 if err != nil {
94 log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
95 out <- onceResult{err: err}
96 close(out)
97 + cancel()
98 return out
99 }
100
101 go func() {
102 + defer cancel()
103 defer close(out)
104 for {
105 select {
@@ -259,3 +173,14 @@ func (r *IpnsResolver) resolveOnceAsync(ctx context.Context, name string, option
173
174 return out
175 }
176 +
177 +func (r *IpnsResolver) searchValue(ctx context.Context, key string, opts ...ropts.Option) (<-chan []byte, error) {
178 + if ir, ok := r.routing.(*dht.IpfsDHT); ok {
179 + return ir.SearchValue(ctx, key, opts...)
180 + }
181 + out := make(chan []byte, 1)
182 + val, err := r.routing.GetValue(ctx, key, opts...)
183 + out <- val
184 + close(out)
185 + return out, err
186 +}