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