github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/p2p/discover/dht/topic.go (about)

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