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