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