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] }