github.com/aswedchain/aswed@v1.0.1/eth/gasprice/prediction.go (about)

     1  package gasprice
     2  
     3  import (
     4  	"context"
     5  	"math/big"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/aswedchain/aswed/core"
    11  	"github.com/aswedchain/aswed/event"
    12  	"github.com/aswedchain/aswed/log"
    13  	"github.com/aswedchain/aswed/rpc"
    14  )
    15  
    16  var (
    17  	tenthGwei = big.NewInt(1e8)
    18  	gwei      = big.NewInt(1e9)
    19  )
    20  
    21  type Prediction struct {
    22  	cfg          *Config
    23  	txCnts       *Stats // tx count statistics of few latest blocks
    24  	backend      OracleBackend
    25  	chainHeadCh  chan core.ChainHeadEvent
    26  	chainHeadSub event.Subscription
    27  	pool         *core.TxPool
    28  
    29  	predis        []uint // gas price prediction in gwei, currently will be 3 items, from hight(fast) to low(slow)
    30  	lockPredis    sync.RWMutex
    31  	wg            sync.WaitGroup
    32  	blockGasLimit uint64
    33  }
    34  
    35  func NewPrediction(cfg Config, backend OracleBackend, pool *core.TxPool) *Prediction {
    36  	if cfg.Blocks == 0 || cfg.FastFactor == 0 {
    37  		//some test case offers no config
    38  		return &Prediction{
    39  			predis: make([]uint, 3),
    40  		}
    41  	}
    42  	p := &Prediction{
    43  		cfg:         &cfg,
    44  		backend:     backend,
    45  		chainHeadCh: make(chan core.ChainHeadEvent),
    46  		pool:        pool,
    47  	}
    48  	price := wei2GWei(cfg.Default)
    49  	p.predis = []uint{price * 2, price, price}
    50  
    51  	// init txCnts
    52  	p.initTxCnts()
    53  
    54  	//subscripts chain head events
    55  	p.chainHeadSub = backend.SubscribeChainHeadEvent(p.chainHeadCh)
    56  	p.wg.Add(1)
    57  	go p.loop()
    58  
    59  	log.Info("Prediction started", "checkBlocks", cfg.Blocks, "Interval", cfg.PredictIntervalSecs,
    60  		"ff", cfg.FastFactor, "mf", cfg.MedianFactor, "lf", cfg.LowFactor, "minMi", cfg.MinMedianIndex,
    61  		"minLi", cfg.MinLowIndex, "fp", cfg.FastPercentile, "mp", cfg.MeidanPercentile, "minCnt", cfg.MinTxCntPerBlock)
    62  	return p
    63  }
    64  
    65  // Stop stops the prediction loop
    66  func (p *Prediction) Stop() {
    67  	if p.chainHeadSub == nil {
    68  		return
    69  	}
    70  	p.chainHeadSub.Unsubscribe()
    71  	p.wg.Wait()
    72  	log.Info("prediction quit")
    73  }
    74  
    75  // CurrentPrices returns the current prediction about gas price in gwei;
    76  // the results should be readonly, and the reason didn't do a copy is that there's no necessary
    77  func (p *Prediction) CurrentPrices() []uint {
    78  	p.lockPredis.RLock()
    79  	defer p.lockPredis.RUnlock()
    80  	prices := p.predis
    81  	return prices
    82  }
    83  
    84  func (p *Prediction) initTxCnts() {
    85  	cnts := make([]int, p.cfg.Blocks)
    86  	ctx := context.Background()
    87  	head, _ := p.backend.HeaderByNumber(context.Background(), rpc.LatestBlockNumber)
    88  	num := head.Number.Uint64()
    89  	if num > uint64(p.cfg.Blocks) {
    90  		for i, j := num-uint64(p.cfg.Blocks), 0; i < num; i, j = i+1, j+1 {
    91  			block, err := p.backend.BlockByNumber(ctx, rpc.BlockNumber(i))
    92  			if err != nil {
    93  				log.Warn("Prediction, get block by number failed", "err", err)
    94  				continue
    95  			}
    96  			cnts[j] = block.Transactions().Len()
    97  		}
    98  	} else if num > 0 {
    99  		i := 1
   100  		for ; i <= int(num); i++ {
   101  			block, err := p.backend.BlockByNumber(ctx, rpc.BlockNumber(i))
   102  			if err != nil {
   103  				log.Warn("Prediction, get block by number failed", "err", err)
   104  				continue
   105  			}
   106  			cnts[i-1] = block.Transactions().Len()
   107  		}
   108  		for ; i < p.cfg.Blocks; i++ {
   109  			cnts[i] = cnts[i-1]
   110  		}
   111  	}
   112  	p.txCnts = NewStats(cnts)
   113  
   114  	//gas limit
   115  	p.blockGasLimit = head.GasLimit
   116  }
   117  
   118  func (p *Prediction) loop() {
   119  	// do an updates first
   120  	p.update()
   121  
   122  	tick := time.NewTicker(time.Duration(p.cfg.PredictIntervalSecs) * time.Second)
   123  	defer tick.Stop()
   124  	defer p.wg.Done()
   125  
   126  	for {
   127  		select {
   128  		case <-tick.C:
   129  			p.update()
   130  		case ev := <-p.chainHeadCh:
   131  			head := ev.Block
   132  			txcnt := len(head.Transactions())
   133  			p.txCnts.Add(txcnt)
   134  			p.blockGasLimit = head.GasLimit()
   135  		case <-p.chainHeadSub.Err():
   136  			log.Warn("prediction loop quitting")
   137  			return
   138  		}
   139  	}
   140  }
   141  
   142  func (p *Prediction) update() {
   143  	txs, err := p.pool.Pending()
   144  	if err != nil {
   145  		log.Error("failed to get pending transactions", "err", err)
   146  		return
   147  	}
   148  	byprice := make(TxByPrice, 0, len(txs))
   149  	for _, ts := range txs {
   150  		byprice = append(byprice, ts...)
   151  	}
   152  	byprice = p.filteroutInvalid(byprice)
   153  	sort.Sort(byprice)
   154  
   155  	minPrice := wei2GWei(p.pool.GasPrice())
   156  	prices := make([]uint, 3)
   157  
   158  	pendingCnt := len(byprice)
   159  	if pendingCnt == 0 {
   160  		// no pending tx, use minimum prices
   161  		prices = []uint{minPrice, minPrice, minPrice}
   162  		p.updatePredis(prices)
   163  		return
   164  	}
   165  
   166  	avgTxCnt := p.txCnts.Avg()
   167  	if avgTxCnt < p.cfg.MinTxCntPerBlock {
   168  		avgTxCnt = p.cfg.MinTxCntPerBlock
   169  	}
   170  
   171  	// fast price
   172  	fi := p.cfg.FastFactor * avgTxCnt
   173  	if pendingCnt <= fi {
   174  		fi = pendingCnt * p.cfg.FastPercentile / 100
   175  	}
   176  	prices[0] = wei2GWei(byprice[fi].GasPrice()) // fast price
   177  	// if the fast price is 1 gwei, and there are lots of pending transactions,
   178  	// then raise the fast price to 2 gwei.
   179  	if prices[0] == 1 && pendingCnt > fi {
   180  		prices[0] = 2
   181  	}
   182  	// median price
   183  	mi := max(p.cfg.MedianFactor*avgTxCnt, p.cfg.MinMedianIndex)
   184  	if pendingCnt <= mi {
   185  		mi = pendingCnt * p.cfg.MeidanPercentile / 100
   186  	}
   187  	prices[1] = wei2GWei(byprice[mi].GasPrice())
   188  
   189  	// low price, notice the differentce
   190  	li := max(p.cfg.LowFactor*avgTxCnt, p.cfg.MinLowIndex)
   191  	if pendingCnt <= li {
   192  		prices[2] = minPrice
   193  	} else {
   194  		prices[2] = wei2GWei(byprice[li].GasPrice())
   195  	}
   196  	// make it more moderation
   197  	if pendingCnt > mi &&
   198  		prices[0] > prices[1]+1 &&
   199  		prices[1] == prices[2] {
   200  		prices[1]++
   201  	}
   202  
   203  	p.updatePredis(prices)
   204  }
   205  
   206  func (p *Prediction) filteroutInvalid(txs TxByPrice) TxByPrice {
   207  	maxgas := (p.blockGasLimit / 10) * 6
   208  	maxlive := time.Duration(p.cfg.MaxValidPendingSecs) * time.Second
   209  	i, j := 0, len(txs)
   210  	for i < j {
   211  		tx := txs[i]
   212  		if tx.Gas() > maxgas ||
   213  			time.Since(tx.LocalSeenTime()) > maxlive ||
   214  			tx.GasPriceIntCmp(gwei) < 0 {
   215  			j--
   216  			txs[i], txs[j] = txs[j], txs[i]
   217  			continue
   218  		}
   219  		//valid
   220  		i++
   221  	}
   222  	return txs[:j]
   223  }
   224  
   225  func (p *Prediction) updatePredis(prices []uint) {
   226  	p.lockPredis.Lock()
   227  	for i := 0; i < 3; i++ {
   228  		p.predis[i] = prices[i]
   229  	}
   230  	p.lockPredis.Unlock()
   231  }
   232  
   233  func max(a, b int) int {
   234  	if a > b {
   235  		return a
   236  	}
   237  	return b
   238  }
   239  
   240  func wei2GWei(w *big.Int) uint {
   241  	if nil == w {
   242  		return 1
   243  	}
   244  	tgwei := new(big.Int).Div(w, tenthGwei).Uint64()
   245  	if tgwei < 10 {
   246  		return 1
   247  	}
   248  	return (uint(tgwei)*2 - 10) / 10 // rounding
   249  }