Skip to content

Commit adad6d0

Browse files
authored
kademlia的简单实现
1 parent 38064ab commit adad6d0

1 file changed

Lines changed: 199 additions & 0 deletions

File tree

ethereum/kademlia.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
对于任意ASCII 字符a, 任意的单ASCII字符与a相同的前缀位(prefix)可以用异或计算, 计算结果对照表如下
2+
3+
prefix位长 | 可能的取值数目 | 异或结果范围
4+
------ | ------ | ------
5+
8 | pow(2,0) | 0
6+
7 | pow(2,0) | 1
7+
6 | pow(2,1) | [2, 3]
8+
5 | pow(2,2) | [4, 7]
9+
4 | pow(2,3) | [8,15]
10+
3 | pow(2,4) | [16, 31]
11+
2 | pow(2,5) | [32, 63]
12+
1 | pow(2,6) | [64, 127]
13+
0 | pow(2,7) | [128, 255]
14+
15+
举个栗子 若 a^b = 5, 对照上表的异或范围a,b 的相同前缀有5个, 验证下 a=248, b=253, a^b =5, bin(a) = "0b1111,1000", bin(b)="0b1111,1101"
16+
17+
对于任意ASCII字符串a, 任意与其长度相同的ASCII字符串与a的前缀(prefix)位长可以通过迭代计算每个字节的累加得出,
18+
19+
我们定义相同长度的ASCII字符串a,b 的距离为a的位长减前缀的位长
20+
+ 若a,b单字节, 则distance(a,b) = 8-length(prefix)
21+
+ 若a,b是相同长度的字符串, 则distance(a, b) = length(a)*8 - length(prefix)
22+
算法实现如下(golang版)
23+
24+
```python
25+
func distanceInByte(x uint8) uint {
26+
switch {
27+
case x == 0:
28+
return 8
29+
case x == 1:
30+
return 7
31+
case x >= 2 && x <= 3:
32+
return 6
33+
case x >= 4 && x <= 7:
34+
return 5
35+
case x >= 8 && x <= 15:
36+
return 4
37+
case x >= 16 && x <= 31:
38+
return 3
39+
case x >= 32 && x <= 63:
40+
return 2
41+
case x >= 64 && x <= 127:
42+
return 1
43+
case x >= 128 && x <= 255:
44+
return 0
45+
}
46+
return 0
47+
}
48+
49+
func calcDistance(a, b []byte) uint {
50+
c := uint(0)
51+
for i := 0; i < len(a) && i < len(b); i++ {
52+
x := a[i] ^ b[i]
53+
if x == 0 {
54+
c += 8
55+
} else {
56+
c += distanceInByte(x)
57+
break
58+
}
59+
}
60+
return uint(len(a))*8 - c
61+
62+
}
63+
```
64+
65+
Kademlia算法作为路由算法来发现节点或者Topic的基本步骤分为三步
66+
1: 注册 Topic (可以是自己的ID或者想要分享给别人的某篇文章的关键字的hash值)
67+
2: 广播 topic table, 同时保存接收到的table
68+
3: 处理搜索请求(接受),
69+
4: 返回找到目标topic对应的IP, 或者与目标topic 距离最近的几个点
70+
下面给出个简易的实现
71+
72+
```python
73+
const (
74+
EntriesLength = 256
75+
NodesLengthPerEntry = 20
76+
ReplacementesLengthPerEntry = 20
77+
TopicMaxIdleTime = 10 * time.Second
78+
EntryGCInterval = 20 * time.Second
79+
)
80+
type Topic [32]byte
81+
type Node struct {
82+
topic Topic // topic, nodeId.....
83+
data []byte // 可以是ip或者其他的任何东西
84+
weight uint // topic 的权重, 最简单的可以通过计算与目标机的链接时间得出
85+
lastUsed time.Time // slot满的时候用来删除
86+
}
87+
type Nodes []*Node // 按照weight排序的list需要实现heap.Interface
88+
//implement heap interface, meth: Push/Pop/Less/Swap/Len
89+
90+
type entry struct {
91+
nodes *Nodes
92+
replacementes *Nodes
93+
lastGCTime time.Time
94+
}
95+
96+
type Table struct {
97+
topic Topic //nodeID
98+
entries []*entry // topic table , length等于最远距离, 通过计算接受的topic与自己的topic 计算距离, 最远距离为8 * len(topic)
99+
}
100+
101+
func NewTable(t Topic) *Table {
102+
// initialize
103+
}
104+
105+
func (tbl *Table) Add(t Topic, weight uint, data []byte) {
106+
dis := calcDistance(tbl.topic, t)
107+
tbl.entries[dis].add(t, weight, data)
108+
}
109+
110+
func (e *entry) add(t Topic, weight uint, data []byte) {
111+
n := new(Node)
112+
n.data = data
113+
n.topic = t
114+
n.lastUsed = time.Now()
115+
n.weight = weight
116+
if e.nodes.Len() < NodesLengthPerEntry {
117+
heap.Push(e.nodes, n)
118+
return
119+
}
120+
if e.replacementes.Len() > 0 {
121+
if (*(e.nodes))[0].weight < weight {
122+
nn := heap.Pop(e.nodes)
123+
heap.Push(e.nodes, n)
124+
n = nn.(*Node)
125+
}
126+
}
127+
heap.Push(e.replacementes, n)
128+
if e.replacementes.Len() == ReplacementesLengthPerEntry {
129+
heap.Pop(e.replacementes)
130+
}
131+
go e.gc()
132+
}
133+
134+
func (e *entry) gc() {
135+
if e.nodes.Len() < NodesLengthPerEntry {
136+
return
137+
}
138+
if e.lastGCTime.Add(EntryGCInterval).Before(time.Now()) {
139+
return
140+
}
141+
e.lastGCTime = time.Now()
142+
nodes := new(Nodes)
143+
for _, t := range *(e.nodes) {
144+
if t.lastUsed.Add(TopicMaxIdleTime).Before(time.Now()) {
145+
continue
146+
}
147+
heap.Push(nodes, t)
148+
}
149+
copy((*e.nodes)[:], (*nodes)[:])
150+
nodes = new(Nodes)
151+
if e.nodes.Len() < NodesLengthPerEntry {
152+
for _, t := range *(e.replacementes) {
153+
if t.lastUsed.Add(TopicMaxIdleTime).Before(time.Now()) {
154+
continue
155+
}
156+
if e.nodes.Len() < NodesLengthPerEntry {
157+
heap.Push(e.nodes, t)
158+
continue
159+
}
160+
heap.Push(nodes, t)
161+
}
162+
e.replacementes = nodes
163+
}
164+
}
165+
166+
func distcmp(target, a, b Topic) int {
167+
for i := range target {
168+
da := a[i] ^ target[i]
169+
db := b[i] ^ target[i]
170+
if da > db {
171+
return 1
172+
} else if da < db {
173+
return -1
174+
}
175+
}
176+
return 0
177+
}
178+
179+
type distance struct {
180+
entries []*Node
181+
target Topic
182+
}
183+
184+
func (d *distance) push(n *Node, maxElems int) {
185+
ix := sort.Search(len(d.entries), func(i int) bool {
186+
return distcmp(d.target, d.entries[i].topic, n.topic) > 0
187+
})
188+
if len(d.entries) < maxElems {
189+
d.entries = append(d.entries, n)
190+
}
191+
if ix < len(d.entries) {
192+
copy(d.entries[ix+1:], d.entries[ix:])
193+
d.entries[ix] = n
194+
}
195+
}
196+
197+
```
198+
199+

0 commit comments

Comments
 (0)