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