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
+}