1 // Package discv5 implements the RLPx v5 Topic Discovery Protocol.
3 // The Topic Discovery protocol provides a way to find RLPx nodes that
4 // can be connected to. It uses a Kademlia-like protocol to maintain a
5 // distributed database of the IDs and endpoints of all listening
16 "github.com/vapor/common"
17 "github.com/vapor/crypto"
21 alpha = 3 // Kademlia concurrency factor
22 bucketSize = 16 // Kademlia bucket size
23 hashBits = len(common.Hash{}) * 8
24 nBuckets = hashBits + 1 // Number of buckets
26 maxBondingPingPongs = 16
27 maxFindnodeFailures = 5
31 count int // number of nodes
32 buckets [nBuckets]*bucket // index of known nodes by distance
33 nodeAddedHook func(*Node) // for testing
34 self *Node // metadata of the local node
37 // bucket contains nodes, ordered by their last activity. the entry
38 // that was most recently active is the first element in entries.
44 func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table {
45 self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port))
46 tab := &Table{self: self}
47 for i := range tab.buckets {
48 tab.buckets[i] = new(bucket)
53 const printTable = false
55 // chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia
56 // buckets filled with live connections and keep the network topology healthy.
57 // This requires selecting addresses closer to our own with a higher probability
58 // in order to refresh closer buckets too.
60 // This algorithm approximates the distance distribution of existing nodes in the
61 // table by selecting a random node from the table and selecting a target address
62 // with a distance less than twice of that of the selected node.
63 // This algorithm will be improved later to specifically target the least recently
65 func (tab *Table) chooseBucketRefreshTarget() common.Hash {
69 fmt.Println("self ", "id:", tab.self.ID, " hex:", crypto.Sha256Hash(tab.self.ID[:]).Hex())
71 for i, b := range &tab.buckets {
72 entries += len(b.entries)
74 for _, e := range b.entries {
75 fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex())
80 prefix := binary.BigEndian.Uint64(tab.self.sha[0:8])
82 entry := int(randUint(uint32(entries + 1)))
83 for _, b := range &tab.buckets {
84 if entry < len(b.entries) {
86 dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix
89 entry -= len(b.entries)
96 targetPrefix := prefix ^ randUint64n(ddist)
98 var target common.Hash
99 binary.BigEndian.PutUint64(target[0:8], targetPrefix)
100 rand.Read(target[8:])
104 // readRandomNodes fills the given slice with random nodes from the
105 // table. It will not write the same node more than once. The nodes in
106 // the slice are copies and can be modified by the caller.
107 func (tab *Table) readRandomNodes(buf []*Node) (n int) {
108 // TODO: tree-based buckets would help here
109 // Find all non-empty buckets and get a fresh slice of their entries.
110 var buckets [][]*Node
111 for _, b := range &tab.buckets {
112 if len(b.entries) > 0 {
113 buckets = append(buckets, b.entries[:])
116 if len(buckets) == 0 {
119 // Shuffle the buckets.
120 for i := uint32(len(buckets)) - 1; i > 0; i-- {
122 buckets[i], buckets[j] = buckets[j], buckets[i]
124 // Move head of each bucket into buf, removing buckets that become empty.
126 for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
131 buckets = append(buckets[:j], buckets[j+1:]...)
133 if len(buckets) == 0 {
141 func randUint(max uint32) uint32 {
147 return binary.BigEndian.Uint32(b[:]) % max
150 func randUint64n(max uint64) uint64 {
156 return binary.BigEndian.Uint64(b[:]) % max
159 // closest returns the n nodes in the table that are closest to the
160 // given id. The caller must hold tab.mutex.
161 func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance {
162 // This is a very wasteful way to find the closest nodes but
163 // obviously correct. I believe that tree-based buckets would make
164 // this easier to implement efficiently.
165 closest := &nodesByDistance{target: target}
166 for _, b := range &tab.buckets {
167 for _, n := range b.entries {
168 closest.push(n, nresults)
174 // add attempts to add the given node its corresponding bucket. If the
175 // bucket has space available, adding the node succeeds immediately.
176 // Otherwise, the node is added to the replacement cache for the bucket.
177 func (tab *Table) add(n *Node) (contested *Node) {
178 //fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex())
179 if n.ID == tab.self.ID {
182 b := tab.buckets[logdist(tab.self.sha, n.sha)]
187 case len(b.entries) < bucketSize:
188 // b has space available.
191 if tab.nodeAddedHook != nil {
196 // b has no space left, add to replacement cache
197 // and revalidate the last entry.
198 tab.deleteFromReplacement(b, n)
199 b.replacements = append(b.replacements, n)
200 if len(b.replacements) > bucketSize {
201 copy(b.replacements, b.replacements[1:])
202 b.replacements = b.replacements[:len(b.replacements)-1]
204 return b.entries[len(b.entries)-1]
208 // stuff adds nodes the table to the end of their corresponding bucket
209 // if the bucket is not full.
210 func (tab *Table) stuff(nodes []*Node) {
212 for _, n := range nodes {
213 if n.ID == tab.self.ID {
214 continue // don't add self
216 bucket := tab.buckets[logdist(tab.self.sha, n.sha)]
217 for i := range bucket.entries {
218 if bucket.entries[i].ID == n.ID {
219 continue outer // already in bucket
222 if len(bucket.entries) < bucketSize {
223 bucket.entries = append(bucket.entries, n)
225 if tab.nodeAddedHook != nil {
232 func (tab *Table) deleteFromReplacement(bucket *bucket, node *Node) {
233 for i := 0; i < len(bucket.replacements); {
234 if bucket.replacements[i].ID == node.ID {
235 bucket.replacements = append(bucket.replacements[:i], bucket.replacements[i+1:]...)
242 // delete removes an entry from the node table (used to evacuate
243 // failed/non-bonded discovery peers).
244 func (tab *Table) delete(node *Node) {
245 //fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex())
246 bucket := tab.buckets[logdist(tab.self.sha, node.sha)]
247 for i := range bucket.entries {
248 if bucket.entries[i].ID == node.ID {
249 bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...)
255 tab.deleteFromReplacement(bucket, node)
258 func (tab *Table) deleteReplace(node *Node) {
259 b := tab.buckets[logdist(tab.self.sha, node.sha)]
261 for i < len(b.entries) {
262 if b.entries[i].ID == node.ID {
263 b.entries = append(b.entries[:i], b.entries[i+1:]...)
270 tab.deleteFromReplacement(b, node)
271 // refill from replacement cache
272 // TODO: maybe use random index
273 if len(b.entries) < bucketSize && len(b.replacements) > 0 {
274 ri := len(b.replacements) - 1
275 b.addFront(b.replacements[ri])
277 b.replacements[ri] = nil
278 b.replacements = b.replacements[:ri]
282 func (b *bucket) addFront(n *Node) {
283 b.entries = append(b.entries, nil)
284 copy(b.entries[1:], b.entries)
288 func (b *bucket) bump(n *Node) bool {
289 for i := range b.entries {
290 if b.entries[i].ID == n.ID {
291 // move it to the front
292 copy(b.entries[1:], b.entries[:i])
300 // nodesByDistance is a list of nodes, ordered by
301 // distance to target.
302 type nodesByDistance struct {
307 // push adds the given node to the list, keeping the total size below maxElems.
308 func (h *nodesByDistance) push(n *Node, maxElems int) {
309 ix := sort.Search(len(h.entries), func(i int) bool {
310 return distcmp(h.target, h.entries[i].sha, n.sha) > 0
312 if len(h.entries) < maxElems {
313 h.entries = append(h.entries, n)
315 if ix == len(h.entries) {
316 // farther away than all nodes we already have.
317 // if there was room for it, the node is now the last element.
319 // slide existing entries down to make room
320 // this will overwrite the entry we just appended.
321 copy(h.entries[ix+1:], h.entries[ix:])