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