From 51dc2d24abeefed4ffdef2b3a3d56e2554650d10 Mon Sep 17 00:00:00 2001 From: Wigy Date: Tue, 29 Nov 2016 15:36:29 +0100 Subject: [PATCH] Added /name/upload command definition License: MIT Signed-off-by: Wigy --- .travis.yml | 1 + core/commands/dht_test.go | 4 +- core/commands/name.go | 6 +- core/commands/publish.go | 134 +++++++++++++--- core/core.go | 2 +- core/corehttp/gateway_test.go | 29 ++-- fuse/ipns/common.go | 1 + namesys/interface.go | 14 +- namesys/namesys.go | 34 +++-- namesys/publisher.go | 244 ++++++++++++++++++++++-------- namesys/republisher/repub.go | 72 ++------- namesys/republisher/repub_test.go | 2 +- package.json | 5 + test/sharness/.gitignore | 2 +- test/sharness/lib/iptb-lib.sh | 2 +- test/sharness/lib/test-lib.sh | 23 ++- 16 files changed, 387 insertions(+), 188 deletions(-) diff --git a/.travis.yml b/.travis.yml index 602ba01d41d..12ad1d48bc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ os: - osx language: go +go_import_path: github.com/ipfs/go-ipfs go: - 1.7 diff --git a/core/commands/dht_test.go b/core/commands/dht_test.go index bf75aff98ff..d49d13f1d04 100644 --- a/core/commands/dht_test.go +++ b/core/commands/dht_test.go @@ -5,11 +5,13 @@ import ( "github.com/ipfs/go-ipfs/namesys" tu "github.com/ipfs/go-ipfs/thirdparty/testutil" + routing "gx/ipfs/QmbkGVaN9W6RYJK4Ws5FvMKXKDqdRQ5snhtaa92qP6L8eU/go-libp2p-routing" ) func TestKeyTranslation(t *testing.T) { pid := tu.RandPeerIDFatal(t) - a, b := namesys.IpnsKeysForID(pid) + a := routing.KeyForPublicKey(pid) + b := namesys.IpnsKeyForID(pid) pkk, err := escapeDhtKey("/pk/" + pid.Pretty()) if err != nil { diff --git a/core/commands/name.go b/core/commands/name.go index 6d9f945f90e..3b20cd1b805 100644 --- a/core/commands/name.go +++ b/core/commands/name.go @@ -28,11 +28,6 @@ Publish an to your identity name: > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy -Publish an to another public key: - - > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n - Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - Resolve the value of your identity: > ipfs name resolve @@ -53,6 +48,7 @@ Resolve the value of a reference: Subcommands: map[string]*cmds.Command{ "publish": PublishCmd, + "upload": UploadNameCmd, "resolve": IpnsCmd, }, } diff --git a/core/commands/publish.go b/core/commands/publish.go index 2b98e8f08f9..79e3ec1025e 100644 --- a/core/commands/publish.go +++ b/core/commands/publish.go @@ -12,12 +12,91 @@ import ( core "github.com/ipfs/go-ipfs/core" path "github.com/ipfs/go-ipfs/path" + multibase "gx/ipfs/QmShp7G5GEsLVZ52imm6VP4nukpc5ipdHbscrxJMNasmSd/go-multibase" peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer" crypto "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto" ) var errNotOnline = errors.New("This command must be run in online mode. Try running 'ipfs daemon' first.") +type UploadResult struct { + Peer string + OldSeq uint64 + NewSeq uint64 + NewPath path.Path +} + +var UploadNameCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "Upload a signed IPNS record to an IPFS node", + }, + + Arguments: []cmds.Argument{ + cmds.StringArg("ipns-rec", true, false, "binary IPNS record").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.StringOption("key", "Public key of the author who signed the IPNS record"), + }, + + Run: func(req cmds.Request, res cmds.Response) { + log.Debug("begin name upload") + n, err := getNodeWithNamesys(req, res) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + if len(req.Arguments()) != 1 { + res.SetError(errors.New("Must provide the IPNS record as the single argument"), cmds.ErrNormal) + return + } + + _, record, err := multibase.Decode(req.Arguments()[0]) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + ctx := req.Context() + pubkeyString, found, err := req.Option("key").String() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + if !found { + res.SetError(errors.New("Must provide a public key as the --key option"), cmds.ErrNormal) + return + } + + _, pubkeyBytes, err := multibase.Decode(pubkeyString) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + pubkey, err := crypto.UnmarshalPublicKey(pubkeyBytes) + crypto.MarshalPublicKey(pubkey) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + id, oldSeq, newSeq, newPath, err := n.Namesys.Upload(ctx, pubkey, record) + res.SetOutput(&UploadResult{Peer: id.Pretty(), OldSeq: oldSeq, NewSeq: newSeq, NewPath: newPath}) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + o := res.Output().(*UploadResult) + return strings.NewReader(fmt.Sprintf("/ipns/%s was set to %s (old seq=%d, new seq=%d)\n", o.Peer, o.NewPath, o.OldSeq, o.NewSeq)), nil + }, + }, + Type: UploadResult{}, +} + var PublishCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Publish an object to IPNS.", @@ -38,11 +117,6 @@ Publish an to your identity name: > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy -Publish an to another public key (not implemented): - - > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n - Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - `, }, @@ -60,32 +134,13 @@ Publish an to another public key (not implemented): }, Run: func(req cmds.Request, res cmds.Response) { log.Debug("begin publish") - n, err := req.InvocContext().GetNode() + n, err := getNodeWithNamesys(req, res) if err != nil { res.SetError(err, cmds.ErrNormal) return } - if !n.OnlineMode() { - err := n.SetupOfflineRouting() - if err != nil { - res.SetError(err, cmds.ErrNormal) - return - } - } - - if n.Mounts.Ipns != nil && n.Mounts.Ipns.IsActive() { - res.SetError(errors.New("You cannot manually publish while IPNS is mounted."), cmds.ErrNormal) - return - } - pstr := req.Arguments()[0] - - if n.Identity == "" { - res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) - return - } - popts := new(publishOpts) popts.verifyExists, _, _ = req.Option("resolve").Bool() @@ -134,6 +189,35 @@ Publish an to another public key (not implemented): Type: IpnsEntry{}, } +func getNodeWithNamesys(req cmds.Request, res cmds.Response) (n *core.IpfsNode, err error) { + n, err = req.InvocContext().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + if !n.OnlineMode() { + err = n.SetupOfflineRouting() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + } + + if n.Mounts.Ipns != nil && n.Mounts.Ipns.IsActive() { + err = errors.New("You cannot manually publish while IPNS is mounted.") + res.SetError(err, cmds.ErrNormal) + return + } + + if n.Identity == "" { + err = errors.New("Identity not loaded!") + res.SetError(err, cmds.ErrNormal) + } + + return +} + type publishOpts struct { verifyExists bool pubValidTime time.Duration diff --git a/core/core.go b/core/core.go index 22a5fd276f4..869dc7aaa88 100644 --- a/core/core.go +++ b/core/core.go @@ -328,7 +328,7 @@ func (n *IpfsNode) setupIpnsRepublisher() error { return err } - n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore) + n.IpnsRepub = ipnsrp.NewRepublisher(n.Namesys, n.Peerstore) n.IpnsRepub.AddName(n.Identity) if cfg.Ipns.RepublishPeriod != "" { diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 9f1eefc95d7..8f5c1e5a6b5 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -10,19 +10,22 @@ import ( "testing" "time" - core "github.com/ipfs/go-ipfs/core" - coreunix "github.com/ipfs/go-ipfs/core/coreunix" + "github.com/ipfs/go-ipfs/core" + "github.com/ipfs/go-ipfs/core/coreunix" dag "github.com/ipfs/go-ipfs/merkledag" - namesys "github.com/ipfs/go-ipfs/namesys" - path "github.com/ipfs/go-ipfs/path" - repo "github.com/ipfs/go-ipfs/repo" - config "github.com/ipfs/go-ipfs/repo/config" - testutil "github.com/ipfs/go-ipfs/thirdparty/testutil" + "github.com/ipfs/go-ipfs/namesys" + "github.com/ipfs/go-ipfs/path" + "github.com/ipfs/go-ipfs/repo" + "github.com/ipfs/go-ipfs/repo/config" + "github.com/ipfs/go-ipfs/thirdparty/testutil" id "gx/ipfs/QmbzCT1CwxVZ2ednptC9RavuJe7Bv8DDi2Ne89qUrA37XM/go-libp2p/p2p/protocol/identify" ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto" + peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer" ) +var errNotImplemented = errors.New("not implemented for mockNamesys") + type mockNamesys map[string]path.Path func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, err error) { @@ -38,11 +41,19 @@ func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (valu } func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { - return errors.New("not implemented for mockNamesys") + return errNotImplemented } func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, _ time.Time) error { - return errors.New("not implemented for mockNamesys") + return errNotImplemented +} + +func (m mockNamesys) RePublish(ctx context.Context, name ci.PrivKey, _ time.Time) error { + return errNotImplemented +} + +func (m mockNamesys) Upload(ctx context.Context, pk ci.PubKey, record []byte) (peer.ID, uint64, uint64, path.Path, error) { + return "", 0, 0, "", errNotImplemented } func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) { diff --git a/fuse/ipns/common.go b/fuse/ipns/common.go index def4fda0b08..004b8b196f0 100644 --- a/fuse/ipns/common.go +++ b/fuse/ipns/common.go @@ -12,6 +12,7 @@ import ( // InitializeKeyspace sets the ipns record for the given key to // point to an empty directory. +// TODO: compare to github.com/ipfs/go-ipfs/namesys/publisher.go func InitializeKeyspace(n *core.IpfsNode, key ci.PrivKey) error { emptyDir := ft.EmptyDirNode() nodek, err := n.DAG.Add(emptyDir) diff --git a/namesys/interface.go b/namesys/interface.go index 3f66498ace0..a393865fd9f 100644 --- a/namesys/interface.go +++ b/namesys/interface.go @@ -30,11 +30,13 @@ For command-line bindings to this functionality, see: package namesys import ( + "context" "errors" "time" - context "context" - path "github.com/ipfs/go-ipfs/path" + "github.com/ipfs/go-ipfs/path" + + peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer" ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto" ) @@ -68,7 +70,7 @@ var ErrPublishFailed = errors.New("Could not publish name.") // key (name). type NameSystem interface { Resolver - Publisher + RePublisher } // Resolver is an object capable of resolving names. @@ -111,3 +113,9 @@ type Publisher interface { // call once the records spec is implemented PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error } + +type RePublisher interface { + Publisher + RePublish(ctx context.Context, sk ci.PrivKey, eol time.Time) error + Upload(ctx context.Context, pk ci.PubKey, record []byte) (id peer.ID, oldSeq uint64, newSeq uint64, newPath path.Path, err error) +} diff --git a/namesys/namesys.go b/namesys/namesys.go index bf1c68967a0..32c5d16136c 100644 --- a/namesys/namesys.go +++ b/namesys/namesys.go @@ -5,7 +5,7 @@ import ( "strings" "time" - path "github.com/ipfs/go-ipfs/path" + "github.com/ipfs/go-ipfs/path" ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore" routing "gx/ipfs/QmbkGVaN9W6RYJK4Ws5FvMKXKDqdRQ5snhtaa92qP6L8eU/go-libp2p-routing" @@ -23,20 +23,26 @@ import ( // It can only publish to: (a) IPFS routing naming. // type mpns struct { + ipnsPub *ipnsPublisher + dhtRes *routingResolver resolvers map[string]resolver - publishers map[string]Publisher + publishers map[string]RePublisher } // NewNameSystem will construct the IPFS naming system based on Routing func NewNameSystem(r routing.ValueStore, ds ds.Datastore, cachesize int) NameSystem { + ipnsPub := NewRoutingPublisher(r, ds) + dhtRes := NewRoutingResolver(r, cachesize) return &mpns{ + ipnsPub: ipnsPub, + dhtRes: dhtRes, resolvers: map[string]resolver{ "dns": newDNSResolver(), "proquint": new(ProquintResolver), - "dht": NewRoutingResolver(r, cachesize), + "dht": dhtRes, }, - publishers: map[string]Publisher{ - "/ipns/": NewRoutingPublisher(r, ds), + publishers: map[string]RePublisher{ + "/ipns/": ipnsPub, }, } } @@ -89,7 +95,7 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) // Publish implements Publisher func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { - err := ns.publishers["/ipns/"].Publish(ctx, name, value) + err := ns.ipnsPub.Publish(ctx, name, value) if err != nil { return err } @@ -98,7 +104,7 @@ func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) e } func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error { - err := ns.publishers["/ipns/"].PublishWithEOL(ctx, name, value, eol) + err := ns.ipnsPub.PublishWithEOL(ctx, name, value, eol) if err != nil { return err } @@ -106,12 +112,16 @@ func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path. return nil } +func (ns *mpns) RePublish(ctx context.Context, name ci.PrivKey, eol time.Time) error { + return ns.ipnsPub.RePublish(ctx, name, eol) +} + +func (ns *mpns) Upload(ctx context.Context, pk ci.PubKey, record []byte) (peer.ID, uint64, uint64, path.Path, error) { + return ns.ipnsPub.Upload(ctx, pk, record) +} + func (ns *mpns) addToDHTCache(key ci.PrivKey, value path.Path, eol time.Time) { - rr, ok := ns.resolvers["dht"].(*routingResolver) - if !ok { - // should never happen, purely for sanity - log.Panicf("unexpected type %T as DHT resolver.", ns.resolvers["dht"]) - } + rr := ns.dhtRes if rr.cache == nil { // resolver has no caching return diff --git a/namesys/publisher.go b/namesys/publisher.go index 54a0e834ef5..a4ef8ea6637 100644 --- a/namesys/publisher.go +++ b/namesys/publisher.go @@ -24,6 +24,8 @@ import ( ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto" ) +var errInvalidEntry = errors.New("invalid previous entry") + // ErrExpiredRecord should be returned when an ipns record is // invalid due to being too old var ErrExpiredRecord = errors.New("expired record") @@ -59,67 +61,151 @@ func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Pa // PublishWithEOL is a temporary stand in for the ipns records implementation // see here for more details: https://github.com/ipfs/specs/tree/master/records -func (p *ipnsPublisher) PublishWithEOL(ctx context.Context, k ci.PrivKey, value path.Path, eol time.Time) error { - - id, err := peer.IDFromPrivateKey(k) +func (p *ipnsPublisher) PublishWithEOL(ctx context.Context, pk ci.PrivKey, path path.Path, eol time.Time) error { + id, err := peer.IDFromPrivateKey(pk) if err != nil { return err } - _, ipnskey := IpnsKeysForID(id) - // get previous records sequence number - seqnum, err := p.getPreviousSeqNo(ctx, ipnskey) + rec, err := p.tryLocalThenRemote(ctx, IpnsKeyForID(id)) + if err != nil { + return err + } + + _, seq, err := p.parseIpnsRecord(rec) if err != nil { return err } // increment it - seqnum++ + seq++ - return PutRecordToRouting(ctx, k, value, seqnum, eol, p.routing, id) + return PutRecordToRouting(ctx, pk, path, seq, eol, p.routing, id) } -func (p *ipnsPublisher) getPreviousSeqNo(ctx context.Context, ipnskey string) (uint64, error) { - prevrec, err := p.ds.Get(dshelp.NewKeyFromBinary([]byte(ipnskey))) - if err != nil && err != ds.ErrNotFound { - // None found, lets start at zero! - return 0, err - } - var val []byte - if err == nil { - prbytes, ok := prevrec.([]byte) - if !ok { - return 0, fmt.Errorf("unexpected type returned from datastore: %#v", prevrec) - } - dhtrec := new(dhtpb.Record) - err := proto.Unmarshal(prbytes, dhtrec) - if err != nil { - return 0, err - } +func (p *ipnsPublisher) RePublish(ctx context.Context, pk ci.PrivKey, eol time.Time) error { + id, err := peer.IDFromPrivateKey(pk) + if err != nil { + return err + } - val = dhtrec.GetValue() - } else { - // try and check the dht for a record - ctx, cancel := context.WithTimeout(ctx, time.Second*30) - defer cancel() + record, err := p.getLocal(ctx, IpnsKeyForID(id)) + if err == ds.ErrNotFound { + // not found means we don't have a previously published entry + return nil + } + if err != nil { + return err + } + + path, seq, err := p.parseIpnsRecord(record) + if err != nil { + return err + } + + // update record with same sequence number + return PutRecordToRouting(ctx, pk, path, seq, eol, p.routing, id) +} + +func (p *ipnsPublisher) Upload(ctx context.Context, pk ci.PubKey, record []byte) (id peer.ID, oldSeq uint64, newSeq uint64, newPath path.Path, err error) { + id, err = peer.IDFromPublicKey(pk) + if err != nil { + return + } - rv, err := p.routing.GetValue(ctx, ipnskey) + // get previous records sequence number + oldRec, err := p.tryLocalThenRemote(ctx, IpnsKeyForID(id)) + if err != nil { + return + } + + _, oldSeq, err = p.parseIpnsRecord(oldRec) + if err != nil { + return + } + + entry := new(pb.IpnsEntry) + err = proto.Unmarshal(record, entry) + if err != nil { + return + } + + if ok, err1 := pk.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err1 != nil || !ok { + err = fmt.Errorf("The IPNS record is not signed by %s", id.Pretty()) + return + } + if entry.Sequence == nil { + err = errors.New("The IPNS record must have a sequence number, but none were provided") + return + } + + newSeq = *entry.Sequence + if newSeq <= oldSeq { + err = fmt.Errorf("There is already a newer IPNS record with sequence number: %d", oldSeq) + return + } + + newPath, err = path.ParsePath(string(entry.Value)) + if err != nil { + return + } + + err = publishEntry(ctx, p.routing, pk, entry) + return +} + +func (p *ipnsPublisher) tryLocalThenRemote(ctx context.Context, dhtKey string) ([]byte, error) { + dhtValue, err := p.getLocal(ctx, dhtKey) + if err == ds.ErrNotFound { + dhtValue, err = p.getRemote(ctx, dhtKey) if err != nil { - // no such record found, start at zero! - return 0, nil + return nil, nil } + } + return dhtValue, err +} + +func (p *ipnsPublisher) getRemote(ctx context.Context, dhtKey string) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, time.Second*30) + defer cancel() - val = rv + return p.routing.GetValue(ctx, dhtKey) +} + +func (p *ipnsPublisher) getLocal(ctx context.Context, dhtKey string) ([]byte, error) { + maybeDsVal, err := p.ds.Get(dshelp.NewKeyFromBinary([]byte(dhtKey))) + if err != nil { + return nil, err + } + + dsVal, ok := maybeDsVal.([]byte) + if !ok { + return nil, errInvalidEntry + } + + dhtRec := new(dhtpb.Record) + err = proto.Unmarshal(dsVal, dhtRec) + if err != nil { + return nil, err + } + + return dhtRec.GetValue(), nil +} + +func (p *ipnsPublisher) parseIpnsRecord(record []byte) (path.Path, uint64, error) { + if record == nil { + return "", 0, nil } - e := new(pb.IpnsEntry) - err = proto.Unmarshal(val, e) + // extract published data from record + ipnsEntry := new(pb.IpnsEntry) + err := proto.Unmarshal(record, ipnsEntry) if err != nil { - return 0, err + return "", 0, err } - return e.GetSequence(), nil + return path.Path(ipnsEntry.Value), ipnsEntry.GetSequence(), nil } // setting the TTL on published records is an experimental feature. @@ -135,29 +221,62 @@ func checkCtxTTL(ctx context.Context) (time.Duration, bool) { return d, ok } -func PutRecordToRouting(ctx context.Context, k ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, r routing.ValueStore, id peer.ID) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - namekey, ipnskey := IpnsKeysForID(id) - entry, err := CreateRoutingEntryData(k, value, seqnum, eol) +func PutRecordToRouting(ctx context.Context, sk ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, r routing.ValueStore, id peer.ID) error { + entry, err := createEntry(ctx, sk, value, seqnum, eol) if err != nil { return err } + pk := sk.GetPublic() + + return publishEntry(ctx, r, pk, entry) +} + +func CreateEntry(sk ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, ttl time.Duration) ([]byte, error) { + ctx := context.WithValue(context.Background(), "ipns-publish-ttl", ttl) + entry, err := createEntry(ctx, sk, value, seqnum, eol) + if err != nil { + return nil, err + } + + result, err := proto.Marshal(entry) + if err != nil { + return nil, err + } + + return result, nil +} + +func createEntry(ctx context.Context, sk ci.PrivKey, value path.Path, seqnum uint64, eol time.Time) (*pb.IpnsEntry, error) { + entry, err := CreateRoutingEntryData(sk, value, seqnum, eol) + if err != nil { + return nil, err + } + ttl, ok := checkCtxTTL(ctx) if ok { entry.Ttl = proto.Uint64(uint64(ttl.Nanoseconds())) } + return entry, nil +} + +func publishEntry(ctx context.Context, r routing.ValueStore, pk ci.PubKey, entry *pb.IpnsEntry) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() errs := make(chan error, 2) + id, err := peer.IDFromPublicKey(pk) + if err != nil { + return err + } + go func() { - errs <- PublishEntry(ctx, r, ipnskey, entry) + errs <- PublishEntry(ctx, r, id, entry) }() go func() { - errs <- PublishPublicKey(ctx, r, namekey, k.GetPublic()) + errs <- PublishPublicKey(ctx, r, id, pk) }() err = waitOnErrChan(ctx, errs) @@ -182,17 +301,19 @@ func waitOnErrChan(ctx context.Context, errs chan error) error { } } -func PublishPublicKey(ctx context.Context, r routing.ValueStore, k string, pubk ci.PubKey) error { - log.Debugf("Storing pubkey at: %s", k) - pkbytes, err := pubk.Bytes() +func PublishPublicKey(ctx context.Context, r routing.ValueStore, id peer.ID, pk ci.PubKey) error { + // Store associated public key + timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout) + defer cancel() + + dhtValue, err := pk.Bytes() if err != nil { return err } - // Store associated public key - timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout) - defer cancel() - err = r.PutValue(timectx, k, pkbytes) + dhtKey := routing.KeyForPublicKey(id) + log.Debugf("Storing pubkey at: %s", dhtKey) + err = r.PutValue(timectx, dhtKey, dhtValue) if err != nil { return err } @@ -200,18 +321,19 @@ func PublishPublicKey(ctx context.Context, r routing.ValueStore, k string, pubk return nil } -func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec *pb.IpnsEntry) error { +func PublishEntry(ctx context.Context, r routing.ValueStore, id peer.ID, rec *pb.IpnsEntry) error { timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout) defer cancel() - data, err := proto.Marshal(rec) + dhtValue, err := proto.Marshal(rec) if err != nil { return err } - log.Debugf("Storing ipns entry at: %s", ipnskey) + dhtKey := IpnsKeyForID(id) + log.Debugf("Storing ipns entry at: %s", dhtKey) // Store ipns entry at "/ipns/"+b58(h(pubkey)) - if err := r.PutValue(timectx, ipnskey, data); err != nil { + if err := r.PutValue(timectx, dhtKey, dhtValue); err != nil { return err } @@ -330,6 +452,7 @@ func ValidateIpnsRecord(k string, val []byte) error { // InitializeKeyspace sets the ipns record for the given key to // point to an empty directory. // TODO: this doesnt feel like it belongs here +// TODO: compare to github.com/ipfs/go-ipfs/fuse/ipns/common.go func InitializeKeyspace(ctx context.Context, ds dag.DAGService, pub Publisher, pins pin.Pinner, key ci.PrivKey) error { emptyDir := ft.EmptyDirNode() nodek, err := ds.Add(emptyDir) @@ -357,9 +480,6 @@ func InitializeKeyspace(ctx context.Context, ds dag.DAGService, pub Publisher, p return nil } -func IpnsKeysForID(id peer.ID) (name, ipns string) { - namekey := "/pk/" + string(id) - ipnskey := "/ipns/" + string(id) - - return namekey, ipnskey +func IpnsKeyForID(id peer.ID) string { + return "/ipns/" + string(id) } diff --git a/namesys/republisher/repub.go b/namesys/republisher/repub.go index e4e0bdc928a..dea73e012ed 100644 --- a/namesys/republisher/repub.go +++ b/namesys/republisher/repub.go @@ -2,28 +2,18 @@ package republisher import ( "context" - "errors" "sync" "time" - namesys "github.com/ipfs/go-ipfs/namesys" - pb "github.com/ipfs/go-ipfs/namesys/pb" - path "github.com/ipfs/go-ipfs/path" - dshelp "github.com/ipfs/go-ipfs/thirdparty/ds-help" + "github.com/ipfs/go-ipfs/namesys" - ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore" - goprocess "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess" + "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess" gpctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context" logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log" - proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto" - routing "gx/ipfs/QmbkGVaN9W6RYJK4Ws5FvMKXKDqdRQ5snhtaa92qP6L8eU/go-libp2p-routing" - recpb "gx/ipfs/QmdM4ohF7cr4MvAECVeD3hRA3HtZrk1ngaek4n8ojVT87h/go-libp2p-record/pb" pstore "gx/ipfs/QmeXj9VAjmYQZxpmVz7VzccbJrpmr8qkCDSjfVNsPTWTYU/go-libp2p-peerstore" peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer" ) -var errNoEntry = errors.New("no previous entry") - var log = logging.Logger("ipns-repub") var DefaultRebroadcastInterval = time.Hour * 4 @@ -31,9 +21,8 @@ var DefaultRebroadcastInterval = time.Hour * 4 const DefaultRecordLifetime = time.Hour * 24 type Republisher struct { - r routing.ValueStore - ds ds.Datastore - ps pstore.Peerstore + repub namesys.RePublisher + peerStore pstore.Peerstore Interval time.Duration @@ -44,11 +33,10 @@ type Republisher struct { entries map[peer.ID]struct{} } -func NewRepublisher(r routing.ValueStore, ds ds.Datastore, ps pstore.Peerstore) *Republisher { +func NewRepublisher(repub namesys.RePublisher, peerStore pstore.Peerstore) *Republisher { return &Republisher{ - r: r, - ps: ps, - ds: ds, + repub: repub, + peerStore: peerStore, entries: make(map[peer.ID]struct{}), Interval: DefaultRebroadcastInterval, RecordLifetime: DefaultRecordLifetime, @@ -78,27 +66,16 @@ func (rp *Republisher) Run(proc goprocess.Process) { } } -func (rp *Republisher) republishEntries(p goprocess.Process) error { - ctx, cancel := context.WithCancel(gpctx.OnClosingContext(p)) +func (rp *Republisher) republishEntries(proc goprocess.Process) error { + ctx, cancel := context.WithCancel(gpctx.OnClosingContext(proc)) defer cancel() - for id, _ := range rp.entries { + for id := range rp.entries { log.Debugf("republishing ipns entry for %s", id) - priv := rp.ps.PrivKey(id) - - // Look for it locally only - _, ipnskey := namesys.IpnsKeysForID(id) - p, seq, err := rp.getLastVal(ipnskey) - if err != nil { - if err == errNoEntry { - continue - } - return err - } - - // update record with same sequence number + sk := rp.peerStore.PrivKey(id) eol := time.Now().Add(rp.RecordLifetime) - err = namesys.PutRecordToRouting(ctx, priv, p, seq, eol, rp.r, id) + + err := rp.repub.RePublish(ctx, sk, eol) if err != nil { return err } @@ -106,26 +83,3 @@ func (rp *Republisher) republishEntries(p goprocess.Process) error { return nil } - -func (rp *Republisher) getLastVal(k string) (path.Path, uint64, error) { - ival, err := rp.ds.Get(dshelp.NewKeyFromBinary([]byte(k))) - if err != nil { - // not found means we dont have a previously published entry - return "", 0, errNoEntry - } - - val := ival.([]byte) - dhtrec := new(recpb.Record) - err = proto.Unmarshal(val, dhtrec) - if err != nil { - return "", 0, err - } - - // extract published data from record - e := new(pb.IpnsEntry) - err = proto.Unmarshal(dhtrec.GetValue(), e) - if err != nil { - return "", 0, err - } - return path.Path(e.Value), e.GetSequence(), nil -} diff --git a/namesys/republisher/repub_test.go b/namesys/republisher/repub_test.go index ff456593d14..7acbdc8e7d9 100644 --- a/namesys/republisher/repub_test.go +++ b/namesys/republisher/repub_test.go @@ -78,7 +78,7 @@ func TestRepublish(t *testing.T) { // The republishers that are contained within the nodes have their timeout set // to 12 hours. Instead of trying to tweak those, we're just going to pretend // they dont exist and make our own. - repub := NewRepublisher(publisher.Routing, publisher.Repo.Datastore(), publisher.Peerstore) + repub := NewRepublisher(publisher.Namesys, publisher.Peerstore) repub.Interval = time.Second repub.RecordLifetime = time.Second * 5 repub.AddName(publisher.Identity) diff --git a/package.json b/package.json index fad38b1a090..b88e064cdff 100644 --- a/package.json +++ b/package.json @@ -299,6 +299,11 @@ "hash": "QmU1N5xVAUXgo3XRTt6GhJ2SuJEbxj2zRgMS7FpjSR2U83", "name": "semver", "version": "3.3.0" + }, + { + "hash": "QmShp7G5GEsLVZ52imm6VP4nukpc5ipdHbscrxJMNasmSd", + "name": "go-multibase", + "version": "0.3.0" } ], "gxVersion": "0.4.0", diff --git a/test/sharness/.gitignore b/test/sharness/.gitignore index 5e59048ac5f..ef70fc32501 100644 --- a/test/sharness/.gitignore +++ b/test/sharness/.gitignore @@ -1,3 +1,3 @@ lib/sharness/ test-results/ -trash directory.*.sh/ +trash.*.sh/ diff --git a/test/sharness/lib/iptb-lib.sh b/test/sharness/lib/iptb-lib.sh index 89ab77899d7..10896b60eab 100644 --- a/test/sharness/lib/iptb-lib.sh +++ b/test/sharness/lib/iptb-lib.sh @@ -33,7 +33,7 @@ startup_cluster() { ' fi - test_expect_success "connect nodes to eachother" ' + test_expect_success "connect nodes to each other" ' iptb connect [1-$bound] 0 ' diff --git a/test/sharness/lib/test-lib.sh b/test/sharness/lib/test-lib.sh index f671efb0de9..8dd14befa84 100644 --- a/test/sharness/lib/test-lib.sh +++ b/test/sharness/lib/test-lib.sh @@ -229,20 +229,27 @@ test_launch_ipfs_daemon() { } do_umount() { - if [ "$(uname -s)" = "Linux" ]; then - fusermount -u "$1" - else - umount "$1" - fi + if [ "$(uname -s)" = "Linux" ]; then + sleep 1s + fusermount -u "$1" 2>umount.err + else + umount "$1" + fi } test_mount_ipfs() { - # make sure stuff is unmounted first. - test_expect_success FUSE "'ipfs mount' succeeds" ' + test_expect_success FUSE "setup and publish default IPNS value" ' do_umount "$(pwd)/ipfs" || true && do_umount "$(pwd)/ipns" || true && - ipfs mount >actual + mkdir ipfs || true && + mkdir ipns || true && + ipfs name publish /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn + ' + + # make sure stuff is unmounted first. + test_expect_success FUSE "'ipfs mount' succeeds" ' + ipfs mount -f "$(pwd)/ipfs" -n "$(pwd)/ipns" >actual ' test_expect_success FUSE "'ipfs mount' output looks good" '