@cryptotaxi247 / kubo / commits / d6464864c

feat(ipns): refactored IPNS package with lean records (#339)

This commit was moved from ipfs/boxo@417c5f7d61ff5dcf0fef88b19dde54c8d5097a0f

Henrique Dias committed Jun 20, 2023 at 14:08 UTC d6464864cbff0cc96d4cb11b292daba77c2366d1
5 files changed +117 -242
core/coreiface/name.go
+2 -9
@@ -5,20 +5,13 @@ import (
5 "errors"
6
7 path "github.com/ipfs/boxo/coreiface/path"
8 + "github.com/ipfs/boxo/ipns"
9
10 "github.com/ipfs/boxo/coreiface/options"
11 )
12
13 var ErrResolveFailed = errors.New("could not resolve name")
14
14 -// IpnsEntry specifies the interface to IpnsEntries
15 -type IpnsEntry interface {
16 - // Name returns IpnsEntry name
17 - Name() string
18 - // Value returns IpnsEntry value
19 - Value() path.Path
20 -}
21 -
15 type IpnsResult struct {
16 path.Path
17 Err error
@@ -34,7 +27,7 @@ type IpnsResult struct {
27 // You can use .Key API to list and generate more names and their respective keys.
28 type NameAPI interface {
29 // Publish announces new IPNS name
37 - Publish(ctx context.Context, path path.Path, opts ...options.NamePublishOption) (IpnsEntry, error)
30 + Publish(ctx context.Context, path path.Path, opts ...options.NamePublishOption) (ipns.Name, error)
31
32 // Resolve attempts to resolve the newest version of the specified name
33 Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (path.Path, error)
core/coreiface/options/name.go
+14 -6
@@ -11,12 +11,11 @@ const (
11 )
12
13 type NamePublishSettings struct {
14 - ValidTime time.Duration
15 - Key string
16 -
17 - TTL *time.Duration
18 -
19 - AllowOffline bool
14 + ValidTime time.Duration
15 + Key string
16 + TTL *time.Duration
17 + CompatibleWithV1 bool
18 + AllowOffline bool
19 }
20
21 type NameResolveSettings struct {
@@ -104,6 +103,15 @@ func (nameOpts) TTL(ttl time.Duration) NamePublishOption {
103 }
104 }
105
106 +// CompatibleWithV1 is an option for [Name.Publish] which specifies if the
107 +// created record should be backwards compatible with V1 IPNS Records.
108 +func (nameOpts) CompatibleWithV1(compatible bool) NamePublishOption {
109 + return func(settings *NamePublishSettings) error {
110 + settings.CompatibleWithV1 = compatible
111 + return nil
112 + }
113 +}
114 +
115 // Cache is an option for Name.Resolve which specifies if cache should be used.
116 // Default value is true
117 func (nameOpts) Cache(cache bool) NameResolveOption {
core/coreiface/options/namesys/opts.go
+10 -2
@@ -84,8 +84,9 @@ func ProcessOpts(opts []ResolveOpt) ResolveOpts {
84
85 // PublishOptions specifies options for publishing an IPNS record.
86 type PublishOptions struct {
87 - EOL time.Time
88 - TTL time.Duration
87 + EOL time.Time
88 + TTL time.Duration
89 + CompatibleWithV1 bool
90 }
91
92 // DefaultPublishOptions returns the default options for publishing an IPNS record.
@@ -113,6 +114,13 @@ func PublishWithTTL(ttl time.Duration) PublishOption {
114 }
115 }
116
117 +// PublishCompatibleWithV1 sets compatibility with IPNS Records V1.
118 +func PublishCompatibleWithV1(compatible bool) PublishOption {
119 + return func(o *PublishOptions) {
120 + o.CompatibleWithV1 = compatible
121 + }
122 +}
123 +
124 // ProcessPublishOptions converts an array of PublishOpt into a PublishOpts object.
125 func ProcessPublishOptions(opts []PublishOption) PublishOptions {
126 rsopts := DefaultPublishOptions()
core/coreiface/tests/name.go
+55 -162
@@ -8,12 +8,12 @@ import (
8 "testing"
9 "time"
10
11 - path "github.com/ipfs/boxo/coreiface/path"
12 -
13 - "github.com/ipfs/boxo/files"
14 -
11 coreiface "github.com/ipfs/boxo/coreiface"
12 opt "github.com/ipfs/boxo/coreiface/options"
13 + path "github.com/ipfs/boxo/coreiface/path"
14 + "github.com/ipfs/boxo/files"
15 + "github.com/ipfs/boxo/ipns"
16 + "github.com/stretchr/testify/require"
17 )
18
19 func (tp *TestSuite) TestName(t *testing.T) {
@@ -44,138 +44,68 @@ func (tp *TestSuite) TestPublishResolve(t *testing.T) {
44 defer cancel()
45 init := func() (coreiface.CoreAPI, path.Path) {
46 apis, err := tp.MakeAPISwarm(t, ctx, 5)
47 - if err != nil {
48 - t.Fatal(err)
49 - return nil, nil
50 - }
47 + require.NoError(t, err)
48 api := apis[0]
49
50 p, err := addTestObject(ctx, api)
54 - if err != nil {
55 - t.Fatal(err)
56 - return nil, nil
57 - }
51 + require.NoError(t, err)
52 return api, p
53 }
54 run := func(t *testing.T, ropts []opt.NameResolveOption) {
55 t.Run("basic", func(t *testing.T) {
56 api, p := init()
63 - e, err := api.Name().Publish(ctx, p)
64 - if err != nil {
65 - t.Fatal(err)
66 - }
57 + name, err := api.Name().Publish(ctx, p)
58 + require.NoError(t, err)
59
60 self, err := api.Key().Self(ctx)
69 - if err != nil {
70 - t.Fatal(err)
71 - }
72 -
73 - if e.Name() != coreiface.FormatKeyID(self.ID()) {
74 - t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
75 - }
76 -
77 - if e.Value().String() != p.String() {
78 - t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
79 - }
80 -
81 - resPath, err := api.Name().Resolve(ctx, e.Name(), ropts...)
82 - if err != nil {
83 - t.Fatal(err)
84 - }
85 -
86 - if resPath.String() != p.String() {
87 - t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
88 - }
61 + require.NoError(t, err)
62 + require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
63 +
64 + resPath, err := api.Name().Resolve(ctx, name.String(), ropts...)
65 + require.NoError(t, err)
66 + require.Equal(t, p.String(), resPath.String())
67 })
68
69 t.Run("publishPath", func(t *testing.T) {
70 api, p := init()
93 - e, err := api.Name().Publish(ctx, appendPath(p, "/test"))
94 - if err != nil {
95 - t.Fatal(err)
96 - }
71 + name, err := api.Name().Publish(ctx, appendPath(p, "/test"))
72 + require.NoError(t, err)
73
74 self, err := api.Key().Self(ctx)
99 - if err != nil {
100 - t.Fatal(err)
101 - }
102 -
103 - if e.Name() != coreiface.FormatKeyID(self.ID()) {
104 - t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
105 - }
106 -
107 - if e.Value().String() != p.String()+"/test" {
108 - t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
109 - }
110 -
111 - resPath, err := api.Name().Resolve(ctx, e.Name(), ropts...)
112 - if err != nil {
113 - t.Fatal(err)
114 - }
115 -
116 - if resPath.String() != p.String()+"/test" {
117 - t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/test")
118 - }
75 + require.NoError(t, err)
76 + require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
77 +
78 + resPath, err := api.Name().Resolve(ctx, name.String(), ropts...)
79 + require.NoError(t, err)
80 + require.Equal(t, p.String()+"/test", resPath.String())
81 })
82
83 t.Run("revolvePath", func(t *testing.T) {
84 api, p := init()
123 - e, err := api.Name().Publish(ctx, p)
124 - if err != nil {
125 - t.Fatal(err)
126 - }
85 + name, err := api.Name().Publish(ctx, p)
86 + require.NoError(t, err)
87
88 self, err := api.Key().Self(ctx)
129 - if err != nil {
130 - t.Fatal(err)
131 - }
132 -
133 - if e.Name() != coreiface.FormatKeyID(self.ID()) {
134 - t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
135 - }
136 -
137 - if e.Value().String() != p.String() {
138 - t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
139 - }
140 -
141 - resPath, err := api.Name().Resolve(ctx, e.Name()+"/test", ropts...)
142 - if err != nil {
143 - t.Fatal(err)
144 - }
145 -
146 - if resPath.String() != p.String()+"/test" {
147 - t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/test")
148 - }
89 + require.NoError(t, err)
90 + require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
91 +
92 + resPath, err := api.Name().Resolve(ctx, name.String()+"/test", ropts...)
93 + require.NoError(t, err)
94 + require.Equal(t, p.String()+"/test", resPath.String())
95 })
96
97 t.Run("publishRevolvePath", func(t *testing.T) {
98 api, p := init()
153 - e, err := api.Name().Publish(ctx, appendPath(p, "/a"))
154 - if err != nil {
155 - t.Fatal(err)
156 - }
99 + name, err := api.Name().Publish(ctx, appendPath(p, "/a"))
100 + require.NoError(t, err)
101
102 self, err := api.Key().Self(ctx)
159 - if err != nil {
160 - t.Fatal(err)
161 - }
162 -
163 - if e.Name() != coreiface.FormatKeyID(self.ID()) {
164 - t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
165 - }
166 -
167 - if e.Value().String() != p.String()+"/a" {
168 - t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
169 - }
170 -
171 - resPath, err := api.Name().Resolve(ctx, e.Name()+"/b", ropts...)
172 - if err != nil {
173 - t.Fatal(err)
174 - }
175 -
176 - if resPath.String() != p.String()+"/a/b" {
177 - t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/a/b")
178 - }
103 + require.NoError(t, err)
104 + require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
105 +
106 + resPath, err := api.Name().Resolve(ctx, name.String()+"/b", ropts...)
107 + require.NoError(t, err)
108 + require.Equal(t, p.String()+"/a/b", resPath.String())
109 })
110 }
111
@@ -192,42 +122,22 @@ func (tp *TestSuite) TestBasicPublishResolveKey(t *testing.T) {
122 ctx, cancel := context.WithCancel(context.Background())
123 defer cancel()
124 apis, err := tp.MakeAPISwarm(t, ctx, 5)
195 - if err != nil {
196 - t.Fatal(err)
197 - }
125 + require.NoError(t, err)
126 api := apis[0]
127
128 k, err := api.Key().Generate(ctx, "foo")
201 - if err != nil {
202 - t.Fatal(err)
203 - }
129 + require.NoError(t, err)
130
131 p, err := addTestObject(ctx, api)
206 - if err != nil {
207 - t.Fatal(err)
208 - }
209 -
210 - e, err := api.Name().Publish(ctx, p, opt.Name.Key(k.Name()))
211 - if err != nil {
212 - t.Fatal(err)
213 - }
214 -
215 - if e.Name() != coreiface.FormatKey(k) {
216 - t.Errorf("expected e.Name to equal %s, got '%s'", e.Name(), coreiface.FormatKey(k))
217 - }
218 -
219 - if e.Value().String() != p.String() {
220 - t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
221 - }
132 + require.NoError(t, err)
133
223 - resPath, err := api.Name().Resolve(ctx, e.Name())
224 - if err != nil {
225 - t.Fatal(err)
226 - }
134 + name, err := api.Name().Publish(ctx, p, opt.Name.Key(k.Name()))
135 + require.NoError(t, err)
136 + require.Equal(t, name.String(), ipns.NameFromPeer(k.ID()).String())
137
228 - if resPath.String() != p.String() {
229 - t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
230 - }
138 + resPath, err := api.Name().Resolve(ctx, name.String())
139 + require.NoError(t, err)
140 + require.Equal(t, p.String(), resPath.String())
141 }
142
143 func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
@@ -236,39 +146,22 @@ func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
146 ctx, cancel := context.WithCancel(context.Background())
147 defer cancel()
148 apis, err := tp.MakeAPISwarm(t, ctx, 5)
239 - if err != nil {
240 - t.Fatal(err)
241 - }
149 + require.NoError(t, err)
150 api := apis[0]
151 p, err := addTestObject(ctx, api)
244 - if err != nil {
245 - t.Fatal(err)
246 - }
247 -
248 - e, err := api.Name().Publish(ctx, p, opt.Name.ValidTime(time.Millisecond*100))
249 - if err != nil {
250 - t.Fatal(err)
251 - }
152 + require.NoError(t, err)
153
154 self, err := api.Key().Self(ctx)
254 - if err != nil {
255 - t.Fatal(err)
256 - }
155 + require.NoError(t, err)
156
258 - if e.Name() != coreiface.FormatKeyID(self.ID()) {
259 - t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
260 - }
261 -
262 - if e.Value().String() != p.String() {
263 - t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
264 - }
157 + name, err := api.Name().Publish(ctx, p, opt.Name.ValidTime(time.Millisecond*100))
158 + require.NoError(t, err)
159 + require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
160
161 time.Sleep(time.Second)
162
268 - _, err = api.Name().Resolve(ctx, e.Name())
269 - if err == nil {
270 - t.Fatal("Expected an error")
271 - }
163 + _, err = api.Name().Resolve(ctx, name.String())
164 + require.NoError(t, err)
165 }
166
167 //TODO: When swarm api is created, add multinode tests
core/coreiface/tests/routing.go
+36 -63
@@ -5,10 +5,11 @@ import (
5 "testing"
6 "time"
7
8 - "github.com/gogo/protobuf/proto"
8 iface "github.com/ipfs/boxo/coreiface"
9 "github.com/ipfs/boxo/coreiface/options"
11 - ipns_pb "github.com/ipfs/boxo/ipns/pb"
10 + "github.com/ipfs/boxo/coreiface/path"
11 + "github.com/ipfs/boxo/ipns"
12 + "github.com/stretchr/testify/require"
13 )
14
15 func (tp *TestSuite) TestRouting(t *testing.T) {
@@ -24,19 +25,15 @@ func (tp *TestSuite) TestRouting(t *testing.T) {
25 t.Run("TestRoutingPutOffline", tp.TestRoutingPutOffline)
26 }
27
27 -func (tp *TestSuite) testRoutingPublishKey(t *testing.T, ctx context.Context, api iface.CoreAPI, opts ...options.NamePublishOption) iface.IpnsEntry {
28 +func (tp *TestSuite) testRoutingPublishKey(t *testing.T, ctx context.Context, api iface.CoreAPI, opts ...options.NamePublishOption) (path.Path, ipns.Name) {
29 p, err := addTestObject(ctx, api)
29 - if err != nil {
30 - t.Fatal(err)
31 - }
30 + require.NoError(t, err)
31
33 - entry, err := api.Name().Publish(ctx, p, opts...)
34 - if err != nil {
35 - t.Fatal(err)
36 - }
32 + name, err := api.Name().Publish(ctx, p, opts...)
33 + require.NoError(t, err)
34
35 time.Sleep(3 * time.Second)
39 - return entry
36 + return p, name
37 }
38
39 func (tp *TestSuite) TestRoutingGet(t *testing.T) {
@@ -44,53 +41,39 @@ func (tp *TestSuite) TestRoutingGet(t *testing.T) {
41 defer cancel()
42
43 apis, err := tp.MakeAPISwarm(t, ctx, 2)
47 - if err != nil {
48 - t.Fatal(err)
49 - }
44 + require.NoError(t, err)
45
46 // Node 1: publishes an IPNS name
52 - ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0])
47 + p, name := tp.testRoutingPublishKey(t, ctx, apis[0])
48
49 // Node 2: retrieves the best value for the IPNS name.
55 - data, err := apis[1].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
56 - if err != nil {
57 - t.Fatal(err)
58 - }
59 -
60 - // Checks if values match.
61 - var entry ipns_pb.IpnsEntry
62 - err = proto.Unmarshal(data, &entry)
63 - if err != nil {
64 - t.Fatal(err)
65 - }
66 -
67 - if string(entry.GetValue()) != ipnsEntry.Value().String() {
68 - t.Fatalf("routing key has wrong value, expected %s, got %s", ipnsEntry.Value().String(), string(entry.GetValue()))
69 - }
50 + data, err := apis[1].Routing().Get(ctx, ipns.NamespacePrefix+name.String())
51 + require.NoError(t, err)
52 +
53 + rec, err := ipns.UnmarshalRecord(data)
54 + require.NoError(t, err)
55 +
56 + val, err := rec.Value()
57 + require.NoError(t, err)
58 + require.Equal(t, p.String(), val.String())
59 }
60
61 func (tp *TestSuite) TestRoutingPut(t *testing.T) {
62 ctx, cancel := context.WithCancel(context.Background())
63 defer cancel()
64 apis, err := tp.MakeAPISwarm(t, ctx, 2)
76 - if err != nil {
77 - t.Fatal(err)
78 - }
65 + require.NoError(t, err)
66
67 // Create and publish IPNS entry.
81 - ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0])
68 + _, name := tp.testRoutingPublishKey(t, ctx, apis[0])
69
70 // Get valid routing value.
84 - data, err := apis[0].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
85 - if err != nil {
86 - t.Fatal(err)
87 - }
71 + data, err := apis[0].Routing().Get(ctx, ipns.NamespacePrefix+name.String())
72 + require.NoError(t, err)
73
74 // Put routing value.
90 - err = apis[1].Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data)
91 - if err != nil {
92 - t.Fatal(err)
93 - }
75 + err = apis[1].Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data)
76 + require.NoError(t, err)
77 }
78
79 func (tp *TestSuite) TestRoutingPutOffline(t *testing.T) {
@@ -99,29 +82,19 @@ func (tp *TestSuite) TestRoutingPutOffline(t *testing.T) {
82
83 // init a swarm & publish an IPNS entry to get a valid payload
84 apis, err := tp.MakeAPISwarm(t, ctx, 2)
102 - if err != nil {
103 - t.Fatal(err)
104 - }
85 + require.NoError(t, err)
86
106 - ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0], options.Name.AllowOffline(true))
107 - data, err := apis[0].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
108 - if err != nil {
109 - t.Fatal(err)
110 - }
87 + _, name := tp.testRoutingPublishKey(t, ctx, apis[0], options.Name.AllowOffline(true))
88 + data, err := apis[0].Routing().Get(ctx, ipns.NamespacePrefix+name.String())
89 + require.NoError(t, err)
90
91 // init our offline node and try to put the payload
92 api, err := tp.makeAPIWithIdentityAndOffline(t, ctx)
114 - if err != nil {
115 - t.Fatal(err)
116 - }
117 -
118 - err = api.Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data)
119 - if err == nil {
120 - t.Fatal("this operation should fail because we are offline")
121 - }
122 -
123 - err = api.Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data, options.Put.AllowOffline(true))
124 - if err != nil {
125 - t.Fatal(err)
126 - }
93 + require.NoError(t, err)
94 +
95 + err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data)
96 + require.Error(t, err, "this operation should fail because we are offline")
97 +
98 + err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data, options.Put.AllowOffline(true))
99 + require.NoError(t, err)
100 }