github.com/ylsGit/go-ethereum@v1.6.5/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/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/internal/ethapi"
    27  	"github.com/ethereum/go-ethereum/params"
    28  	"github.com/ethereum/go-ethereum/rpc"
    29  )
    30  
    31  var maxPrice = big.NewInt(500 * params.Shannon)
    32  
    33  type Config struct {
    34  	Blocks     int
    35  	Percentile int
    36  	Default    *big.Int `toml:",omitempty"`
    37  }
    38  
    39  // Oracle recommends gas prices based on the content of recent
    40  // blocks. Suitable for both light and full clients.
    41  type Oracle struct {
    42  	backend   ethapi.Backend
    43  	lastHead  common.Hash
    44  	lastPrice *big.Int
    45  	cacheLock sync.RWMutex
    46  	fetchLock sync.Mutex
    47  
    48  	checkBlocks, maxEmpty, maxBlocks int
    49  	percentile                       int
    50  }
    51  
    52  // NewOracle returns a new oracle.
    53  func NewOracle(backend ethapi.Backend, params Config) *Oracle {
    54  	blocks := params.Blocks
    55  	if blocks < 1 {
    56  		blocks = 1
    57  	}
    58  	percent := params.Percentile
    59  	if percent < 0 {
    60  		percent = 0
    61  	}
    62  	if percent > 100 {
    63  		percent = 100
    64  	}
    65  	return &Oracle{
    66  		backend:     backend,
    67  		lastPrice:   params.Default,
    68  		checkBlocks: blocks,
    69  		maxEmpty:    blocks / 2,
    70  		maxBlocks:   blocks * 5,
    71  		percentile:  percent,
    72  	}
    73  }
    74  
    75  // SuggestPrice returns the recommended gas price.
    76  func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
    77  	gpo.cacheLock.RLock()
    78  	lastHead := gpo.lastHead
    79  	lastPrice := gpo.lastPrice
    80  	gpo.cacheLock.RUnlock()
    81  
    82  	head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
    83  	headHash := head.Hash()
    84  	if headHash == lastHead {
    85  		return lastPrice, nil
    86  	}
    87  
    88  	gpo.fetchLock.Lock()
    89  	defer gpo.fetchLock.Unlock()
    90  
    91  	// try checking the cache again, maybe the last fetch fetched what we need
    92  	gpo.cacheLock.RLock()
    93  	lastHead = gpo.lastHead
    94  	lastPrice = gpo.lastPrice
    95  	gpo.cacheLock.RUnlock()
    96  	if headHash == lastHead {
    97  		return lastPrice, nil
    98  	}
    99  
   100  	blockNum := head.Number.Uint64()
   101  	ch := make(chan getBlockPricesResult, gpo.checkBlocks)
   102  	sent := 0
   103  	exp := 0
   104  	var txPrices []*big.Int
   105  	for sent < gpo.checkBlocks && blockNum > 0 {
   106  		go gpo.getBlockPrices(ctx, blockNum, ch)
   107  		sent++
   108  		exp++
   109  		blockNum--
   110  	}
   111  	maxEmpty := gpo.maxEmpty
   112  	for exp > 0 {
   113  		res := <-ch
   114  		if res.err != nil {
   115  			return lastPrice, res.err
   116  		}
   117  		exp--
   118  		if len(res.prices) > 0 {
   119  			txPrices = append(txPrices, res.prices...)
   120  			continue
   121  		}
   122  		if maxEmpty > 0 {
   123  			maxEmpty--
   124  			continue
   125  		}
   126  		if blockNum > 0 && sent < gpo.maxBlocks {
   127  			go gpo.getBlockPrices(ctx, blockNum, ch)
   128  			sent++
   129  			exp++
   130  			blockNum--
   131  		}
   132  	}
   133  	price := lastPrice
   134  	if len(txPrices) > 0 {
   135  		sort.Sort(bigIntArray(txPrices))
   136  		price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
   137  	}
   138  	if price.Cmp(maxPrice) > 0 {
   139  		price = new(big.Int).Set(maxPrice)
   140  	}
   141  
   142  	gpo.cacheLock.Lock()
   143  	gpo.lastHead = headHash
   144  	gpo.lastPrice = price
   145  	gpo.cacheLock.Unlock()
   146  	return price, nil
   147  }
   148  
   149  type getBlockPricesResult struct {
   150  	prices []*big.Int
   151  	err    error
   152  }
   153  
   154  // getLowestPrice calculates the lowest transaction gas price in a given block
   155  // and sends it to the result channel. If the block is empty, price is nil.
   156  func (gpo *Oracle) getBlockPrices(ctx context.Context, blockNum uint64, ch chan getBlockPricesResult) {
   157  	block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   158  	if block == nil {
   159  		ch <- getBlockPricesResult{nil, err}
   160  		return
   161  	}
   162  	txs := block.Transactions()
   163  	prices := make([]*big.Int, len(txs))
   164  	for i, tx := range txs {
   165  		prices[i] = tx.GasPrice()
   166  	}
   167  	ch <- getBlockPricesResult{prices, nil}
   168  }
   169  
   170  type bigIntArray []*big.Int
   171  
   172  func (s bigIntArray) Len() int           { return len(s) }
   173  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   174  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }