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
}