github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/eth/gasprice/gasprice.go (about)

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