github.com/ylsGit/go-ethereum@v1.6.5/p2p/discv5/ticket.go (about)

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