@cryptotaxi247 / kubo / commits / cfcc3d6a1

ipns record selection via sequence numbers

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>

Jeromy committed Sep 18, 2015 at 10:27 UTC cfcc3d6a1b77a47cdf6512e6be24e8b3bbd9fb05
15 files changed +301 -37
core/core.go
+1
@@ -501,6 +501,7 @@ func startListening(ctx context.Context, host p2phost.Host, cfg *config.Config)
501 func constructDHTRouting(ctx context.Context, host p2phost.Host, dstore ds.ThreadSafeDatastore) (routing.IpfsRouting, error) {
502 dhtRouting := dht.NewDHT(ctx, host, dstore)
503 dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator
504 + dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
505 return dhtRouting, nil
506 }
507
namesys/pb/namesys.pb.go
+8
@@ -56,6 +56,7 @@ type IpnsEntry struct {
56 Signature []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
57 ValidityType *IpnsEntry_ValidityType `protobuf:"varint,3,opt,name=validityType,enum=namesys.pb.IpnsEntry_ValidityType" json:"validityType,omitempty"`
58 Validity []byte `protobuf:"bytes,4,opt,name=validity" json:"validity,omitempty"`
59 + Sequence *uint64 `protobuf:"varint,5,opt,name=sequence" json:"sequence,omitempty"`
60 XXX_unrecognized []byte `json:"-"`
61 }
62
@@ -91,6 +92,13 @@ func (m *IpnsEntry) GetValidity() []byte {
92 return nil
93 }
94
95 +func (m *IpnsEntry) GetSequence() uint64 {
96 + if m != nil && m.Sequence != nil {
97 + return *m.Sequence
98 + }
99 + return 0
100 +}
101 +
102 func init() {
103 proto.RegisterEnum("namesys.pb.IpnsEntry_ValidityType", IpnsEntry_ValidityType_name, IpnsEntry_ValidityType_value)
104 }
namesys/pb/namesys.proto
+2
@@ -10,4 +10,6 @@ message IpnsEntry {
10
11 optional ValidityType validityType = 3;
12 optional bytes validity = 4;
13 +
14 + optional uint64 sequence = 5;
15 }
namesys/publisher.go
+70 -7
@@ -7,6 +7,7 @@ import (
7 "time"
8
9 proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
10 + ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
11 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
12
13 key "github.com/ipfs/go-ipfs/blocks/key"
@@ -45,10 +46,6 @@ func NewRoutingPublisher(route routing.IpfsRouting) Publisher {
46 func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error {
47 log.Debugf("Publish %s", value)
48
48 - data, err := createRoutingEntryData(k, value)
49 - if err != nil {
50 - return err
51 - }
49 pubkey := k.GetPublic()
50 pkbytes, err := pubkey.Bytes()
51 if err != nil {
@@ -57,6 +54,27 @@ func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Pa
54
55 nameb := u.Hash(pkbytes)
56 namekey := key.Key("/pk/" + string(nameb))
57 + ipnskey := key.Key("/ipns/" + string(nameb))
58 +
59 + // get previous records sequence number, and add one to it
60 + var seqnum uint64
61 + prevrec, err := p.routing.GetValues(ctx, ipnskey, 0)
62 + if err == nil {
63 + e := new(pb.IpnsEntry)
64 + err := proto.Unmarshal(prevrec[0].Val, e)
65 + if err != nil {
66 + return err
67 + }
68 +
69 + seqnum = e.GetSequence() + 1
70 + } else if err != ds.ErrNotFound {
71 + return err
72 + }
73 +
74 + data, err := createRoutingEntryData(k, value, seqnum)
75 + if err != nil {
76 + return err
77 + }
78
79 log.Debugf("Storing pubkey at: %s", namekey)
80 // Store associated public key
@@ -67,8 +85,6 @@ func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Pa
85 return err
86 }
87
70 - ipnskey := key.Key("/ipns/" + string(nameb))
71 -
88 log.Debugf("Storing ipns entry at: %s", ipnskey)
89 // Store ipns entry at "/ipns/"+b58(h(pubkey))
90 timectx, cancel = context.WithDeadline(ctx, time.Now().Add(time.Second*10))
@@ -80,12 +96,13 @@ func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Pa
96 return nil
97 }
98
83 -func createRoutingEntryData(pk ci.PrivKey, val path.Path) ([]byte, error) {
99 +func createRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64) ([]byte, error) {
100 entry := new(pb.IpnsEntry)
101
102 entry.Value = []byte(val)
103 typ := pb.IpnsEntry_EOL
104 entry.ValidityType = &typ
105 + entry.Sequence = proto.Uint64(seq)
106 entry.Validity = []byte(u.FormatRFC3339(time.Now().Add(time.Hour * 24)))
107
108 sig, err := pk.Sign(ipnsEntryDataForSig(entry))
@@ -110,6 +127,52 @@ var IpnsRecordValidator = &record.ValidChecker{
127 Sign: true,
128 }
129
130 +func IpnsSelectorFunc(k key.Key, vals [][]byte) (int, error) {
131 + var recs []*pb.IpnsEntry
132 + for _, v := range vals {
133 + e := new(pb.IpnsEntry)
134 + err := proto.Unmarshal(v, e)
135 + if err == nil {
136 + recs = append(recs, e)
137 + } else {
138 + recs = append(recs, nil)
139 + }
140 + }
141 +
142 + var best_seq uint64
143 + best_i := -1
144 +
145 + for i, r := range recs {
146 + if r == nil {
147 + continue
148 + }
149 + if best_i == -1 || r.GetSequence() > best_seq {
150 + best_seq = r.GetSequence()
151 + best_i = i
152 + } else if r.GetSequence() == best_seq {
153 + rt, err := u.ParseRFC3339(string(r.GetValidity()))
154 + if err != nil {
155 + continue
156 + }
157 +
158 + bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
159 + if err != nil {
160 + continue
161 + }
162 +
163 + if rt.After(bestt) {
164 + best_seq = r.GetSequence()
165 + best_i = i
166 + }
167 + }
168 + }
169 + if best_i == -1 {
170 + return 0, errors.New("no usable records in given set")
171 + }
172 +
173 + return best_i, nil
174 +}
175 +
176 // ValidateIpnsRecord implements ValidatorFunc and verifies that the
177 // given 'val' is an IpnsEntry and that that entry is valid.
178 func ValidateIpnsRecord(k key.Key, val []byte) error {
routing/dht/dht.go
+11 -6
@@ -54,6 +54,7 @@ type IpfsDHT struct {
54 diaglock sync.Mutex // lock to make diagnostics work better
55
56 Validator record.Validator // record validator funcs
57 + Selector record.Selector // record selection funcs
58
59 ctx context.Context
60 proc goprocess.Process
@@ -89,6 +90,9 @@ func NewDHT(ctx context.Context, h host.Host, dstore ds.ThreadSafeDatastore) *Ip
90 dht.Validator = make(record.Validator)
91 dht.Validator["pk"] = record.PublicKeyValidator
92
93 + dht.Selector = make(record.Selector)
94 + dht.Selector["pk"] = record.PublicKeySelector
95 +
96 return dht
97 }
98
@@ -152,13 +156,16 @@ func (dht *IpfsDHT) putProvider(ctx context.Context, p peer.ID, skey string) err
156 // NOTE: it will update the dht's peerstore with any new addresses
157 // it finds for the given peer.
158 func (dht *IpfsDHT) getValueOrPeers(ctx context.Context, p peer.ID,
155 - key key.Key) ([]byte, []peer.PeerInfo, error) {
159 + key key.Key) (*pb.Record, []peer.PeerInfo, error) {
160
161 pmes, err := dht.getValueSingle(ctx, p, key)
162 if err != nil {
163 return nil, nil, err
164 }
165
166 + // Perhaps we were given closer peers
167 + peers := pb.PBPeersToPeerInfos(pmes.GetCloserPeers())
168 +
169 if record := pmes.GetRecord(); record != nil {
170 // Success! We were given the value
171 log.Debug("getValueOrPeers: got value")
@@ -169,11 +176,9 @@ func (dht *IpfsDHT) getValueOrPeers(ctx context.Context, p peer.ID,
176 log.Info("Received invalid record! (discarded)")
177 return nil, nil, err
178 }
172 - return record.GetValue(), nil, nil
179 + return record, peers, nil
180 }
181
175 - // Perhaps we were given closer peers
176 - peers := pb.PBPeersToPeerInfos(pmes.GetCloserPeers())
182 if len(peers) > 0 {
183 log.Debug("getValueOrPeers: peers")
184 return nil, peers, nil
@@ -193,7 +198,7 @@ func (dht *IpfsDHT) getValueSingle(ctx context.Context, p peer.ID,
198 }
199
200 // getLocal attempts to retrieve the value from the datastore
196 -func (dht *IpfsDHT) getLocal(key key.Key) ([]byte, error) {
201 +func (dht *IpfsDHT) getLocal(key key.Key) (*pb.Record, error) {
202
203 log.Debug("getLocal %s", key)
204 v, err := dht.datastore.Get(key.DsKey())
@@ -221,7 +226,7 @@ func (dht *IpfsDHT) getLocal(key key.Key) ([]byte, error) {
226 }
227 }
228
224 - return rec.GetValue(), nil
229 + return rec, nil
230 }
231
232 // getOwnPrivateKey attempts to load the local peers private
routing/dht/dht_test.go
+9 -3
@@ -131,8 +131,14 @@ func TestValueGetSet(t *testing.T) {
131 },
132 Sign: false,
133 }
134 + nulsel := func(_ key.Key, bs [][]byte) (int, error) {
135 + return 0, nil
136 + }
137 +
138 dhtA.Validator["v"] = vf
139 dhtB.Validator["v"] = vf
140 + dhtA.Selector["v"] = nulsel
141 + dhtB.Selector["v"] = nulsel
142
143 connect(t, ctx, dhtA, dhtB)
144
@@ -193,7 +199,7 @@ func TestProvides(t *testing.T) {
199 if err != nil {
200 t.Fatal(err)
201 }
196 - if !bytes.Equal(bits, v) {
202 + if !bytes.Equal(bits.GetValue(), v) {
203 t.Fatal("didn't store the right bits (%s, %s)", k, v)
204 }
205 }
@@ -466,7 +472,7 @@ func TestProvidesMany(t *testing.T) {
472 if err != nil {
473 t.Fatal(err)
474 }
469 - if !bytes.Equal(bits, v) {
475 + if !bytes.Equal(bits.GetValue(), v) {
476 t.Fatal("didn't store the right bits (%s, %s)", k, v)
477 }
478
@@ -558,7 +564,7 @@ func TestProvidesAsync(t *testing.T) {
564 }
565
566 bits, err := dhts[3].getLocal(k)
561 - if err != nil && bytes.Equal(bits, val) {
567 + if err != nil && bytes.Equal(bits.GetValue(), val) {
568 t.Fatal(err)
569 }
570
routing/dht/handlers.go
+1 -1
@@ -108,7 +108,7 @@ func (dht *IpfsDHT) handlePutValue(ctx context.Context, p peer.ID, pmes *pb.Mess
108 dskey := key.Key(pmes.GetKey()).DsKey()
109
110 if err := dht.verifyRecordLocally(pmes.GetRecord()); err != nil {
111 - log.Debugf("Bad dht record in PUT from: %s. %s", key.Key(pmes.GetRecord().GetAuthor()), err)
111 + log.Warningf("Bad dht record in PUT from: %s. %s", key.Key(pmes.GetRecord().GetAuthor()), err)
112 return nil, err
113 }
114
routing/dht/routing.go
+84 -18
@@ -1,6 +1,7 @@
1 package dht
2
3 import (
4 + "bytes"
5 "sync"
6
7 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
@@ -76,16 +77,71 @@ func (dht *IpfsDHT) PutValue(ctx context.Context, key key.Key, value []byte) err
77 }
78
79 // GetValue searches for the value corresponding to given Key.
79 -// If the search does not succeed, a multiaddr string of a closer peer is
80 -// returned along with util.ErrSearchIncomplete
80 func (dht *IpfsDHT) GetValue(ctx context.Context, key key.Key) ([]byte, error) {
81 + vals, err := dht.GetValues(ctx, key, 3)
82 + if err != nil {
83 + return nil, err
84 + }
85 +
86 + var recs [][]byte
87 + for _, v := range vals {
88 + recs = append(recs, v.Val)
89 + }
90 +
91 + i, err := dht.Selector.BestRecord(key, recs)
92 + if err != nil {
93 + return nil, err
94 + }
95 +
96 + best := recs[i]
97 + log.Debugf("GetValue %v %v", key, best)
98 + if best == nil {
99 + log.Errorf("GetValue yielded correct record with nil value.")
100 + return nil, routing.ErrNotFound
101 + }
102 +
103 + fixupRec, err := record.MakePutRecord(dht.peerstore.PrivKey(dht.self), key, best, true)
104 + if err != nil {
105 + // probably shouldnt actually 'error' here as we have found a value we like,
106 + // but this call failing probably isnt something we want to ignore
107 + return nil, err
108 + }
109 +
110 + for _, v := range vals {
111 + // if someone sent us a different 'less-valid' record, lets correct them
112 + if !bytes.Equal(v.Val, best) {
113 + go func(v routing.RecvdVal) {
114 + err := dht.putValueToPeer(ctx, v.From, key, fixupRec)
115 + if err != nil {
116 + log.Error("Error correcting DHT entry: ", err)
117 + }
118 + }(v)
119 + }
120 + }
121 +
122 + return best, nil
123 +}
124 +
125 +func (dht *IpfsDHT) GetValues(ctx context.Context, key key.Key, nvals int) ([]routing.RecvdVal, error) {
126 + var vals []routing.RecvdVal
127 + var valslock sync.Mutex
128 +
129 // If we have it local, dont bother doing an RPC!
83 - val, err := dht.getLocal(key)
130 + lrec, err := dht.getLocal(key)
131 if err == nil {
132 + // TODO: this is tricky, we dont always want to trust our own value
133 + // what if the authoritative source updated it?
134 log.Debug("have it locally")
86 - return val, nil
87 - } else {
88 - log.Debug("failed to get value locally: %s", err)
135 + vals = append(vals, routing.RecvdVal{
136 + Val: lrec.GetValue(),
137 + From: dht.self,
138 + })
139 +
140 + if nvals <= 1 {
141 + return vals, nil
142 + }
143 + } else if nvals == 0 {
144 + return nil, err
145 }
146
147 // get closest peers in the routing table
@@ -104,14 +160,26 @@ func (dht *IpfsDHT) GetValue(ctx context.Context, key key.Key) ([]byte, error) {
160 ID: p,
161 })
162
107 - val, peers, err := dht.getValueOrPeers(ctx, p, key)
163 + rec, peers, err := dht.getValueOrPeers(ctx, p, key)
164 if err != nil {
165 return nil, err
166 }
167
112 - res := &dhtQueryResult{value: val, closerPeers: peers}
113 - if val != nil {
114 - res.success = true
168 + res := &dhtQueryResult{closerPeers: peers}
169 +
170 + if rec.GetValue() != nil {
171 + rv := routing.RecvdVal{
172 + Val: rec.GetValue(),
173 + From: p,
174 + }
175 + valslock.Lock()
176 + vals = append(vals, rv)
177 +
178 + // If weve collected enough records, we're done
179 + if len(vals) >= nvals {
180 + res.success = true
181 + }
182 + valslock.Unlock()
183 }
184
185 notif.PublishQueryEvent(parent, &notif.QueryEvent{
@@ -124,17 +192,15 @@ func (dht *IpfsDHT) GetValue(ctx context.Context, key key.Key) ([]byte, error) {
192 })
193
194 // run it!
127 - result, err := query.Run(ctx, rtp)
128 - if err != nil {
129 - return nil, err
195 + _, err = query.Run(ctx, rtp)
196 + if len(vals) == 0 {
197 + if err != nil {
198 + return nil, err
199 + }
200 }
201
132 - log.Debugf("GetValue %v %v", key, result.value)
133 - if result.value == nil {
134 - return nil, routing.ErrNotFound
135 - }
202 + return vals, nil
203
137 - return result.value, nil
204 }
205
206 // Value provider layer of indirection.
routing/mock/centralized_client.go
+15
@@ -44,6 +44,21 @@ func (c *client) GetValue(ctx context.Context, key key.Key) ([]byte, error) {
44 return data, nil
45 }
46
47 +func (c *client) GetValues(ctx context.Context, key key.Key, count int) ([]routing.RecvdVal, error) {
48 + log.Debugf("GetValue: %s", key)
49 + v, err := c.datastore.Get(key.DsKey())
50 + if err != nil {
51 + return nil, err
52 + }
53 +
54 + data, ok := v.([]byte)
55 + if !ok {
56 + return nil, errors.New("could not cast value from datastore")
57 + }
58 +
59 + return []routing.RecvdVal{{Val: data, From: c.peer.ID()}}, nil
60 +}
61 +
62 func (c *client) FindProviders(ctx context.Context, key key.Key) ([]peer.PeerInfo, error) {
63 return c.server.Providers(key), nil
64 }
routing/none/none_client.go
+4
@@ -25,6 +25,10 @@ func (c *nilclient) GetValue(_ context.Context, _ key.Key) ([]byte, error) {
25 return nil, errors.New("Tried GetValue from nil routing.")
26 }
27
28 +func (c *nilclient) GetValues(_ context.Context, _ key.Key, _ int) ([]routing.RecvdVal, error) {
29 + return nil, errors.New("Tried GetValues from nil routing.")
30 +}
31 +
32 func (c *nilclient) FindPeer(_ context.Context, _ peer.ID) (peer.PeerInfo, error) {
33 return peer.PeerInfo{}, nil
34 }
routing/offline/offline.go
+21
@@ -67,6 +67,27 @@ func (c *offlineRouting) GetValue(ctx context.Context, key key.Key) ([]byte, err
67 return rec.GetValue(), nil
68 }
69
70 +func (c *offlineRouting) GetValues(ctx context.Context, key key.Key, _ int) ([]routing.RecvdVal, error) {
71 + v, err := c.datastore.Get(key.DsKey())
72 + if err != nil {
73 + return nil, err
74 + }
75 +
76 + byt, ok := v.([]byte)
77 + if !ok {
78 + return nil, errors.New("value stored in datastore not []byte")
79 + }
80 + rec := new(pb.Record)
81 + err = proto.Unmarshal(byt, rec)
82 + if err != nil {
83 + return nil, err
84 + }
85 +
86 + return []routing.RecvdVal{
87 + {Val: rec.GetValue()},
88 + }, nil
89 +}
90 +
91 func (c *offlineRouting) FindProviders(ctx context.Context, key key.Key) ([]peer.PeerInfo, error) {
92 return nil, ErrOffline
93 }
routing/record/record.go
-2
@@ -6,13 +6,11 @@ import (
6 proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
7
8 key "github.com/ipfs/go-ipfs/blocks/key"
9 - dag "github.com/ipfs/go-ipfs/merkledag"
9 ci "github.com/ipfs/go-ipfs/p2p/crypto"
10 pb "github.com/ipfs/go-ipfs/routing/dht/pb"
11 logging "github.com/ipfs/go-ipfs/vendor/go-log-v1.0.0"
12 )
13
15 -var _ = dag.FetchGraph
14 var log = logging.Logger("routing/record")
15
16 // MakePutRecord creates and signs a dht record for the given key/value pair
routing/record/selection.go new
+40
@@ -0,0 +1,40 @@
1 +package record
2 +
3 +import (
4 + "errors"
5 + "strings"
6 +
7 + key "github.com/ipfs/go-ipfs/blocks/key"
8 +)
9 +
10 +// A SelectorFunc selects the best value for the given key from
11 +// a slice of possible values and returns the index of the chosen one
12 +type SelectorFunc func(key.Key, [][]byte) (int, error)
13 +
14 +type Selector map[string]SelectorFunc
15 +
16 +func (s Selector) BestRecord(k key.Key, recs [][]byte) (int, error) {
17 + if len(recs) == 0 {
18 + return 0, errors.New("no records given!")
19 + }
20 +
21 + parts := strings.Split(string(k), "/")
22 + if len(parts) < 3 {
23 + log.Infof("Record key does not have selectorfunc: %s", k)
24 + return 0, errors.New("record key does not have selectorfunc")
25 + }
26 +
27 + sel, ok := s[parts[1]]
28 + if !ok {
29 + log.Infof("Unrecognized key prefix: %s", parts[1])
30 + return 0, ErrInvalidRecordType
31 + }
32 +
33 + return sel(k, recs)
34 +}
35 +
36 +// PublicKeySelector just selects the first entry.
37 +// All valid public key records will be equivalent.
38 +func PublicKeySelector(k key.Key, vals [][]byte) (int, error) {
39 + return 0, nil
40 +}
routing/routing.go
+19
@@ -26,6 +26,18 @@ type IpfsRouting interface {
26 // GetValue searches for the value corresponding to given Key.
27 GetValue(context.Context, key.Key) ([]byte, error)
28
29 + // GetValues searches for values corresponding to given Key.
30 + //
31 + // Passing a value of '0' for the count argument will cause the
32 + // routing interface to return values only from cached or local storage
33 + // and return an error if no cached value is found.
34 + //
35 + // Passing a value of '1' will return a local value if found, and query
36 + // the network for the first value it finds otherwise.
37 + // As a result, a value of '1' is mostly useful for cases where the record
38 + // in question has only one valid value (such as public keys)
39 + GetValues(c context.Context, k key.Key, count int) ([]RecvdVal, error)
40 +
41 // Value provider layer of indirection.
42 // This is what DSHTs (Coral and MainlineDHT) do to store large values in a DHT.
43
@@ -44,6 +56,13 @@ type IpfsRouting interface {
56 // TODO expose io.Closer or plain-old Close error
57 }
58
59 +// RecvdVal represents a dht value record that has been received from a given peer
60 +// it is used to track peers with expired records in order to correct them.
61 +type RecvdVal struct {
62 + From peer.ID
63 + Val []byte
64 +}
65 +
66 type PubKeyFetcher interface {
67 GetPublicKey(context.Context, peer.ID) (ci.PubKey, error)
68 }
routing/supernode/client.go
+16
@@ -81,6 +81,22 @@ func (c *Client) GetValue(ctx context.Context, k key.Key) ([]byte, error) {
81 return response.Record.GetValue(), nil
82 }
83
84 +func (c *Client) GetValues(ctx context.Context, k key.Key, _ int) ([]routing.RecvdVal, error) {
85 + defer log.EventBegin(ctx, "getValue", &k).Done()
86 + msg := pb.NewMessage(pb.Message_GET_VALUE, string(k), 0)
87 + response, err := c.proxy.SendRequest(ctx, msg) // TODO wrap to hide the remote
88 + if err != nil {
89 + return nil, err
90 + }
91 +
92 + return []routing.RecvdVal{
93 + {
94 + Val: response.Record.GetValue(),
95 + From: c.local,
96 + },
97 + }, nil
98 +}
99 +
100 func (c *Client) Provide(ctx context.Context, k key.Key) error {
101 defer log.EventBegin(ctx, "provide", &k).Done()
102 msg := pb.NewMessage(pb.Message_ADD_PROVIDER, string(k), 0)