Skip to content

Commit c5a71fa

Browse files
committed
feat(ipns): refactored IPNS package with lean records
1 parent abced78 commit c5a71fa

40 files changed

+2022
-2930
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ The following emojis are used to highlight certain changes:
1818

1919
### Changed
2020

21+
* 🛠 The `ipns` package has been refactored. You should no longer use the direct Protobuf
22+
version of the IPNS Record. Instead, we have a shiny new `ipns.Record` type that wraps
23+
all the required functionality to work the best as possible with IPNS v2 Records. Please
24+
check the [documentation](https://pkg.go.dev/github.com/ipfs/boxo/ipns) for more information.
25+
2126
### Removed
2227

2328
### Fixed

coreiface/name.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,13 @@ import (
55
"errors"
66

77
path "github.com/ipfs/boxo/coreiface/path"
8+
"github.com/ipfs/boxo/ipns"
89

910
"github.com/ipfs/boxo/coreiface/options"
1011
)
1112

1213
var ErrResolveFailed = errors.New("could not resolve name")
1314

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-
2215
type IpnsResult struct {
2316
path.Path
2417
Err error
@@ -34,7 +27,7 @@ type IpnsResult struct {
3427
// You can use .Key API to list and generate more names and their respective keys.
3528
type NameAPI interface {
3629
// 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)
3831

3932
// Resolve attempts to resolve the newest version of the specified name
4033
Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (path.Path, error)

coreiface/options/name.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ const (
1111
)
1212

1313
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
2019
}
2120

2221
type NameResolveSettings struct {
@@ -104,6 +103,15 @@ func (nameOpts) TTL(ttl time.Duration) NamePublishOption {
104103
}
105104
}
106105

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+
107115
// Cache is an option for Name.Resolve which specifies if cache should be used.
108116
// Default value is true
109117
func (nameOpts) Cache(cache bool) NameResolveOption {

coreiface/options/namesys/opts.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ func ProcessOpts(opts []ResolveOpt) ResolveOpts {
8484

8585
// PublishOptions specifies options for publishing an IPNS record.
8686
type PublishOptions struct {
87-
EOL time.Time
88-
TTL time.Duration
87+
EOL time.Time
88+
TTL time.Duration
89+
CompatibleWithV1 bool
8990
}
9091

9192
// DefaultPublishOptions returns the default options for publishing an IPNS record.
@@ -113,6 +114,13 @@ func PublishWithTTL(ttl time.Duration) PublishOption {
113114
}
114115
}
115116

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+
116124
// ProcessPublishOptions converts an array of PublishOpt into a PublishOpts object.
117125
func ProcessPublishOptions(opts []PublishOption) PublishOptions {
118126
rsopts := DefaultPublishOptions()

coreiface/tests/name.go

+55-162
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import (
88
"testing"
99
"time"
1010

11-
path "github.com/ipfs/boxo/coreiface/path"
12-
13-
"github.com/ipfs/boxo/files"
14-
1511
coreiface "github.com/ipfs/boxo/coreiface"
1612
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"
1717
)
1818

1919
func (tp *TestSuite) TestName(t *testing.T) {
@@ -44,138 +44,68 @@ func (tp *TestSuite) TestPublishResolve(t *testing.T) {
4444
defer cancel()
4545
init := func() (coreiface.CoreAPI, path.Path) {
4646
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)
5148
api := apis[0]
5249

5350
p, err := addTestObject(ctx, api)
54-
if err != nil {
55-
t.Fatal(err)
56-
return nil, nil
57-
}
51+
require.NoError(t, err)
5852
return api, p
5953
}
6054
run := func(t *testing.T, ropts []opt.NameResolveOption) {
6155
t.Run("basic", func(t *testing.T) {
6256
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)
6759

6860
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())
8967
})
9068

9169
t.Run("publishPath", func(t *testing.T) {
9270
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)
9773

9874
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())
11981
})
12082

12183
t.Run("revolvePath", func(t *testing.T) {
12284
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)
12787

12888
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())
14995
})
15096

15197
t.Run("publishRevolvePath", func(t *testing.T) {
15298
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)
157101

158102
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())
179109
})
180110
}
181111

@@ -192,42 +122,22 @@ func (tp *TestSuite) TestBasicPublishResolveKey(t *testing.T) {
192122
ctx, cancel := context.WithCancel(context.Background())
193123
defer cancel()
194124
apis, err := tp.MakeAPISwarm(t, ctx, 5)
195-
if err != nil {
196-
t.Fatal(err)
197-
}
125+
require.NoError(t, err)
198126
api := apis[0]
199127

200128
k, err := api.Key().Generate(ctx, "foo")
201-
if err != nil {
202-
t.Fatal(err)
203-
}
129+
require.NoError(t, err)
204130

205131
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)
222133

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())
227137

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())
231141
}
232142

233143
func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
@@ -236,39 +146,22 @@ func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
236146
ctx, cancel := context.WithCancel(context.Background())
237147
defer cancel()
238148
apis, err := tp.MakeAPISwarm(t, ctx, 5)
239-
if err != nil {
240-
t.Fatal(err)
241-
}
149+
require.NoError(t, err)
242150
api := apis[0]
243151
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)
252153

253154
self, err := api.Key().Self(ctx)
254-
if err != nil {
255-
t.Fatal(err)
256-
}
155+
require.NoError(t, err)
257156

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())
265160

266161
time.Sleep(time.Second)
267162

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)
272165
}
273166

274167
//TODO: When swarm api is created, add multinode tests

0 commit comments

Comments
 (0)