gasprice.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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. package gasprice
  17. import (
  18. "context"
  19. "math/big"
  20. "sort"
  21. "sync"
  22. "github.com/ethereum/go-ethereum/common"
  23. "github.com/ethereum/go-ethereum/core"
  24. "github.com/ethereum/go-ethereum/core/types"
  25. "github.com/ethereum/go-ethereum/event"
  26. "github.com/ethereum/go-ethereum/log"
  27. "github.com/ethereum/go-ethereum/params"
  28. "github.com/ethereum/go-ethereum/rpc"
  29. lru "github.com/hashicorp/golang-lru"
  30. )
  31. const sampleNumber = 3 // Number of transactions sampled in a block
  32. var (
  33. DefaultMaxPrice = big.NewInt(500 * params.GWei)
  34. DefaultIgnorePrice = big.NewInt(2 * params.Wei)
  35. )
  36. type Config struct {
  37. Blocks int
  38. Percentile int
  39. MaxHeaderHistory int
  40. MaxBlockHistory int
  41. Default *big.Int `toml:",omitempty"`
  42. MaxPrice *big.Int `toml:",omitempty"`
  43. IgnorePrice *big.Int `toml:",omitempty"`
  44. }
  45. // OracleBackend includes all necessary background APIs for oracle.
  46. type OracleBackend interface {
  47. HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
  48. BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
  49. GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
  50. PendingBlockAndReceipts() (*types.Block, types.Receipts)
  51. ChainConfig() *params.ChainConfig
  52. SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
  53. }
  54. // Oracle recommends gas prices based on the content of recent
  55. // blocks. Suitable for both light and full clients.
  56. type Oracle struct {
  57. backend OracleBackend
  58. lastHead common.Hash
  59. lastPrice *big.Int
  60. maxPrice *big.Int
  61. ignorePrice *big.Int
  62. cacheLock sync.RWMutex
  63. fetchLock sync.Mutex
  64. checkBlocks, percentile int
  65. maxHeaderHistory, maxBlockHistory int
  66. historyCache *lru.Cache
  67. }
  68. // NewOracle returns a new gasprice oracle which can recommend suitable
  69. // gasprice for newly created transaction.
  70. func NewOracle(backend OracleBackend, params Config) *Oracle {
  71. blocks := params.Blocks
  72. if blocks < 1 {
  73. blocks = 1
  74. log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
  75. }
  76. percent := params.Percentile
  77. if percent < 0 {
  78. percent = 0
  79. log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
  80. } else if percent > 100 {
  81. percent = 100
  82. log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
  83. }
  84. maxPrice := params.MaxPrice
  85. if maxPrice == nil || maxPrice.Int64() <= 0 {
  86. maxPrice = DefaultMaxPrice
  87. log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
  88. }
  89. ignorePrice := params.IgnorePrice
  90. if ignorePrice == nil || ignorePrice.Int64() <= 0 {
  91. ignorePrice = DefaultIgnorePrice
  92. log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice)
  93. } else if ignorePrice.Int64() > 0 {
  94. log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
  95. }
  96. maxHeaderHistory := params.MaxHeaderHistory
  97. if maxHeaderHistory < 1 {
  98. maxHeaderHistory = 1
  99. log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory)
  100. }
  101. maxBlockHistory := params.MaxBlockHistory
  102. if maxBlockHistory < 1 {
  103. maxBlockHistory = 1
  104. log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
  105. }
  106. cache, _ := lru.New(2048)
  107. headEvent := make(chan core.ChainHeadEvent, 1)
  108. backend.SubscribeChainHeadEvent(headEvent)
  109. go func() {
  110. var lastHead common.Hash
  111. for ev := range headEvent {
  112. if ev.Block.ParentHash() != lastHead {
  113. cache.Purge()
  114. }
  115. lastHead = ev.Block.Hash()
  116. }
  117. }()
  118. return &Oracle{
  119. backend: backend,
  120. lastPrice: params.Default,
  121. maxPrice: maxPrice,
  122. ignorePrice: ignorePrice,
  123. checkBlocks: blocks,
  124. percentile: percent,
  125. maxHeaderHistory: maxHeaderHistory,
  126. maxBlockHistory: maxBlockHistory,
  127. historyCache: cache,
  128. }
  129. }
  130. // SuggestTipCap returns a tip cap so that newly created transaction can have a
  131. // very high chance to be included in the following blocks.
  132. //
  133. // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
  134. // necessary to add the basefee to the returned number to fall back to the legacy
  135. // behavior.
  136. func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
  137. head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
  138. headHash := head.Hash()
  139. // If the latest gasprice is still available, return it.
  140. oracle.cacheLock.RLock()
  141. lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
  142. oracle.cacheLock.RUnlock()
  143. if headHash == lastHead {
  144. return new(big.Int).Set(lastPrice), nil
  145. }
  146. oracle.fetchLock.Lock()
  147. defer oracle.fetchLock.Unlock()
  148. // Try checking the cache again, maybe the last fetch fetched what we need
  149. oracle.cacheLock.RLock()
  150. lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
  151. oracle.cacheLock.RUnlock()
  152. if headHash == lastHead {
  153. return new(big.Int).Set(lastPrice), nil
  154. }
  155. var (
  156. sent, exp int
  157. number = head.Number.Uint64()
  158. result = make(chan results, oracle.checkBlocks)
  159. quit = make(chan struct{})
  160. results []*big.Int
  161. )
  162. for sent < oracle.checkBlocks && number > 0 {
  163. go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
  164. sent++
  165. exp++
  166. number--
  167. }
  168. for exp > 0 {
  169. res := <-result
  170. if res.err != nil {
  171. close(quit)
  172. return new(big.Int).Set(lastPrice), res.err
  173. }
  174. exp--
  175. // Nothing returned. There are two special cases here:
  176. // - The block is empty
  177. // - All the transactions included are sent by the miner itself.
  178. // In these cases, use the latest calculated price for sampling.
  179. if len(res.values) == 0 {
  180. res.values = []*big.Int{lastPrice}
  181. }
  182. // Besides, in order to collect enough data for sampling, if nothing
  183. // meaningful returned, try to query more blocks. But the maximum
  184. // is 2*checkBlocks.
  185. if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
  186. go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
  187. sent++
  188. exp++
  189. number--
  190. }
  191. results = append(results, res.values...)
  192. }
  193. price := lastPrice
  194. if len(results) > 0 {
  195. sort.Sort(bigIntArray(results))
  196. price = results[(len(results)-1)*oracle.percentile/100]
  197. }
  198. if price.Cmp(oracle.maxPrice) > 0 {
  199. price = new(big.Int).Set(oracle.maxPrice)
  200. }
  201. oracle.cacheLock.Lock()
  202. oracle.lastHead = headHash
  203. oracle.lastPrice = price
  204. oracle.cacheLock.Unlock()
  205. return new(big.Int).Set(price), nil
  206. }
  207. type results struct {
  208. values []*big.Int
  209. err error
  210. }
  211. type txSorter struct {
  212. txs []*types.Transaction
  213. baseFee *big.Int
  214. }
  215. func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
  216. return &txSorter{
  217. txs: txs,
  218. baseFee: baseFee,
  219. }
  220. }
  221. func (s *txSorter) Len() int { return len(s.txs) }
  222. func (s *txSorter) Swap(i, j int) {
  223. s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
  224. }
  225. func (s *txSorter) Less(i, j int) bool {
  226. // It's okay to discard the error because a tx would never be
  227. // accepted into a block with an invalid effective tip.
  228. tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
  229. tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
  230. return tip1.Cmp(tip2) < 0
  231. }
  232. // getBlockPrices calculates the lowest transaction gas price in a given block
  233. // and sends it to the result channel. If the block is empty or all transactions
  234. // are sent by the miner itself(it doesn't make any sense to include this kind of
  235. // transaction prices for sampling), nil gasprice is returned.
  236. func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
  237. block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
  238. if block == nil {
  239. select {
  240. case result <- results{nil, err}:
  241. case <-quit:
  242. }
  243. return
  244. }
  245. // Sort the transaction by effective tip in ascending sort.
  246. txs := make([]*types.Transaction, len(block.Transactions()))
  247. copy(txs, block.Transactions())
  248. sorter := newSorter(txs, block.BaseFee())
  249. sort.Sort(sorter)
  250. var prices []*big.Int
  251. for _, tx := range sorter.txs {
  252. tip, _ := tx.EffectiveGasTip(block.BaseFee())
  253. if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
  254. continue
  255. }
  256. sender, err := types.Sender(signer, tx)
  257. if err == nil && sender != block.Coinbase() {
  258. prices = append(prices, tip)
  259. if len(prices) >= limit {
  260. break
  261. }
  262. }
  263. }
  264. select {
  265. case result <- results{prices, nil}:
  266. case <-quit:
  267. }
  268. }
  269. type bigIntArray []*big.Int
  270. func (s bigIntArray) Len() int { return len(s) }
  271. func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
  272. func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }