github.com/core-coin/go-core/v2@v2.1.9/p2p/discv5/ticket.go (about)

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