github.com/blocknative/go-ethereum@v1.9.7/p2p/discv5/topic.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  	"container/heap"
    21  	"fmt"
    22  	"math"
    23  	"math/rand"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common/mclock"
    27  	"github.com/ethereum/go-ethereum/log"
    28  )
    29  
    30  const (
    31  	maxEntries         = 10000
    32  	maxEntriesPerTopic = 50
    33  
    34  	fallbackRegistrationExpiry = 1 * time.Hour
    35  )
    36  
    37  type Topic string
    38  
    39  type topicEntry struct {
    40  	topic   Topic
    41  	fifoIdx uint64
    42  	node    *Node
    43  	expire  mclock.AbsTime
    44  }
    45  
    46  type topicInfo struct {
    47  	entries            map[uint64]*topicEntry
    48  	fifoHead, fifoTail uint64
    49  	rqItem             *topicRequestQueueItem
    50  	wcl                waitControlLoop
    51  }
    52  
    53  // removes tail element from the fifo
    54  func (t *topicInfo) getFifoTail() *topicEntry {
    55  	for t.entries[t.fifoTail] == nil {
    56  		t.fifoTail++
    57  	}
    58  	tail := t.entries[t.fifoTail]
    59  	t.fifoTail++
    60  	return tail
    61  }
    62  
    63  type nodeInfo struct {
    64  	entries                          map[Topic]*topicEntry
    65  	lastIssuedTicket, lastUsedTicket uint32
    66  	// you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time)
    67  	noRegUntil mclock.AbsTime
    68  }
    69  
    70  type topicTable struct {
    71  	db                    *nodeDB
    72  	self                  *Node
    73  	nodes                 map[*Node]*nodeInfo
    74  	topics                map[Topic]*topicInfo
    75  	globalEntries         uint64
    76  	requested             topicRequestQueue
    77  	requestCnt            uint64
    78  	lastGarbageCollection mclock.AbsTime
    79  }
    80  
    81  func newTopicTable(db *nodeDB, self *Node) *topicTable {
    82  	if printTestImgLogs {
    83  		fmt.Printf("*N %016x\n", self.sha[:8])
    84  	}
    85  	return &topicTable{
    86  		db:     db,
    87  		nodes:  make(map[*Node]*nodeInfo),
    88  		topics: make(map[Topic]*topicInfo),
    89  		self:   self,
    90  	}
    91  }
    92  
    93  func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo {
    94  	ti := t.topics[topic]
    95  	if ti == nil {
    96  		rqItem := &topicRequestQueueItem{
    97  			topic:    topic,
    98  			priority: t.requestCnt,
    99  		}
   100  		ti = &topicInfo{
   101  			entries: make(map[uint64]*topicEntry),
   102  			rqItem:  rqItem,
   103  		}
   104  		t.topics[topic] = ti
   105  		heap.Push(&t.requested, rqItem)
   106  	}
   107  	return ti
   108  }
   109  
   110  func (t *topicTable) checkDeleteTopic(topic Topic) {
   111  	ti := t.topics[topic]
   112  	if ti == nil {
   113  		return
   114  	}
   115  	if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() {
   116  		delete(t.topics, topic)
   117  		heap.Remove(&t.requested, ti.rqItem.index)
   118  	}
   119  }
   120  
   121  func (t *topicTable) getOrNewNode(node *Node) *nodeInfo {
   122  	n := t.nodes[node]
   123  	if n == nil {
   124  		//fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8])
   125  		var issued, used uint32
   126  		if t.db != nil {
   127  			issued, used = t.db.fetchTopicRegTickets(node.ID)
   128  		}
   129  		n = &nodeInfo{
   130  			entries:          make(map[Topic]*topicEntry),
   131  			lastIssuedTicket: issued,
   132  			lastUsedTicket:   used,
   133  		}
   134  		t.nodes[node] = n
   135  	}
   136  	return n
   137  }
   138  
   139  func (t *topicTable) checkDeleteNode(node *Node) {
   140  	if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < mclock.Now() {
   141  		//fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8])
   142  		delete(t.nodes, node)
   143  	}
   144  }
   145  
   146  func (t *topicTable) storeTicketCounters(node *Node) {
   147  	n := t.getOrNewNode(node)
   148  	if t.db != nil {
   149  		t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket)
   150  	}
   151  }
   152  
   153  func (t *topicTable) getEntries(topic Topic) []*Node {
   154  	t.collectGarbage()
   155  
   156  	te := t.topics[topic]
   157  	if te == nil {
   158  		return nil
   159  	}
   160  	nodes := make([]*Node, len(te.entries))
   161  	i := 0
   162  	for _, e := range te.entries {
   163  		nodes[i] = e.node
   164  		i++
   165  	}
   166  	t.requestCnt++
   167  	t.requested.update(te.rqItem, t.requestCnt)
   168  	return nodes
   169  }
   170  
   171  func (t *topicTable) addEntry(node *Node, topic Topic) {
   172  	n := t.getOrNewNode(node)
   173  	// clear previous entries by the same node
   174  	for _, e := range n.entries {
   175  		t.deleteEntry(e)
   176  	}
   177  	// ***
   178  	n = t.getOrNewNode(node)
   179  
   180  	tm := mclock.Now()
   181  	te := t.getOrNewTopic(topic)
   182  
   183  	if len(te.entries) == maxEntriesPerTopic {
   184  		t.deleteEntry(te.getFifoTail())
   185  	}
   186  
   187  	if t.globalEntries == maxEntries {
   188  		t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil
   189  	}
   190  
   191  	fifoIdx := te.fifoHead
   192  	te.fifoHead++
   193  	entry := &topicEntry{
   194  		topic:   topic,
   195  		fifoIdx: fifoIdx,
   196  		node:    node,
   197  		expire:  tm + mclock.AbsTime(fallbackRegistrationExpiry),
   198  	}
   199  	if printTestImgLogs {
   200  		fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8])
   201  	}
   202  	te.entries[fifoIdx] = entry
   203  	n.entries[topic] = entry
   204  	t.globalEntries++
   205  	te.wcl.registered(tm)
   206  }
   207  
   208  // removes least requested element from the fifo
   209  func (t *topicTable) leastRequested() *topicEntry {
   210  	for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil {
   211  		heap.Pop(&t.requested)
   212  	}
   213  	if t.requested.Len() == 0 {
   214  		return nil
   215  	}
   216  	return t.topics[t.requested[0].topic].getFifoTail()
   217  }
   218  
   219  // entry should exist
   220  func (t *topicTable) deleteEntry(e *topicEntry) {
   221  	if printTestImgLogs {
   222  		fmt.Printf("*- %d %v %016x %016x\n", mclock.Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8])
   223  	}
   224  	ne := t.nodes[e.node].entries
   225  	delete(ne, e.topic)
   226  	if len(ne) == 0 {
   227  		t.checkDeleteNode(e.node)
   228  	}
   229  	te := t.topics[e.topic]
   230  	delete(te.entries, e.fifoIdx)
   231  	if len(te.entries) == 0 {
   232  		t.checkDeleteTopic(e.topic)
   233  	}
   234  	t.globalEntries--
   235  }
   236  
   237  // It is assumed that topics and waitPeriods have the same length.
   238  func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) {
   239  	log.Trace("Using discovery ticket", "serial", serialNo, "topics", topics, "waits", waitPeriods)
   240  	//fmt.Println("useTicket", serialNo, topics, waitPeriods)
   241  	t.collectGarbage()
   242  
   243  	n := t.getOrNewNode(node)
   244  	if serialNo < n.lastUsedTicket {
   245  		return false
   246  	}
   247  
   248  	tm := mclock.Now()
   249  	if serialNo > n.lastUsedTicket && tm < n.noRegUntil {
   250  		return false
   251  	}
   252  	if serialNo != n.lastUsedTicket {
   253  		n.lastUsedTicket = serialNo
   254  		n.noRegUntil = tm + mclock.AbsTime(noRegTimeout())
   255  		t.storeTicketCounters(node)
   256  	}
   257  
   258  	currTime := uint64(tm / mclock.AbsTime(time.Second))
   259  	regTime := issueTime + uint64(waitPeriods[idx])
   260  	relTime := int64(currTime - regTime)
   261  	if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends
   262  		if e := n.entries[topics[idx]]; e == nil {
   263  			t.addEntry(node, topics[idx])
   264  		} else {
   265  			// if there is an active entry, don't move to the front of the FIFO but prolong expire time
   266  			e.expire = tm + mclock.AbsTime(fallbackRegistrationExpiry)
   267  		}
   268  		return true
   269  	}
   270  
   271  	return false
   272  }
   273  
   274  func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket {
   275  	t.collectGarbage()
   276  
   277  	now := mclock.Now()
   278  	n := t.getOrNewNode(node)
   279  	n.lastIssuedTicket++
   280  	t.storeTicketCounters(node)
   281  
   282  	tic := &ticket{
   283  		issueTime: now,
   284  		topics:    topics,
   285  		serial:    n.lastIssuedTicket,
   286  		regTime:   make([]mclock.AbsTime, len(topics)),
   287  	}
   288  	for i, topic := range topics {
   289  		var waitPeriod time.Duration
   290  		if topic := t.topics[topic]; topic != nil {
   291  			waitPeriod = topic.wcl.waitPeriod
   292  		} else {
   293  			waitPeriod = minWaitPeriod
   294  		}
   295  
   296  		tic.regTime[i] = now + mclock.AbsTime(waitPeriod)
   297  	}
   298  	return tic
   299  }
   300  
   301  const gcInterval = time.Minute
   302  
   303  func (t *topicTable) collectGarbage() {
   304  	tm := mclock.Now()
   305  	if time.Duration(tm-t.lastGarbageCollection) < gcInterval {
   306  		return
   307  	}
   308  	t.lastGarbageCollection = tm
   309  
   310  	for node, n := range t.nodes {
   311  		for _, e := range n.entries {
   312  			if e.expire <= tm {
   313  				t.deleteEntry(e)
   314  			}
   315  		}
   316  
   317  		t.checkDeleteNode(node)
   318  	}
   319  
   320  	for topic := range t.topics {
   321  		t.checkDeleteTopic(topic)
   322  	}
   323  }
   324  
   325  const (
   326  	minWaitPeriod   = time.Minute
   327  	regTimeWindow   = 10 // seconds
   328  	avgnoRegTimeout = time.Minute * 10
   329  	// target average interval between two incoming ad requests
   330  	wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic
   331  	//
   332  	wcTimeConst = time.Minute * 10
   333  )
   334  
   335  // initialization is not required, will set to minWaitPeriod at first registration
   336  type waitControlLoop struct {
   337  	lastIncoming mclock.AbsTime
   338  	waitPeriod   time.Duration
   339  }
   340  
   341  func (w *waitControlLoop) registered(tm mclock.AbsTime) {
   342  	w.waitPeriod = w.nextWaitPeriod(tm)
   343  	w.lastIncoming = tm
   344  }
   345  
   346  func (w *waitControlLoop) nextWaitPeriod(tm mclock.AbsTime) time.Duration {
   347  	period := tm - w.lastIncoming
   348  	wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst)))
   349  	if wp < minWaitPeriod {
   350  		wp = minWaitPeriod
   351  	}
   352  	return wp
   353  }
   354  
   355  func (w *waitControlLoop) hasMinimumWaitPeriod() bool {
   356  	return w.nextWaitPeriod(mclock.Now()) == minWaitPeriod
   357  }
   358  
   359  func noRegTimeout() time.Duration {
   360  	e := rand.ExpFloat64()
   361  	if e > 100 {
   362  		e = 100
   363  	}
   364  	return time.Duration(float64(avgnoRegTimeout) * e)
   365  }
   366  
   367  type topicRequestQueueItem struct {
   368  	topic    Topic
   369  	priority uint64
   370  	index    int
   371  }
   372  
   373  // A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems.
   374  type topicRequestQueue []*topicRequestQueueItem
   375  
   376  func (tq topicRequestQueue) Len() int { return len(tq) }
   377  
   378  func (tq topicRequestQueue) Less(i, j int) bool {
   379  	return tq[i].priority < tq[j].priority
   380  }
   381  
   382  func (tq topicRequestQueue) Swap(i, j int) {
   383  	tq[i], tq[j] = tq[j], tq[i]
   384  	tq[i].index = i
   385  	tq[j].index = j
   386  }
   387  
   388  func (tq *topicRequestQueue) Push(x interface{}) {
   389  	n := len(*tq)
   390  	item := x.(*topicRequestQueueItem)
   391  	item.index = n
   392  	*tq = append(*tq, item)
   393  }
   394  
   395  func (tq *topicRequestQueue) Pop() interface{} {
   396  	old := *tq
   397  	n := len(old)
   398  	item := old[n-1]
   399  	item.index = -1
   400  	*tq = old[0 : n-1]
   401  	return item
   402  }
   403  
   404  func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) {
   405  	item.priority = priority
   406  	heap.Fix(tq, item.index)
   407  }