Skip to content

Commit

Permalink
feat: bubble TTL out of NameSys
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Sep 4, 2023
1 parent c0f757a commit b335bde
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 75 deletions.
2 changes: 1 addition & 1 deletion gateway/blocks_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ func (bb *BlocksBackend) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte,

func (bb *BlocksBackend) GetDNSLinkRecord(ctx context.Context, hostname string) (ifacepath.Path, error) {
if bb.namesys != nil {
p, err := bb.namesys.Resolve(ctx, "/ipns/"+hostname, namesys.ResolveWithDepth(1))
p, _, err := bb.namesys.Resolve(ctx, "/ipns/"+hostname, namesys.ResolveWithDepth(1))

Check warning on line 610 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L610

Added line #L610 was not covered by tests
if err == namesys.ErrResolveRecursion {
err = nil
}
Expand Down
15 changes: 8 additions & 7 deletions gateway/utilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"regexp"
"strings"
"testing"
"time"

"github.com/ipfs/boxo/blockservice"
ipath "github.com/ipfs/boxo/coreiface/path"
Expand Down Expand Up @@ -53,7 +54,7 @@ func mustDo(t *testing.T, req *http.Request) *http.Response {

type mockNamesys map[string]path.Path

func (m mockNamesys) Resolve(ctx context.Context, name string, opts ...namesys.ResolveOption) (value path.Path, err error) {
func (m mockNamesys) Resolve(ctx context.Context, name string, opts ...namesys.ResolveOption) (value path.Path, ttl time.Duration, err error) {
cfg := namesys.DefaultResolveOptions()
for _, o := range opts {
o(&cfg)
Expand All @@ -65,24 +66,24 @@ func (m mockNamesys) Resolve(ctx context.Context, name string, opts ...namesys.R
}
for strings.HasPrefix(name, "/ipns/") {
if depth == 0 {
return value, namesys.ErrResolveRecursion
return value, 0, namesys.ErrResolveRecursion
}
depth--

var ok bool
value, ok = m[name]
if !ok {
return "", namesys.ErrResolveFailed
return "", 0, namesys.ErrResolveFailed
}
name = value.String()
}
return value, nil
return value, 0, nil
}

func (m mockNamesys) ResolveAsync(ctx context.Context, name string, opts ...namesys.ResolveOption) <-chan namesys.ResolveResult {
out := make(chan namesys.ResolveResult, 1)
v, err := m.Resolve(ctx, name, opts...)
out <- namesys.ResolveResult{Path: v, Err: err}
v, ttl, err := m.Resolve(ctx, name, opts...)
out <- namesys.ResolveResult{Path: v, TTL: ttl, Err: err}
close(out)
return out
}
Expand Down Expand Up @@ -162,7 +163,7 @@ func (mb *mockBackend) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, er

func (mb *mockBackend) GetDNSLinkRecord(ctx context.Context, hostname string) (ipath.Path, error) {
if mb.namesys != nil {
p, err := mb.namesys.Resolve(ctx, "/ipns/"+hostname, namesys.ResolveWithDepth(1))
p, _, err := mb.namesys.Resolve(ctx, "/ipns/"+hostname, namesys.ResolveWithDepth(1))
if err == namesys.ErrResolveRecursion {
err = nil
}
Expand Down
21 changes: 11 additions & 10 deletions namesys/dns_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net"
gpath "path"
"strings"
"time"

path "github.com/ipfs/boxo/path"
dns "github.com/miekg/dns"
Expand All @@ -31,7 +32,7 @@ func NewDNSResolver(lookup LookupTXTFunc) *DNSResolver {
return &DNSResolver{lookupTXT: lookup}
}

func (r *DNSResolver) Resolve(ctx context.Context, name string, options ...ResolveOption) (path.Path, error) {
func (r *DNSResolver) Resolve(ctx context.Context, name string, options ...ResolveOption) (path.Path, time.Duration, error) {
ctx, span := startSpan(ctx, "DNSResolver.Resolve")
defer span.End()

Expand All @@ -45,17 +46,17 @@ func (r *DNSResolver) ResolveAsync(ctx context.Context, name string, options ...
return resolveAsync(ctx, r, name, ProcessResolveOptions(options))

Check warning on line 46 in namesys/dns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/dns_resolver.go#L46

Added line #L46 was not covered by tests
}

func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options ResolveOptions) <-chan onceResult {
func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options ResolveOptions) <-chan ResolveResult {
ctx, span := startSpan(ctx, "DNSResolver.ResolveOnceAsync")
defer span.End()

var fqdn string
out := make(chan onceResult, 1)
out := make(chan ResolveResult, 1)
segments := strings.SplitN(name, "/", 2)
domain := segments[0]

if _, ok := dns.IsDomainName(domain); !ok {
out <- onceResult{err: fmt.Errorf("not a valid domain name: %s", domain)}
out <- ResolveResult{Err: fmt.Errorf("not a valid domain name: %s", domain)}

Check warning on line 59 in namesys/dns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/dns_resolver.go#L59

Added line #L59 was not covered by tests
close(out)
return out
}
Expand Down Expand Up @@ -95,7 +96,7 @@ func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options
}
if subRes.Err == nil {
p, err := appendPath(subRes.Path)
emitOnceResult(ctx, out, onceResult{value: p, err: err})
emitOnceResult(ctx, out, ResolveResult{Path: p, Err: err})
// Return without waiting for rootRes, since this result
// (for "_dnslink."+fqdn) takes precedence
return
Expand All @@ -108,7 +109,7 @@ func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options
}
if rootRes.Err == nil {
p, err := appendPath(rootRes.Path)
emitOnceResult(ctx, out, onceResult{value: p, err: err})
emitOnceResult(ctx, out, ResolveResult{Path: p, Err: err})
// Do not return here. Wait for subRes so that it is
// output last if good, thereby giving subRes precedence.
} else {
Expand All @@ -125,7 +126,7 @@ func (r *DNSResolver) resolveOnceAsync(ctx context.Context, name string, options
if rootResErr == ErrResolveFailed && subResErr == ErrResolveFailed {
// Wrap error so that it can be tested if it is a ErrResolveFailed
err := fmt.Errorf("%w: _dnslink subdomain at %q is missing a TXT record (https://docs.ipfs.tech/concepts/dnslink/)", ErrResolveFailed, gpath.Base(name))
emitOnceResult(ctx, out, onceResult{err: err})
emitOnceResult(ctx, out, ResolveResult{Err: err})

Check warning on line 129 in namesys/dns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/dns_resolver.go#L129

Added line #L129 was not covered by tests
}
return
}
Expand All @@ -151,20 +152,20 @@ func workDomain(ctx context.Context, r *DNSResolver, name string, res chan Resol
}
}
// Could not look up any text records for name
res <- ResolveResult{"", err}
res <- ResolveResult{Path: "", Err: err}
return
}

for _, t := range txt {
p, err := parseEntry(t)
if err == nil {
res <- ResolveResult{p, nil}
res <- ResolveResult{Path: p, Err: nil}
return
}
}

// There were no TXT records with a dnslink
res <- ResolveResult{"", ErrResolveFailed}
res <- ResolveResult{Path: "", Err: ErrResolveFailed}
}

func parseEntry(txt string) (path.Path, error) {
Expand Down
18 changes: 9 additions & 9 deletions namesys/ipns_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewIPNSResolver(route routing.ValueStore) *IPNSResolver {
}
}

func (r *IPNSResolver) Resolve(ctx context.Context, name string, options ...ResolveOption) (path.Path, error) {
func (r *IPNSResolver) Resolve(ctx context.Context, name string, options ...ResolveOption) (path.Path, time.Duration, error) {
ctx, span := startSpan(ctx, "IpnsResolver.Resolve", trace.WithAttributes(attribute.String("Name", name)))
defer span.End()

Expand All @@ -47,11 +47,11 @@ func (r *IPNSResolver) ResolveAsync(ctx context.Context, name string, options ..
return resolveAsync(ctx, r, name, ProcessResolveOptions(options))

Check warning on line 47 in namesys/ipns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/ipns_resolver.go#L46-L47

Added lines #L46 - L47 were not covered by tests
}

func (r *IPNSResolver) resolveOnceAsync(ctx context.Context, name string, options ResolveOptions) <-chan onceResult {
func (r *IPNSResolver) resolveOnceAsync(ctx context.Context, name string, options ResolveOptions) <-chan ResolveResult {
ctx, span := startSpan(ctx, "IpnsResolver.ResolveOnceAsync", trace.WithAttributes(attribute.String("Name", name)))
defer span.End()

out := make(chan onceResult, 1)
out := make(chan ResolveResult, 1)
log.Debugf("RoutingResolver resolving %s", name)
cancel := func() {}

Expand All @@ -65,7 +65,7 @@ func (r *IPNSResolver) resolveOnceAsync(ctx context.Context, name string, option
pid, err := peer.Decode(name)
if err != nil {
log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
out <- onceResult{err: err}
out <- ResolveResult{Err: err}

Check warning on line 68 in namesys/ipns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/ipns_resolver.go#L68

Added line #L68 was not covered by tests
close(out)
cancel()
return out
Expand All @@ -79,7 +79,7 @@ func (r *IPNSResolver) resolveOnceAsync(ctx context.Context, name string, option
vals, err := r.routing.SearchValue(ctx, ipnsKey, dht.Quorum(int(options.DhtRecordCount)))
if err != nil {
log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
out <- onceResult{err: err}
out <- ResolveResult{Err: err}

Check warning on line 82 in namesys/ipns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/ipns_resolver.go#L82

Added line #L82 was not covered by tests
close(out)
cancel()
return out
Expand All @@ -101,13 +101,13 @@ func (r *IPNSResolver) resolveOnceAsync(ctx context.Context, name string, option
rec, err := ipns.UnmarshalRecord(val)
if err != nil {
log.Debugf("RoutingResolver: could not unmarshal value for name %s: %s", name, err)
emitOnceResult(ctx, out, onceResult{err: err})
emitOnceResult(ctx, out, ResolveResult{Err: err})

Check warning on line 104 in namesys/ipns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/ipns_resolver.go#L104

Added line #L104 was not covered by tests
return
}

p, err := rec.Value()
if err != nil {
emitOnceResult(ctx, out, onceResult{err: err})
emitOnceResult(ctx, out, ResolveResult{Err: err})

Check warning on line 110 in namesys/ipns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/ipns_resolver.go#L110

Added line #L110 was not covered by tests
return
}

Expand All @@ -129,11 +129,11 @@ func (r *IPNSResolver) resolveOnceAsync(ctx context.Context, name string, option
}
default:
log.Errorf("encountered error when parsing EOL: %s", err)
emitOnceResult(ctx, out, onceResult{err: err})
emitOnceResult(ctx, out, ResolveResult{Err: err})

Check warning on line 132 in namesys/ipns_resolver.go

View check run for this annotation

Codecov / codecov/patch

namesys/ipns_resolver.go#L132

Added line #L132 was not covered by tests
return
}

emitOnceResult(ctx, out, onceResult{value: path.Path(p.String()), ttl: ttl})
emitOnceResult(ctx, out, ResolveResult{Path: path.Path(p.String()), TTL: ttl})
case <-ctx.Done():
return
}
Expand Down
4 changes: 2 additions & 2 deletions namesys/ipns_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestRoutingResolve(t *testing.T) {
err := publisher.Publish(context.Background(), identity.PrivateKey(), h)
require.NoError(t, err)

res, err := resolver.Resolve(context.Background(), identity.ID().Pretty())
res, _, err := resolver.Resolve(context.Background(), identity.ID().Pretty())
require.NoError(t, err)
require.Equal(t, h, res)
}
Expand Down Expand Up @@ -89,7 +89,7 @@ func TestPrexistingRecord(t *testing.T) {
}

func verifyCanResolve(r Resolver, name string, exp path.Path) error {
res, err := r.Resolve(context.Background(), name)
res, _, err := r.Resolve(context.Background(), name)
if err != nil {
return err
}
Expand Down
42 changes: 22 additions & 20 deletions namesys/mpns.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,18 @@ func NewNameSystem(r routing.ValueStore, opts ...Option) (NameSystem, error) {
}

// Resolve implements Resolver.
func (ns *nameSys) Resolve(ctx context.Context, name string, options ...ResolveOption) (path.Path, error) {
func (ns *nameSys) Resolve(ctx context.Context, name string, options ...ResolveOption) (path.Path, time.Duration, error) {
ctx, span := startSpan(ctx, "MPNS.Resolve", trace.WithAttributes(attribute.String("Name", name)))
defer span.End()

if strings.HasPrefix(name, "/ipfs/") {
return path.ParsePath(name)
p, err := path.ParsePath(name)
return p, 0, err
}

Check warning on line 132 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L130-L132

Added lines #L130 - L132 were not covered by tests

if !strings.HasPrefix(name, "/") {
return path.ParsePath("/ipfs/" + name)
p, err := path.ParsePath("/ipfs/" + name)
return p, 0, err
}

return resolve(ctx, ns, name, ProcessResolveOptions(options))
Expand All @@ -144,15 +146,15 @@ func (ns *nameSys) ResolveAsync(ctx context.Context, name string, options ...Res
if strings.HasPrefix(name, "/ipfs/") {
p, err := path.ParsePath(name)
res := make(chan ResolveResult, 1)
res <- ResolveResult{p, err}
res <- ResolveResult{Path: p, Err: err}
close(res)
return res
}

Check warning on line 152 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L142-L152

Added lines #L142 - L152 were not covered by tests

if !strings.HasPrefix(name, "/") {
p, err := path.ParsePath("/ipfs/" + name)
res := make(chan ResolveResult, 1)
res <- ResolveResult{p, err}
res <- ResolveResult{Path: p, Err: err}
close(res)
return res
}

Check warning on line 160 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L154-L160

Added lines #L154 - L160 were not covered by tests
Expand All @@ -161,19 +163,19 @@ func (ns *nameSys) ResolveAsync(ctx context.Context, name string, options ...Res
}

// resolveOnce implements resolver.
func (ns *nameSys) resolveOnceAsync(ctx context.Context, name string, options ResolveOptions) <-chan onceResult {
func (ns *nameSys) resolveOnceAsync(ctx context.Context, name string, options ResolveOptions) <-chan ResolveResult {
ctx, span := startSpan(ctx, "MPNS.ResolveOnceAsync")
defer span.End()

out := make(chan onceResult, 1)
out := make(chan ResolveResult, 1)

if !strings.HasPrefix(name, ipns.NamespacePrefix) {
name = ipns.NamespacePrefix + name
}
segments := strings.SplitN(name, "/", 4)
if len(segments) < 3 || segments[0] != "" {
log.Debugf("invalid name syntax for %s", name)
out <- onceResult{err: ErrResolveFailed}
out <- ResolveResult{Err: ErrResolveFailed}
close(out)
return out
}

Check warning on line 181 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L177-L181

Added lines #L177 - L181 were not covered by tests
Expand All @@ -194,7 +196,7 @@ func (ns *nameSys) resolveOnceAsync(ctx context.Context, name string, options Re
fixedCid := cid.NewCidV1(cid.Libp2pKey, ipnsCid.Hash()).String()
codecErr := fmt.Errorf("peer ID represented as CIDv1 require libp2p-key multicodec: retry with /ipns/%s", fixedCid)
log.Debugf("RoutingResolver: could not convert public key hash %q to peer ID: %s\n", key, codecErr)
out <- onceResult{err: codecErr}
out <- ResolveResult{Err: codecErr}
close(out)
return out
}

Check warning on line 202 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L196-L202

Added lines #L196 - L202 were not covered by tests
Expand All @@ -213,7 +215,7 @@ func (ns *nameSys) resolveOnceAsync(ctx context.Context, name string, options Re
span.SetAttributes(attribute.Bool("CacheHit", true))
span.RecordError(err)

out <- onceResult{value: p, err: err}
out <- ResolveResult{Path: p, Err: err}
close(out)
return out

Check warning on line 220 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L211-L220

Added lines #L211 - L220 were not covered by tests
}
Expand All @@ -224,37 +226,37 @@ func (ns *nameSys) resolveOnceAsync(ctx context.Context, name string, options Re
} else if _, ok := dns.IsDomainName(key); ok {
res = ns.dnsResolver
} else {
out <- onceResult{err: fmt.Errorf("invalid IPNS root: %q", key)}
out <- ResolveResult{Err: fmt.Errorf("invalid IPNS root: %q", key)}
close(out)
return out
}

Check warning on line 232 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L229-L232

Added lines #L229 - L232 were not covered by tests

resCh := res.resolveOnceAsync(ctx, key, options)
var best onceResult
var best ResolveResult
go func() {
defer close(out)
for {
select {
case res, ok := <-resCh:
if !ok {
if best != (onceResult{}) {
ns.cacheSet(cacheKey, best.value, best.ttl)
if best != (ResolveResult{}) {
ns.cacheSet(cacheKey, best.Path, best.TTL)
}
return
}
if res.err == nil {
if res.Err == nil {
best = res
}
p := res.value
err := res.err
ttl := res.ttl
p := res.Path
err := res.Err
ttl := res.TTL

// Attach rest of the path
if len(segments) > 3 {
p, err = path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[3])
}

Check warning on line 257 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L256-L257

Added lines #L256 - L257 were not covered by tests

emitOnceResult(ctx, out, onceResult{value: p, ttl: ttl, err: err})
emitOnceResult(ctx, out, ResolveResult{Path: p, TTL: ttl, Err: err})
case <-ctx.Done():
return

Check warning on line 261 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L260-L261

Added lines #L260 - L261 were not covered by tests
}
Expand All @@ -264,7 +266,7 @@ func (ns *nameSys) resolveOnceAsync(ctx context.Context, name string, options Re
return out
}

func emitOnceResult(ctx context.Context, outCh chan<- onceResult, r onceResult) {
func emitOnceResult(ctx context.Context, outCh chan<- ResolveResult, r ResolveResult) {
select {
case outCh <- r:
case <-ctx.Done():

Check warning on line 272 in namesys/mpns.go

View check run for this annotation

Codecov / codecov/patch

namesys/mpns.go#L272

Added line #L272 was not covered by tests
Expand Down
3 changes: 2 additions & 1 deletion namesys/namesys.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type NameSystem interface {
// ResolveResult is the return type for [Resolver.ResolveAsync].
type ResolveResult struct {
Path path.Path
TTL time.Duration
Err error
}

Expand All @@ -94,7 +95,7 @@ type Resolver interface {
//
// There is a default depth-limit to avoid infinite recursion. Most users will be fine with
// this default limit, but if you need to adjust the limit you can specify it as an option.
Resolve(ctx context.Context, name string, options ...ResolveOption) (value path.Path, err error)
Resolve(ctx context.Context, name string, options ...ResolveOption) (value path.Path, ttl time.Duration, err error)

// ResolveAsync performs recursive name lookup, like Resolve, but it returns entries as
// they are discovered in the DHT. Each returned result is guaranteed to be "better"
Expand Down
Loading

0 comments on commit b335bde

Please sign in to comment.