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