github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/discover/table.go (about) 1 // Package discover implements the Node Discovery Protocol. 2 // 3 // The Node 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 6 // nodes. 7 package discover 8 9 import ( 10 "net" 11 "sort" 12 "sync" 13 "time" 14 ) 15 16 const ( 17 alpha = 3 // Kademlia concurrency factor 18 bucketSize = 16 // Kademlia bucket size 19 nBuckets = nodeIDBits + 1 // Number of buckets 20 ) 21 22 type Table struct { 23 mutex sync.Mutex // protects buckets, their content, and nursery 24 buckets [nBuckets]*bucket // index of known nodes by distance 25 nursery []*Node // bootstrap nodes 26 27 net transport 28 self *Node // metadata of the local node 29 } 30 31 // transport is implemented by the UDP transport. 32 // it is an interface so we can test without opening lots of UDP 33 // sockets and without generating a private key. 34 type transport interface { 35 ping(*Node) error 36 findnode(e *Node, target NodeID) ([]*Node, error) 37 close() 38 } 39 40 // bucket contains nodes, ordered by their last activity. 41 type bucket struct { 42 lastLookup time.Time 43 entries []*Node 44 } 45 46 func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr) *Table { 47 tab := &Table{net: t, self: newNode(ourID, ourAddr)} 48 for i := range tab.buckets { 49 tab.buckets[i] = new(bucket) 50 } 51 return tab 52 } 53 54 // Self returns the local node ID. 55 func (tab *Table) Self() NodeID { 56 return tab.self.ID 57 } 58 59 // Close terminates the network listener. 60 func (tab *Table) Close() { 61 tab.net.close() 62 } 63 64 // Bootstrap sets the bootstrap nodes. These nodes are used to connect 65 // to the network if the table is empty. Bootstrap will also attempt to 66 // fill the table by performing random lookup operations on the 67 // network. 68 func (tab *Table) Bootstrap(nodes []*Node) { 69 tab.mutex.Lock() 70 // TODO: maybe filter nodes with bad fields (nil, etc.) to avoid strange crashes 71 tab.nursery = make([]*Node, 0, len(nodes)) 72 for _, n := range nodes { 73 cpy := *n 74 tab.nursery = append(tab.nursery, &cpy) 75 } 76 tab.mutex.Unlock() 77 tab.refresh() 78 } 79 80 // Lookup performs a network search for nodes close 81 // to the given target. It approaches the target by querying 82 // nodes that are closer to it on each iteration. 83 func (tab *Table) Lookup(target NodeID) []*Node { 84 var ( 85 asked = make(map[NodeID]bool) 86 seen = make(map[NodeID]bool) 87 reply = make(chan []*Node, alpha) 88 pendingQueries = 0 89 ) 90 // don't query further if we hit the target or ourself. 91 // unlikely to happen often in practice. 92 asked[target] = true 93 asked[tab.self.ID] = true 94 95 tab.mutex.Lock() 96 // update last lookup stamp (for refresh logic) 97 tab.buckets[logdist(tab.self.ID, target)].lastLookup = time.Now() 98 // generate initial result set 99 result := tab.closest(target, bucketSize) 100 tab.mutex.Unlock() 101 102 for { 103 // ask the alpha closest nodes that we haven't asked yet 104 for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { 105 n := result.entries[i] 106 if !asked[n.ID] { 107 asked[n.ID] = true 108 pendingQueries++ 109 go func() { 110 result, _ := tab.net.findnode(n, target) 111 reply <- result 112 }() 113 } 114 } 115 if pendingQueries == 0 { 116 // we have asked all closest nodes, stop the search 117 break 118 } 119 120 // wait for the next reply 121 for _, n := range <-reply { 122 cn := n 123 if !seen[n.ID] { 124 seen[n.ID] = true 125 result.push(cn, bucketSize) 126 } 127 } 128 pendingQueries-- 129 } 130 return result.entries 131 } 132 133 // refresh performs a lookup for a random target to keep buckets full. 134 func (tab *Table) refresh() { 135 ld := -1 // logdist of chosen bucket 136 tab.mutex.Lock() 137 for i, b := range tab.buckets { 138 if i > 0 && b.lastLookup.Before(time.Now().Add(-1*time.Hour)) { 139 ld = i 140 break 141 } 142 } 143 tab.mutex.Unlock() 144 145 result := tab.Lookup(randomID(tab.self.ID, ld)) 146 if len(result) == 0 { 147 // bootstrap the table with a self lookup 148 tab.mutex.Lock() 149 tab.add(tab.nursery) 150 tab.mutex.Unlock() 151 tab.Lookup(tab.self.ID) 152 // TODO: the Kademlia paper says that we're supposed to perform 153 // random lookups in all buckets further away than our closest neighbor. 154 } 155 } 156 157 // closest returns the n nodes in the table that are closest to the 158 // given id. The caller must hold tab.mutex. 159 func (tab *Table) closest(target NodeID, nresults int) *nodesByDistance { 160 // This is a very wasteful way to find the closest nodes but 161 // obviously correct. I believe that tree-based buckets would make 162 // this easier to implement efficiently. 163 close := &nodesByDistance{target: target} 164 for _, b := range tab.buckets { 165 for _, n := range b.entries { 166 close.push(n, nresults) 167 } 168 } 169 return close 170 } 171 172 func (tab *Table) len() (n int) { 173 for _, b := range tab.buckets { 174 n += len(b.entries) 175 } 176 return n 177 } 178 179 // bumpOrAdd updates the activity timestamp for the given node and 180 // attempts to insert the node into a bucket. The returned Node might 181 // not be part of the table. The caller must hold tab.mutex. 182 func (tab *Table) bumpOrAdd(node NodeID, from *net.UDPAddr) (n *Node) { 183 b := tab.buckets[logdist(tab.self.ID, node)] 184 if n = b.bump(node); n == nil { 185 n = newNode(node, from) 186 if len(b.entries) == bucketSize { 187 tab.pingReplace(n, b) 188 } else { 189 b.entries = append(b.entries, n) 190 } 191 } 192 return n 193 } 194 195 func (tab *Table) pingReplace(n *Node, b *bucket) { 196 old := b.entries[bucketSize-1] 197 go func() { 198 if err := tab.net.ping(old); err == nil { 199 // it responded, we don't need to replace it. 200 return 201 } 202 // it didn't respond, replace the node if it is still the oldest node. 203 tab.mutex.Lock() 204 if len(b.entries) > 0 && b.entries[len(b.entries)-1] == old { 205 // slide down other entries and put the new one in front. 206 // TODO: insert in correct position to keep the order 207 copy(b.entries[1:], b.entries) 208 b.entries[0] = n 209 } 210 tab.mutex.Unlock() 211 }() 212 } 213 214 // bump updates the activity timestamp for the given node. 215 // The caller must hold tab.mutex. 216 func (tab *Table) bump(node NodeID) { 217 tab.buckets[logdist(tab.self.ID, node)].bump(node) 218 } 219 220 // add puts the entries into the table if their corresponding 221 // bucket is not full. The caller must hold tab.mutex. 222 func (tab *Table) add(entries []*Node) { 223 outer: 224 for _, n := range entries { 225 if n == nil || n.ID == tab.self.ID { 226 // skip bad entries. The RLP decoder returns nil for empty 227 // input lists. 228 continue 229 } 230 bucket := tab.buckets[logdist(tab.self.ID, n.ID)] 231 for i := range bucket.entries { 232 if bucket.entries[i].ID == n.ID { 233 // already in bucket 234 continue outer 235 } 236 } 237 if len(bucket.entries) < bucketSize { 238 bucket.entries = append(bucket.entries, n) 239 } 240 } 241 } 242 243 func (b *bucket) bump(id NodeID) *Node { 244 for i, n := range b.entries { 245 if n.ID == id { 246 n.active = time.Now() 247 // move it to the front 248 copy(b.entries[1:], b.entries[:i+1]) 249 b.entries[0] = n 250 return n 251 } 252 } 253 return nil 254 } 255 256 // nodesByDistance is a list of nodes, ordered by 257 // distance to target. 258 type nodesByDistance struct { 259 entries []*Node 260 target NodeID 261 } 262 263 // push adds the given node to the list, keeping the total size below maxElems. 264 func (h *nodesByDistance) push(n *Node, maxElems int) { 265 ix := sort.Search(len(h.entries), func(i int) bool { 266 return distcmp(h.target, h.entries[i].ID, n.ID) > 0 267 }) 268 if len(h.entries) < maxElems { 269 h.entries = append(h.entries, n) 270 } 271 if ix == len(h.entries) { 272 // farther away than all nodes we already have. 273 // if there was room for it, the node is now the last element. 274 } else { 275 // slide existing entries down to make room 276 // this will overwrite the entry we just appended. 277 copy(h.entries[ix+1:], h.entries[ix:]) 278 h.entries[ix] = n 279 } 280 }