github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/discv5/ticket.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 "bytes" 16 "encoding/binary" 17 "fmt" 18 "math" 19 "math/rand" 20 "sort" 21 "time" 22 23 "github.com/Sberex/go-sberex/common" 24 "github.com/Sberex/go-sberex/common/mclock" 25 "github.com/Sberex/go-sberex/crypto" 26 "github.com/Sberex/go-sberex/log" 27 ) 28 29 const ( 30 ticketTimeBucketLen = time.Minute 31 timeWindow = 10 // * ticketTimeBucketLen 32 wantTicketsInWindow = 10 33 collectFrequency = time.Second * 30 34 registerFrequency = time.Second * 60 35 maxCollectDebt = 10 36 maxRegisterDebt = 5 37 keepTicketConst = time.Minute * 10 38 keepTicketExp = time.Minute * 5 39 targetWaitTime = time.Minute * 10 40 topicQueryTimeout = time.Second * 5 41 topicQueryResend = time.Minute 42 // topic radius detection 43 maxRadius = 0xffffffffffffffff 44 radiusTC = time.Minute * 20 45 radiusBucketsPerBit = 8 46 minSlope = 1 47 minPeakSize = 40 48 maxNoAdjust = 20 49 lookupWidth = 8 50 minRightSum = 20 51 searchForceQuery = 4 52 ) 53 54 // timeBucket represents absolute monotonic time in minutes. 55 // It is used as the index into the per-topic ticket buckets. 56 type timeBucket int 57 58 type ticket struct { 59 topics []Topic 60 regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used. 61 62 // The serial number that was issued by the server. 63 serial uint32 64 // Used by registrar, tracks absolute time when the ticket was created. 65 issueTime mclock.AbsTime 66 67 // Fields used only by registrants 68 node *Node // the registrar node that signed this ticket 69 refCnt int // tracks number of topics that will be registered using this ticket 70 pong []byte // encoded pong packet signed by the registrar 71 } 72 73 // ticketRef refers to a single topic in a ticket. 74 type ticketRef struct { 75 t *ticket 76 idx int // index of the topic in t.topics and t.regTime 77 } 78 79 func (ref ticketRef) topic() Topic { 80 return ref.t.topics[ref.idx] 81 } 82 83 func (ref ticketRef) topicRegTime() mclock.AbsTime { 84 return ref.t.regTime[ref.idx] 85 } 86 87 func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) { 88 wps := p.data.(*pong).WaitPeriods 89 if len(topics) != len(wps) { 90 return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps)) 91 } 92 if rlpHash(topics) != p.data.(*pong).TopicHash { 93 return nil, fmt.Errorf("bad topic hash") 94 } 95 t := &ticket{ 96 issueTime: localTime, 97 node: node, 98 topics: topics, 99 pong: p.rawData, 100 regTime: make([]mclock.AbsTime, len(wps)), 101 } 102 // Convert wait periods to local absolute time. 103 for i, wp := range wps { 104 t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp)) 105 } 106 return t, nil 107 } 108 109 func ticketToPong(t *ticket, pong *pong) { 110 pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second)) 111 pong.TopicHash = rlpHash(t.topics) 112 pong.TicketSerial = t.serial 113 pong.WaitPeriods = make([]uint32, len(t.regTime)) 114 for i, regTime := range t.regTime { 115 pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second) 116 } 117 } 118 119 type ticketStore struct { 120 // radius detector and target address generator 121 // exists for both searched and registered topics 122 radius map[Topic]*topicRadius 123 124 // Contains buckets (for each absolute minute) of tickets 125 // that can be used in that minute. 126 // This is only set if the topic is being registered. 127 tickets map[Topic]*topicTickets 128 129 regQueue []Topic // Topic registration queue for round robin attempts 130 regSet map[Topic]struct{} // Topic registration queue contents for fast filling 131 132 nodes map[*Node]*ticket 133 nodeLastReq map[*Node]reqInfo 134 135 lastBucketFetched timeBucket 136 nextTicketCached *ticketRef 137 nextTicketReg mclock.AbsTime 138 139 searchTopicMap map[Topic]searchTopic 140 nextTopicQueryCleanup mclock.AbsTime 141 queriesSent map[*Node]map[common.Hash]sentQuery 142 } 143 144 type searchTopic struct { 145 foundChn chan<- *Node 146 } 147 148 type sentQuery struct { 149 sent mclock.AbsTime 150 lookup lookupInfo 151 } 152 153 type topicTickets struct { 154 buckets map[timeBucket][]ticketRef 155 nextLookup mclock.AbsTime 156 nextReg mclock.AbsTime 157 } 158 159 func newTicketStore() *ticketStore { 160 return &ticketStore{ 161 radius: make(map[Topic]*topicRadius), 162 tickets: make(map[Topic]*topicTickets), 163 regSet: make(map[Topic]struct{}), 164 nodes: make(map[*Node]*ticket), 165 nodeLastReq: make(map[*Node]reqInfo), 166 searchTopicMap: make(map[Topic]searchTopic), 167 queriesSent: make(map[*Node]map[common.Hash]sentQuery), 168 } 169 } 170 171 // addTopic starts tracking a topic. If register is true, 172 // the local node will register the topic and tickets will be collected. 173 func (s *ticketStore) addTopic(topic Topic, register bool) { 174 log.Trace("Adding discovery topic", "topic", topic, "register", register) 175 if s.radius[topic] == nil { 176 s.radius[topic] = newTopicRadius(topic) 177 } 178 if register && s.tickets[topic] == nil { 179 s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)} 180 } 181 } 182 183 func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) { 184 s.addTopic(t, false) 185 if s.searchTopicMap[t].foundChn == nil { 186 s.searchTopicMap[t] = searchTopic{foundChn: foundChn} 187 } 188 } 189 190 func (s *ticketStore) removeSearchTopic(t Topic) { 191 if st := s.searchTopicMap[t]; st.foundChn != nil { 192 delete(s.searchTopicMap, t) 193 } 194 } 195 196 // removeRegisterTopic deletes all tickets for the given topic. 197 func (s *ticketStore) removeRegisterTopic(topic Topic) { 198 log.Trace("Removing discovery topic", "topic", topic) 199 if s.tickets[topic] == nil { 200 log.Warn("Removing non-existent discovery topic", "topic", topic) 201 return 202 } 203 for _, list := range s.tickets[topic].buckets { 204 for _, ref := range list { 205 ref.t.refCnt-- 206 if ref.t.refCnt == 0 { 207 delete(s.nodes, ref.t.node) 208 delete(s.nodeLastReq, ref.t.node) 209 } 210 } 211 } 212 delete(s.tickets, topic) 213 } 214 215 func (s *ticketStore) regTopicSet() []Topic { 216 topics := make([]Topic, 0, len(s.tickets)) 217 for topic := range s.tickets { 218 topics = append(topics, topic) 219 } 220 return topics 221 } 222 223 // nextRegisterLookup returns the target of the next lookup for ticket collection. 224 func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) { 225 // Queue up any new topics (or discarded ones), preserving iteration order 226 for topic := range s.tickets { 227 if _, ok := s.regSet[topic]; !ok { 228 s.regQueue = append(s.regQueue, topic) 229 s.regSet[topic] = struct{}{} 230 } 231 } 232 // Iterate over the set of all topics and look up the next suitable one 233 for len(s.regQueue) > 0 { 234 // Fetch the next topic from the queue, and ensure it still exists 235 topic := s.regQueue[0] 236 s.regQueue = s.regQueue[1:] 237 delete(s.regSet, topic) 238 239 if s.tickets[topic] == nil { 240 continue 241 } 242 // If the topic needs more tickets, return it 243 if s.tickets[topic].nextLookup < mclock.Now() { 244 next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond 245 log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay) 246 return next, delay 247 } 248 } 249 // No registration topics found or all exhausted, sleep 250 delay := 40 * time.Second 251 log.Trace("No topic found to register", "delay", delay) 252 return lookupInfo{}, delay 253 } 254 255 func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo { 256 tr := s.radius[topic] 257 target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery) 258 if target.radiusLookup { 259 tr.radiusLookupCnt++ 260 } else { 261 tr.radiusLookupCnt = 0 262 } 263 return target 264 } 265 266 // ticketsInWindow returns the tickets of a given topic in the registration window. 267 func (s *ticketStore) ticketsInWindow(topic Topic) []ticketRef { 268 // Sanity check that the topic still exists before operating on it 269 if s.tickets[topic] == nil { 270 log.Warn("Listing non-existing discovery tickets", "topic", topic) 271 return nil 272 } 273 // Gather all the tickers in the next time window 274 var tickets []ticketRef 275 276 buckets := s.tickets[topic].buckets 277 for idx := timeBucket(0); idx < timeWindow; idx++ { 278 tickets = append(tickets, buckets[s.lastBucketFetched+idx]...) 279 } 280 log.Trace("Retrieved discovery registration tickets", "topic", topic, "from", s.lastBucketFetched, "tickets", len(tickets)) 281 return tickets 282 } 283 284 func (s *ticketStore) removeExcessTickets(t Topic) { 285 tickets := s.ticketsInWindow(t) 286 if len(tickets) <= wantTicketsInWindow { 287 return 288 } 289 sort.Sort(ticketRefByWaitTime(tickets)) 290 for _, r := range tickets[wantTicketsInWindow:] { 291 s.removeTicketRef(r) 292 } 293 } 294 295 type ticketRefByWaitTime []ticketRef 296 297 // Len is the number of elements in the collection. 298 func (s ticketRefByWaitTime) Len() int { 299 return len(s) 300 } 301 302 func (r ticketRef) waitTime() mclock.AbsTime { 303 return r.t.regTime[r.idx] - r.t.issueTime 304 } 305 306 // Less reports whether the element with 307 // index i should sort before the element with index j. 308 func (s ticketRefByWaitTime) Less(i, j int) bool { 309 return s[i].waitTime() < s[j].waitTime() 310 } 311 312 // Swap swaps the elements with indexes i and j. 313 func (s ticketRefByWaitTime) Swap(i, j int) { 314 s[i], s[j] = s[j], s[i] 315 } 316 317 func (s *ticketStore) addTicketRef(r ticketRef) { 318 topic := r.t.topics[r.idx] 319 tickets := s.tickets[topic] 320 if tickets == nil { 321 log.Warn("Adding ticket to non-existent topic", "topic", topic) 322 return 323 } 324 bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen)) 325 tickets.buckets[bucket] = append(tickets.buckets[bucket], r) 326 r.t.refCnt++ 327 328 min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt 329 if tickets.nextLookup < min { 330 tickets.nextLookup = min 331 } 332 tickets.nextLookup += mclock.AbsTime(collectFrequency) 333 334 //s.removeExcessTickets(topic) 335 } 336 337 func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) { 338 now := mclock.Now() 339 for { 340 ticket, wait := s.nextRegisterableTicket() 341 if ticket == nil { 342 return ticket, wait 343 } 344 log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait) 345 346 regTime := now + mclock.AbsTime(wait) 347 topic := ticket.t.topics[ticket.idx] 348 if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg { 349 return ticket, wait 350 } 351 s.removeTicketRef(*ticket) 352 } 353 } 354 355 func (s *ticketStore) ticketRegistered(ref ticketRef) { 356 now := mclock.Now() 357 358 topic := ref.t.topics[ref.idx] 359 tickets := s.tickets[topic] 360 min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt 361 if min > tickets.nextReg { 362 tickets.nextReg = min 363 } 364 tickets.nextReg += mclock.AbsTime(registerFrequency) 365 s.tickets[topic] = tickets 366 367 s.removeTicketRef(ref) 368 } 369 370 // nextRegisterableTicket returns the next ticket that can be used 371 // to register. 372 // 373 // If the returned wait time <= zero the ticket can be used. For a positive 374 // wait time, the caller should requery the next ticket later. 375 // 376 // A ticket can be returned more than once with <= zero wait time in case 377 // the ticket contains multiple topics. 378 func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) { 379 now := mclock.Now() 380 if s.nextTicketCached != nil { 381 return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now) 382 } 383 384 for bucket := s.lastBucketFetched; ; bucket++ { 385 var ( 386 empty = true // true if there are no tickets 387 nextTicket ticketRef // uninitialized if this bucket is empty 388 ) 389 for _, tickets := range s.tickets { 390 //s.removeExcessTickets(topic) 391 if len(tickets.buckets) != 0 { 392 empty = false 393 394 list := tickets.buckets[bucket] 395 for _, ref := range list { 396 //debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now))) 397 if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() { 398 nextTicket = ref 399 } 400 } 401 } 402 } 403 if empty { 404 return nil, 0 405 } 406 if nextTicket.t != nil { 407 s.nextTicketCached = &nextTicket 408 return &nextTicket, time.Duration(nextTicket.topicRegTime() - now) 409 } 410 s.lastBucketFetched = bucket 411 } 412 } 413 414 // removeTicket removes a ticket from the ticket store 415 func (s *ticketStore) removeTicketRef(ref ticketRef) { 416 log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial) 417 418 // Make nextRegisterableTicket return the next available ticket. 419 s.nextTicketCached = nil 420 421 topic := ref.topic() 422 tickets := s.tickets[topic] 423 424 if tickets == nil { 425 log.Trace("Removing tickets from unknown topic", "topic", topic) 426 return 427 } 428 bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen)) 429 list := tickets.buckets[bucket] 430 idx := -1 431 for i, bt := range list { 432 if bt.t == ref.t { 433 idx = i 434 break 435 } 436 } 437 if idx == -1 { 438 panic(nil) 439 } 440 list = append(list[:idx], list[idx+1:]...) 441 if len(list) != 0 { 442 tickets.buckets[bucket] = list 443 } else { 444 delete(tickets.buckets, bucket) 445 } 446 ref.t.refCnt-- 447 if ref.t.refCnt == 0 { 448 delete(s.nodes, ref.t.node) 449 delete(s.nodeLastReq, ref.t.node) 450 } 451 } 452 453 type lookupInfo struct { 454 target common.Hash 455 topic Topic 456 radiusLookup bool 457 } 458 459 type reqInfo struct { 460 pingHash []byte 461 lookup lookupInfo 462 time mclock.AbsTime 463 } 464 465 // returns -1 if not found 466 func (t *ticket) findIdx(topic Topic) int { 467 for i, tt := range t.topics { 468 if tt == topic { 469 return i 470 } 471 } 472 return -1 473 } 474 475 func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) { 476 now := mclock.Now() 477 for i, n := range nodes { 478 if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { 479 if lookup.radiusLookup { 480 if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { 481 s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} 482 } 483 } else { 484 if s.nodes[n] == nil { 485 s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} 486 } 487 } 488 } 489 } 490 } 491 492 func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, query func(n *Node, topic Topic) []byte) { 493 now := mclock.Now() 494 for i, n := range nodes { 495 if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { 496 if lookup.radiusLookup { 497 if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { 498 s.nodeLastReq[n] = reqInfo{pingHash: nil, lookup: lookup, time: now} 499 } 500 } // else { 501 if s.canQueryTopic(n, lookup.topic) { 502 hash := query(n, lookup.topic) 503 if hash != nil { 504 s.addTopicQuery(common.BytesToHash(hash), n, lookup) 505 } 506 } 507 //} 508 } 509 } 510 } 511 512 func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) { 513 for i, topic := range t.topics { 514 if tt, ok := s.radius[topic]; ok { 515 tt.adjustWithTicket(now, targetHash, ticketRef{t, i}) 516 } 517 } 518 } 519 520 func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, ticket *ticket) { 521 log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial) 522 523 lastReq, ok := s.nodeLastReq[ticket.node] 524 if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) { 525 return 526 } 527 s.adjustWithTicket(localTime, lastReq.lookup.target, ticket) 528 529 if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil { 530 return 531 } 532 533 topic := lastReq.lookup.topic 534 topicIdx := ticket.findIdx(topic) 535 if topicIdx == -1 { 536 return 537 } 538 539 bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen)) 540 if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched { 541 s.lastBucketFetched = bucket 542 } 543 544 if _, ok := s.tickets[topic]; ok { 545 wait := ticket.regTime[topicIdx] - localTime 546 rnd := rand.ExpFloat64() 547 if rnd > 10 { 548 rnd = 10 549 } 550 if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd { 551 // use the ticket to register this topic 552 //fmt.Println("addTicket", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong) 553 s.addTicketRef(ticketRef{ticket, topicIdx}) 554 } 555 } 556 557 if ticket.refCnt > 0 { 558 s.nextTicketCached = nil 559 s.nodes[ticket.node] = ticket 560 } 561 } 562 563 func (s *ticketStore) getNodeTicket(node *Node) *ticket { 564 if s.nodes[node] == nil { 565 log.Trace("Retrieving node ticket", "node", node.ID, "serial", nil) 566 } else { 567 log.Trace("Retrieving node ticket", "node", node.ID, "serial", s.nodes[node].serial) 568 } 569 return s.nodes[node] 570 } 571 572 func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool { 573 qq := s.queriesSent[node] 574 if qq != nil { 575 now := mclock.Now() 576 for _, sq := range qq { 577 if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) { 578 return false 579 } 580 } 581 } 582 return true 583 } 584 585 func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) { 586 now := mclock.Now() 587 qq := s.queriesSent[node] 588 if qq == nil { 589 qq = make(map[common.Hash]sentQuery) 590 s.queriesSent[node] = qq 591 } 592 qq[hash] = sentQuery{sent: now, lookup: lookup} 593 s.cleanupTopicQueries(now) 594 } 595 596 func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) { 597 if s.nextTopicQueryCleanup > now { 598 return 599 } 600 exp := now - mclock.AbsTime(topicQueryResend) 601 for n, qq := range s.queriesSent { 602 for h, q := range qq { 603 if q.sent < exp { 604 delete(qq, h) 605 } 606 } 607 if len(qq) == 0 { 608 delete(s.queriesSent, n) 609 } 610 } 611 s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout) 612 } 613 614 func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) { 615 now := mclock.Now() 616 //fmt.Println("got", from.addr().String(), hash, len(nodes)) 617 qq := s.queriesSent[from] 618 if qq == nil { 619 return true 620 } 621 q, ok := qq[hash] 622 if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) { 623 return true 624 } 625 inside := float64(0) 626 if len(nodes) > 0 { 627 inside = 1 628 } 629 s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside) 630 chn := s.searchTopicMap[q.lookup.topic].foundChn 631 if chn == nil { 632 //fmt.Println("no channel") 633 return false 634 } 635 for _, node := range nodes { 636 ip := node.IP 637 if ip.IsUnspecified() || ip.IsLoopback() { 638 ip = from.IP 639 } 640 n := NewNode(node.ID, ip, node.UDP, node.TCP) 641 select { 642 case chn <- n: 643 default: 644 return false 645 } 646 } 647 return false 648 } 649 650 type topicRadius struct { 651 topic Topic 652 topicHashPrefix uint64 653 radius, minRadius uint64 654 buckets []topicRadiusBucket 655 converged bool 656 radiusLookupCnt int 657 } 658 659 type topicRadiusEvent int 660 661 const ( 662 trOutside topicRadiusEvent = iota 663 trInside 664 trNoAdjust 665 trCount 666 ) 667 668 type topicRadiusBucket struct { 669 weights [trCount]float64 670 lastTime mclock.AbsTime 671 value float64 672 lookupSent map[common.Hash]mclock.AbsTime 673 } 674 675 func (b *topicRadiusBucket) update(now mclock.AbsTime) { 676 if now == b.lastTime { 677 return 678 } 679 exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC)) 680 for i, w := range b.weights { 681 b.weights[i] = w * exp 682 } 683 b.lastTime = now 684 685 for target, tm := range b.lookupSent { 686 if now-tm > mclock.AbsTime(respTimeout) { 687 b.weights[trNoAdjust] += 1 688 delete(b.lookupSent, target) 689 } 690 } 691 } 692 693 func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) { 694 b.update(now) 695 if inside <= 0 { 696 b.weights[trOutside] += 1 697 } else { 698 if inside >= 1 { 699 b.weights[trInside] += 1 700 } else { 701 b.weights[trInside] += inside 702 b.weights[trOutside] += 1 - inside 703 } 704 } 705 } 706 707 func newTopicRadius(t Topic) *topicRadius { 708 topicHash := crypto.Keccak256Hash([]byte(t)) 709 topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8]) 710 711 return &topicRadius{ 712 topic: t, 713 topicHashPrefix: topicHashPrefix, 714 radius: maxRadius, 715 minRadius: maxRadius, 716 } 717 } 718 719 func (r *topicRadius) getBucketIdx(addrHash common.Hash) int { 720 prefix := binary.BigEndian.Uint64(addrHash[0:8]) 721 var log2 float64 722 if prefix != r.topicHashPrefix { 723 log2 = math.Log2(float64(prefix ^ r.topicHashPrefix)) 724 } 725 bucket := int((64 - log2) * radiusBucketsPerBit) 726 max := 64*radiusBucketsPerBit - 1 727 if bucket > max { 728 return max 729 } 730 if bucket < 0 { 731 return 0 732 } 733 return bucket 734 } 735 736 func (r *topicRadius) targetForBucket(bucket int) common.Hash { 737 min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit) 738 max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit) 739 a := uint64(min) 740 b := randUint64n(uint64(max - min)) 741 xor := a + b 742 if xor < a { 743 xor = ^uint64(0) 744 } 745 prefix := r.topicHashPrefix ^ xor 746 var target common.Hash 747 binary.BigEndian.PutUint64(target[0:8], prefix) 748 globalRandRead(target[8:]) 749 return target 750 } 751 752 // package rand provides a Read function in Go 1.6 and later, but 753 // we can't use it yet because we still support Go 1.5. 754 func globalRandRead(b []byte) { 755 pos := 0 756 val := 0 757 for n := 0; n < len(b); n++ { 758 if pos == 0 { 759 val = rand.Int() 760 pos = 7 761 } 762 b[n] = byte(val) 763 val >>= 8 764 pos-- 765 } 766 } 767 768 func (r *topicRadius) isInRadius(addrHash common.Hash) bool { 769 nodePrefix := binary.BigEndian.Uint64(addrHash[0:8]) 770 dist := nodePrefix ^ r.topicHashPrefix 771 return dist < r.radius 772 } 773 774 func (r *topicRadius) chooseLookupBucket(a, b int) int { 775 if a < 0 { 776 a = 0 777 } 778 if a > b { 779 return -1 780 } 781 c := 0 782 for i := a; i <= b; i++ { 783 if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { 784 c++ 785 } 786 } 787 if c == 0 { 788 return -1 789 } 790 rnd := randUint(uint32(c)) 791 for i := a; i <= b; i++ { 792 if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { 793 if rnd == 0 { 794 return i 795 } 796 rnd-- 797 } 798 } 799 panic(nil) // should never happen 800 } 801 802 func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool { 803 var max float64 804 if a < 0 { 805 a = 0 806 } 807 if b >= len(r.buckets) { 808 b = len(r.buckets) - 1 809 if r.buckets[b].value > max { 810 max = r.buckets[b].value 811 } 812 } 813 if b >= a { 814 for i := a; i <= b; i++ { 815 if r.buckets[i].value > max { 816 max = r.buckets[i].value 817 } 818 } 819 } 820 return maxValue-max < minPeakSize 821 } 822 823 func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) { 824 maxBucket := 0 825 maxValue := float64(0) 826 now := mclock.Now() 827 v := float64(0) 828 for i := range r.buckets { 829 r.buckets[i].update(now) 830 v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside] 831 r.buckets[i].value = v 832 //fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust]) 833 } 834 //fmt.Println() 835 slopeCross := -1 836 for i, b := range r.buckets { 837 v := b.value 838 if v < float64(i)*minSlope { 839 slopeCross = i 840 break 841 } 842 if v > maxValue { 843 maxValue = v 844 maxBucket = i + 1 845 } 846 } 847 848 minRadBucket := len(r.buckets) 849 sum := float64(0) 850 for minRadBucket > 0 && sum < minRightSum { 851 minRadBucket-- 852 b := r.buckets[minRadBucket] 853 sum += b.weights[trInside] + b.weights[trOutside] 854 } 855 r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit)) 856 857 lookupLeft := -1 858 if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) { 859 lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1) 860 } 861 lookupRight := -1 862 if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) { 863 for len(r.buckets) <= maxBucket+lookupWidth { 864 r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)}) 865 } 866 lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1) 867 } 868 if lookupLeft == -1 { 869 radiusLookup = lookupRight 870 } else { 871 if lookupRight == -1 { 872 radiusLookup = lookupLeft 873 } else { 874 if randUint(2) == 0 { 875 radiusLookup = lookupLeft 876 } else { 877 radiusLookup = lookupRight 878 } 879 } 880 } 881 882 //fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue) 883 884 if radiusLookup == -1 { 885 // no more radius lookups needed at the moment, return a radius 886 r.converged = true 887 rad := maxBucket 888 if minRadBucket < rad { 889 rad = minRadBucket 890 } 891 radius = ^uint64(0) 892 if rad > 0 { 893 radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit)) 894 } 895 r.radius = radius 896 } 897 898 return 899 } 900 901 func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo { 902 if !forceRegular { 903 _, radiusLookup := r.recalcRadius() 904 if radiusLookup != -1 { 905 target := r.targetForBucket(radiusLookup) 906 r.buckets[radiusLookup].lookupSent[target] = mclock.Now() 907 return lookupInfo{target: target, topic: r.topic, radiusLookup: true} 908 } 909 } 910 911 radExt := r.radius / 2 912 if radExt > maxRadius-r.radius { 913 radExt = maxRadius - r.radius 914 } 915 rnd := randUint64n(r.radius) + randUint64n(2*radExt) 916 if rnd > radExt { 917 rnd -= radExt 918 } else { 919 rnd = radExt - rnd 920 } 921 922 prefix := r.topicHashPrefix ^ rnd 923 var target common.Hash 924 binary.BigEndian.PutUint64(target[0:8], prefix) 925 globalRandRead(target[8:]) 926 return lookupInfo{target: target, topic: r.topic, radiusLookup: false} 927 } 928 929 func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) { 930 wait := t.t.regTime[t.idx] - t.t.issueTime 931 inside := float64(wait)/float64(targetWaitTime) - 0.5 932 if inside > 1 { 933 inside = 1 934 } 935 if inside < 0 { 936 inside = 0 937 } 938 r.adjust(now, targetHash, t.t.node.sha, inside) 939 } 940 941 func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) { 942 bucket := r.getBucketIdx(addrHash) 943 //fmt.Println("adjust", bucket, len(r.buckets), inside) 944 if bucket >= len(r.buckets) { 945 return 946 } 947 r.buckets[bucket].adjust(now, inside) 948 delete(r.buckets[bucket].lookupSent, targetHash) 949 }