mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
feat(ipns): refactored IPNS package with lean records (#339)
This commit was moved from ipfs/boxo@417c5f7d61
This commit is contained in:
parent
0134124cdd
commit
d6464864cb
@ -5,20 +5,13 @@ import (
|
||||
"errors"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
"github.com/ipfs/boxo/ipns"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
var ErrResolveFailed = errors.New("could not resolve name")
|
||||
|
||||
// IpnsEntry specifies the interface to IpnsEntries
|
||||
type IpnsEntry interface {
|
||||
// Name returns IpnsEntry name
|
||||
Name() string
|
||||
// Value returns IpnsEntry value
|
||||
Value() path.Path
|
||||
}
|
||||
|
||||
type IpnsResult struct {
|
||||
path.Path
|
||||
Err error
|
||||
@ -34,7 +27,7 @@ type IpnsResult struct {
|
||||
// You can use .Key API to list and generate more names and their respective keys.
|
||||
type NameAPI interface {
|
||||
// Publish announces new IPNS name
|
||||
Publish(ctx context.Context, path path.Path, opts ...options.NamePublishOption) (IpnsEntry, error)
|
||||
Publish(ctx context.Context, path path.Path, opts ...options.NamePublishOption) (ipns.Name, error)
|
||||
|
||||
// Resolve attempts to resolve the newest version of the specified name
|
||||
Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (path.Path, error)
|
||||
|
||||
@ -11,12 +11,11 @@ const (
|
||||
)
|
||||
|
||||
type NamePublishSettings struct {
|
||||
ValidTime time.Duration
|
||||
Key string
|
||||
|
||||
TTL *time.Duration
|
||||
|
||||
AllowOffline bool
|
||||
ValidTime time.Duration
|
||||
Key string
|
||||
TTL *time.Duration
|
||||
CompatibleWithV1 bool
|
||||
AllowOffline bool
|
||||
}
|
||||
|
||||
type NameResolveSettings struct {
|
||||
@ -104,6 +103,15 @@ func (nameOpts) TTL(ttl time.Duration) NamePublishOption {
|
||||
}
|
||||
}
|
||||
|
||||
// CompatibleWithV1 is an option for [Name.Publish] which specifies if the
|
||||
// created record should be backwards compatible with V1 IPNS Records.
|
||||
func (nameOpts) CompatibleWithV1(compatible bool) NamePublishOption {
|
||||
return func(settings *NamePublishSettings) error {
|
||||
settings.CompatibleWithV1 = compatible
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Cache is an option for Name.Resolve which specifies if cache should be used.
|
||||
// Default value is true
|
||||
func (nameOpts) Cache(cache bool) NameResolveOption {
|
||||
|
||||
@ -84,8 +84,9 @@ func ProcessOpts(opts []ResolveOpt) ResolveOpts {
|
||||
|
||||
// PublishOptions specifies options for publishing an IPNS record.
|
||||
type PublishOptions struct {
|
||||
EOL time.Time
|
||||
TTL time.Duration
|
||||
EOL time.Time
|
||||
TTL time.Duration
|
||||
CompatibleWithV1 bool
|
||||
}
|
||||
|
||||
// DefaultPublishOptions returns the default options for publishing an IPNS record.
|
||||
@ -113,6 +114,13 @@ func PublishWithTTL(ttl time.Duration) PublishOption {
|
||||
}
|
||||
}
|
||||
|
||||
// PublishCompatibleWithV1 sets compatibility with IPNS Records V1.
|
||||
func PublishCompatibleWithV1(compatible bool) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.CompatibleWithV1 = compatible
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessPublishOptions converts an array of PublishOpt into a PublishOpts object.
|
||||
func ProcessPublishOptions(opts []PublishOption) PublishOptions {
|
||||
rsopts := DefaultPublishOptions()
|
||||
|
||||
@ -8,12 +8,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
|
||||
coreiface "github.com/ipfs/boxo/coreiface"
|
||||
opt "github.com/ipfs/boxo/coreiface/options"
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
"github.com/ipfs/boxo/files"
|
||||
"github.com/ipfs/boxo/ipns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestName(t *testing.T) {
|
||||
@ -44,138 +44,68 @@ func (tp *TestSuite) TestPublishResolve(t *testing.T) {
|
||||
defer cancel()
|
||||
init := func() (coreiface.CoreAPI, path.Path) {
|
||||
apis, err := tp.MakeAPISwarm(t, ctx, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil
|
||||
}
|
||||
require.NoError(t, err)
|
||||
api := apis[0]
|
||||
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil
|
||||
}
|
||||
require.NoError(t, err)
|
||||
return api, p
|
||||
}
|
||||
run := func(t *testing.T, ropts []opt.NameResolveOption) {
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, p)
|
||||
require.NoError(t, err)
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name(), ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
|
||||
}
|
||||
resPath, err := api.Name().Resolve(ctx, name.String(), ropts...)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.String(), resPath.String())
|
||||
})
|
||||
|
||||
t.Run("publishPath", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, appendPath(p, "/test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, appendPath(p, "/test"))
|
||||
require.NoError(t, err)
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String()+"/test" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name(), ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String()+"/test" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/test")
|
||||
}
|
||||
resPath, err := api.Name().Resolve(ctx, name.String(), ropts...)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.String()+"/test", resPath.String())
|
||||
})
|
||||
|
||||
t.Run("revolvePath", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, p)
|
||||
require.NoError(t, err)
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name()+"/test", ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String()+"/test" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/test")
|
||||
}
|
||||
resPath, err := api.Name().Resolve(ctx, name.String()+"/test", ropts...)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.String()+"/test", resPath.String())
|
||||
})
|
||||
|
||||
t.Run("publishRevolvePath", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, appendPath(p, "/a"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, appendPath(p, "/a"))
|
||||
require.NoError(t, err)
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String()+"/a" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name()+"/b", ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String()+"/a/b" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/a/b")
|
||||
}
|
||||
resPath, err := api.Name().Resolve(ctx, name.String()+"/b", ropts...)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.String()+"/a/b", resPath.String())
|
||||
})
|
||||
}
|
||||
|
||||
@ -192,42 +122,22 @@ func (tp *TestSuite) TestBasicPublishResolveKey(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(t, ctx, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
api := apis[0]
|
||||
|
||||
k, err := api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
e, err := api.Name().Publish(ctx, p, opt.Name.Key(k.Name()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, p, opt.Name.Key(k.Name()))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, name.String(), ipns.NameFromPeer(k.ID()).String())
|
||||
|
||||
if e.Name() != coreiface.FormatKey(k) {
|
||||
t.Errorf("expected e.Name to equal %s, got '%s'", e.Name(), coreiface.FormatKey(k))
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
|
||||
}
|
||||
resPath, err := api.Name().Resolve(ctx, name.String())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.String(), resPath.String())
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
|
||||
@ -236,39 +146,22 @@ func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(t, ctx, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
api := apis[0]
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
e, err := api.Name().Publish(ctx, p, opt.Name.ValidTime(time.Millisecond*100))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, p, opt.Name.ValidTime(time.Millisecond*100))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, name.String(), ipns.NameFromPeer(self.ID()).String())
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
_, err = api.Name().Resolve(ctx, e.Name())
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
_, err = api.Name().Resolve(ctx, name.String())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
//TODO: When swarm api is created, add multinode tests
|
||||
|
||||
@ -5,10 +5,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
ipns_pb "github.com/ipfs/boxo/ipns/pb"
|
||||
"github.com/ipfs/boxo/coreiface/path"
|
||||
"github.com/ipfs/boxo/ipns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestRouting(t *testing.T) {
|
||||
@ -24,19 +25,15 @@ func (tp *TestSuite) TestRouting(t *testing.T) {
|
||||
t.Run("TestRoutingPutOffline", tp.TestRoutingPutOffline)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) testRoutingPublishKey(t *testing.T, ctx context.Context, api iface.CoreAPI, opts ...options.NamePublishOption) iface.IpnsEntry {
|
||||
func (tp *TestSuite) testRoutingPublishKey(t *testing.T, ctx context.Context, api iface.CoreAPI, opts ...options.NamePublishOption) (path.Path, ipns.Name) {
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
entry, err := api.Name().Publish(ctx, p, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name, err := api.Name().Publish(ctx, p, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
return entry
|
||||
return p, name
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRoutingGet(t *testing.T) {
|
||||
@ -44,53 +41,39 @@ func (tp *TestSuite) TestRoutingGet(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
apis, err := tp.MakeAPISwarm(t, ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Node 1: publishes an IPNS name
|
||||
ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0])
|
||||
p, name := tp.testRoutingPublishKey(t, ctx, apis[0])
|
||||
|
||||
// Node 2: retrieves the best value for the IPNS name.
|
||||
data, err := apis[1].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := apis[1].Routing().Get(ctx, ipns.NamespacePrefix+name.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Checks if values match.
|
||||
var entry ipns_pb.IpnsEntry
|
||||
err = proto.Unmarshal(data, &entry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec, err := ipns.UnmarshalRecord(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
if string(entry.GetValue()) != ipnsEntry.Value().String() {
|
||||
t.Fatalf("routing key has wrong value, expected %s, got %s", ipnsEntry.Value().String(), string(entry.GetValue()))
|
||||
}
|
||||
val, err := rec.Value()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.String(), val.String())
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRoutingPut(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(t, ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create and publish IPNS entry.
|
||||
ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0])
|
||||
_, name := tp.testRoutingPublishKey(t, ctx, apis[0])
|
||||
|
||||
// Get valid routing value.
|
||||
data, err := apis[0].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := apis[0].Routing().Get(ctx, ipns.NamespacePrefix+name.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Put routing value.
|
||||
err = apis[1].Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = apis[1].Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRoutingPutOffline(t *testing.T) {
|
||||
@ -99,29 +82,19 @@ func (tp *TestSuite) TestRoutingPutOffline(t *testing.T) {
|
||||
|
||||
// init a swarm & publish an IPNS entry to get a valid payload
|
||||
apis, err := tp.MakeAPISwarm(t, ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0], options.Name.AllowOffline(true))
|
||||
data, err := apis[0].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, name := tp.testRoutingPublishKey(t, ctx, apis[0], options.Name.AllowOffline(true))
|
||||
data, err := apis[0].Routing().Get(ctx, ipns.NamespacePrefix+name.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
// init our offline node and try to put the payload
|
||||
api, err := tp.makeAPIWithIdentityAndOffline(t, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
err = api.Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data)
|
||||
if err == nil {
|
||||
t.Fatal("this operation should fail because we are offline")
|
||||
}
|
||||
err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data)
|
||||
require.Error(t, err, "this operation should fail because we are offline")
|
||||
|
||||
err = api.Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data, options.Put.AllowOffline(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data, options.Put.AllowOffline(true))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user