github.com/flashbots/go-ethereum@v1.9.7/p2p/discv5/ticket.go (about)

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