github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/qct/gasprice/gasprice.go (about)

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