github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/discv5/topic.go (about)

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