publisher.go 8.78 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3
package namesys

import (
Jeromy's avatar
Jeromy committed
4
	"bytes"
5
	"context"
Jeromy's avatar
Jeromy committed
6
	"errors"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
7
	"fmt"
8 9
	"time"

10
	dag "github.com/ipfs/go-ipfs/merkledag"
11
	pb "github.com/ipfs/go-ipfs/namesys/pb"
12
	path "github.com/ipfs/go-ipfs/path"
13
	pin "github.com/ipfs/go-ipfs/pin"
14
	dshelp "github.com/ipfs/go-ipfs/thirdparty/ds-help"
15
	ft "github.com/ipfs/go-ipfs/unixfs"
16

Jeromy's avatar
Jeromy committed
17
	ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
18
	proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
19
	u "gx/ipfs/Qmb912gdngC1UWwTkhuW8knyRbcWeu5kqkxBpveLmW8bSr/go-ipfs-util"
Jeromy's avatar
Jeromy committed
20
	routing "gx/ipfs/QmbkGVaN9W6RYJK4Ws5FvMKXKDqdRQ5snhtaa92qP6L8eU/go-libp2p-routing"
21 22 23 24
	record "gx/ipfs/QmdM4ohF7cr4MvAECVeD3hRA3HtZrk1ngaek4n8ojVT87h/go-libp2p-record"
	dhtpb "gx/ipfs/QmdM4ohF7cr4MvAECVeD3hRA3HtZrk1ngaek4n8ojVT87h/go-libp2p-record/pb"
	peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
	ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
Jeromy's avatar
Jeromy committed
25 26
)

Jeromy's avatar
Jeromy committed
27 28 29 30
// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")

Jeromy's avatar
Jeromy committed
31 32
// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
Jeromy's avatar
Jeromy committed
33 34
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")

Jeromy's avatar
Jeromy committed
35
var PublishPutValTimeout = time.Minute
36
var DefaultRecortTTL = 24 * time.Hour
Jeromy's avatar
Jeromy committed
37

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
38 39
// ipnsPublisher is capable of publishing and resolving names to the IPFS
// routing system.
40
type ipnsPublisher struct {
41
	routing routing.ValueStore
42
	ds      ds.Datastore
Jeromy's avatar
Jeromy committed
43 44
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
45
// NewRoutingPublisher constructs a publisher for the IPFS Routing name system.
46
func NewRoutingPublisher(route routing.ValueStore, ds ds.Datastore) *ipnsPublisher {
47 48 49 50
	if ds == nil {
		panic("nil datastore")
	}
	return &ipnsPublisher{routing: route, ds: ds}
51 52
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53
// Publish implements Publisher. Accepts a keypair and a value,
Jeromy's avatar
Jeromy committed
54
// and publishes it out to the routing system
55
func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error {
56
	log.Debugf("Publish %s", value)
57
	return p.PublishWithEOL(ctx, k, value, time.Now().Add(DefaultRecortTTL))
Jeromy's avatar
Jeromy committed
58
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59

Jeromy's avatar
Jeromy committed
60 61 62 63 64
// 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)
Jeromy's avatar
Jeromy committed
65
	if err != nil {
Jeromy's avatar
Jeromy committed
66
		return err
Jeromy's avatar
Jeromy committed
67 68
	}

Jeromy's avatar
Jeromy committed
69
	_, ipnskey := IpnsKeysForID(id)
70

71 72 73 74 75 76 77 78 79 80 81 82
	// get previous records sequence number
	seqnum, err := p.getPreviousSeqNo(ctx, ipnskey)
	if err != nil {
		return err
	}

	// increment it
	seqnum++

	return PutRecordToRouting(ctx, k, value, seqnum, eol, p.routing, id)
}

83
func (p *ipnsPublisher) getPreviousSeqNo(ctx context.Context, ipnskey string) (uint64, error) {
84
	prevrec, err := p.ds.Get(dshelp.NewKeyFromBinary([]byte(ipnskey)))
85 86 87 88 89
	if err != nil && err != ds.ErrNotFound {
		// None found, lets start at zero!
		return 0, err
	}
	var val []byte
90
	if err == nil {
91 92 93 94 95 96
		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)
97
		if err != nil {
98
			return 0, err
99 100
		}

101 102 103 104 105 106 107 108 109 110 111 112 113
		val = dhtrec.GetValue()
	} else {
		// try and check the dht for a record
		ctx, cancel := context.WithTimeout(ctx, time.Second*30)
		defer cancel()

		rv, err := p.routing.GetValue(ctx, ipnskey)
		if err != nil {
			// no such record found, start at zero!
			return 0, nil
		}

		val = rv
114 115
	}

116 117 118 119 120 121 122
	e := new(pb.IpnsEntry)
	err = proto.Unmarshal(val, e)
	if err != nil {
		return 0, err
	}

	return e.GetSequence(), nil
Jeromy's avatar
Jeromy committed
123 124
}

125 126 127 128 129 130 131 132 133 134 135 136 137
// 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
}

138
func PutRecordToRouting(ctx context.Context, k ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, r routing.ValueStore, id peer.ID) error {
139 140 141
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

Jeromy's avatar
Jeromy committed
142
	namekey, ipnskey := IpnsKeysForID(id)
Jeromy's avatar
Jeromy committed
143 144 145 146 147
	entry, err := CreateRoutingEntryData(k, value, seqnum, eol)
	if err != nil {
		return err
	}

148 149 150 151 152
	ttl, ok := checkCtxTTL(ctx)
	if ok {
		entry.Ttl = proto.Uint64(uint64(ttl.Nanoseconds()))
	}

153
	errs := make(chan error, 2)
154 155 156 157 158 159 160 161 162 163

	go func() {
		errs <- PublishEntry(ctx, r, ipnskey, entry)
	}()

	go func() {
		errs <- PublishPublicKey(ctx, r, namekey, k.GetPublic())
	}()

	err = waitOnErrChan(ctx, errs)
Jeromy's avatar
Jeromy committed
164 165 166 167
	if err != nil {
		return err
	}

168
	err = waitOnErrChan(ctx, errs)
Jeromy's avatar
Jeromy committed
169 170 171 172 173 174 175
	if err != nil {
		return err
	}

	return nil
}

176 177 178
func waitOnErrChan(ctx context.Context, errs chan error) error {
	select {
	case err := <-errs:
Jeromy's avatar
Jeromy committed
179
		return err
180 181 182 183 184
	case <-ctx.Done():
		return ctx.Err()
	}
}

185
func PublishPublicKey(ctx context.Context, r routing.ValueStore, k string, pubk ci.PubKey) error {
Jeromy's avatar
Jeromy committed
186 187
	log.Debugf("Storing pubkey at: %s", k)
	pkbytes, err := pubk.Bytes()
188 189 190
	if err != nil {
		return err
	}
Jeromy's avatar
Jeromy committed
191 192

	// Store associated public key
Jeromy's avatar
Jeromy committed
193
	timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
rht's avatar
rht committed
194
	defer cancel()
Jeromy's avatar
Jeromy committed
195 196 197 198 199 200 201 202
	err = r.PutValue(timectx, k, pkbytes)
	if err != nil {
		return err
	}

	return nil
}

203
func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec *pb.IpnsEntry) error {
Jeromy's avatar
Jeromy committed
204 205 206 207
	timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
	defer cancel()

	data, err := proto.Marshal(rec)
Jeromy's avatar
Jeromy committed
208 209 210 211
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
212
	log.Debugf("Storing ipns entry at: %s", ipnskey)
213
	// Store ipns entry at "/ipns/"+b58(h(pubkey))
Jeromy's avatar
Jeromy committed
214
	if err := r.PutValue(timectx, ipnskey, data); err != nil {
Jeromy's avatar
Jeromy committed
215 216 217 218 219 220
		return err
	}

	return nil
}

Jeromy's avatar
Jeromy committed
221
func CreateRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64, eol time.Time) (*pb.IpnsEntry, error) {
222
	entry := new(pb.IpnsEntry)
Jeromy's avatar
Jeromy committed
223 224 225 226

	entry.Value = []byte(val)
	typ := pb.IpnsEntry_EOL
	entry.ValidityType = &typ
227
	entry.Sequence = proto.Uint64(seq)
Jeromy's avatar
Jeromy committed
228
	entry.Validity = []byte(u.FormatRFC3339(eol))
Jeromy's avatar
Jeromy committed
229 230

	sig, err := pk.Sign(ipnsEntryDataForSig(entry))
Jeromy's avatar
Jeromy committed
231 232 233 234
	if err != nil {
		return nil, err
	}
	entry.Signature = sig
Jeromy's avatar
Jeromy committed
235
	return entry, nil
Jeromy's avatar
Jeromy committed
236
}
Jeromy's avatar
Jeromy committed
237 238 239 240 241 242 243 244 245 246

func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
	return bytes.Join([][]byte{
		e.Value,
		e.Validity,
		[]byte(fmt.Sprint(e.GetValidityType())),
	},
		[]byte{})
}

247 248 249 250 251
var IpnsRecordValidator = &record.ValidChecker{
	Func: ValidateIpnsRecord,
	Sign: true,
}

252
func IpnsSelectorFunc(k string, vals [][]byte) (int, error) {
253 254 255 256 257 258 259 260 261 262 263
	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)
		}
	}

Jeromy's avatar
Jeromy committed
264 265 266 267
	return selectRecord(recs, vals)
}

func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
268 269 270 271
	var best_seq uint64
	best_i := -1

	for i, r := range recs {
Jeromy's avatar
Jeromy committed
272
		if r == nil || r.GetSequence() < best_seq {
273 274
			continue
		}
Jeromy's avatar
Jeromy committed
275

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
		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_i = i
Jeromy's avatar
Jeromy committed
292 293 294 295
			} else if rt == bestt {
				if bytes.Compare(vals[i], vals[best_i]) > 0 {
					best_i = i
				}
296 297 298 299 300 301 302 303 304 305
			}
		}
	}
	if best_i == -1 {
		return 0, errors.New("no usable records in given set")
	}

	return best_i, nil
}

Jeromy's avatar
Jeromy committed
306 307
// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
308
func ValidateIpnsRecord(k string, val []byte) error {
Jeromy's avatar
Jeromy committed
309 310 311 312 313 314 315
	entry := new(pb.IpnsEntry)
	err := proto.Unmarshal(val, entry)
	if err != nil {
		return err
	}
	switch entry.GetValidityType() {
	case pb.IpnsEntry_EOL:
316
		t, err := u.ParseRFC3339(string(entry.GetValidity()))
Jeromy's avatar
Jeromy committed
317
		if err != nil {
318
			log.Debug("failed parsing time for ipns record EOL")
Jeromy's avatar
Jeromy committed
319 320 321 322 323 324 325 326 327 328
			return err
		}
		if time.Now().After(t) {
			return ErrExpiredRecord
		}
	default:
		return ErrUnrecognizedValidity
	}
	return nil
}
329 330 331 332 333

// 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 {
334
	emptyDir := ft.EmptyDirNode()
335 336 337 338 339
	nodek, err := ds.Add(emptyDir)
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
340 341
	// pin recursively because this might already be pinned
	// and doing a direct pin would throw an error in that case
Jeromy's avatar
Jeromy committed
342
	err = pins.Pin(ctx, emptyDir, true)
343 344 345 346 347 348 349 350 351
	if err != nil {
		return err
	}

	err = pins.Flush()
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
352
	err = pub.Publish(ctx, key, path.FromCid(nodek))
353 354 355 356 357 358
	if err != nil {
		return err
	}

	return nil
}
Jeromy's avatar
Jeromy committed
359

360 361 362
func IpnsKeysForID(id peer.ID) (name, ipns string) {
	namekey := "/pk/" + string(id)
	ipnskey := "/ipns/" + string(id)
Jeromy's avatar
Jeromy committed
363 364 365

	return namekey, ipnskey
}