mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-30 09:59:13 +08:00
Merge pull request #4477 from ipfs/feat/coreapi/name
RFC: coreapi.Name,Key
This commit is contained in:
@ -29,6 +29,14 @@ func (api *CoreAPI) Dag() coreiface.DagAPI {
|
|||||||
return &DagAPI{api, nil}
|
return &DagAPI{api, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *CoreAPI) Name() coreiface.NameAPI {
|
||||||
|
return &NameAPI{api, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *CoreAPI) Key() coreiface.KeyAPI {
|
||||||
|
return &KeyAPI{api, nil}
|
||||||
|
}
|
||||||
|
|
||||||
func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) {
|
func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) {
|
||||||
p, err := api.ResolvePath(ctx, p)
|
p, err := api.ResolvePath(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
options "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
|
options "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
|
||||||
|
|
||||||
@ -32,11 +33,23 @@ type Reader interface {
|
|||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IpnsEntry interface {
|
||||||
|
Name() string
|
||||||
|
Value() Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key interface {
|
||||||
|
Name() string
|
||||||
|
Path() Path
|
||||||
|
}
|
||||||
|
|
||||||
// CoreAPI defines an unified interface to IPFS for Go programs.
|
// CoreAPI defines an unified interface to IPFS for Go programs.
|
||||||
type CoreAPI interface {
|
type CoreAPI interface {
|
||||||
// Unixfs returns an implementation of Unixfs API
|
// Unixfs returns an implementation of Unixfs API
|
||||||
Unixfs() UnixfsAPI
|
Unixfs() UnixfsAPI
|
||||||
Dag() DagAPI
|
Dag() DagAPI
|
||||||
|
Name() NameAPI
|
||||||
|
Key() KeyAPI
|
||||||
|
|
||||||
// ResolvePath resolves the path using Unixfs resolver
|
// ResolvePath resolves the path using Unixfs resolver
|
||||||
ResolvePath(context.Context, Path) (Path, error)
|
ResolvePath(context.Context, Path) (Path, error)
|
||||||
@ -90,6 +103,81 @@ type DagAPI interface {
|
|||||||
WithDepth(depth int) options.DagTreeOption
|
WithDepth(depth int) options.DagTreeOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NameAPI specifies the interface to IPNS.
|
||||||
|
//
|
||||||
|
// IPNS is a PKI namespace, where names are the hashes of public keys, and the
|
||||||
|
// private key enables publishing new (signed) values. In both publish and
|
||||||
|
// resolve, the default name used is the node's own PeerID, which is the hash of
|
||||||
|
// its public key.
|
||||||
|
//
|
||||||
|
// 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, opts ...options.NamePublishOption) (IpnsEntry, error)
|
||||||
|
|
||||||
|
// WithValidTime is an option for Publish which specifies for how long the
|
||||||
|
// entry will remain valid. Default value is 24h
|
||||||
|
WithValidTime(validTime time.Duration) options.NamePublishOption
|
||||||
|
|
||||||
|
// WithKey is an option for Publish which specifies the key to use for
|
||||||
|
// publishing. Default value is "self" which is the node's own PeerID.
|
||||||
|
// The key parameter must be either PeerID or keystore key alias.
|
||||||
|
//
|
||||||
|
// You can use KeyAPI to list and generate more names and their respective keys.
|
||||||
|
WithKey(key string) options.NamePublishOption
|
||||||
|
|
||||||
|
// Resolve attempts to resolve the newest version of the specified name
|
||||||
|
Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (Path, error)
|
||||||
|
|
||||||
|
// WithRecursive is an option for Resolve which specifies whether to perform a
|
||||||
|
// recursive lookup. Default value is false
|
||||||
|
WithRecursive(recursive bool) options.NameResolveOption
|
||||||
|
|
||||||
|
// WithLocal is an option for Resolve which specifies if the lookup should be
|
||||||
|
// offline. Default value is false
|
||||||
|
WithLocal(local bool) options.NameResolveOption
|
||||||
|
|
||||||
|
// WithCache is an option for Resolve which specifies if cache should be used.
|
||||||
|
// Default value is true
|
||||||
|
WithCache(cache bool) options.NameResolveOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyAPI specifies the interface to Keystore
|
||||||
|
type KeyAPI interface {
|
||||||
|
// Generate generates new key, stores it in the keystore under the specified
|
||||||
|
// name and returns a base58 encoded multihash of it's public key
|
||||||
|
Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (Key, error)
|
||||||
|
|
||||||
|
// WithType is an option for Generate which specifies which algorithm
|
||||||
|
// should be used for the key. Default is options.RSAKey
|
||||||
|
//
|
||||||
|
// Supported key types:
|
||||||
|
// * options.RSAKey
|
||||||
|
// * options.Ed25519Key
|
||||||
|
WithType(algorithm string) options.KeyGenerateOption
|
||||||
|
|
||||||
|
// WithSize is an option for Generate which specifies the size of the key to
|
||||||
|
// generated. Default is -1
|
||||||
|
//
|
||||||
|
// value of -1 means 'use default size for key type':
|
||||||
|
// * 2048 for RSA
|
||||||
|
WithSize(size int) options.KeyGenerateOption
|
||||||
|
|
||||||
|
// Rename renames oldName key to newName. Returns the key and whether another
|
||||||
|
// key was overwritten, or an error
|
||||||
|
Rename(ctx context.Context, oldName string, newName string, opts ...options.KeyRenameOption) (Key, bool, error)
|
||||||
|
|
||||||
|
// WithForce is an option for Rename which specifies whether to allow to
|
||||||
|
// replace existing keys.
|
||||||
|
WithForce(force bool) options.KeyRenameOption
|
||||||
|
|
||||||
|
// List lists keys stored in keystore
|
||||||
|
List(ctx context.Context) ([]Key, error)
|
||||||
|
|
||||||
|
// Remove removes keys from keystore. Returns ipns path of the removed key
|
||||||
|
Remove(ctx context.Context, name string) (Path, error)
|
||||||
|
}
|
||||||
|
|
||||||
// type ObjectAPI interface {
|
// type ObjectAPI interface {
|
||||||
// New() (cid.Cid, Object)
|
// New() (cid.Cid, Object)
|
||||||
// Get(string) (Object, error)
|
// Get(string) (Object, error)
|
||||||
|
72
core/coreapi/interface/options/key.go
Normal file
72
core/coreapi/interface/options/key.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package options
|
||||||
|
|
||||||
|
const (
|
||||||
|
RSAKey = "rsa"
|
||||||
|
Ed25519Key = "ed25519"
|
||||||
|
|
||||||
|
DefaultRSALen = 2048
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyGenerateSettings struct {
|
||||||
|
Algorithm string
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyRenameSettings struct {
|
||||||
|
Force bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyGenerateOption func(*KeyGenerateSettings) error
|
||||||
|
type KeyRenameOption func(*KeyRenameSettings) error
|
||||||
|
|
||||||
|
func KeyGenerateOptions(opts ...KeyGenerateOption) (*KeyGenerateSettings, error) {
|
||||||
|
options := &KeyGenerateSettings{
|
||||||
|
Algorithm: RSAKey,
|
||||||
|
Size: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyRenameOptions(opts ...KeyRenameOption) (*KeyRenameSettings, error) {
|
||||||
|
options := &KeyRenameSettings{
|
||||||
|
Force: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyOptions struct{}
|
||||||
|
|
||||||
|
func (api *KeyOptions) WithType(algorithm string) KeyGenerateOption {
|
||||||
|
return func(settings *KeyGenerateSettings) error {
|
||||||
|
settings.Algorithm = algorithm
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyOptions) WithSize(size int) KeyGenerateOption {
|
||||||
|
return func(settings *KeyGenerateSettings) error {
|
||||||
|
settings.Size = size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyOptions) WithForce(force bool) KeyRenameOption {
|
||||||
|
return func(settings *KeyRenameSettings) error {
|
||||||
|
settings.Force = force
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
93
core/coreapi/interface/options/name.go
Normal file
93
core/coreapi/interface/options/name.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultNameValidTime = 24 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamePublishSettings struct {
|
||||||
|
ValidTime time.Duration
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameResolveSettings struct {
|
||||||
|
Recursive bool
|
||||||
|
Local bool
|
||||||
|
Cache bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamePublishOption func(*NamePublishSettings) error
|
||||||
|
type NameResolveOption func(*NameResolveSettings) error
|
||||||
|
|
||||||
|
func NamePublishOptions(opts ...NamePublishOption) (*NamePublishSettings, error) {
|
||||||
|
options := &NamePublishSettings{
|
||||||
|
ValidTime: DefaultNameValidTime,
|
||||||
|
Key: "self",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NameResolveOptions(opts ...NameResolveOption) (*NameResolveSettings, error) {
|
||||||
|
options := &NameResolveSettings{
|
||||||
|
Recursive: false,
|
||||||
|
Local: false,
|
||||||
|
Cache: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameOptions struct{}
|
||||||
|
|
||||||
|
func (api *NameOptions) WithValidTime(validTime time.Duration) NamePublishOption {
|
||||||
|
return func(settings *NamePublishSettings) error {
|
||||||
|
settings.ValidTime = validTime
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameOptions) WithKey(key string) NamePublishOption {
|
||||||
|
return func(settings *NamePublishSettings) error {
|
||||||
|
settings.Key = key
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameOptions) WithRecursive(recursive bool) NameResolveOption {
|
||||||
|
return func(settings *NameResolveSettings) error {
|
||||||
|
settings.Recursive = recursive
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameOptions) WithLocal(local bool) NameResolveOption {
|
||||||
|
return func(settings *NameResolveSettings) error {
|
||||||
|
settings.Local = local
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameOptions) WithCache(cache bool) NameResolveOption {
|
||||||
|
return func(settings *NameResolveSettings) error {
|
||||||
|
settings.Cache = cache
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
201
core/coreapi/key.go
Normal file
201
core/coreapi/key.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package coreapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||||
|
caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
|
||||||
|
ipfspath "github.com/ipfs/go-ipfs/path"
|
||||||
|
|
||||||
|
peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer"
|
||||||
|
crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyAPI struct {
|
||||||
|
*CoreAPI
|
||||||
|
*caopts.KeyOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type key struct {
|
||||||
|
name string
|
||||||
|
peerId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *key) Name() string {
|
||||||
|
return k.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *key) Path() coreiface.Path {
|
||||||
|
return &path{path: ipfspath.FromString(ipfspath.Join([]string{"/ipns", k.peerId}))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.KeyGenerateOption) (coreiface.Key, error) {
|
||||||
|
options, err := caopts.KeyGenerateOptions(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "self" {
|
||||||
|
return nil, fmt.Errorf("cannot overwrite key with name 'self'")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.node.Repo.Keystore().Get(name)
|
||||||
|
if err == nil {
|
||||||
|
return nil, fmt.Errorf("key with name '%s' already exists", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sk crypto.PrivKey
|
||||||
|
var pk crypto.PubKey
|
||||||
|
|
||||||
|
switch options.Algorithm {
|
||||||
|
case "rsa":
|
||||||
|
if options.Size == -1 {
|
||||||
|
options.Size = caopts.DefaultRSALen
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, options.Size, rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sk = priv
|
||||||
|
pk = pub
|
||||||
|
case "ed25519":
|
||||||
|
priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sk = priv
|
||||||
|
pk = pub
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unrecognized key type: %s", options.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.node.Repo.Keystore().Put(name, sk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := peer.IDFromPublicKey(pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &key{name, pid.Pretty()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyAPI) List(ctx context.Context) ([]coreiface.Key, error) {
|
||||||
|
keys, err := api.node.Repo.Keystore().List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
out := make([]coreiface.Key, len(keys)+1)
|
||||||
|
out[0] = &key{"self", api.node.Identity.Pretty()}
|
||||||
|
|
||||||
|
for n, k := range keys {
|
||||||
|
privKey, err := api.node.Repo.Keystore().Get(k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := privKey.GetPublic()
|
||||||
|
|
||||||
|
pid, err := peer.IDFromPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out[n+1] = &key{k, pid.Pretty()}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, opts ...caopts.KeyRenameOption) (coreiface.Key, bool, error) {
|
||||||
|
options, err := caopts.KeyRenameOptions(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ks := api.node.Repo.Keystore()
|
||||||
|
|
||||||
|
if oldName == "self" {
|
||||||
|
return nil, false, fmt.Errorf("cannot rename key with name 'self'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newName == "self" {
|
||||||
|
return nil, false, fmt.Errorf("cannot overwrite key with name 'self'")
|
||||||
|
}
|
||||||
|
|
||||||
|
oldKey, err := ks.Get(oldName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("no key named %s was found", oldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := oldKey.GetPublic()
|
||||||
|
|
||||||
|
pid, err := peer.IDFromPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
overwrite := false
|
||||||
|
if options.Force {
|
||||||
|
exist, err := ks.Has(newName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist {
|
||||||
|
overwrite = true
|
||||||
|
err := ks.Delete(newName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ks.Put(newName, oldKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &key{newName, pid.Pretty()}, overwrite, ks.Delete(oldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyAPI) Remove(ctx context.Context, name string) (coreiface.Path, error) {
|
||||||
|
ks := api.node.Repo.Keystore()
|
||||||
|
|
||||||
|
if name == "self" {
|
||||||
|
return nil, fmt.Errorf("cannot remove key with name 'self'")
|
||||||
|
}
|
||||||
|
|
||||||
|
removed, err := ks.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("no key named %s was found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := removed.GetPublic()
|
||||||
|
|
||||||
|
pid, err := peer.IDFromPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ks.Delete(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return (&key{"", pid.Pretty()}).Path(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *KeyAPI) core() coreiface.CoreAPI {
|
||||||
|
return api.CoreAPI
|
||||||
|
}
|
416
core/coreapi/key_test.go
Normal file
416
core/coreapi/key_test.go
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
package coreapi_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
opts "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListSelf(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := api.Key().List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list keys: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keys) != 1 {
|
||||||
|
t.Fatalf("there should be 1 key (self), got %d", len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys[0].Name() != "self" {
|
||||||
|
t.Errorf("expected the key to be called 'self', got '%s'", keys[0].Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys[0].Path().String() != "/ipns/Qmfoo" {
|
||||||
|
t.Errorf("expected the key to have path '/ipns/Qmfoo', got '%s'", keys[0].Path().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenameSelf(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = api.Key().Rename(ctx, "self", "foo")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "cannot rename key with name 'self'" {
|
||||||
|
t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = api.Key().Rename(ctx, "self", "foo", api.Key().WithForce(true))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "cannot rename key with name 'self'" {
|
||||||
|
t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveSelf(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Remove(ctx, "self")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "cannot remove key with name 'self'" {
|
||||||
|
t.Fatalf("expected error 'cannot remove key with name 'self'', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Name() != "foo" {
|
||||||
|
t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(k.Path().String(), "/ipns/Qm") {
|
||||||
|
t.Errorf("expected the key to be prefixed with '/ipns/Qm', got '%s'", k.Path().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSize(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := api.Key().Generate(ctx, "foo", api.Key().WithSize(1024))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Name() != "foo" {
|
||||||
|
t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(k.Path().String(), "/ipns/Qm") {
|
||||||
|
t.Errorf("expected the key to be prefixed with '/ipns/Qm', got '%s'", k.Path().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateType(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := api.Key().Generate(ctx, "bar", api.Key().WithType(opts.Ed25519Key))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Name() != "bar" {
|
||||||
|
t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(k.Path().String(), "/ipns/Qm") {
|
||||||
|
t.Errorf("expected the key to be prefixed with '/ipns/Qm', got '%s'", k.Path().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateExisting(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "key with name 'foo' already exists" {
|
||||||
|
t.Fatalf("expected error 'key with name 'foo' already exists', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "self")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "cannot overwrite key with name 'self'" {
|
||||||
|
t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := api.Key().List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l) != 2 {
|
||||||
|
t.Fatalf("expected to get 2 keys, got %d", len(l))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l[0].Name() != "self" {
|
||||||
|
t.Fatalf("expected key 0 to be called 'self', got '%s'", l[0].Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l[1].Name() != "foo" {
|
||||||
|
t.Fatalf("expected key 1 to be called 'foo', got '%s'", l[1].Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(l[0].Path().String(), "/ipns/Qm") {
|
||||||
|
t.Fatalf("expected key 0 to be prefixed with '/ipns/Qm', got '%s'", l[0].Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(l[1].Path().String(), "/ipns/Qm") {
|
||||||
|
t.Fatalf("expected key 1 to be prefixed with '/ipns/Qm', got '%s'", l[1].Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRename(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k, overwrote, err := api.Key().Rename(ctx, "foo", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if overwrote {
|
||||||
|
t.Error("overwrote should be false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Name() != "bar" {
|
||||||
|
t.Errorf("returned key should be called 'bar', got '%s'", k.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenameToSelf(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = api.Key().Rename(ctx, "foo", "self")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "cannot overwrite key with name 'self'" {
|
||||||
|
t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenameToSelfForce(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = api.Key().Rename(ctx, "foo", "self", api.Key().WithForce(true))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "cannot overwrite key with name 'self'" {
|
||||||
|
t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenameOverwriteNoForce(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = api.Key().Rename(ctx, "foo", "bar")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to not be nil")
|
||||||
|
} else {
|
||||||
|
if err.Error() != "key by that name already exists, refusing to overwrite" {
|
||||||
|
t.Fatalf("expected error 'key by that name already exists, refusing to overwrite', got '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenameOverwrite(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kfoo, err := api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Key().Generate(ctx, "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k, overwrote, err := api.Key().Rename(ctx, "foo", "bar", api.Key().WithForce(true))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !overwrote {
|
||||||
|
t.Error("overwrote should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Name() != "bar" {
|
||||||
|
t.Errorf("returned key should be called 'bar', got '%s'", k.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Path().String() != kfoo.Path().String() {
|
||||||
|
t.Errorf("k and kfoo should have equal paths, '%s'!='%s'", k.Path().String(), kfoo.Path().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemove(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := api.Key().List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l) != 2 {
|
||||||
|
t.Fatalf("expected to get 2 keys, got %d", len(l))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := api.Key().Remove(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Path().String() != p.String() {
|
||||||
|
t.Errorf("k and p should have equal paths, '%s'!='%s'", k.Path().String(), p.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err = api.Key().List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l) != 1 {
|
||||||
|
t.Fatalf("expected to get 1 key, got %d", len(l))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l[0].Name() != "self" {
|
||||||
|
t.Errorf("expected the key to be called 'self', got '%s'", l[0].Name())
|
||||||
|
}
|
||||||
|
}
|
170
core/coreapi/name.go
Normal file
170
core/coreapi/name.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package coreapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
core "github.com/ipfs/go-ipfs/core"
|
||||||
|
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||||
|
caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
|
||||||
|
keystore "github.com/ipfs/go-ipfs/keystore"
|
||||||
|
namesys "github.com/ipfs/go-ipfs/namesys"
|
||||||
|
ipath "github.com/ipfs/go-ipfs/path"
|
||||||
|
offline "github.com/ipfs/go-ipfs/routing/offline"
|
||||||
|
|
||||||
|
peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer"
|
||||||
|
crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NameAPI struct {
|
||||||
|
*CoreAPI
|
||||||
|
*caopts.NameOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipnsEntry struct {
|
||||||
|
name string
|
||||||
|
value coreiface.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ipnsEntry) Name() string {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ipnsEntry) Value() coreiface.Path {
|
||||||
|
return e.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, opts ...caopts.NamePublishOption) (coreiface.IpnsEntry, error) {
|
||||||
|
options, err := caopts.NamePublishOptions(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n := api.node
|
||||||
|
|
||||||
|
if !n.OnlineMode() {
|
||||||
|
err := n.SetupOfflineRouting()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.Mounts.Ipns != nil && n.Mounts.Ipns.IsActive() {
|
||||||
|
return nil, errors.New("cannot manually publish while IPNS is mounted")
|
||||||
|
}
|
||||||
|
|
||||||
|
pth, err := ipath.ParsePath(p.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := keylookup(n, options.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eol := time.Now().Add(options.ValidTime)
|
||||||
|
err = n.Namesys.PublishWithEOL(ctx, k, pth, eol)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := peer.IDFromPrivateKey(k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ipnsEntry{
|
||||||
|
name: pid.Pretty(),
|
||||||
|
value: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.NameResolveOption) (coreiface.Path, error) {
|
||||||
|
options, err := caopts.NameResolveOptions(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := api.node
|
||||||
|
|
||||||
|
if !n.OnlineMode() {
|
||||||
|
err := n.SetupOfflineRouting()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolver namesys.Resolver = n.Namesys
|
||||||
|
|
||||||
|
if options.Local && !options.Cache {
|
||||||
|
return nil, errors.New("cannot specify both local and nocache")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Local {
|
||||||
|
offroute := offline.NewOfflineRouter(n.Repo.Datastore(), n.PrivateKey)
|
||||||
|
resolver = namesys.NewRoutingResolver(offroute, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !options.Cache {
|
||||||
|
resolver = namesys.NewNameSystem(n.Routing, n.Repo.Datastore(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
depth := 1
|
||||||
|
if options.Recursive {
|
||||||
|
depth = namesys.DefaultDepthLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(name, "/ipns/") {
|
||||||
|
name = "/ipns/" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := resolver.ResolveN(ctx, name, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &path{path: output}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *NameAPI) core() coreiface.CoreAPI {
|
||||||
|
return api.CoreAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
func keylookup(n *core.IpfsNode, k string) (crypto.PrivKey, error) {
|
||||||
|
res, err := n.GetKey(k)
|
||||||
|
if res != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err != keystore.ErrNoSuchKey {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := n.Repo.Keystore().List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
privKey, err := n.Repo.Keystore().Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := privKey.GetPublic()
|
||||||
|
|
||||||
|
pid, err := peer.IDFromPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid.Pretty() == k {
|
||||||
|
return privKey, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no key by the given name or PeerID was found")
|
||||||
|
}
|
144
core/coreapi/name_test.go
Normal file
144
core/coreapi/name_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package coreapi_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ipath "github.com/ipfs/go-ipfs/path"
|
||||||
|
|
||||||
|
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rnd = rand.New(rand.NewSource(0x62796532303137))
|
||||||
|
|
||||||
|
func addTestObject(ctx context.Context, api coreiface.CoreAPI) (coreiface.Path, error) {
|
||||||
|
return api.Unixfs().Add(ctx, &io.LimitedReader{R: rnd, N: 4092})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicPublishResolve(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
n, api, err := makeAPIIdent(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := addTestObject(ctx, api)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := api.Name().Publish(ctx, p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Name() != n.Identity.Pretty() {
|
||||||
|
t.Errorf("expected e.Name to equal '%s', got '%s'", n.Identity.Pretty(), 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())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resPath.String() != p.String() {
|
||||||
|
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicPublishResolveKey(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, api, err := makeAPIIdent(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := api.Key().Generate(ctx, "foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := addTestObject(ctx, api)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := api.Name().Publish(ctx, p, api.Name().WithKey(k.Name()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipath.Join([]string{"/ipns", e.Name()}) != k.Path().String() {
|
||||||
|
t.Errorf("expected e.Name to equal '%s', got '%s'", e.Name(), k.Path().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resPath.String() != p.String() {
|
||||||
|
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicPublishResolveTimeout(t *testing.T) {
|
||||||
|
t.Skip("ValidTime doesn't appear to work at this time resolution")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
n, api, err := makeAPIIdent(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := addTestObject(ctx, api)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := api.Name().Publish(ctx, p, api.Name().WithValidTime(time.Millisecond*100))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Name() != n.Identity.Pretty() {
|
||||||
|
t.Errorf("expected e.Name to equal '%s', got '%s'", n.Identity.Pretty(), e.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Value().String() != p.String() {
|
||||||
|
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
_, err = api.Name().Resolve(ctx, e.Name())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: When swarm api is created, add multinode tests
|
@ -3,6 +3,7 @@ package coreapi_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
@ -12,12 +13,15 @@ import (
|
|||||||
coreapi "github.com/ipfs/go-ipfs/core/coreapi"
|
coreapi "github.com/ipfs/go-ipfs/core/coreapi"
|
||||||
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
|
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||||
coreunix "github.com/ipfs/go-ipfs/core/coreunix"
|
coreunix "github.com/ipfs/go-ipfs/core/coreunix"
|
||||||
|
keystore "github.com/ipfs/go-ipfs/keystore"
|
||||||
mdag "github.com/ipfs/go-ipfs/merkledag"
|
mdag "github.com/ipfs/go-ipfs/merkledag"
|
||||||
repo "github.com/ipfs/go-ipfs/repo"
|
repo "github.com/ipfs/go-ipfs/repo"
|
||||||
config "github.com/ipfs/go-ipfs/repo/config"
|
config "github.com/ipfs/go-ipfs/repo/config"
|
||||||
ds2 "github.com/ipfs/go-ipfs/thirdparty/datastore2"
|
ds2 "github.com/ipfs/go-ipfs/thirdparty/datastore2"
|
||||||
unixfs "github.com/ipfs/go-ipfs/unixfs"
|
unixfs "github.com/ipfs/go-ipfs/unixfs"
|
||||||
|
|
||||||
|
peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer"
|
||||||
|
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
|
||||||
cbor "gx/ipfs/QmeZv9VXw2SfVbX55LV6kGTWASKBc9ZxAVqGBeJcDGdoXy/go-ipld-cbor"
|
cbor "gx/ipfs/QmeZv9VXw2SfVbX55LV6kGTWASKBc9ZxAVqGBeJcDGdoXy/go-ipld-cbor"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,14 +35,40 @@ var emptyDir = coreapi.ResolvedPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbs
|
|||||||
// `echo -n | ipfs add`
|
// `echo -n | ipfs add`
|
||||||
var emptyFile = coreapi.ResolvedPath("/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", nil, nil)
|
var emptyFile = coreapi.ResolvedPath("/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", nil, nil)
|
||||||
|
|
||||||
func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) {
|
func makeAPIIdent(ctx context.Context, fullIdentity bool) (*core.IpfsNode, coreiface.CoreAPI, error) {
|
||||||
|
var ident config.Identity
|
||||||
|
if fullIdentity {
|
||||||
|
sk, pk, err := ci.GenerateKeyPair(ci.RSA, 512)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := peer.IDFromPublicKey(pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kbytes, err := sk.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ident = config.Identity{
|
||||||
|
PeerID: id.Pretty(),
|
||||||
|
PrivKey: base64.StdEncoding.EncodeToString(kbytes),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ident = config.Identity{
|
||||||
|
PeerID: "Qmfoo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r := &repo.Mock{
|
r := &repo.Mock{
|
||||||
C: config.Config{
|
C: config.Config{
|
||||||
Identity: config.Identity{
|
Identity: ident,
|
||||||
PeerID: "Qmfoo", // required by offline node
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
D: ds2.ThreadSafeCloserMapDatastore(),
|
D: ds2.ThreadSafeCloserMapDatastore(),
|
||||||
|
K: keystore.NewMemKeystore(),
|
||||||
}
|
}
|
||||||
node, err := core.NewNode(ctx, &core.BuildCfg{Repo: r})
|
node, err := core.NewNode(ctx, &core.BuildCfg{Repo: r})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,6 +78,10 @@ func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) {
|
|||||||
return node, api, nil
|
return node, api, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) {
|
||||||
|
return makeAPIIdent(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, api, err := makeAPI(ctx)
|
_, api, err := makeAPI(ctx)
|
||||||
|
@ -48,7 +48,7 @@ func (m *Mock) Close() error { return errTODO }
|
|||||||
|
|
||||||
func (m *Mock) SetAPIAddr(addr ma.Multiaddr) error { return errTODO }
|
func (m *Mock) SetAPIAddr(addr ma.Multiaddr) error { return errTODO }
|
||||||
|
|
||||||
func (m *Mock) Keystore() keystore.Keystore { return nil }
|
func (m *Mock) Keystore() keystore.Keystore { return m.K }
|
||||||
|
|
||||||
func (m *Mock) SwarmKey() ([]byte, error) {
|
func (m *Mock) SwarmKey() ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
Reference in New Issue
Block a user