github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/eth/gasprice/gasprice.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  package gasprice
    13  
    14  import (
    15  	"context"
    16  	"math/big"
    17  	"sort"
    18  	"sync"
    19  
    20  	"github.com/Sberex/go-sberex/common"
    21  	"github.com/Sberex/go-sberex/core/types"
    22  	"github.com/Sberex/go-sberex/internal/ethapi"
    23  	"github.com/Sberex/go-sberex/params"
    24  	"github.com/Sberex/go-sberex/rpc"
    25  )
    26  
    27  var maxPrice = big.NewInt(500 * params.Serg)
    28  
    29  type Config struct {
    30  	Blocks     int
    31  	Percentile int
    32  	Default    *big.Int `toml:",omitempty"`
    33  }
    34  
    35  // Oracle recommends gas prices based on the content of recent
    36  // blocks. Suitable for both light and full clients.
    37  type Oracle struct {
    38  	backend   ethapi.Backend
    39  	lastHead  common.Hash
    40  	lastPrice *big.Int
    41  	cacheLock sync.RWMutex
    42  	fetchLock sync.Mutex
    43  
    44  	checkBlocks, maxEmpty, maxBlocks int
    45  	percentile                       int
    46  }
    47  
    48  // NewOracle returns a new oracle.
    49  func NewOracle(backend ethapi.Backend, params Config) *Oracle {
    50  	blocks := params.Blocks
    51  	if blocks < 1 {
    52  		blocks = 1
    53  	}
    54  	percent := params.Percentile
    55  	if percent < 0 {
    56  		percent = 0
    57  	}
    58  	if percent > 100 {
    59  		percent = 100
    60  	}
    61  	return &Oracle{
    62  		backend:     backend,
    63  		lastPrice:   params.Default,
    64  		checkBlocks: blocks,
    65  		maxEmpty:    blocks / 2,
    66  		maxBlocks:   blocks * 5,
    67  		percentile:  percent,
    68  	}
    69  }
    70  
    71  // SuggestPrice returns the recommended gas price.
    72  func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
    73  	gpo.cacheLock.RLock()
    74  	lastHead := gpo.lastHead
    75  	lastPrice := gpo.lastPrice
    76  	gpo.cacheLock.RUnlock()
    77  
    78  	head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
    79  	headHash := head.Hash()
    80  	if headHash == lastHead {
    81  		return lastPrice, nil
    82  	}
    83  
    84  	gpo.fetchLock.Lock()
    85  	defer gpo.fetchLock.Unlock()
    86  
    87  	// try checking the cache again, maybe the last fetch fetched what we need
    88  	gpo.cacheLock.RLock()
    89  	lastHead = gpo.lastHead
    90  	lastPrice = gpo.lastPrice
    91  	gpo.cacheLock.RUnlock()
    92  	if headHash == lastHead {
    93  		return lastPrice, nil
    94  	}
    95  
    96  	blockNum := head.Number.Uint64()
    97  	ch := make(chan getBlockPricesResult, gpo.checkBlocks)
    98  	sent := 0
    99  	exp := 0
   100  	var blockPrices []*big.Int
   101  	for sent < gpo.checkBlocks && blockNum > 0 {
   102  		go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
   103  		sent++
   104  		exp++
   105  		blockNum--
   106  	}
   107  	maxEmpty := gpo.maxEmpty
   108  	for exp > 0 {
   109  		res := <-ch
   110  		if res.err != nil {
   111  			return lastPrice, res.err
   112  		}
   113  		exp--
   114  		if res.price != nil {
   115  			blockPrices = append(blockPrices, res.price)
   116  			continue
   117  		}
   118  		if maxEmpty > 0 {
   119  			maxEmpty--
   120  			continue
   121  		}
   122  		if blockNum > 0 && sent < gpo.maxBlocks {
   123  			go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
   124  			sent++
   125  			exp++
   126  			blockNum--
   127  		}
   128  	}
   129  	price := lastPrice
   130  	if len(blockPrices) > 0 {
   131  		sort.Sort(bigIntArray(blockPrices))
   132  		price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100]
   133  	}
   134  	if price.Cmp(maxPrice) > 0 {
   135  		price = new(big.Int).Set(maxPrice)
   136  	}
   137  
   138  	gpo.cacheLock.Lock()
   139  	gpo.lastHead = headHash
   140  	gpo.lastPrice = price
   141  	gpo.cacheLock.Unlock()
   142  	return price, nil
   143  }
   144  
   145  type getBlockPricesResult struct {
   146  	price *big.Int
   147  	err   error
   148  }
   149  
   150  type transactionsByGasPrice []*types.Transaction
   151  
   152  func (t transactionsByGasPrice) Len() int           { return len(t) }
   153  func (t transactionsByGasPrice) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
   154  func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 }
   155  
   156  // getBlockPrices calculates the lowest transaction gas price in a given block
   157  // and sends it to the result channel. If the block is empty, price is nil.
   158  func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) {
   159  	block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   160  	if block == nil {
   161  		ch <- getBlockPricesResult{nil, err}
   162  		return
   163  	}
   164  
   165  	blockTxs := block.Transactions()
   166  	txs := make([]*types.Transaction, len(blockTxs))
   167  	copy(txs, blockTxs)
   168  	sort.Sort(transactionsByGasPrice(txs))
   169  
   170  	for _, tx := range txs {
   171  		sender, err := types.Sender(signer, tx)
   172  		if err == nil && sender != block.Coinbase() {
   173  			ch <- getBlockPricesResult{tx.GasPrice(), nil}
   174  			return
   175  		}
   176  	}
   177  	ch <- getBlockPricesResult{nil, nil}
   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] }