github.com/beyonderyue/gochain@v2.2.26+incompatible/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/gochain-io/gochain/common" 26 "github.com/gochain-io/gochain/core/types" 27 "github.com/gochain-io/gochain/params" 28 "github.com/gochain-io/gochain/rpc" 29 ) 30 31 var ( 32 Default = new(big.Int).SetUint64(2 * params.Shannon) 33 maxPrice = big.NewInt(500 * params.Shannon) 34 ) 35 36 // Backend is a subset of the methods from the interface ethapi.Backend. 37 type Backend interface { 38 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 39 BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) 40 ChainConfig() *params.ChainConfig 41 } 42 43 type Config struct { 44 Blocks int 45 Percentile int 46 Default *big.Int `toml:",omitempty"` 47 } 48 49 // Oracle recommends gas prices based on the content of recent 50 // blocks. Suitable for both light and full clients. 51 type Oracle struct { 52 backend Backend 53 cfg Config 54 55 lastMu sync.RWMutex 56 lastHead common.Hash 57 lastPrice *big.Int 58 59 fetchLock sync.Mutex 60 } 61 62 // NewOracle returns a new oracle. 63 func NewOracle(backend Backend, cfg Config) *Oracle { 64 if cfg.Blocks < 1 { 65 cfg.Blocks = 1 66 } 67 if cfg.Percentile < 0 { 68 cfg.Percentile = 0 69 } else if cfg.Percentile > 100 { 70 cfg.Percentile = 100 71 } 72 if cfg.Default == nil { 73 cfg.Default = Default 74 } 75 return &Oracle{ 76 backend: backend, 77 cfg: cfg, 78 } 79 } 80 81 // SuggestPrice returns the recommended gas price. 82 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 83 gpo.lastMu.RLock() 84 lastHead := gpo.lastHead 85 lastPrice := gpo.lastPrice 86 gpo.lastMu.RUnlock() 87 88 head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 89 headHash := head.Hash() 90 if headHash == lastHead { 91 return lastPrice, nil 92 } 93 94 gpo.fetchLock.Lock() 95 defer gpo.fetchLock.Unlock() 96 97 // try checking the cache again, maybe the last fetch fetched what we need 98 gpo.lastMu.RLock() 99 lastHead = gpo.lastHead 100 lastPrice = gpo.lastPrice 101 gpo.lastMu.RUnlock() 102 if headHash == lastHead { 103 return lastPrice, nil 104 } 105 106 // Calculate block prices concurrently. 107 results := make(chan result, gpo.cfg.Blocks) 108 blocks := 0 109 for blockNum := head.Number.Uint64(); blocks < gpo.cfg.Blocks && blockNum > 0; blockNum-- { 110 blocks++ 111 go gpo.fetchMinBlockPrice(ctx, blockNum, results) 112 } 113 if blocks == 0 { 114 return gpo.cfg.Default, nil 115 } 116 117 // Collect results. 118 blockPrices := make([]*big.Int, blocks) 119 for i := 0; i < blocks; i++ { 120 res := <-results 121 if res.err != nil { 122 return gpo.cfg.Default, res.err 123 } 124 if res.price == nil { 125 res.price = gpo.cfg.Default 126 } 127 blockPrices[i] = res.price 128 } 129 sort.Sort(bigIntArray(blockPrices)) 130 price := blockPrices[(len(blockPrices)-1)*gpo.cfg.Percentile/100] 131 132 if price.Cmp(maxPrice) > 0 { 133 price = new(big.Int).Set(maxPrice) 134 } 135 136 gpo.lastMu.Lock() 137 gpo.lastHead = headHash 138 gpo.lastPrice = price 139 gpo.lastMu.Unlock() 140 return price, nil 141 } 142 143 type result struct { 144 price *big.Int 145 err error 146 } 147 148 // fetchMinBlockPrice responds on ch with the minimum gas price required to have been included in the block. 149 // Sends nil price if the block is not full, or all local txs. Sends an error if block look-up fails. 150 func (gpo *Oracle) fetchMinBlockPrice(ctx context.Context, blockNum uint64, ch chan<- result) { 151 block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 152 if block == nil || err != nil { 153 ch <- result{err: err} 154 return 155 } 156 if block.GasUsed()+params.TxGas < block.GasLimit() { 157 // Block wasn't full - room for at least one more transaction. 158 ch <- result{} 159 return 160 } 161 signer := types.MakeSigner(gpo.backend.ChainConfig(), new(big.Int).SetUint64(blockNum)) 162 ch <- result{price: minBlockPrice(ctx, signer, block)} 163 } 164 165 // minBlockPrice returns the lowest-priced, non-local transaction, or nil if none can be found. 166 func minBlockPrice(ctx context.Context, signer types.Signer, block *types.Block) *big.Int { 167 var min *big.Int 168 for _, tx := range block.Transactions() { 169 sender, err := types.Sender(signer, tx) 170 if err != nil || sender == block.Coinbase() { 171 continue 172 } 173 if min == nil || tx.CmpGasPrice(min) < 0 { 174 min = tx.GasPrice() 175 } 176 } 177 return min 178 } 179 180 type bigIntArray []*big.Int 181 182 func (s bigIntArray) Len() int { return len(s) } 183 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 184 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }