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