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 }