github.com/bcskill/bcschain/v3@v3.4.9-beta2/eth/gasprice/gasprice.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package gasprice 18 19 import ( 20 "context" 21 "math/big" 22 "sort" 23 "sync" 24 25 "github.com/bcskill/bcschain/v3/log" 26 27 "github.com/bcskill/bcschain/v3/common" 28 "github.com/bcskill/bcschain/v3/core/types" 29 "github.com/bcskill/bcschain/v3/params" 30 "github.com/bcskill/bcschain/v3/rpc" 31 ) 32 33 var ( 34 // Deprecated: use DefaultFn 35 Default = new(big.Int).SetUint64(2 * params.Shannon) 36 DefaultMaxPrice = big.NewInt(500000 * params.Shannon) 37 ) 38 39 // DefaultFn returns a function to return the default gas price at a given block. 40 func DefaultFn(config *params.ChainConfig) func(*big.Int) *big.Int { 41 return func(num *big.Int) *big.Int { 42 if config.IsDarvaza(num) { 43 if g := config.DarvazaDefaultGas; g != nil { 44 return g 45 } 46 } 47 return Default 48 } 49 } 50 51 type Config struct { 52 Blocks int 53 Percentile int 54 Default *big.Int `toml:",omitempty"` // nil for default/dynamic 55 MaxPrice *big.Int `toml:",omitempty"` 56 } 57 58 // OracleBackend includes all necessary background APIs for oracle. 59 type OracleBackend interface { 60 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 61 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 62 ChainConfig() *params.ChainConfig 63 } 64 65 // Oracle recommends gas prices based on the content of recent 66 // blocks. Suitable for both light and full clients. 67 type Oracle struct { 68 backend OracleBackend 69 lastHead common.Hash 70 defaultPrice *big.Int // optional user-configured default/min 71 lastPrice *big.Int 72 maxPrice *big.Int 73 cacheLock sync.RWMutex 74 fetchLock sync.Mutex 75 76 checkBlocks int 77 percentile int 78 } 79 80 // NewOracle returns a new gasprice oracle which can recommend suitable 81 // gasprice for newly created transaction. 82 func NewOracle(backend OracleBackend, params Config) *Oracle { 83 blocks := params.Blocks 84 if blocks < 1 { 85 blocks = 1 86 log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) 87 } 88 percent := params.Percentile 89 if percent < 0 { 90 percent = 0 91 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) 92 } 93 if percent > 100 { 94 percent = 100 95 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) 96 } 97 maxPrice := params.MaxPrice 98 if maxPrice == nil || maxPrice.Int64() <= 0 { 99 maxPrice = DefaultMaxPrice 100 log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) 101 } 102 lastPrice := params.Default 103 if lastPrice == nil { 104 lastPrice = Default 105 } else if maxPrice.Int64() <= 0 { 106 lastPrice = Default 107 log.Warn("Sanitizing invalid gasprice oracle price default", "provided", params.Default, "updated", lastPrice) 108 } 109 return &Oracle{ 110 backend: backend, 111 defaultPrice: params.Default, 112 lastPrice: lastPrice, 113 maxPrice: maxPrice, 114 checkBlocks: blocks, 115 percentile: percent, 116 } 117 } 118 119 // SuggestPrice returns a gasprice so that newly created transaction can 120 // have a very high chance to be included in the following blocks. 121 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 122 head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 123 headHash := head.Hash() 124 125 // If the latest gasprice is still available, return it. 126 gpo.cacheLock.RLock() 127 lastHead, lastPrice := gpo.lastHead, gpo.lastPrice 128 gpo.cacheLock.RUnlock() 129 if headHash == lastHead { 130 return lastPrice, nil 131 } 132 gpo.fetchLock.Lock() 133 defer gpo.fetchLock.Unlock() 134 135 // Try checking the cache again, maybe the last fetch fetched what we need 136 gpo.cacheLock.RLock() 137 lastHead, lastPrice = gpo.lastHead, gpo.lastPrice 138 gpo.cacheLock.RUnlock() 139 if headHash == lastHead { 140 return lastPrice, nil 141 } 142 143 // Calculate block prices concurrently. 144 results := make(chan result, gpo.checkBlocks) 145 blocks := 0 146 for blockNum := head.Number.Uint64(); blocks < gpo.checkBlocks && blockNum > 0; blockNum-- { 147 blocks++ 148 go gpo.fetchMinBlockPrice(ctx, blockNum, results) 149 } 150 if blocks == 0 { 151 return lastPrice, nil 152 } 153 154 // Collect results. 155 blockPrices := make([]*big.Int, blocks) 156 for i := 0; i < blocks; i++ { 157 res := <-results 158 if res.err != nil { 159 return lastPrice, res.err 160 } 161 if res.price == nil { 162 res.price = lastPrice 163 } 164 blockPrices[i] = res.price 165 } 166 sort.Sort(bigIntArray(blockPrices)) 167 price := blockPrices[(len(blockPrices)-1)*gpo.percentile/100] 168 if price.Cmp(gpo.maxPrice) > 0 { 169 price = new(big.Int).Set(gpo.maxPrice) 170 } else if min := gpo.minPrice(head.Number); price.Cmp(min) < 0 { 171 price = min 172 } 173 gpo.cacheLock.Lock() 174 gpo.lastHead = headHash 175 gpo.lastPrice = price 176 gpo.cacheLock.Unlock() 177 return price, nil 178 } 179 180 func (gpo *Oracle) minPrice(num *big.Int) *big.Int { 181 if gpo.defaultPrice != nil { 182 return gpo.defaultPrice 183 } 184 const blockOffset = 60 * 12 // look ~1 hour ahead, since we are suggesting gas for near-future txs 185 return DefaultFn(gpo.backend.ChainConfig())(new(big.Int).Add(big.NewInt(blockOffset), num)) 186 } 187 188 type result struct { 189 price *big.Int 190 err error 191 } 192 193 // fetchMinBlockPrice responds on ch with the minimum gas price required to have been included in the block. 194 // Sends nil price if the block is not full, or all local txs. Sends an error if block look-up fails. 195 func (gpo *Oracle) fetchMinBlockPrice(ctx context.Context, blockNum uint64, ch chan<- result) { 196 block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 197 if block == nil || err != nil { 198 ch <- result{err: err} 199 return 200 } 201 if block.GasUsed()+params.TxGas < block.GasLimit() { 202 // Block wasn't full - room for at least one more transaction. 203 ch <- result{} 204 return 205 } 206 signer := types.MakeSigner(gpo.backend.ChainConfig(), new(big.Int).SetUint64(blockNum)) 207 ch <- result{price: minBlockPrice(ctx, signer, block)} 208 } 209 210 // minBlockPrice returns the lowest-priced, non-local transaction, or nil if none can be found. 211 func minBlockPrice(ctx context.Context, signer types.Signer, block *types.Block) *big.Int { 212 var min *big.Int 213 for _, tx := range block.Transactions() { 214 sender, err := types.Sender(signer, tx) 215 if err != nil || sender == block.Coinbase() { 216 continue 217 } 218 if min == nil || tx.CmpGasPrice(min) < 0 { 219 min = tx.GasPrice() 220 } 221 } 222 return min 223 } 224 225 type bigIntArray []*big.Int 226 227 func (s bigIntArray) Len() int { return len(s) } 228 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 229 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }