//encPubkey取hash func (e encPubkey) id() enode.ID { return enode.ID(crypto.Keccak256Hash(e[:])) } //pubkey -> encpubkey func encodePubkey(key *ecdsa.PublicKey) encPubkey { var e encPubkey math.ReadBits(key.X, e[:len(e)/2]) math.ReadBits(key.Y, e[len(e)/2:]) return e }
桶的基本结构
1 2 3 4 5
type Table struct { mutex sync.Mutex // protects buckets, bucket content, nursery, rand buckets [nBuckets]*bucket // index of known nodes by distance ... }
1 2 3 4 5 6 7
// bucket contains nodes, ordered by their last activity. the entry // that was most recently active is the first element in entries. type bucket struct { entries []*node // live entries, sorted by time of last contact replacements []*node // recently seen nodes to be used if revalidation fails ips netutil.DistinctNetSet }
//增。如果对应的桶b, b.entries的长度没超过最大数量的话,就增加到entries中,否则增加到replacements中 func (tab *Table) add(n *node) { if n.ID() == tab.self.ID() { return } tab.mutex.Lock() defer tab.mutex.Unlock() b := tab.bucket(n.ID()) if !tab.bumpOrAdd(b, n) { // Node is not in table. Add it to the replacement list. tab.addReplacement(b, n) } }
//查,查呢是返回所有桶中距离target最近的nresults个节点 func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance { // This is a very wasteful way to find the closest nodes but // obviously correct. I believe that tree-based buckets would make // this easier to implement efficiently. close := &nodesByDistance{target: target} for _, b := range &tab.buckets { for _, n := range b.entries { close.push(n, nresults) } } return close }
//通过自己为target去找neighbor,前提是需要自己有secp256k1字段,v4版本的node里是含有secp256k1的,即v4版本是满足这个if条件。load后key中的值为node的公钥 var key ecdsa.PublicKey if err := tab.self.Load((*enode.Secp256k1)(&key)); err == nil { tab.lookup(encodePubkey(&key), false) }
//⭐️通过随机target去找neighbor。这里的疑问是注释中提到为什么不使用kad本来刷新的方式是因为findnode taget是512bit,这里为什么是512bit? (是因为findnode target是公钥,公钥长度是64byte/512bit) 后半句说不容易生成一个属于所选桶的sha3前镜像,kad本来刷新的方式是选择最近最少使用的桶,所以这里的意思是知道桶了,但是node的ID是经过hash生成的,hash的范围确定,但倒回去找ID就很复杂的意思吗 // The Kademlia paper specifies that the bucket refresh should // perform a lookup in the least recently used bucket. We cannot // adhere to this because the findnode target is a 512bit value // (not hash-sized) and it is not easily possible to generate a // sha3 preimage that falls into a chosen bucket. // We perform a few lookups with a random target instead. for i := 0; i < 3; i++ { var target encPubkey crand.Read(target[:]) tab.lookup(target, false) } }
//将现有的k桶节点们填充到result中 for { tab.mutex.Lock() //从自己的所有桶中找出离target最近的bucketSize个节点 result = tab.closest(target, bucketSize) tab.mutex.Unlock() //如果桶中有节点或者refreshIfEmpty为false,跳出循环 if len(result.entries) > 0 || !refreshIfEmpty { break } //如果k桶中无节点,则请求刷新(doRefresh -> 从种子节点中出发..) <-tab.refresh() //请求一次刷新,将refreshIfEmpty置为false,防止一直无节点,一直在这里循环,相当于这个for循环最多只执行两次 refreshIfEmpty = false }
for { //向近的节点询问节点,询问过的就不再询问,并且每次询问的节点数<alpha,即每次只询问最近的alpha个节点。之前一直卡在为什么外层要for循环,难道不是每次问的都是相同的吗?还真的不是..要是相同的话,pendingQueries的值就不会++,这个询问for循环就不会再进行了,然后等回答完毕这个方法就完毕了。之所以桶里的前alpha个会不一样,是因为在后面从reply中读出的为问到的节点们,然后添加到本桶里的时候会影响整个排序,result中的应该是最近的就在前面。所以这个就是不断问到新的节点后,不断查询到离自己比较近的节点。到没有最近的节点后,就不再询问。 for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { n := result.entries[i] if !asked[n.ID()] { asked[n.ID()] = true pendingQueries++ //向节点n询问targetKey的节点们 go tab.findnode(n, targetKey, reply) } } //所有人都回复了,就不再询问了 if pendingQueries == 0 { // we have asked all closest nodes, stop the search break }
//<-reply是最迷惑的,这个是每次从reply中读出一个回复,一个回复为一个节点回复的他的桶节点们,然后range它的桶节点,添加到本桶中 // wait for the next reply for _, n := range <-reply { if n != nil && !seen[n.ID()] { seen[n.ID()] = true //result.push方法,result中数组的长度为bucketSize,且按离target的距离排序,有点像距离最“近”堆排序.. result.push(n, bucketSize) } } //有过一次回复,pendingQueries递减 pendingQueries-- }
func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { // If we haven't seen a ping from the destination node for a while, it won't remember // our endpoint proof and reject findnode. Solicit a ping first. if time.Since(t.db.LastPingReceived(toid)) > bondExpiration { t.ping(toid, toaddr) t.waitping(toid) }