github.com/aswedchain/aswed@v1.0.1/core/tx_jam_indexer.go (about)

     1  package core
     2  
     3  import (
     4  	"math/big"
     5  	"sort"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/aswedchain/aswed/core/types"
    10  	"github.com/aswedchain/aswed/log"
    11  	"github.com/aswedchain/aswed/metrics"
    12  )
    13  
    14  var (
    15  	jamIndexMeter = metrics.NewRegisteredMeter("txpool/jamindex", nil)
    16  )
    17  
    18  var oneGwei = big.NewInt(1e9)
    19  
    20  var DefaultJamConfig = TxJamConfig{
    21  	PeriodsSecs:         3,
    22  	JamSecs:             15,
    23  	UnderPricedFactor:   3,
    24  	PendingFactor:       1,
    25  	MaxValidPendingSecs: 300,
    26  }
    27  
    28  type TxJamConfig struct {
    29  	PeriodsSecs       int // how many seconds to do a reflesh to the jam index
    30  	JamSecs           int // how many seconds for a tx pending that will meams a tx-jam.
    31  	UnderPricedFactor int
    32  	PendingFactor     int
    33  
    34  	MaxValidPendingSecs int //
    35  }
    36  
    37  func (c *TxJamConfig) sanity() TxJamConfig {
    38  	cfg := *c
    39  	if cfg.PeriodsSecs < 1 {
    40  		log.Info("JamConfig sanity PeriodsSecs", "old", cfg.PeriodsSecs, "new", DefaultJamConfig.PeriodsSecs)
    41  		cfg.PeriodsSecs = DefaultJamConfig.PeriodsSecs
    42  	}
    43  	if cfg.JamSecs < 3 {
    44  		log.Info("JamConfig sanity JamSecs", "old", cfg.JamSecs, "new", DefaultJamConfig.JamSecs)
    45  		cfg.JamSecs = DefaultJamConfig.JamSecs
    46  	}
    47  	if cfg.UnderPricedFactor < 1 {
    48  		log.Info("JamConfig sanity UnderPricedFactor", "old", cfg.UnderPricedFactor, "new", DefaultJamConfig.UnderPricedFactor)
    49  		cfg.UnderPricedFactor = DefaultJamConfig.UnderPricedFactor
    50  	}
    51  	if cfg.PendingFactor < 1 {
    52  		log.Info("JamConfig sanity PendingFactor", "old", cfg.PendingFactor, "new", DefaultJamConfig.PendingFactor)
    53  		cfg.PendingFactor = DefaultJamConfig.PendingFactor
    54  	}
    55  	if cfg.MaxValidPendingSecs <= cfg.JamSecs {
    56  		log.Info("JamConfig sanity MaxValidPendingSecs", "old", cfg.MaxValidPendingSecs, "new", DefaultJamConfig.MaxValidPendingSecs)
    57  		cfg.MaxValidPendingSecs = DefaultJamConfig.MaxValidPendingSecs
    58  	}
    59  	return cfg
    60  }
    61  
    62  // txJamIndexer try to give a quantitative index to reflects the tx-jam.
    63  type txJamIndexer struct {
    64  	cfg  TxJamConfig
    65  	pool *TxPool
    66  	head *types.Header
    67  
    68  	undCounter      *underPricedCounter
    69  	currentJamIndex int
    70  
    71  	pendingLock sync.Mutex
    72  	jamLock     sync.RWMutex
    73  
    74  	quit        chan struct{}
    75  	chainHeadCh chan *types.Header
    76  }
    77  
    78  func newTxJamIndexer(cfg TxJamConfig, pool *TxPool) *txJamIndexer {
    79  	cfg = (&cfg).sanity()
    80  
    81  	indexer := &txJamIndexer{
    82  		cfg:         cfg,
    83  		pool:        pool,
    84  		undCounter:  newUnderPricedCounter(cfg.PeriodsSecs),
    85  		quit:        make(chan struct{}),
    86  		chainHeadCh: make(chan *types.Header, 1),
    87  	}
    88  
    89  	go indexer.updateLoop()
    90  
    91  	return indexer
    92  }
    93  
    94  // Stop stops the loop goroutines of this TxJamIndexer
    95  func (indexer *txJamIndexer) Stop() {
    96  	indexer.undCounter.Stop()
    97  	close(indexer.quit)
    98  }
    99  
   100  // JamIndex returns the current jam index
   101  func (indexer *txJamIndexer) JamIndex() int {
   102  	indexer.jamLock.RLock()
   103  	defer indexer.jamLock.RUnlock()
   104  	return indexer.currentJamIndex
   105  }
   106  
   107  func (indexer *txJamIndexer) updateLoop() {
   108  	tick := time.NewTicker(time.Second * time.Duration(indexer.cfg.PeriodsSecs))
   109  	defer tick.Stop()
   110  
   111  	for {
   112  		select {
   113  		case h := <-indexer.chainHeadCh:
   114  			indexer.head = h
   115  		case <-tick.C:
   116  			d := indexer.undCounter.Sum()
   117  			pendings, _ := indexer.pool.Pending()
   118  			if d == 0 && len(pendings) == 0 {
   119  				break
   120  			}
   121  			// flatten
   122  			var p int
   123  			max := indexer.cfg.MaxValidPendingSecs
   124  			jamsecs := indexer.cfg.JamSecs
   125  			maxGas := uint64(10000000)
   126  			if indexer.head != nil {
   127  				maxGas = (indexer.head.GasLimit / 10) * 6
   128  			}
   129  			durs := make([]time.Duration, 0, 1024)
   130  			for _, txs := range pendings {
   131  				for _, tx := range txs {
   132  					// filtering
   133  					if tx.GasPrice().Cmp(oneGwei) < 0 ||
   134  						tx.Gas() > maxGas {
   135  						continue
   136  					}
   137  
   138  					dur := time.Since(tx.LocalSeenTime())
   139  					sec := int(dur / time.Second)
   140  					if sec > max {
   141  						continue
   142  					}
   143  
   144  					durs = append(durs, dur)
   145  					if sec >= jamsecs {
   146  						p += sec / jamsecs
   147  					}
   148  				}
   149  			}
   150  			nTotal := len(durs)
   151  
   152  			if nTotal == 0 {
   153  				p = 0
   154  			} else {
   155  				p = 100 * p / nTotal
   156  			}
   157  
   158  			idx := d*indexer.cfg.UnderPricedFactor + p*indexer.cfg.PendingFactor
   159  			indexer.jamLock.Lock()
   160  			indexer.currentJamIndex = idx
   161  			indexer.jamLock.Unlock()
   162  			jamIndexMeter.Mark(int64(idx))
   163  
   164  			var dists []time.Duration
   165  			sort.Slice(durs, func(i, j int) bool {
   166  				return durs[i] < durs[j]
   167  			})
   168  			if nTotal > 10 {
   169  				dists = append(dists, durs[0])
   170  				for i := 1; i < 10; i++ {
   171  					dists = append(dists, durs[nTotal*i/10])
   172  				}
   173  				dists = append(dists, durs[nTotal-1])
   174  			} else {
   175  				dists = durs
   176  			}
   177  
   178  			log.Trace("TxJamIndexer", "jamIndex", idx, "d", d, "p", p, "n", nTotal, "dists", dists)
   179  		case <-indexer.quit:
   180  			return
   181  		}
   182  	}
   183  }
   184  
   185  func (indexer *txJamIndexer) UpdateHeader(h *types.Header) {
   186  	indexer.chainHeadCh <- h
   187  }
   188  
   189  func (indexer *txJamIndexer) UnderPricedInc() {
   190  	indexer.undCounter.Inc()
   191  }
   192  
   193  type underPricedCounter struct {
   194  	counts  []int // the lenght of this slice is 2 times of periodSecs
   195  	periods int   //how many periods to cache, each period cache records of 0.5 seconds.
   196  	idx     int   //current index
   197  	sum     int   //current sum
   198  
   199  	inCh       chan struct{}
   200  	quit       chan struct{}
   201  	queryCh    chan struct{}
   202  	queryResCh chan int
   203  }
   204  
   205  func newUnderPricedCounter(periodSecs int) *underPricedCounter {
   206  	c := &underPricedCounter{
   207  		counts:     make([]int, 2*periodSecs),
   208  		periods:    2 * periodSecs,
   209  		inCh:       make(chan struct{}, 10),
   210  		quit:       make(chan struct{}),
   211  		queryCh:    make(chan struct{}),
   212  		queryResCh: make(chan int),
   213  	}
   214  	go c.loop()
   215  	return c
   216  }
   217  
   218  func (c *underPricedCounter) loop() {
   219  	tick := time.NewTicker(500 * time.Millisecond)
   220  	defer tick.Stop()
   221  
   222  	for {
   223  		select {
   224  		case <-tick.C:
   225  			c.idx = (c.idx + 1) % c.periods
   226  			c.sum -= c.counts[c.idx]
   227  			c.counts[c.idx] = 0
   228  		case <-c.inCh:
   229  			c.counts[c.idx]++
   230  			c.sum++
   231  		case <-c.queryCh:
   232  			c.queryResCh <- c.sum
   233  		case <-c.quit:
   234  			return
   235  		}
   236  	}
   237  }
   238  
   239  func (c *underPricedCounter) Sum() int {
   240  	var sum int
   241  	c.queryCh <- struct{}{}
   242  	sum = <-c.queryResCh
   243  	return sum
   244  }
   245  
   246  func (c *underPricedCounter) Inc() {
   247  	c.inCh <- struct{}{}
   248  }
   249  
   250  func (c *underPricedCounter) Stop() {
   251  	close(c.quit)
   252  }