github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/discv5/ticket.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package discv5
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/binary"
    23  	"fmt"
    24  	"math"
    25  	"math/rand"
    26  	"sort"
    27  	"time"
    28  
    29  	"github.com/AigarNetwork/aigar/common"
    30  	"github.com/AigarNetwork/aigar/common/mclock"
    31  	"github.com/AigarNetwork/aigar/crypto"
    32  	"github.com/AigarNetwork/aigar/log"
    33  )
    34  
    35  const (
    36  	ticketTimeBucketLen = time.Minute
    37  	timeWindow          = 10 // * ticketTimeBucketLen
    38  	wantTicketsInWindow = 10
    39  	collectFrequency    = time.Second * 30
    40  	registerFrequency   = time.Second * 60
    41  	maxCollectDebt      = 10
    42  	maxRegisterDebt     = 5
    43  	keepTicketConst     = time.Minute * 10
    44  	keepTicketExp       = time.Minute * 5
    45  	targetWaitTime      = time.Minute * 10
    46  	topicQueryTimeout   = time.Second * 5
    47  	topicQueryResend    = time.Minute
    48  	// topic radius detection
    49  	maxRadius           = 0xffffffffffffffff
    50  	radiusTC            = time.Minute * 20
    51  	radiusBucketsPerBit = 8
    52  	minSlope            = 1
    53  	minPeakSize         = 40
    54  	maxNoAdjust         = 20
    55  	lookupWidth         = 8
    56  	minRightSum         = 20
    57  	searchForceQuery    = 4
    58  )
    59  
    60  // timeBucket represents absolute monotonic time in minutes.
    61  // It is used as the index into the per-topic ticket buckets.
    62  type timeBucket int
    63  
    64  type ticket struct {
    65  	topics  []Topic
    66  	regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used.
    67  
    68  	// The serial number that was issued by the server.
    69  	serial uint32
    70  	// Used by registrar, tracks absolute time when the ticket was created.
    71  	issueTime mclock.AbsTime
    72  
    73  	// Fields used only by registrants
    74  	node   *Node  // the registrar node that signed this ticket
    75  	refCnt int    // tracks number of topics that will be registered using this ticket
    76  	pong   []byte // encoded pong packet signed by the registrar
    77  }
    78  
    79  // ticketRef refers to a single topic in a ticket.
    80  type ticketRef struct {
    81  	t   *ticket
    82  	idx int // index of the topic in t.topics and t.regTime
    83  }
    84  
    85  func (ref ticketRef) topic() Topic {
    86  	return ref.t.topics[ref.idx]
    87  }
    88  
    89  func (ref ticketRef) topicRegTime() mclock.AbsTime {
    90  	return ref.t.regTime[ref.idx]
    91  }
    92  
    93  func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) {
    94  	wps := p.data.(*pong).WaitPeriods
    95  	if len(topics) != len(wps) {
    96  		return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps))
    97  	}
    98  	if rlpHash(topics) != p.data.(*pong).TopicHash {
    99  		return nil, fmt.Errorf("bad topic hash")
   100  	}
   101  	t := &ticket{
   102  		issueTime: localTime,
   103  		node:      node,
   104  		topics:    topics,
   105  		pong:      p.rawData,
   106  		regTime:   make([]mclock.AbsTime, len(wps)),
   107  	}
   108  	// Convert wait periods to local absolute time.
   109  	for i, wp := range wps {
   110  		t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp))
   111  	}
   112  	return t, nil
   113  }
   114  
   115  func ticketToPong(t *ticket, pong *pong) {
   116  	pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second))
   117  	pong.TopicHash = rlpHash(t.topics)
   118  	pong.TicketSerial = t.serial
   119  	pong.WaitPeriods = make([]uint32, len(t.regTime))
   120  	for i, regTime := range t.regTime {
   121  		pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second)
   122  	}
   123  }
   124  
   125  type ticketStore struct {
   126  	// radius detector and target address generator
   127  	// exists for both searched and registered topics
   128  	radius map[Topic]*topicRadius
   129  
   130  	// Contains buckets (for each absolute minute) of tickets
   131  	// that can be used in that minute.
   132  	// This is only set if the topic is being registered.
   133  	tickets map[Topic]*topicTickets
   134  
   135  	regQueue []Topic            // Topic registration queue for round robin attempts
   136  	regSet   map[Topic]struct{} // Topic registration queue contents for fast filling
   137  
   138  	nodes       map[*Node]*ticket
   139  	nodeLastReq map[*Node]reqInfo
   140  
   141  	lastBucketFetched timeBucket
   142  	nextTicketCached  *ticketRef
   143  	nextTicketReg     mclock.AbsTime
   144  
   145  	searchTopicMap        map[Topic]searchTopic
   146  	nextTopicQueryCleanup mclock.AbsTime
   147  	queriesSent           map[*Node]map[common.Hash]sentQuery
   148  }
   149  
   150  type searchTopic struct {
   151  	foundChn chan<- *Node
   152  }
   153  
   154  type sentQuery struct {
   155  	sent   mclock.AbsTime
   156  	lookup lookupInfo
   157  }
   158  
   159  type topicTickets struct {
   160  	buckets    map[timeBucket][]ticketRef
   161  	nextLookup mclock.AbsTime
   162  	nextReg    mclock.AbsTime
   163  }
   164  
   165  func newTicketStore() *ticketStore {
   166  	return &ticketStore{
   167  		radius:         make(map[Topic]*topicRadius),
   168  		tickets:        make(map[Topic]*topicTickets),
   169  		regSet:         make(map[Topic]struct{}),
   170  		nodes:          make(map[*Node]*ticket),
   171  		nodeLastReq:    make(map[*Node]reqInfo),
   172  		searchTopicMap: make(map[Topic]searchTopic),
   173  		queriesSent:    make(map[*Node]map[common.Hash]sentQuery),
   174  	}
   175  }
   176  
   177  // addTopic starts tracking a topic. If register is true,
   178  // the local node will register the topic and tickets will be collected.
   179  func (s *ticketStore) addTopic(topic Topic, register bool) {
   180  	log.Trace("Adding discovery topic", "topic", topic, "register", register)
   181  	if s.radius[topic] == nil {
   182  		s.radius[topic] = newTopicRadius(topic)
   183  	}
   184  	if register && s.tickets[topic] == nil {
   185  		s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)}
   186  	}
   187  }
   188  
   189  func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) {
   190  	s.addTopic(t, false)
   191  	if s.searchTopicMap[t].foundChn == nil {
   192  		s.searchTopicMap[t] = searchTopic{foundChn: foundChn}
   193  	}
   194  }
   195  
   196  func (s *ticketStore) removeSearchTopic(t Topic) {
   197  	if st := s.searchTopicMap[t]; st.foundChn != nil {
   198  		delete(s.searchTopicMap, t)
   199  	}
   200  }
   201  
   202  // removeRegisterTopic deletes all tickets for the given topic.
   203  func (s *ticketStore) removeRegisterTopic(topic Topic) {
   204  	log.Trace("Removing discovery topic", "topic", topic)
   205  	if s.tickets[topic] == nil {
   206  		log.Warn("Removing non-existent discovery topic", "topic", topic)
   207  		return
   208  	}
   209  	for _, list := range s.tickets[topic].buckets {
   210  		for _, ref := range list {
   211  			ref.t.refCnt--
   212  			if ref.t.refCnt == 0 {
   213  				delete(s.nodes, ref.t.node)
   214  				delete(s.nodeLastReq, ref.t.node)
   215  			}
   216  		}
   217  	}
   218  	delete(s.tickets, topic)
   219  }
   220  
   221  func (s *ticketStore) regTopicSet() []Topic {
   222  	topics := make([]Topic, 0, len(s.tickets))
   223  	for topic := range s.tickets {
   224  		topics = append(topics, topic)
   225  	}
   226  	return topics
   227  }
   228  
   229  // nextRegisterLookup returns the target of the next lookup for ticket collection.
   230  func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) {
   231  	// Queue up any new topics (or discarded ones), preserving iteration order
   232  	for topic := range s.tickets {
   233  		if _, ok := s.regSet[topic]; !ok {
   234  			s.regQueue = append(s.regQueue, topic)
   235  			s.regSet[topic] = struct{}{}
   236  		}
   237  	}
   238  	// Iterate over the set of all topics and look up the next suitable one
   239  	for len(s.regQueue) > 0 {
   240  		// Fetch the next topic from the queue, and ensure it still exists
   241  		topic := s.regQueue[0]
   242  		s.regQueue = s.regQueue[1:]
   243  		delete(s.regSet, topic)
   244  
   245  		if s.tickets[topic] == nil {
   246  			continue
   247  		}
   248  		// If the topic needs more tickets, return it
   249  		if s.tickets[topic].nextLookup < mclock.Now() {
   250  			next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond
   251  			log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay)
   252  			return next, delay
   253  		}
   254  	}
   255  	// No registration topics found or all exhausted, sleep
   256  	delay := 40 * time.Second
   257  	log.Trace("No topic found to register", "delay", delay)
   258  	return lookupInfo{}, delay
   259  }
   260  
   261  func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo {
   262  	tr := s.radius[topic]
   263  	target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery)
   264  	if target.radiusLookup {
   265  		tr.radiusLookupCnt++
   266  	} else {
   267  		tr.radiusLookupCnt = 0
   268  	}
   269  	return target
   270  }
   271  
   272  // ticketsInWindow returns the tickets of a given topic in the registration window.
   273  func (s *ticketStore) ticketsInWindow(topic Topic) []ticketRef {
   274  	// Sanity check that the topic still exists before operating on it
   275  	if s.tickets[topic] == nil {
   276  		log.Warn("Listing non-existing discovery tickets", "topic", topic)
   277  		return nil
   278  	}
   279  	// Gather all the tickers in the next time window
   280  	var tickets []ticketRef
   281  
   282  	buckets := s.tickets[topic].buckets
   283  	for idx := timeBucket(0); idx < timeWindow; idx++ {
   284  		tickets = append(tickets, buckets[s.lastBucketFetched+idx]...)
   285  	}
   286  	log.Trace("Retrieved discovery registration tickets", "topic", topic, "from", s.lastBucketFetched, "tickets", len(tickets))
   287  	return tickets
   288  }
   289  
   290  func (s *ticketStore) removeExcessTickets(t Topic) {
   291  	tickets := s.ticketsInWindow(t)
   292  	if len(tickets) <= wantTicketsInWindow {
   293  		return
   294  	}
   295  	sort.Sort(ticketRefByWaitTime(tickets))
   296  	for _, r := range tickets[wantTicketsInWindow:] {
   297  		s.removeTicketRef(r)
   298  	}
   299  }
   300  
   301  type ticketRefByWaitTime []ticketRef
   302  
   303  // Len is the number of elements in the collection.
   304  func (s ticketRefByWaitTime) Len() int {
   305  	return len(s)
   306  }
   307  
   308  func (ref ticketRef) waitTime() mclock.AbsTime {
   309  	return ref.t.regTime[ref.idx] - ref.t.issueTime
   310  }
   311  
   312  // Less reports whether the element with
   313  // index i should sort before the element with index j.
   314  func (s ticketRefByWaitTime) Less(i, j int) bool {
   315  	return s[i].waitTime() < s[j].waitTime()
   316  }
   317  
   318  // Swap swaps the elements with indexes i and j.
   319  func (s ticketRefByWaitTime) Swap(i, j int) {
   320  	s[i], s[j] = s[j], s[i]
   321  }
   322  
   323  func (s *ticketStore) addTicketRef(r ticketRef) {
   324  	topic := r.t.topics[r.idx]
   325  	tickets := s.tickets[topic]
   326  	if tickets == nil {
   327  		log.Warn("Adding ticket to non-existent topic", "topic", topic)
   328  		return
   329  	}
   330  	bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen))
   331  	tickets.buckets[bucket] = append(tickets.buckets[bucket], r)
   332  	r.t.refCnt++
   333  
   334  	min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt
   335  	if tickets.nextLookup < min {
   336  		tickets.nextLookup = min
   337  	}
   338  	tickets.nextLookup += mclock.AbsTime(collectFrequency)
   339  
   340  	//s.removeExcessTickets(topic)
   341  }
   342  
   343  func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) {
   344  	now := mclock.Now()
   345  	for {
   346  		ticket, wait := s.nextRegisterableTicket()
   347  		if ticket == nil {
   348  			return ticket, wait
   349  		}
   350  		log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait)
   351  
   352  		regTime := now + mclock.AbsTime(wait)
   353  		topic := ticket.t.topics[ticket.idx]
   354  		if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg {
   355  			return ticket, wait
   356  		}
   357  		s.removeTicketRef(*ticket)
   358  	}
   359  }
   360  
   361  func (s *ticketStore) ticketRegistered(ref ticketRef) {
   362  	now := mclock.Now()
   363  
   364  	topic := ref.t.topics[ref.idx]
   365  	tickets := s.tickets[topic]
   366  	min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt
   367  	if min > tickets.nextReg {
   368  		tickets.nextReg = min
   369  	}
   370  	tickets.nextReg += mclock.AbsTime(registerFrequency)
   371  	s.tickets[topic] = tickets
   372  
   373  	s.removeTicketRef(ref)
   374  }
   375  
   376  // nextRegisterableTicket returns the next ticket that can be used
   377  // to register.
   378  //
   379  // If the returned wait time <= zero the ticket can be used. For a positive
   380  // wait time, the caller should requery the next ticket later.
   381  //
   382  // A ticket can be returned more than once with <= zero wait time in case
   383  // the ticket contains multiple topics.
   384  func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) {
   385  	now := mclock.Now()
   386  	if s.nextTicketCached != nil {
   387  		return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now)
   388  	}
   389  
   390  	for bucket := s.lastBucketFetched; ; bucket++ {
   391  		var (
   392  			empty      = true    // true if there are no tickets
   393  			nextTicket ticketRef // uninitialized if this bucket is empty
   394  		)
   395  		for _, tickets := range s.tickets {
   396  			//s.removeExcessTickets(topic)
   397  			if len(tickets.buckets) != 0 {
   398  				empty = false
   399  
   400  				list := tickets.buckets[bucket]
   401  				for _, ref := range list {
   402  					//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)))
   403  					if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() {
   404  						nextTicket = ref
   405  					}
   406  				}
   407  			}
   408  		}
   409  		if empty {
   410  			return nil, 0
   411  		}
   412  		if nextTicket.t != nil {
   413  			s.nextTicketCached = &nextTicket
   414  			return &nextTicket, time.Duration(nextTicket.topicRegTime() - now)
   415  		}
   416  		s.lastBucketFetched = bucket
   417  	}
   418  }
   419  
   420  // removeTicket removes a ticket from the ticket store
   421  func (s *ticketStore) removeTicketRef(ref ticketRef) {
   422  	log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial)
   423  
   424  	// Make nextRegisterableTicket return the next available ticket.
   425  	s.nextTicketCached = nil
   426  
   427  	topic := ref.topic()
   428  	tickets := s.tickets[topic]
   429  
   430  	if tickets == nil {
   431  		log.Trace("Removing tickets from unknown topic", "topic", topic)
   432  		return
   433  	}
   434  	bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen))
   435  	list := tickets.buckets[bucket]
   436  	idx := -1
   437  	for i, bt := range list {
   438  		if bt.t == ref.t {
   439  			idx = i
   440  			break
   441  		}
   442  	}
   443  	if idx == -1 {
   444  		panic(nil)
   445  	}
   446  	list = append(list[:idx], list[idx+1:]...)
   447  	if len(list) != 0 {
   448  		tickets.buckets[bucket] = list
   449  	} else {
   450  		delete(tickets.buckets, bucket)
   451  	}
   452  	ref.t.refCnt--
   453  	if ref.t.refCnt == 0 {
   454  		delete(s.nodes, ref.t.node)
   455  		delete(s.nodeLastReq, ref.t.node)
   456  	}
   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, 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: nil, 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, ticket *ticket) {
   527  	log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial)
   528  
   529  	lastReq, ok := s.nodeLastReq[ticket.node]
   530  	if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) {
   531  		return
   532  	}
   533  	s.adjustWithTicket(localTime, lastReq.lookup.target, ticket)
   534  
   535  	if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil {
   536  		return
   537  	}
   538  
   539  	topic := lastReq.lookup.topic
   540  	topicIdx := ticket.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 := ticket.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", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong)
   559  			s.addTicketRef(ticketRef{ticket, topicIdx})
   560  		}
   561  	}
   562  
   563  	if ticket.refCnt > 0 {
   564  		s.nextTicketCached = nil
   565  		s.nodes[ticket.node] = ticket
   566  	}
   567  }
   568  
   569  func (s *ticketStore) getNodeTicket(node *Node) *ticket {
   570  	if s.nodes[node] == nil {
   571  		log.Trace("Retrieving node ticket", "node", node.ID, "serial", nil)
   572  	} else {
   573  		log.Trace("Retrieving node ticket", "node", node.ID, "serial", 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, node.TCP)
   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  }