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