github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/gasprice/reactive.go (about) 1 package gasprice 2 3 import ( 4 "math/big" 5 "sort" 6 "sync/atomic" 7 "time" 8 9 "github.com/unicornultrafoundation/go-helios/utils/piecefunc" 10 "github.com/unicornultrafoundation/go-u2u/core/types" 11 ) 12 13 const ( 14 percentilesPerStat = 20 15 statUpdatePeriod = 1 * time.Second 16 statsBuffer = int((15 * time.Second) / statUpdatePeriod) 17 maxGasToIndex = 40000000 18 ) 19 20 type txpoolStat struct { 21 totalGas uint64 22 percentiles [percentilesPerStat]*big.Int 23 } 24 25 type circularTxpoolStats struct { 26 stats [statsBuffer]txpoolStat 27 i int 28 activated uint32 29 avg atomic.Value 30 } 31 32 var certaintyToGasAbove = piecefunc.NewFunc([]piecefunc.Dot{ 33 { 34 X: 0, 35 Y: 50000000, 36 }, 37 { 38 X: 0.2 * DecimalUnit, 39 Y: 20000000, 40 }, 41 { 42 X: 0.5 * DecimalUnit, 43 Y: 8000000, 44 }, 45 { 46 X: DecimalUnit, 47 Y: 0, 48 }, 49 }) 50 51 func (gpo *Oracle) reactiveGasPrice(certainty uint64) *big.Int { 52 gasAbove := certaintyToGasAbove(certainty) 53 54 return gpo.c.getGasPriceForGasAbove(gasAbove) 55 } 56 57 func (gpo *Oracle) txpoolStatsTick() { 58 c := &gpo.c 59 // calculate txpool statistic and push into the circular buffer 60 c.stats[c.i] = gpo.calcTxpoolStat() 61 c.i = (c.i + 1) % len(c.stats) 62 // calculate average of statistics in the circular buffer 63 c.avg.Store(c.calcAvg()) 64 } 65 66 func (gpo *Oracle) txpoolStatsLoop() { 67 ticker := time.NewTicker(statUpdatePeriod) 68 defer ticker.Stop() 69 for i := uint32(0); ; i++ { 70 select { 71 case <-ticker.C: 72 // calculate more frequently after first request 73 if atomic.LoadUint32(&gpo.c.activated) != 0 || i%5 == 0 { 74 gpo.txpoolStatsTick() 75 } 76 case <-gpo.quit: 77 return 78 } 79 } 80 } 81 82 func (c *circularTxpoolStats) dec(v int) int { 83 if v == 0 { 84 return len(c.stats) - 1 85 } 86 return v - 1 87 } 88 89 // calcAvg calculates average of statistics in the circular buffer 90 func (c *circularTxpoolStats) calcAvg() txpoolStat { 91 avg := txpoolStat{} 92 for p := range avg.percentiles { 93 avg.percentiles[p] = new(big.Int) 94 } 95 nonZero := uint64(0) 96 for _, s := range c.stats { 97 if s.totalGas == 0 { 98 continue 99 } 100 nonZero++ 101 avg.totalGas += s.totalGas 102 for p := range s.percentiles { 103 if s.percentiles[p] == nil { 104 continue 105 } 106 avg.percentiles[p].Add(avg.percentiles[p], s.percentiles[p]) 107 } 108 } 109 if nonZero == 0 { 110 return avg 111 } 112 avg.totalGas /= nonZero 113 nonZeroBn := new(big.Int).SetUint64(nonZero) 114 for p := range avg.percentiles { 115 avg.percentiles[p].Div(avg.percentiles[p], nonZeroBn) 116 } 117 118 // take maximum from previous 4 stats plus 5% 119 rec := txpoolStat{} 120 for p := range rec.percentiles { 121 rec.percentiles[p] = new(big.Int) 122 } 123 recI1 := c.dec(c.i) 124 recI2 := c.dec(recI1) 125 recI3 := c.dec(recI2) 126 recI4 := c.dec(recI3) 127 for _, s := range []txpoolStat{c.stats[recI1], c.stats[recI2], c.stats[recI3], c.stats[recI4]} { 128 for p := range s.percentiles { 129 if s.percentiles[p] == nil { 130 continue 131 } 132 if rec.percentiles[p].Cmp(s.percentiles[p]) < 0 { 133 rec.percentiles[p].Set(s.percentiles[p]) 134 } 135 } 136 } 137 // increase by 5% 138 for p := range rec.percentiles { 139 rec.percentiles[p].Mul(rec.percentiles[p], big.NewInt(21)) 140 rec.percentiles[p].Div(rec.percentiles[p], big.NewInt(20)) 141 } 142 143 // return minimum from max(recent two stats * 1.05) and avg stats 144 res := txpoolStat{} 145 res.totalGas = avg.totalGas 146 for _, s := range []txpoolStat{avg, rec} { 147 for p := range s.percentiles { 148 if res.percentiles[p] == nil || res.percentiles[p].Cmp(s.percentiles[p]) > 0 { 149 res.percentiles[p] = s.percentiles[p] 150 } 151 } 152 } 153 154 return res 155 } 156 157 func (c *circularTxpoolStats) getGasPriceForGasAbove(gas uint64) *big.Int { 158 atomic.StoreUint32(&c.activated, 1) 159 avg_c := c.avg.Load() 160 if avg_c == nil { 161 return new(big.Int) 162 } 163 avg := avg_c.(txpoolStat) 164 if avg.totalGas == 0 { 165 return new(big.Int) 166 } 167 if gas > maxGasToIndex { 168 // extrapolate linearly 169 v := new(big.Int).Mul(avg.percentiles[len(avg.percentiles)-1], new(big.Int).SetUint64(maxGasToIndex)) 170 v.Div(v, new(big.Int).SetUint64(gas+1)) 171 return v 172 } 173 p0 := gas * uint64(len(avg.percentiles)) / maxGasToIndex 174 if p0 >= uint64(len(avg.percentiles))-1 { 175 return avg.percentiles[len(avg.percentiles)-1] 176 } 177 // interpolate linearly 178 p1 := p0 + 1 179 x := gas 180 x0, x1 := p0*maxGasToIndex/uint64(len(avg.percentiles)), p1*maxGasToIndex/uint64(len(avg.percentiles)) 181 y0, y1 := avg.percentiles[p0], avg.percentiles[p1] 182 return div64I(addBigI(mul64N(y0, x1-x), mul64N(y1, x-x0)), x1-x0) 183 } 184 185 func mul64N(a *big.Int, b uint64) *big.Int { 186 return new(big.Int).Mul(a, new(big.Int).SetUint64(b)) 187 } 188 189 func div64I(a *big.Int, b uint64) *big.Int { 190 return a.Div(a, new(big.Int).SetUint64(b)) 191 } 192 193 func addBigI(a, b *big.Int) *big.Int { 194 return a.Add(a, b) 195 } 196 197 func (c *circularTxpoolStats) totalGas() uint64 { 198 atomic.StoreUint32(&c.activated, 1) 199 avgC := c.avg.Load() 200 if avgC == nil { 201 return 0 202 } 203 avg := avgC.(txpoolStat) 204 return avg.totalGas 205 } 206 207 // calcTxpoolStat retrieves txpool transactions and calculates statistics 208 func (gpo *Oracle) calcTxpoolStat() txpoolStat { 209 txsMap := gpo.backend.PendingTxs() 210 s := txpoolStat{} 211 if len(txsMap) == 0 { 212 // short circuit if empty txpool 213 return s 214 } 215 // take only one tx from each account 216 txs := make(types.Transactions, 0, 1000) 217 for _, aTxs := range txsMap { 218 txs = append(txs, aTxs[0]) 219 } 220 221 // don't index more transactions than needed for GPO purposes 222 const maxTxsToIndex = 400 223 224 minGasPrice := gpo.backend.GetRules().Economy.MinGasPrice 225 // txs are sorted from large price to small 226 sorted := txs 227 sort.Slice(sorted, func(i, j int) bool { 228 a, b := sorted[i], sorted[j] 229 cmp := a.EffectiveGasTipCmp(b, minGasPrice) 230 if cmp == 0 { 231 return a.Gas() > b.Gas() 232 } 233 return cmp > 0 234 }) 235 236 for i, tx := range sorted { 237 s.totalGas += tx.Gas() 238 if s.totalGas > maxGasToIndex || i > maxTxsToIndex { 239 sorted = sorted[:i+1] 240 break 241 } 242 } 243 244 gasCounter := uint64(0) 245 p := uint64(0) 246 for _, tx := range sorted { 247 for p < uint64(len(s.percentiles)) && gasCounter >= p*maxGasToIndex/uint64(len(s.percentiles)) { 248 s.percentiles[p] = tx.EffectiveGasTipValue(minGasPrice) 249 if s.percentiles[p].Sign() < 0 { 250 s.percentiles[p] = minGasPrice 251 } else { 252 s.percentiles[p].Add(s.percentiles[p], minGasPrice) 253 } 254 p++ 255 } 256 gasCounter += tx.Gas() 257 } 258 259 return s 260 }