go libp2p kad的value可用于存放任意数据,目前kad默认在value中存放了ipns和pk这两种数据(pk也是为ipns服务的,详情请阅读我的另一篇博文《ipns实现机制解读》)。
Record
Record proto:
// Record represents a dht record that contains a value
// for a key value pair
type Record struct {
// The key that references this record
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// The actual value this record is storing
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
// Time the record was received, set by receiver
TimeReceived string `protobuf:"bytes,5,opt,name=timeReceived,proto3" json:"timeReceived,omitempty"`
}
具体到ipns,key形式为为/ipns/{pid}
// RecordKey returns the libp2p record key for a given peer ID.
func RecordKey(pid peer.ID) string {
return "/ipns/" + string(pid)
}
handlePutValue处理接收到的record,在保存之前,会先检查它,具体步骤如下:
- validate:检查record是否有效
- select:比较收到的record和本地保存的record的新旧
- 保存:将TimeReceived设置为当前的时间,然后保存
// Store a value in this peer local storage
func (dht *IpfsDHT) handlePutValue(ctx context.Context, p peer.ID, pmes *pb.Message) (_ *pb.Message, err error) {
if len(pmes.GetKey()) == 0 {
return nil, errors.New("handleGetValue but no key was provided")
}
rec := pmes.GetRecord()
if rec == nil {
logger.Debugw("got nil record from", "from", p)
return nil, errors.New("nil record")
}
if !bytes.Equal(pmes.GetKey(), rec.GetKey()) {
return nil, errors.New("put key doesn't match record key")
}
cleanRecord(rec)
// Make sure the record is valid (not expired, valid signature etc)
if err = dht.Validator.Validate(string(rec.GetKey()), rec.GetValue()); err != nil {
logger.Infow("bad dht record in PUT", "from", p, "key", loggableRecordKeyBytes(rec.GetKey()), "error", err)
return nil, err
}
dskey := convertToDsKey(rec.GetKey())
// fetch the striped lock for this key
var indexForLock byte
if len(rec.GetKey()) == 0 {
indexForLock = 0
} else {
indexForLock = rec.GetKey()[len(rec.GetKey())-1]
}
lk := &dht.stripedPutLocks[indexForLock]
lk.Lock()
defer lk.Unlock()
// Make sure the new record is "better" than the record we have locally.
// This prevents a record with for example a lower sequence number from
// overwriting a record with a higher sequence number.
existing, err := dht.getRecordFromDatastore(dskey)
if err != nil {
return nil, err
}
if existing != nil {
recs := [][]byte{rec.GetValue(), existing.GetValue()}
i, err := dht.Validator.Select(string(rec.GetKey()), recs)
if err != nil {
logger.Warnw("dht record passed validation but failed select", "from", p, "key", loggableRecordKeyBytes(rec.GetKey()), "error", err)
return nil, err
}
if i != 0 {
logger.Infow("DHT record in PUT older than existing record (ignoring)", "peer", p, "key", loggableRecordKeyBytes(rec.GetKey()))
return nil, errors.New("old record")
}
}
// record the time we receive every record
rec.TimeReceived = u.FormatRFC3339(time.Now())
data, err := proto.Marshal(rec)
if err != nil {
return nil, err
}
err = dht.datastore.Put(dskey, data)
return pmes, err
}
保存时,将record key的base32,作为key保存到datastore。
// putLocal stores the key value pair in the datastore
func (dht *IpfsDHT) putLocal(key string, rec *recpb.Record) error {
data, err := proto.Marshal(rec)
if err != nil {
logger.Warnw("failed to put marshal record for local put", "error", err, "key", loggableRecordKeyString(key))
return err
}
return dht.datastore.Put(mkDsKey(key), data)
}
Validator
kad使用Validator验证value的有效性。
// Validator is an interface that should be implemented by record validators.
type Validator interface {
// Validate validates the given record, returning an error if it's
// invalid (e.g., expired, signed by the wrong key, etc.).
Validate(key string, value []byte) error
// Select selects the best record from the set of records (e.g., the
// newest).
//
// Decisions made by select should be stable.
Select(key string, values [][]byte) (int, error)
}
目前Validator主要有两种实现:
ipns::Validator
PublicKeyValidator
如前所述,pk是为ipns服务的,它的存在有些历史原因,新版的ipfs已经不需要它了,为简单起见,下面只讨论ipns。
IpnsEntry是ipns记录的proto结构。
type IpnsEntry struct {
Value []byte `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
Signature []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
ValidityType *IpnsEntry_ValidityType `protobuf:"varint,3,opt,name=validityType,enum=ipns.pb.IpnsEntry_ValidityType" json:"validityType,omitempty"`
Validity []byte `protobuf:"bytes,4,opt,name=validity" json:"validity,omitempty"`
Sequence *uint64 `protobuf:"varint,5,opt,name=sequence" json:"sequence,omitempty"`
Ttl *uint64 `protobuf:"varint,6,opt,name=ttl" json:"ttl,omitempty"`
// in order for nodes to properly validate a record upon receipt, they need the public
// key associated with it. For old RSA keys, its easiest if we just send this as part of
// the record itself. For newer ed25519 keys, the public key can be embedded in the
// peerID, making this field unnecessary.
PubKey []byte `protobuf:"bytes,7,opt,name=pubKey" json:"pubKey,omitempty"`
}
Validator 用于校验ipns记录,keyBook主要用于在peerstore里面(通过peerID)查找公钥,如果节点的公私钥是由ed25519生成的,则可以直接通过peerID反推出公钥。
// Validator is an IPNS record validator that satisfies the libp2p record
// validator interface.
type Validator struct {
// KeyBook, if non-nil, will be used to lookup keys for validating IPNS
// records.
KeyBook pstore.KeyBook
}
Validate用于检查ipns记录的有效性,具体是先检查签名,后检查有效时间。
// Validate validates an IPNS record.
func (v Validator) Validate(key string, value []byte) error {
ns, pidString, err := record.SplitKey(key)
if err != nil || ns != "ipns" {
return ErrInvalidPath
}
// Parse the value into an IpnsEntry
entry := new(pb.IpnsEntry)
err = proto.Unmarshal(value, entry)
if err != nil {
return ErrBadRecord
}
// Get the public key defined by the ipns path
pid, err := peer.IDFromString(pidString)
if err != nil {
log.Debugf("failed to parse ipns record key %s into peer ID", pidString)
return ErrKeyFormat
}
pubk, err := v.getPublicKey(pid, entry)
if err != nil {
return err
}
return Validate(pubk, entry)
}
// Validates validates the given IPNS entry against the given public key.
func Validate(pk ic.PubKey, entry *pb.IpnsEntry) error {
// Check the ipns record signature with the public key
if ok, err := pk.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok {
return ErrSignature
}
eol, err := GetEOL(entry)
if err != nil {
return err
}
if time.Now().After(eol) {
return ErrExpiredRecord
}
return nil
}
Select用于挑选最佳的ipns记录,具体步骤是:
- 比较sequence,sequence越大的记录越新
- 比较validity,validity越大的记录越新
- 比较value,字节码越大的记录越新
// Select selects the best record by checking which has the highest sequence
// number and latest EOL.
//
// This function returns an error if any of the records fail to parse. Validate
// your records first!
func (v Validator) Select(k string, vals [][]byte) (int, error) {
var recs []*pb.IpnsEntry
for _, v := range vals {
e := new(pb.IpnsEntry)
if err := proto.Unmarshal(v, e); err != nil {
return -1, err
}
recs = append(recs, e)
}
return selectRecord(recs, vals)
}
func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
switch len(recs) {
case 0:
return -1, errors.New("no usable records in given set")
case 1:
return 0, nil
}
var i int
for j := 1; j < len(recs); j++ {
cmp, err := Compare(recs[i], recs[j])
if err != nil {
return -1, err
}
if cmp == 0 {
cmp = bytes.Compare(vals[i], vals[j])
}
if cmp < 0 {
i = j
}
}
return i, nil
}
// Compare compares two IPNS entries. It returns:
//
// * -1 if a is older than b
// * 0 if a and b cannot be ordered (this doesn't mean that they are equal)
// * +1 if a is newer than b
func Compare(a, b *pb.IpnsEntry) (int, error) {
as := a.GetSequence()
bs := b.GetSequence()
if as > bs {
return 1, nil
} else if as < bs {
return -1, nil
}
at, err := u.ParseRFC3339(string(a.GetValidity()))
if err != nil {
return 0, err
}
bt, err := u.ParseRFC3339(string(b.GetValidity()))
if err != nil {
return 0, err
}
if at.After(bt) {
return 1, nil
} else if bt.After(at) {
return -1, nil
}
return 0, nil
}