github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/discv5/ticket.go (about)

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