ipld-eth-server/vendor/github.com/ipfs/go-ipfs/namesys/publisher.go

313 lines
8.2 KiB
Go

package namesys
import (
"context"
"strings"
"sync"
"time"
pin "github.com/ipfs/go-ipfs/pin"
proto "github.com/gogo/protobuf/proto"
ds "github.com/ipfs/go-datastore"
dsquery "github.com/ipfs/go-datastore/query"
ipns "github.com/ipfs/go-ipns"
pb "github.com/ipfs/go-ipns/pb"
path "github.com/ipfs/go-path"
ft "github.com/ipfs/go-unixfs"
ci "github.com/libp2p/go-libp2p-crypto"
peer "github.com/libp2p/go-libp2p-peer"
routing "github.com/libp2p/go-libp2p-routing"
base32 "github.com/whyrusleeping/base32"
)
const ipnsPrefix = "/ipns/"
const PublishPutValTimeout = time.Minute
const DefaultRecordEOL = 24 * time.Hour
// IpnsPublisher is capable of publishing and resolving names to the IPFS
// routing system.
type IpnsPublisher struct {
routing routing.ValueStore
ds ds.Datastore
// Used to ensure we assign IPNS records *sequential* sequence numbers.
mu sync.Mutex
}
// NewIpnsPublisher constructs a publisher for the IPFS Routing name system.
func NewIpnsPublisher(route routing.ValueStore, ds ds.Datastore) *IpnsPublisher {
if ds == nil {
panic("nil datastore")
}
return &IpnsPublisher{routing: route, ds: ds}
}
// Publish implements Publisher. Accepts a keypair and a value,
// and publishes it out to the routing system
func (p *IpnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error {
log.Debugf("Publish %s", value)
return p.PublishWithEOL(ctx, k, value, time.Now().Add(DefaultRecordEOL))
}
func IpnsDsKey(id peer.ID) ds.Key {
return ds.NewKey("/ipns/" + base32.RawStdEncoding.EncodeToString([]byte(id)))
}
// PublishedNames returns the latest IPNS records published by this node and
// their expiration times.
//
// This method will not search the routing system for records published by other
// nodes.
func (p *IpnsPublisher) ListPublished(ctx context.Context) (map[peer.ID]*pb.IpnsEntry, error) {
query, err := p.ds.Query(dsquery.Query{
Prefix: ipnsPrefix,
})
if err != nil {
return nil, err
}
defer query.Close()
records := make(map[peer.ID]*pb.IpnsEntry)
for {
select {
case result, ok := <-query.Next():
if !ok {
return records, nil
}
if result.Error != nil {
return nil, result.Error
}
e := new(pb.IpnsEntry)
if err := proto.Unmarshal(result.Value, e); err != nil {
// Might as well return what we can.
log.Error("found an invalid IPNS entry:", err)
continue
}
if !strings.HasPrefix(result.Key, ipnsPrefix) {
log.Errorf("datastore query for keys with prefix %s returned a key: %s", ipnsPrefix, result.Key)
continue
}
k := result.Key[len(ipnsPrefix):]
pid, err := base32.RawStdEncoding.DecodeString(k)
if err != nil {
log.Errorf("ipns ds key invalid: %s", result.Key)
continue
}
records[peer.ID(pid)] = e
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
// GetPublished returns the record this node has published corresponding to the
// given peer ID.
//
// If `checkRouting` is true and we have no existing record, this method will
// check the routing system for any existing records.
func (p *IpnsPublisher) GetPublished(ctx context.Context, id peer.ID, checkRouting bool) (*pb.IpnsEntry, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
value, err := p.ds.Get(IpnsDsKey(id))
switch err {
case nil:
case ds.ErrNotFound:
if !checkRouting {
return nil, nil
}
ipnskey := ipns.RecordKey(id)
value, err = p.routing.GetValue(ctx, ipnskey)
if err != nil {
// Not found or other network issue. Can't really do
// anything about this case.
if err != routing.ErrNotFound {
log.Debugf("error when determining the last published IPNS record for %s: %s", id, err)
}
return nil, nil
}
default:
return nil, err
}
e := new(pb.IpnsEntry)
if err := proto.Unmarshal(value, e); err != nil {
return nil, err
}
return e, nil
}
func (p *IpnsPublisher) updateRecord(ctx context.Context, k ci.PrivKey, value path.Path, eol time.Time) (*pb.IpnsEntry, error) {
id, err := peer.IDFromPrivateKey(k)
if err != nil {
return nil, err
}
p.mu.Lock()
defer p.mu.Unlock()
// get previous records sequence number
rec, err := p.GetPublished(ctx, id, true)
if err != nil {
return nil, err
}
seqno := rec.GetSequence() // returns 0 if rec is nil
if rec != nil && value != path.Path(rec.GetValue()) {
// Don't bother incrementing the sequence number unless the
// value changes.
seqno++
}
// Create record
entry, err := ipns.Create(k, []byte(value), seqno, eol)
if err != nil {
return nil, err
}
// Set the TTL
// TODO: Make this less hacky.
ttl, ok := checkCtxTTL(ctx)
if ok {
entry.Ttl = proto.Uint64(uint64(ttl.Nanoseconds()))
}
data, err := proto.Marshal(entry)
if err != nil {
return nil, err
}
// Put the new record.
if err := p.ds.Put(IpnsDsKey(id), data); err != nil {
return nil, err
}
return entry, nil
}
// 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 {
record, err := p.updateRecord(ctx, k, value, eol)
if err != nil {
return err
}
return PutRecordToRouting(ctx, p.routing, k.GetPublic(), record)
}
// setting the TTL on published records is an experimental feature.
// as such, i'm using the context to wire it through to avoid changing too
// much code along the way.
func checkCtxTTL(ctx context.Context) (time.Duration, bool) {
v := ctx.Value("ipns-publish-ttl")
if v == nil {
return 0, false
}
d, ok := v.(time.Duration)
return d, ok
}
func PutRecordToRouting(ctx context.Context, r routing.ValueStore, k ci.PubKey, entry *pb.IpnsEntry) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
errs := make(chan error, 2) // At most two errors (IPNS, and public key)
if err := ipns.EmbedPublicKey(k, entry); err != nil {
return err
}
id, err := peer.IDFromPublicKey(k)
if err != nil {
return err
}
go func() {
errs <- PublishEntry(ctx, r, ipns.RecordKey(id), entry)
}()
// Publish the public key if a public key cannot be extracted from the ID
// TODO: once v0.4.16 is widespread enough, we can stop doing this
// and at that point we can even deprecate the /pk/ namespace in the dht
//
// NOTE: This check actually checks if the public key has been embedded
// in the IPNS entry. This check is sufficient because we embed the
// public key in the IPNS entry if it can't be extracted from the ID.
if entry.PubKey != nil {
go func() {
errs <- PublishPublicKey(ctx, r, PkKeyForID(id), k)
}()
if err := waitOnErrChan(ctx, errs); err != nil {
return err
}
}
return waitOnErrChan(ctx, errs)
}
func waitOnErrChan(ctx context.Context, errs chan error) error {
select {
case err := <-errs:
return err
case <-ctx.Done():
return ctx.Err()
}
}
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()
if err != nil {
return err
}
// Store associated public key
timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
defer cancel()
return r.PutValue(timectx, k, pkbytes)
}
func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec *pb.IpnsEntry) error {
timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
defer cancel()
data, err := proto.Marshal(rec)
if err != nil {
return err
}
log.Debugf("Storing ipns entry at: %s", ipnskey)
// Store ipns entry at "/ipns/"+h(pubkey)
return r.PutValue(timectx, ipnskey, data)
}
// InitializeKeyspace sets the ipns record for the given key to
// point to an empty directory.
// TODO: this doesnt feel like it belongs here
func InitializeKeyspace(ctx context.Context, pub Publisher, pins pin.Pinner, key ci.PrivKey) error {
emptyDir := ft.EmptyDirNode()
// pin recursively because this might already be pinned
// and doing a direct pin would throw an error in that case
err := pins.Pin(ctx, emptyDir, true)
if err != nil {
return err
}
err = pins.Flush()
if err != nil {
return err
}
return pub.Publish(ctx, key, path.FromCid(emptyDir.Cid()))
}
// PkKeyForID returns the public key routing key for the given peer ID.
func PkKeyForID(id peer.ID) string {
return "/pk/" + string(id)
}