github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/neatptc/gasprice/gasprice.go (about)

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