github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/gasprice/gasprice.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package gasprice
    18  
    19  import (
    20  	"fmt"
    21  	"math/big"
    22  	"sync"
    23  	"time"
    24  
    25  	lru "github.com/hashicorp/golang-lru"
    26  
    27  	"github.com/unicornultrafoundation/go-u2u/common"
    28  	"github.com/unicornultrafoundation/go-u2u/common/math"
    29  	"github.com/unicornultrafoundation/go-u2u/core/types"
    30  	"github.com/unicornultrafoundation/go-u2u/log"
    31  	"github.com/unicornultrafoundation/go-u2u/params"
    32  
    33  	"github.com/unicornultrafoundation/go-helios/native/idx"
    34  	"github.com/unicornultrafoundation/go-helios/utils/piecefunc"
    35  
    36  	"github.com/unicornultrafoundation/go-u2u/u2u"
    37  )
    38  
    39  var (
    40  	DefaultMaxGasPrice = big.NewInt(10000000 * params.GWei)
    41  	DecimalUnitBn      = big.NewInt(DecimalUnit)
    42  	secondBn           = new(big.Int).SetUint64(uint64(time.Second))
    43  )
    44  
    45  const (
    46  	AsDefaultCertainty = math.MaxUint64
    47  	DecimalUnit        = piecefunc.DecimalUnit
    48  )
    49  
    50  type Config struct {
    51  	MaxGasPrice      *big.Int `toml:",omitempty"`
    52  	MinGasPrice      *big.Int `toml:",omitempty"`
    53  	MinGasTip        *big.Int `toml:",omitempty"`
    54  	DefaultCertainty uint64   `toml:",omitempty"`
    55  }
    56  
    57  type Reader interface {
    58  	GetLatestBlockIndex() idx.Block
    59  	TotalGasPowerLeft() uint64
    60  	GetRules() u2u.Rules
    61  	GetPendingRules() u2u.Rules
    62  	PendingTxs() map[common.Address]types.Transactions
    63  }
    64  
    65  type tipCache struct {
    66  	upd time.Time
    67  	tip *big.Int
    68  }
    69  
    70  type effectiveMinGasPriceCache struct {
    71  	head  idx.Block
    72  	lock  sync.RWMutex
    73  	value *big.Int
    74  }
    75  
    76  // Oracle recommends gas prices based on the content of recent
    77  // blocks. Suitable for both light and full clients.
    78  type Oracle struct {
    79  	backend Reader
    80  
    81  	c circularTxpoolStats
    82  
    83  	cfg Config
    84  
    85  	eCache effectiveMinGasPriceCache
    86  	tCache *lru.Cache
    87  
    88  	wg   sync.WaitGroup
    89  	quit chan struct{}
    90  }
    91  
    92  func sanitizeBigInt(val, min, max, _default *big.Int, name string) *big.Int {
    93  	if val == nil || (val.Sign() == 0 && _default.Sign() != 0) {
    94  		log.Warn(fmt.Sprintf("Sanitizing invalid parameter %s of gasprice oracle", name), "provided", val, "updated", _default)
    95  		return _default
    96  	}
    97  	if min != nil && val.Cmp(min) < 0 {
    98  		log.Warn(fmt.Sprintf("Sanitizing invalid parameter %s of gasprice oracle", name), "provided", val, "updated", min)
    99  		return min
   100  	}
   101  	if max != nil && val.Cmp(max) > 0 {
   102  		log.Warn(fmt.Sprintf("Sanitizing invalid parameter %s of gasprice oracle", name), "provided", val, "updated", max)
   103  		return max
   104  	}
   105  	return val
   106  }
   107  
   108  // NewOracle returns a new gasprice oracle which can recommend suitable
   109  // gasprice for newly created transaction.
   110  func NewOracle(params Config) *Oracle {
   111  	params.MaxGasPrice = sanitizeBigInt(params.MaxGasPrice, nil, nil, DefaultMaxGasPrice, "MaxGasPrice")
   112  	params.MinGasPrice = sanitizeBigInt(params.MinGasPrice, nil, params.MaxGasPrice, new(big.Int), "MinGasPrice")
   113  	params.MinGasTip = sanitizeBigInt(params.MinGasTip, nil, new(big.Int).Sub(params.MaxGasPrice, params.MinGasPrice), new(big.Int), "MinGasTip")
   114  	params.DefaultCertainty = sanitizeBigInt(new(big.Int).SetUint64(params.DefaultCertainty), big.NewInt(0), DecimalUnitBn, big.NewInt(DecimalUnit/2), "DefaultCertainty").Uint64()
   115  	tCache, _ := lru.New(100)
   116  	return &Oracle{
   117  		cfg:    params,
   118  		tCache: tCache,
   119  		quit:   make(chan struct{}),
   120  	}
   121  }
   122  
   123  func (gpo *Oracle) Start(backend Reader) {
   124  	gpo.backend = backend
   125  	gpo.wg.Add(1)
   126  	go func() {
   127  		defer gpo.wg.Done()
   128  		gpo.txpoolStatsLoop()
   129  	}()
   130  }
   131  
   132  func (gpo *Oracle) Stop() {
   133  	close(gpo.quit)
   134  	gpo.wg.Wait()
   135  }
   136  
   137  func (gpo *Oracle) suggestTip(certainty uint64) *big.Int {
   138  	minPrice := gpo.backend.GetRules().Economy.MinGasPrice
   139  	pendingMinPrice := gpo.backend.GetPendingRules().Economy.MinGasPrice
   140  	adjustedMinGasPrice := math.BigMax(minPrice, pendingMinPrice)
   141  
   142  	reactive := gpo.reactiveGasPrice(certainty)
   143  	constructive := gpo.constructiveGasPrice(gpo.c.totalGas(), 0.005*DecimalUnit+certainty/25, adjustedMinGasPrice)
   144  
   145  	combined := math.BigMax(reactive, constructive)
   146  	if combined.Cmp(gpo.cfg.MinGasPrice) < 0 {
   147  		combined = gpo.cfg.MinGasPrice
   148  	}
   149  	if combined.Cmp(gpo.cfg.MaxGasPrice) > 0 {
   150  		combined = gpo.cfg.MaxGasPrice
   151  	}
   152  
   153  	tip := new(big.Int).Sub(combined, minPrice)
   154  	if tip.Cmp(gpo.cfg.MinGasTip) < 0 {
   155  		return new(big.Int).Set(gpo.cfg.MinGasTip)
   156  	}
   157  	return tip
   158  }
   159  
   160  // SuggestTip returns a tip cap so that newly created transaction can have a
   161  // very high chance to be included in the following blocks.
   162  //
   163  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   164  // necessary to add the basefee to the returned number to fall back to the legacy
   165  // behavior.
   166  func (gpo *Oracle) SuggestTip(certainty uint64) *big.Int {
   167  	if gpo.backend == nil {
   168  		return new(big.Int)
   169  	}
   170  	if certainty == AsDefaultCertainty {
   171  		certainty = gpo.cfg.DefaultCertainty
   172  	}
   173  
   174  	const cacheSlack = DecimalUnit * 0.05
   175  	roundedCertainty := certainty / cacheSlack
   176  	if cached, ok := gpo.tCache.Get(roundedCertainty); ok {
   177  		cache := cached.(tipCache)
   178  		if time.Since(cache.upd) < statUpdatePeriod {
   179  			return new(big.Int).Set(cache.tip)
   180  		} else {
   181  			gpo.tCache.Remove(roundedCertainty)
   182  		}
   183  	}
   184  
   185  	tip := gpo.suggestTip(certainty)
   186  
   187  	gpo.tCache.Add(roundedCertainty, tipCache{
   188  		upd: time.Now(),
   189  		tip: tip,
   190  	})
   191  	return new(big.Int).Set(tip)
   192  }
   193  
   194  // EffectiveMinGasPrice returns softly enforced minimum gas price on top of on-chain minimum gas price (base fee)
   195  func (gpo *Oracle) EffectiveMinGasPrice() *big.Int {
   196  	if gpo.backend == nil {
   197  		return new(big.Int).Set(gpo.cfg.MinGasPrice)
   198  	}
   199  	head := gpo.backend.GetLatestBlockIndex()
   200  
   201  	// If the latest gasprice is still available, return it.
   202  	gpo.eCache.lock.RLock()
   203  	cachedHead, cachedValue := gpo.eCache.head, gpo.eCache.value
   204  	gpo.eCache.lock.RUnlock()
   205  	if head <= cachedHead {
   206  		return new(big.Int).Set(cachedValue)
   207  	}
   208  
   209  	value := gpo.effectiveMinGasPrice()
   210  
   211  	gpo.eCache.lock.Lock()
   212  	if head > gpo.eCache.head {
   213  		gpo.eCache.head = head
   214  		gpo.eCache.value = value
   215  	}
   216  	gpo.eCache.lock.Unlock()
   217  	return new(big.Int).Set(value)
   218  }