github.com/amazechain/amc@v0.1.3/internal/api/gasprice.go (about)

     1  // Copyright 2023 The AmazeChain Authors
     2  // This file is part of the AmazeChain library.
     3  //
     4  // The AmazeChain 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 AmazeChain 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 AmazeChain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	common2 "github.com/amazechain/amc/common"
    22  	"github.com/amazechain/amc/common/transaction"
    23  	types2 "github.com/amazechain/amc/common/types"
    24  	"github.com/amazechain/amc/conf"
    25  	"github.com/amazechain/amc/internal/avm/types"
    26  	"github.com/amazechain/amc/log"
    27  	event "github.com/amazechain/amc/modules/event/v2"
    28  	"github.com/amazechain/amc/modules/rpc/jsonrpc"
    29  	"github.com/amazechain/amc/params"
    30  	lru "github.com/hashicorp/golang-lru"
    31  	"github.com/holiman/uint256"
    32  	"math/big"
    33  	"sort"
    34  	"sync"
    35  )
    36  
    37  const sampleNumber = 3 // Number of transactions sampled in a block
    38  
    39  // Oracle recommends gas prices based on the content of recent
    40  // blocks. Suitable for both light and full clients.
    41  type Oracle struct {
    42  	backend     common2.IBlockChain
    43  	miner       common2.IMiner
    44  	lastHead    types2.Hash
    45  	lastPrice   *big.Int
    46  	maxPrice    *big.Int
    47  	ignorePrice *big.Int
    48  	cacheLock   sync.RWMutex
    49  	fetchLock   sync.Mutex
    50  
    51  	checkBlocks, percentile           int
    52  	maxHeaderHistory, maxBlockHistory int
    53  	historyCache                      *lru.Cache
    54  	//
    55  	chainConfig *params.ChainConfig
    56  }
    57  
    58  // NewOracle returns a new gasprice oracle which can recommend suitable
    59  // gasprice for newly created transaction.
    60  func NewOracle(backend common2.IBlockChain, miner common2.IMiner, chainConfig *params.ChainConfig, params conf.GpoConfig) *Oracle {
    61  	blocks := params.Blocks
    62  	if blocks < 1 {
    63  		blocks = 1
    64  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
    65  	}
    66  	percent := params.Percentile
    67  	if percent < 0 {
    68  		percent = 0
    69  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    70  	} else if percent > 100 {
    71  		percent = 100
    72  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    73  	}
    74  	maxPrice := params.MaxPrice
    75  	if maxPrice == nil || maxPrice.Int64() <= 0 {
    76  		maxPrice = conf.DefaultMaxPrice
    77  		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
    78  	}
    79  	ignorePrice := params.IgnorePrice
    80  	if ignorePrice == nil || ignorePrice.Int64() <= 0 {
    81  		ignorePrice = conf.DefaultIgnorePrice
    82  		log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice)
    83  	} else if ignorePrice.Int64() > 0 {
    84  		log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
    85  	}
    86  	maxHeaderHistory := params.MaxHeaderHistory
    87  	if maxHeaderHistory < 1 {
    88  		maxHeaderHistory = 1
    89  		log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory)
    90  	}
    91  	maxBlockHistory := params.MaxBlockHistory
    92  	if maxBlockHistory < 1 {
    93  		maxBlockHistory = 1
    94  		log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
    95  	}
    96  
    97  	cache, _ := lru.New(2048)
    98  
    99  	highestBlockCh := make(chan common2.ChainHighestBlock)
   100  	defer close(highestBlockCh)
   101  	highestSub := event.GlobalEvent.Subscribe(highestBlockCh)
   102  	defer highestSub.Unsubscribe()
   103  
   104  	go func() {
   105  		var lastHead types2.Hash
   106  		for ev := range highestBlockCh {
   107  			if ev.Block.ParentHash() != lastHead {
   108  				cache.Purge()
   109  			}
   110  			lastHead = ev.Block.Hash()
   111  		}
   112  	}()
   113  
   114  	return &Oracle{
   115  		backend:          backend,
   116  		miner:            miner,
   117  		lastPrice:        params.Default,
   118  		maxPrice:         maxPrice,
   119  		ignorePrice:      ignorePrice,
   120  		checkBlocks:      blocks,
   121  		percentile:       percent,
   122  		maxHeaderHistory: maxHeaderHistory,
   123  		maxBlockHistory:  maxBlockHistory,
   124  		historyCache:     cache,
   125  		chainConfig:      chainConfig,
   126  	}
   127  }
   128  
   129  // SuggestTipCap returns a tip cap so that newly created transaction can have a
   130  // very high chance to be included in the following blocks.
   131  //
   132  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   133  // necessary to add the basefee to the returned number to fall back to the legacy
   134  // behavior.
   135  func (oracle *Oracle) SuggestTipCap(ctx context.Context, chainConfig *params.ChainConfig) (*big.Int, error) {
   136  	//var latestNumber jsonrpc.BlockNumber
   137  	//latestNumber = jsonrpc.LatestBlockNumber
   138  
   139  	head := oracle.backend.CurrentBlock().Header()
   140  	var headHash types2.Hash
   141  	if head == nil {
   142  		headHash = types2.Hash{}
   143  	} else {
   144  		headHash = types2.Hash(head.Hash())
   145  	}
   146  
   147  	// If the latest gasprice is still available, return it.
   148  	oracle.cacheLock.RLock()
   149  	lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
   150  	oracle.cacheLock.RUnlock()
   151  	if headHash == lastHead {
   152  		return new(big.Int).Set(lastPrice), nil
   153  	}
   154  	oracle.fetchLock.Lock()
   155  	defer oracle.fetchLock.Unlock()
   156  
   157  	// Try checking the cache again, maybe the last fetch fetched what we need
   158  	oracle.cacheLock.RLock()
   159  	lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
   160  	oracle.cacheLock.RUnlock()
   161  	if headHash == lastHead {
   162  		return new(big.Int).Set(lastPrice), nil
   163  	}
   164  	var (
   165  		sent, exp int
   166  		number    = head.Number64().Uint64()
   167  		result    = make(chan results, oracle.checkBlocks)
   168  		quit      = make(chan struct{})
   169  		results   []*big.Int
   170  	)
   171  	for sent < oracle.checkBlocks && number > 0 {
   172  		go oracle.getBlockValues(ctx, types.MakeSigner(chainConfig, big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
   173  		sent++
   174  		exp++
   175  		number--
   176  	}
   177  	for exp > 0 {
   178  		res := <-result
   179  		if res.err != nil {
   180  			close(quit)
   181  			return new(big.Int).Set(lastPrice), res.err
   182  		}
   183  		exp--
   184  		// Nothing returned. There are two special cases here:
   185  		// - The block is empty
   186  		// - All the transactions included are sent by the miner itself.
   187  		// In these cases, use the latest calculated price for sampling.
   188  		if len(res.values) == 0 {
   189  			res.values = []*big.Int{lastPrice}
   190  		}
   191  		// Besides, in order to collect enough data for sampling, if nothing
   192  		// meaningful returned, try to query more blocks. But the maximum
   193  		// is 2*checkBlocks.
   194  		if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
   195  			go oracle.getBlockValues(ctx, types.MakeSigner(chainConfig, big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
   196  			sent++
   197  			exp++
   198  			number--
   199  		}
   200  		results = append(results, res.values...)
   201  	}
   202  	price := lastPrice
   203  	if len(results) > 0 {
   204  		sort.Sort(bigIntArray(results))
   205  		price = results[(len(results)-1)*oracle.percentile/100]
   206  	}
   207  	if price.Cmp(oracle.maxPrice) > 0 {
   208  		price = new(big.Int).Set(oracle.maxPrice)
   209  	}
   210  	oracle.cacheLock.Lock()
   211  	oracle.lastHead = headHash
   212  	oracle.lastPrice = price
   213  	oracle.cacheLock.Unlock()
   214  
   215  	return new(big.Int).Set(price), nil
   216  }
   217  
   218  type results struct {
   219  	values []*big.Int
   220  	err    error
   221  }
   222  
   223  // getBlockPrices calculates the lowest transaction gas price in a given block
   224  // and sends it to the result channel. If the block is empty or all transactions
   225  // are sent by the miner itself(it doesn't make any sense to include this kind of
   226  // transaction prices for sampling), nil gasprice is returned.
   227  func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
   228  	block, err := oracle.backend.GetBlockByNumber(uint256.NewInt(uint64(jsonrpc.BlockNumber(blockNum))))
   229  	if block == nil {
   230  		select {
   231  		case result <- results{nil, err}:
   232  		case <-quit:
   233  		}
   234  		return
   235  	}
   236  	// Sort the transaction by effective tip in ascending sort.
   237  	txs := make([]*transaction.Transaction, len(block.Transactions()))
   238  	copy(txs, block.Transactions())
   239  	sorter := newSorter(txs, block.BaseFee64())
   240  	sort.Sort(sorter)
   241  
   242  	var prices []*big.Int
   243  	for _, tx := range sorter.txs {
   244  		tip, _ := tx.EffectiveGasTip(block.BaseFee64())
   245  		ignoreUnderx, _ := uint256.FromBig(ignoreUnder)
   246  		if ignoreUnder != nil && tip.Cmp(ignoreUnderx) == -1 {
   247  			continue
   248  		}
   249  		if *tx.From() != block.Coinbase() {
   250  			prices = append(prices, tip.ToBig())
   251  			if len(prices) >= limit {
   252  				break
   253  			}
   254  		}
   255  	}
   256  	select {
   257  	case result <- results{prices, nil}:
   258  	case <-quit:
   259  	}
   260  }
   261  
   262  type txSorter struct {
   263  	txs     []*transaction.Transaction
   264  	baseFee *uint256.Int
   265  }
   266  
   267  func newSorter(txs []*transaction.Transaction, baseFee *uint256.Int) *txSorter {
   268  	return &txSorter{
   269  		txs:     txs,
   270  		baseFee: baseFee,
   271  	}
   272  }
   273  
   274  func (s *txSorter) Len() int { return len(s.txs) }
   275  func (s *txSorter) Swap(i, j int) {
   276  	s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
   277  }
   278  func (s *txSorter) Less(i, j int) bool {
   279  	// It's okay to discard the error because a tx would never be
   280  	// accepted into a block with an invalid effective tip.
   281  	tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
   282  	tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
   283  	x, _ := uint256.FromBig(tip2.ToBig())
   284  	return tip1.Cmp(x) < 0
   285  }
   286  
   287  type bigIntArray []*big.Int
   288  
   289  func (s bigIntArray) Len() int           { return len(s) }
   290  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   291  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }