github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/p2p/discover/dht/topic.go (about) 1 package dht 2 3 import ( 4 "container/heap" 5 "fmt" 6 "math" 7 "math/rand" 8 "time" 9 10 log "github.com/sirupsen/logrus" 11 ) 12 13 const ( 14 maxEntries = 10000 15 maxEntriesPerTopic = 50 16 17 fallbackRegistrationExpiry = 1 * time.Hour 18 ) 19 20 type Topic string 21 22 type AbsTime time.Duration // absolute monotonic time 23 24 func Now() AbsTime { 25 return AbsTime(uint64(time.Now().UnixNano())) 26 } 27 28 type topicEntry struct { 29 topic Topic 30 fifoIdx uint64 31 node *Node 32 expire AbsTime 33 } 34 35 type topicInfo struct { 36 entries map[uint64]*topicEntry 37 fifoHead, fifoTail uint64 38 rqItem *topicRequestQueueItem 39 wcl waitControlLoop 40 } 41 42 // removes tail element from the fifo 43 func (t *topicInfo) getFifoTail() *topicEntry { 44 for t.entries[t.fifoTail] == nil { 45 t.fifoTail++ 46 } 47 tail := t.entries[t.fifoTail] 48 t.fifoTail++ 49 return tail 50 } 51 52 type nodeInfo struct { 53 entries map[Topic]*topicEntry 54 lastIssuedTicket, lastUsedTicket uint32 55 // you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time) 56 noRegUntil AbsTime 57 } 58 59 type topicTable struct { 60 db *nodeDB 61 self *Node 62 nodes map[*Node]*nodeInfo 63 topics map[Topic]*topicInfo 64 globalEntries uint64 65 requested topicRequestQueue 66 requestCnt uint64 67 lastGarbageCollection AbsTime 68 } 69 70 func newTopicTable(db *nodeDB, self *Node) *topicTable { 71 if printTestImgLogs { 72 fmt.Printf("*N %016x\n", self.sha[:8]) 73 } 74 return &topicTable{ 75 db: db, 76 nodes: make(map[*Node]*nodeInfo), 77 topics: make(map[Topic]*topicInfo), 78 self: self, 79 } 80 } 81 82 func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo { 83 ti := t.topics[topic] 84 if ti == nil { 85 rqItem := &topicRequestQueueItem{ 86 topic: topic, 87 priority: t.requestCnt, 88 } 89 ti = &topicInfo{ 90 entries: make(map[uint64]*topicEntry), 91 rqItem: rqItem, 92 } 93 t.topics[topic] = ti 94 heap.Push(&t.requested, rqItem) 95 } 96 return ti 97 } 98 99 func (t *topicTable) checkDeleteTopic(topic Topic) { 100 ti := t.topics[topic] 101 if ti == nil { 102 return 103 } 104 if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() { 105 delete(t.topics, topic) 106 heap.Remove(&t.requested, ti.rqItem.index) 107 } 108 } 109 110 func (t *topicTable) getOrNewNode(node *Node) *nodeInfo { 111 n := t.nodes[node] 112 if n == nil { 113 //fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) 114 var issued, used uint32 115 if t.db != nil { 116 issued, used = t.db.fetchTopicRegTickets(node.ID) 117 } 118 n = &nodeInfo{ 119 entries: make(map[Topic]*topicEntry), 120 lastIssuedTicket: issued, 121 lastUsedTicket: used, 122 } 123 t.nodes[node] = n 124 } 125 return n 126 } 127 128 func (t *topicTable) checkDeleteNode(node *Node) { 129 if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < Now() { 130 //fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) 131 delete(t.nodes, node) 132 } 133 } 134 135 func (t *topicTable) storeTicketCounters(node *Node) { 136 n := t.getOrNewNode(node) 137 if t.db != nil { 138 t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket) 139 } 140 } 141 142 func (t *topicTable) getEntries(topic Topic) []*Node { 143 t.collectGarbage() 144 145 te := t.topics[topic] 146 if te == nil { 147 return nil 148 } 149 nodes := make([]*Node, len(te.entries)) 150 i := 0 151 for _, e := range te.entries { 152 nodes[i] = e.node 153 i++ 154 } 155 t.requestCnt++ 156 t.requested.update(te.rqItem, t.requestCnt) 157 return nodes 158 } 159 160 func (t *topicTable) addEntry(node *Node, topic Topic) { 161 n := t.getOrNewNode(node) 162 // clear previous entries by the same node 163 for _, e := range n.entries { 164 t.deleteEntry(e) 165 } 166 // *** 167 n = t.getOrNewNode(node) 168 169 tm := Now() 170 te := t.getOrNewTopic(topic) 171 172 if len(te.entries) == maxEntriesPerTopic { 173 t.deleteEntry(te.getFifoTail()) 174 } 175 176 if t.globalEntries == maxEntries { 177 t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil 178 } 179 180 fifoIdx := te.fifoHead 181 te.fifoHead++ 182 entry := &topicEntry{ 183 topic: topic, 184 fifoIdx: fifoIdx, 185 node: node, 186 expire: tm + AbsTime(fallbackRegistrationExpiry), 187 } 188 if printTestImgLogs { 189 fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8]) 190 } 191 te.entries[fifoIdx] = entry 192 n.entries[topic] = entry 193 t.globalEntries++ 194 te.wcl.registered(tm) 195 } 196 197 // removes least requested element from the fifo 198 func (t *topicTable) leastRequested() *topicEntry { 199 for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil { 200 heap.Pop(&t.requested) 201 } 202 if t.requested.Len() == 0 { 203 return nil 204 } 205 return t.topics[t.requested[0].topic].getFifoTail() 206 } 207 208 // entry should exist 209 func (t *topicTable) deleteEntry(e *topicEntry) { 210 if printTestImgLogs { 211 fmt.Printf("*- %d %v %016x %016x\n", Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8]) 212 } 213 ne := t.nodes[e.node].entries 214 delete(ne, e.topic) 215 if len(ne) == 0 { 216 t.checkDeleteNode(e.node) 217 } 218 te := t.topics[e.topic] 219 delete(te.entries, e.fifoIdx) 220 if len(te.entries) == 0 { 221 t.checkDeleteTopic(e.topic) 222 } 223 t.globalEntries-- 224 } 225 226 // It is assumed that topics and waitPeriods have the same length. 227 func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) { 228 log.WithFields(log.Fields{"module": logModule, "serial": serialNo, "topics": topics, "waits": waitPeriods}).Debug("Using discovery ticket") 229 t.collectGarbage() 230 n := t.getOrNewNode(node) 231 if serialNo < n.lastUsedTicket { 232 return false 233 } 234 235 tm := Now() 236 if serialNo > n.lastUsedTicket && tm < n.noRegUntil { 237 return false 238 } 239 if serialNo != n.lastUsedTicket { 240 n.lastUsedTicket = serialNo 241 n.noRegUntil = tm + AbsTime(noRegTimeout()) 242 t.storeTicketCounters(node) 243 } 244 245 currTime := uint64(tm / AbsTime(time.Second)) 246 regTime := issueTime + uint64(waitPeriods[idx]) 247 relTime := int64(currTime - regTime) 248 if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends 249 if e := n.entries[topics[idx]]; e == nil { 250 t.addEntry(node, topics[idx]) 251 } else { 252 // if there is an active entry, don't move to the front of the FIFO but prolong expire time 253 e.expire = tm + AbsTime(fallbackRegistrationExpiry) 254 } 255 return true 256 } 257 258 return false 259 } 260 261 func (topictab *topicTable) getTicket(node *Node, topics []Topic) *ticket { 262 topictab.collectGarbage() 263 264 now := Now() 265 n := topictab.getOrNewNode(node) 266 n.lastIssuedTicket++ 267 topictab.storeTicketCounters(node) 268 269 t := &ticket{ 270 issueTime: now, 271 topics: topics, 272 serial: n.lastIssuedTicket, 273 regTime: make([]AbsTime, len(topics)), 274 } 275 for i, topic := range topics { 276 var waitPeriod time.Duration 277 if topic := topictab.topics[topic]; topic != nil { 278 waitPeriod = topic.wcl.waitPeriod 279 } else { 280 waitPeriod = minWaitPeriod 281 } 282 283 t.regTime[i] = now + AbsTime(waitPeriod) 284 } 285 return t 286 } 287 288 const gcInterval = time.Minute 289 290 func (t *topicTable) collectGarbage() { 291 tm := Now() 292 if time.Duration(tm-t.lastGarbageCollection) < gcInterval { 293 return 294 } 295 t.lastGarbageCollection = tm 296 297 for node, n := range t.nodes { 298 for _, e := range n.entries { 299 if e.expire <= tm { 300 t.deleteEntry(e) 301 } 302 } 303 304 t.checkDeleteNode(node) 305 } 306 307 for topic := range t.topics { 308 t.checkDeleteTopic(topic) 309 } 310 } 311 312 const ( 313 minWaitPeriod = time.Minute 314 regTimeWindow = 10 // seconds 315 avgnoRegTimeout = time.Minute * 10 316 // target average interval between two incoming ad requests 317 wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic 318 // 319 wcTimeConst = time.Minute * 10 320 ) 321 322 // initialization is not required, will set to minWaitPeriod at first registration 323 type waitControlLoop struct { 324 lastIncoming AbsTime 325 waitPeriod time.Duration 326 } 327 328 func (w *waitControlLoop) registered(tm AbsTime) { 329 w.waitPeriod = w.nextWaitPeriod(tm) 330 w.lastIncoming = tm 331 } 332 333 func (w *waitControlLoop) nextWaitPeriod(tm AbsTime) time.Duration { 334 period := tm - w.lastIncoming 335 wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst))) 336 if wp < minWaitPeriod { 337 wp = minWaitPeriod 338 } 339 return wp 340 } 341 342 func (w *waitControlLoop) hasMinimumWaitPeriod() bool { 343 return w.nextWaitPeriod(Now()) == minWaitPeriod 344 } 345 346 func noRegTimeout() time.Duration { 347 e := rand.New(rand.NewSource(time.Now().UnixNano())).ExpFloat64() 348 if e > 100 { 349 e = 100 350 } 351 return time.Duration(float64(avgnoRegTimeout) * e) 352 } 353 354 type topicRequestQueueItem struct { 355 topic Topic 356 priority uint64 357 index int 358 } 359 360 // A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems. 361 type topicRequestQueue []*topicRequestQueueItem 362 363 func (tq topicRequestQueue) Len() int { return len(tq) } 364 365 func (tq topicRequestQueue) Less(i, j int) bool { 366 return tq[i].priority < tq[j].priority 367 } 368 369 func (tq topicRequestQueue) Swap(i, j int) { 370 tq[i], tq[j] = tq[j], tq[i] 371 tq[i].index = i 372 tq[j].index = j 373 } 374 375 func (tq *topicRequestQueue) Push(x interface{}) { 376 n := len(*tq) 377 item := x.(*topicRequestQueueItem) 378 item.index = n 379 *tq = append(*tq, item) 380 } 381 382 func (tq *topicRequestQueue) Pop() interface{} { 383 old := *tq 384 n := len(old) 385 item := old[n-1] 386 item.index = -1 387 *tq = old[0 : n-1] 388 return item 389 } 390 391 func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) { 392 item.priority = priority 393 heap.Fix(tq, item.index) 394 }