mirror of
https://github.com/ipfs/kubo.git
synced 2025-09-09 19:32:24 +08:00

This commit adds a sequence number to the IpnsEntry protobuf that is used to determine which among a set of entries for the same key is the 'most correct'. GetValues has been added to the routing interface to retrieve a set of records from the dht, for the caller to select from. GetValue (singular) will call GetValues, select the 'best' record, and then update that record to peers we received outdated records from. This will help keep the dht consistent. License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
229 lines
5.6 KiB
Go
229 lines
5.6 KiB
Go
package namesys
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
|
|
ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
|
|
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
|
|
|
key "github.com/ipfs/go-ipfs/blocks/key"
|
|
dag "github.com/ipfs/go-ipfs/merkledag"
|
|
pb "github.com/ipfs/go-ipfs/namesys/pb"
|
|
ci "github.com/ipfs/go-ipfs/p2p/crypto"
|
|
path "github.com/ipfs/go-ipfs/path"
|
|
pin "github.com/ipfs/go-ipfs/pin"
|
|
routing "github.com/ipfs/go-ipfs/routing"
|
|
record "github.com/ipfs/go-ipfs/routing/record"
|
|
ft "github.com/ipfs/go-ipfs/unixfs"
|
|
u "github.com/ipfs/go-ipfs/util"
|
|
)
|
|
|
|
// ErrExpiredRecord should be returned when an ipns record is
|
|
// invalid due to being too old
|
|
var ErrExpiredRecord = errors.New("expired record")
|
|
|
|
// ErrUnrecognizedValidity is returned when an IpnsRecord has an
|
|
// unknown validity type.
|
|
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")
|
|
|
|
// ipnsPublisher is capable of publishing and resolving names to the IPFS
|
|
// routing system.
|
|
type ipnsPublisher struct {
|
|
routing routing.IpfsRouting
|
|
}
|
|
|
|
// NewRoutingPublisher constructs a publisher for the IPFS Routing name system.
|
|
func NewRoutingPublisher(route routing.IpfsRouting) Publisher {
|
|
return &ipnsPublisher{routing: route}
|
|
}
|
|
|
|
// 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)
|
|
|
|
pubkey := k.GetPublic()
|
|
pkbytes, err := pubkey.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nameb := u.Hash(pkbytes)
|
|
namekey := key.Key("/pk/" + string(nameb))
|
|
ipnskey := key.Key("/ipns/" + string(nameb))
|
|
|
|
// get previous records sequence number, and add one to it
|
|
var seqnum uint64
|
|
prevrec, err := p.routing.GetValues(ctx, ipnskey, 0)
|
|
if err == nil {
|
|
e := new(pb.IpnsEntry)
|
|
err := proto.Unmarshal(prevrec[0].Val, e)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
seqnum = e.GetSequence() + 1
|
|
} else if err != ds.ErrNotFound {
|
|
return err
|
|
}
|
|
|
|
data, err := createRoutingEntryData(k, value, seqnum)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Storing pubkey at: %s", namekey)
|
|
// Store associated public key
|
|
timectx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*10))
|
|
defer cancel()
|
|
err = p.routing.PutValue(timectx, namekey, pkbytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Storing ipns entry at: %s", ipnskey)
|
|
// Store ipns entry at "/ipns/"+b58(h(pubkey))
|
|
timectx, cancel = context.WithDeadline(ctx, time.Now().Add(time.Second*10))
|
|
defer cancel()
|
|
if err := p.routing.PutValue(timectx, ipnskey, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64) ([]byte, error) {
|
|
entry := new(pb.IpnsEntry)
|
|
|
|
entry.Value = []byte(val)
|
|
typ := pb.IpnsEntry_EOL
|
|
entry.ValidityType = &typ
|
|
entry.Sequence = proto.Uint64(seq)
|
|
entry.Validity = []byte(u.FormatRFC3339(time.Now().Add(time.Hour * 24)))
|
|
|
|
sig, err := pk.Sign(ipnsEntryDataForSig(entry))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entry.Signature = sig
|
|
return proto.Marshal(entry)
|
|
}
|
|
|
|
func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
|
|
return bytes.Join([][]byte{
|
|
e.Value,
|
|
e.Validity,
|
|
[]byte(fmt.Sprint(e.GetValidityType())),
|
|
},
|
|
[]byte{})
|
|
}
|
|
|
|
var IpnsRecordValidator = &record.ValidChecker{
|
|
Func: ValidateIpnsRecord,
|
|
Sign: true,
|
|
}
|
|
|
|
func IpnsSelectorFunc(k key.Key, vals [][]byte) (int, error) {
|
|
var recs []*pb.IpnsEntry
|
|
for _, v := range vals {
|
|
e := new(pb.IpnsEntry)
|
|
err := proto.Unmarshal(v, e)
|
|
if err == nil {
|
|
recs = append(recs, e)
|
|
} else {
|
|
recs = append(recs, nil)
|
|
}
|
|
}
|
|
|
|
var best_seq uint64
|
|
best_i := -1
|
|
|
|
for i, r := range recs {
|
|
if r == nil {
|
|
continue
|
|
}
|
|
if best_i == -1 || r.GetSequence() > best_seq {
|
|
best_seq = r.GetSequence()
|
|
best_i = i
|
|
} else if r.GetSequence() == best_seq {
|
|
rt, err := u.ParseRFC3339(string(r.GetValidity()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if rt.After(bestt) {
|
|
best_seq = r.GetSequence()
|
|
best_i = i
|
|
}
|
|
}
|
|
}
|
|
if best_i == -1 {
|
|
return 0, errors.New("no usable records in given set")
|
|
}
|
|
|
|
return best_i, nil
|
|
}
|
|
|
|
// ValidateIpnsRecord implements ValidatorFunc and verifies that the
|
|
// given 'val' is an IpnsEntry and that that entry is valid.
|
|
func ValidateIpnsRecord(k key.Key, val []byte) error {
|
|
entry := new(pb.IpnsEntry)
|
|
err := proto.Unmarshal(val, entry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch entry.GetValidityType() {
|
|
case pb.IpnsEntry_EOL:
|
|
t, err := u.ParseRFC3339(string(entry.GetValidity()))
|
|
if err != nil {
|
|
log.Debug("Failed parsing time for ipns record EOL")
|
|
return err
|
|
}
|
|
if time.Now().After(t) {
|
|
return ErrExpiredRecord
|
|
}
|
|
default:
|
|
return ErrUnrecognizedValidity
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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, ds dag.DAGService, pub Publisher, pins pin.Pinner, key ci.PrivKey) error {
|
|
emptyDir := &dag.Node{Data: ft.FolderPBData()}
|
|
nodek, err := ds.Add(emptyDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
err = pub.Publish(ctx, key, path.FromKey(nodek))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|