github.com/murrekatt/go-ethereum@v1.5.8-0.20170123175102-fc52f2c007fb/p2p/discv5/ticket.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discv5 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "fmt" 23 "math" 24 "math/rand" 25 "sort" 26 "time" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/mclock" 30 "github.com/ethereum/go-ethereum/crypto" 31 ) 32 33 const ( 34 ticketTimeBucketLen = time.Minute 35 timeWindow = 10 // * ticketTimeBucketLen 36 wantTicketsInWindow = 10 37 collectFrequency = time.Second * 30 38 registerFrequency = time.Second * 60 39 maxCollectDebt = 10 40 maxRegisterDebt = 5 41 keepTicketConst = time.Minute * 10 42 keepTicketExp = time.Minute * 5 43 targetWaitTime = time.Minute * 10 44 topicQueryTimeout = time.Second * 5 45 topicQueryResend = time.Minute 46 // topic radius detection 47 maxRadius = 0xffffffffffffffff 48 radiusTC = time.Minute * 20 49 radiusBucketsPerBit = 8 50 minSlope = 1 51 minPeakSize = 40 52 maxNoAdjust = 20 53 lookupWidth = 8 54 minRightSum = 20 55 searchForceQuery = 4 56 ) 57 58 // timeBucket represents absolute monotonic time in minutes. 59 // It is used as the index into the per-topic ticket buckets. 60 type timeBucket int 61 62 type ticket struct { 63 topics []Topic 64 regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used. 65 66 // The serial number that was issued by the server. 67 serial uint32 68 // Used by registrar, tracks absolute time when the ticket was created. 69 issueTime mclock.AbsTime 70 71 // Fields used only by registrants 72 node *Node // the registrar node that signed this ticket 73 refCnt int // tracks number of topics that will be registered using this ticket 74 pong []byte // encoded pong packet signed by the registrar 75 } 76 77 // ticketRef refers to a single topic in a ticket. 78 type ticketRef struct { 79 t *ticket 80 idx int // index of the topic in t.topics and t.regTime 81 } 82 83 func (ref ticketRef) topic() Topic { 84 return ref.t.topics[ref.idx] 85 } 86 87 func (ref ticketRef) topicRegTime() mclock.AbsTime { 88 return ref.t.regTime[ref.idx] 89 } 90 91 func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) { 92 wps := p.data.(*pong).WaitPeriods 93 if len(topics) != len(wps) { 94 return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps)) 95 } 96 if rlpHash(topics) != p.data.(*pong).TopicHash { 97 return nil, fmt.Errorf("bad topic hash") 98 } 99 t := &ticket{ 100 issueTime: localTime, 101 node: node, 102 topics: topics, 103 pong: p.rawData, 104 regTime: make([]mclock.AbsTime, len(wps)), 105 } 106 // Convert wait periods to local absolute time. 107 for i, wp := range wps { 108 t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp)) 109 } 110 return t, nil 111 } 112 113 func ticketToPong(t *ticket, pong *pong) { 114 pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second)) 115 pong.TopicHash = rlpHash(t.topics) 116 pong.TicketSerial = t.serial 117 pong.WaitPeriods = make([]uint32, len(t.regTime)) 118 for i, regTime := range t.regTime { 119 pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second) 120 } 121 } 122 123 type ticketStore struct { 124 // radius detector and target address generator 125 // exists for both searched and registered topics 126 radius map[Topic]*topicRadius 127 128 // Contains buckets (for each absolute minute) of tickets 129 // that can be used in that minute. 130 // This is only set if the topic is being registered. 131 tickets map[Topic]topicTickets 132 regtopics []Topic 133 nodes map[*Node]*ticket 134 nodeLastReq map[*Node]reqInfo 135 136 lastBucketFetched timeBucket 137 nextTicketCached *ticketRef 138 nextTicketReg mclock.AbsTime 139 140 searchTopicMap map[Topic]searchTopic 141 nextTopicQueryCleanup mclock.AbsTime 142 queriesSent map[*Node]map[common.Hash]sentQuery 143 } 144 145 type searchTopic struct { 146 foundChn chan<- *Node 147 } 148 149 type sentQuery struct { 150 sent mclock.AbsTime 151 lookup lookupInfo 152 } 153 154 type topicTickets struct { 155 buckets map[timeBucket][]ticketRef 156 nextLookup, 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 nodes: make(map[*Node]*ticket), 164 nodeLastReq: make(map[*Node]reqInfo), 165 searchTopicMap: make(map[Topic]searchTopic), 166 queriesSent: make(map[*Node]map[common.Hash]sentQuery), 167 } 168 } 169 170 // addTopic starts tracking a topic. If register is true, 171 // the local node will register the topic and tickets will be collected. 172 func (s *ticketStore) addTopic(t Topic, register bool) { 173 debugLog(fmt.Sprintf(" addTopic(%v, %v)", t, register)) 174 if s.radius[t] == nil { 175 s.radius[t] = newTopicRadius(t) 176 } 177 if register && s.tickets[t].buckets == nil { 178 s.tickets[t] = topicTickets{buckets: make(map[timeBucket][]ticketRef)} 179 } 180 } 181 182 func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) { 183 s.addTopic(t, false) 184 if s.searchTopicMap[t].foundChn == nil { 185 s.searchTopicMap[t] = searchTopic{foundChn: foundChn} 186 } 187 } 188 189 func (s *ticketStore) removeSearchTopic(t Topic) { 190 if st := s.searchTopicMap[t]; st.foundChn != nil { 191 delete(s.searchTopicMap, t) 192 } 193 } 194 195 // removeRegisterTopic deletes all tickets for the given topic. 196 func (s *ticketStore) removeRegisterTopic(topic Topic) { 197 debugLog(fmt.Sprintf(" removeRegisterTopic(%v)", topic)) 198 for _, list := range s.tickets[topic].buckets { 199 for _, ref := range list { 200 ref.t.refCnt-- 201 if ref.t.refCnt == 0 { 202 delete(s.nodes, ref.t.node) 203 delete(s.nodeLastReq, ref.t.node) 204 } 205 } 206 } 207 delete(s.tickets, topic) 208 } 209 210 func (s *ticketStore) regTopicSet() []Topic { 211 topics := make([]Topic, 0, len(s.tickets)) 212 for topic := range s.tickets { 213 topics = append(topics, topic) 214 } 215 return topics 216 } 217 218 // nextRegisterLookup returns the target of the next lookup for ticket collection. 219 func (s *ticketStore) nextRegisterLookup() (lookup lookupInfo, delay time.Duration) { 220 debugLog("nextRegisterLookup()") 221 firstTopic, ok := s.iterRegTopics() 222 for topic := firstTopic; ok; { 223 debugLog(fmt.Sprintf(" checking topic %v, len(s.tickets[topic]) = %d", topic, len(s.tickets[topic].buckets))) 224 if s.tickets[topic].buckets != nil && s.needMoreTickets(topic) { 225 next := s.radius[topic].nextTarget(false) 226 debugLog(fmt.Sprintf(" %x 1s", next.target[:8])) 227 return next, 100 * time.Millisecond 228 } 229 topic, ok = s.iterRegTopics() 230 if topic == firstTopic { 231 break // We have checked all topics. 232 } 233 } 234 debugLog(" null, 40s") 235 return lookupInfo{}, 40 * time.Second 236 } 237 238 func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo { 239 tr := s.radius[topic] 240 target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery) 241 if target.radiusLookup { 242 tr.radiusLookupCnt++ 243 } else { 244 tr.radiusLookupCnt = 0 245 } 246 return target 247 } 248 249 // iterRegTopics returns topics to register in arbitrary order. 250 // The second return value is false if there are no topics. 251 func (s *ticketStore) iterRegTopics() (Topic, bool) { 252 debugLog("iterRegTopics()") 253 if len(s.regtopics) == 0 { 254 if len(s.tickets) == 0 { 255 debugLog(" false") 256 return "", false 257 } 258 // Refill register list. 259 for t := range s.tickets { 260 s.regtopics = append(s.regtopics, t) 261 } 262 } 263 topic := s.regtopics[len(s.regtopics)-1] 264 s.regtopics = s.regtopics[:len(s.regtopics)-1] 265 debugLog(" " + string(topic) + " true") 266 return topic, true 267 } 268 269 func (s *ticketStore) needMoreTickets(t Topic) bool { 270 return s.tickets[t].nextLookup < mclock.Now() 271 } 272 273 // ticketsInWindow returns the tickets of a given topic in the registration window. 274 func (s *ticketStore) ticketsInWindow(t Topic) []ticketRef { 275 ltBucket := s.lastBucketFetched 276 var res []ticketRef 277 tickets := s.tickets[t].buckets 278 for g := ltBucket; g < ltBucket+timeWindow; g++ { 279 res = append(res, tickets[g]...) 280 } 281 debugLog(fmt.Sprintf("ticketsInWindow(%v) = %v", t, len(res))) 282 return res 283 } 284 285 func (s *ticketStore) removeExcessTickets(t Topic) { 286 tickets := s.ticketsInWindow(t) 287 if len(tickets) <= wantTicketsInWindow { 288 return 289 } 290 sort.Sort(ticketRefByWaitTime(tickets)) 291 for _, r := range tickets[wantTicketsInWindow:] { 292 s.removeTicketRef(r) 293 } 294 } 295 296 type ticketRefByWaitTime []ticketRef 297 298 // Len is the number of elements in the collection. 299 func (s ticketRefByWaitTime) Len() int { 300 return len(s) 301 } 302 303 func (r ticketRef) waitTime() mclock.AbsTime { 304 return r.t.regTime[r.idx] - r.t.issueTime 305 } 306 307 // Less reports whether the element with 308 // index i should sort before the element with index j. 309 func (s ticketRefByWaitTime) Less(i, j int) bool { 310 return s[i].waitTime() < s[j].waitTime() 311 } 312 313 // Swap swaps the elements with indexes i and j. 314 func (s ticketRefByWaitTime) Swap(i, j int) { 315 s[i], s[j] = s[j], s[i] 316 } 317 318 func (s *ticketStore) addTicketRef(r ticketRef) { 319 topic := r.t.topics[r.idx] 320 t := s.tickets[topic] 321 if t.buckets == nil { 322 return 323 } 324 bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen)) 325 t.buckets[bucket] = append(t.buckets[bucket], r) 326 r.t.refCnt++ 327 328 min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt 329 if t.nextLookup < min { 330 t.nextLookup = min 331 } 332 t.nextLookup += mclock.AbsTime(collectFrequency) 333 s.tickets[topic] = t 334 335 //s.removeExcessTickets(topic) 336 } 337 338 func (s *ticketStore) nextFilteredTicket() (t *ticketRef, wait time.Duration) { 339 now := mclock.Now() 340 for { 341 t, wait = s.nextRegisterableTicket() 342 if t == nil { 343 return 344 } 345 regTime := now + mclock.AbsTime(wait) 346 topic := t.t.topics[t.idx] 347 if regTime >= s.tickets[topic].nextReg { 348 return 349 } 350 s.removeTicketRef(*t) 351 } 352 } 353 354 func (s *ticketStore) ticketRegistered(t ticketRef) { 355 now := mclock.Now() 356 357 topic := t.t.topics[t.idx] 358 tt := s.tickets[topic] 359 min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt 360 if min > tt.nextReg { 361 tt.nextReg = min 362 } 363 tt.nextReg += mclock.AbsTime(registerFrequency) 364 s.tickets[topic] = tt 365 366 s.removeTicketRef(t) 367 } 368 369 // nextRegisterableTicket returns the next ticket that can be used 370 // to register. 371 // 372 // If the returned wait time <= zero the ticket can be used. For a positive 373 // wait time, the caller should requery the next ticket later. 374 // 375 // A ticket can be returned more than once with <= zero wait time in case 376 // the ticket contains multiple topics. 377 func (s *ticketStore) nextRegisterableTicket() (t *ticketRef, wait time.Duration) { 378 defer func() { 379 if t == nil { 380 debugLog(" nil") 381 } else { 382 debugLog(fmt.Sprintf(" node = %x sn = %v wait = %v", t.t.node.ID[:8], t.t.serial, wait)) 383 } 384 }() 385 386 debugLog("nextRegisterableTicket()") 387 now := mclock.Now() 388 if s.nextTicketCached != nil { 389 return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now) 390 } 391 392 for bucket := s.lastBucketFetched; ; bucket++ { 393 var ( 394 empty = true // true if there are no tickets 395 nextTicket ticketRef // uninitialized if this bucket is empty 396 ) 397 for _, tickets := range s.tickets { 398 //s.removeExcessTickets(topic) 399 if len(tickets.buckets) != 0 { 400 empty = false 401 if list := tickets.buckets[bucket]; list != nil { 402 for _, ref := range list { 403 //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))) 404 if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() { 405 nextTicket = ref 406 } 407 } 408 } 409 } 410 } 411 if empty { 412 return nil, 0 413 } 414 if nextTicket.t != nil { 415 wait = time.Duration(nextTicket.topicRegTime() - now) 416 s.nextTicketCached = &nextTicket 417 return &nextTicket, wait 418 } 419 s.lastBucketFetched = bucket 420 } 421 } 422 423 // removeTicket removes a ticket from the ticket store 424 func (s *ticketStore) removeTicketRef(ref ticketRef) { 425 debugLog(fmt.Sprintf("removeTicketRef(node = %x sn = %v)", ref.t.node.ID[:8], ref.t.serial)) 426 topic := ref.topic() 427 tickets := s.tickets[topic].buckets 428 if tickets == nil { 429 return 430 } 431 bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen)) 432 list := tickets[bucket] 433 idx := -1 434 for i, bt := range list { 435 if bt.t == ref.t { 436 idx = i 437 break 438 } 439 } 440 if idx == -1 { 441 panic(nil) 442 } 443 list = append(list[:idx], list[idx+1:]...) 444 if len(list) != 0 { 445 tickets[bucket] = list 446 } else { 447 delete(tickets, bucket) 448 } 449 ref.t.refCnt-- 450 if ref.t.refCnt == 0 { 451 delete(s.nodes, ref.t.node) 452 delete(s.nodeLastReq, ref.t.node) 453 } 454 455 // Make nextRegisterableTicket return the next available ticket. 456 s.nextTicketCached = nil 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, ping func(n *Node) []byte, 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: ping(n), 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, t *ticket) { 527 debugLog(fmt.Sprintf("add(node = %x sn = %v)", t.node.ID[:8], t.serial)) 528 529 lastReq, ok := s.nodeLastReq[t.node] 530 if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) { 531 return 532 } 533 s.adjustWithTicket(localTime, lastReq.lookup.target, t) 534 535 if lastReq.lookup.radiusLookup || s.nodes[t.node] != nil { 536 return 537 } 538 539 topic := lastReq.lookup.topic 540 topicIdx := t.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 := t.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", t.node.ID[:8], t.node.addr().String(), t.serial, t.pong) 559 s.addTicketRef(ticketRef{t, topicIdx}) 560 } 561 } 562 563 if t.refCnt > 0 { 564 s.nextTicketCached = nil 565 s.nodes[t.node] = t 566 } 567 } 568 569 func (s *ticketStore) getNodeTicket(node *Node) *ticket { 570 if s.nodes[node] == nil { 571 debugLog(fmt.Sprintf("getNodeTicket(%x) sn = nil", node.ID[:8])) 572 } else { 573 debugLog(fmt.Sprintf("getNodeTicket(%x) sn = %v", node.ID[:8], 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-1, node.TCP-1) // subtract one from port while discv5 is running in test mode on UDPport+1 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 }