github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/p2p/discover/dht/ticket.go (about)

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