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