回顾一下,前面说到以太坊分布式网络采用了Kademlia协议,它的特点是:
1、采用了二叉树的拓扑结构;
2、每个节点都对整树进行拆分,分成n棵子树;
3、从每棵树中取K个节点,构成“k-桶”,每个节点控制着n个k-桶;
4、节点的距离是通过异或的二进制运算得到的;
5、k桶中的节点不是固定不变的,而是不断刷新变化的。
下面,我们来看看Kademlia协议在以太坊中的具体实现。
一、以太坊的k桶
以太坊的k值是16,也就是说每个k桶包含16个节点,一共256个k桶。K桶中记录了节点的NodeId,distance,endpoint,ip等信息,按照与target节点的距离进行排序。
distance 0:[2^0, 2^1) | node0 | node1 | node2 | ... | node15 |
distance 1:[2^1, 2^2) | node0 | node1 | node2 | ... | node15 |
distance 2:[2^2, 2^3) | node0 | node1 | node2 | ... | node15 |
distance 3:[2^3, 2^4) | node0 | node1 | node2 | ... | node15 |
... | node0 | node1 | node2 | ... | node15 |
distance 255:[2^255, 2^256) | node0 | node1 | node2 | ... | node15 |
这个表在源码里为Table对象(p2p/discover/table.go):
type Table struct {
mutex sync.Mutex // protects buckets, bucket content, nursery, rand
buckets [nBuckets]*bucket // index of known nodes by distance
nursery []*Node // bootstrap nodes
rand *mrand.Rand // source of randomness, periodically reseeded
ips netutil.DistinctNetSet
db *nodeDB // database of known nodes
refreshReq chan chan struct{}
initDone chan struct{}
closeReq chan struct{}
closed chan struct{}
nodeAddedHook func(*Node) // for testing
net transport
self *Node // metadata of the local node
}
这里有几项是比较重要的:
1)buckets 类型是[nBuckets]*bucket,可以看到这是一个数组,一个bucket就是一个K-桶,一共256个bucket;
2)nursery 信任的种子节点,一个节点启动的时候首先最多能够连接35个种子节点,其中5个是由以太坊官方提供的,另外30个是从数据库里取的;
3)db 以太坊中有两个数据库实例,一个是用来储存区块链,另一个用来储存p2p的节点。
4)refreshReq 刷新K桶事件的管道,其他节点或者其他应用场景可以通过这个管道强制刷新该节点的k桶。
二、table对象的相关方法
1、newTable()新建table
task1:根据外部或默认参数初始化Table类
task2:加载种子节点
task3:启动数据库刷新go程
task4:启动事件监听go程
func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string, bootnodes []*Node)
(*Table, error) {
// If no node database was given, use an in-memory one
db, err := newNodeDB(nodeDBPath, nodeDBVersion, ourID)
if err != nil {
return nil, err
}
// 初始化Table类
tab := &Table{
net: t,
d