github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/discv5/topic.go (about)

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