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