github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/eth/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 eth
    18  
    19  import (
    20  	"math/big"
    21  	"math/rand"
    22  	"sync"
    23  
    24  	"github.com/ethereumproject/go-ethereum/core"
    25  	"github.com/ethereumproject/go-ethereum/core/types"
    26  	"github.com/ethereumproject/go-ethereum/logger"
    27  	"github.com/ethereumproject/go-ethereum/logger/glog"
    28  )
    29  
    30  const (
    31  	gpoProcessPastBlocks = 100
    32  
    33  	gpoDefaultMinGasPrice = 10000000000000
    34  )
    35  
    36  type blockPriceInfo struct {
    37  	baseGasPrice *big.Int
    38  }
    39  
    40  // GasPriceOracle recommends gas prices based on the content of recent
    41  // blocks.
    42  type GasPriceOracle struct {
    43  	eth           *Ethereum
    44  	initOnce      sync.Once
    45  	minPrice      *big.Int
    46  	lastBaseMutex sync.Mutex
    47  	lastBase      *big.Int
    48  
    49  	// state of listenLoop
    50  	blocks                        map[uint64]*blockPriceInfo
    51  	firstProcessed, lastProcessed uint64
    52  	minBase                       *big.Int
    53  }
    54  
    55  // NewGasPriceOracle returns a new oracle.
    56  func NewGasPriceOracle(eth *Ethereum) *GasPriceOracle {
    57  	minprice := eth.GpoMinGasPrice
    58  	if minprice == nil {
    59  		minprice = big.NewInt(gpoDefaultMinGasPrice)
    60  	}
    61  	minbase := new(big.Int).Mul(minprice, big.NewInt(100))
    62  	if eth.GpobaseCorrectionFactor > 0 {
    63  		minbase = minbase.Div(minbase, big.NewInt(int64(eth.GpobaseCorrectionFactor)))
    64  	}
    65  	return &GasPriceOracle{
    66  		eth:      eth,
    67  		blocks:   make(map[uint64]*blockPriceInfo),
    68  		minBase:  minbase,
    69  		minPrice: minprice,
    70  		lastBase: minprice,
    71  	}
    72  }
    73  
    74  func (gpo *GasPriceOracle) init() {
    75  	gpo.initOnce.Do(func() {
    76  		gpo.processPastBlocks(gpo.eth.BlockChain())
    77  		go gpo.listenLoop()
    78  	})
    79  }
    80  
    81  func (self *GasPriceOracle) processPastBlocks(chain *core.BlockChain) {
    82  	last := int64(-1)
    83  	cblock := chain.CurrentBlock()
    84  	if cblock != nil {
    85  		last = int64(cblock.NumberU64())
    86  	}
    87  	first := int64(0)
    88  	if last > gpoProcessPastBlocks {
    89  		first = last - gpoProcessPastBlocks
    90  	}
    91  	self.firstProcessed = uint64(first)
    92  	for i := first; i <= last; i++ {
    93  		block := chain.GetBlockByNumber(uint64(i))
    94  		if block != nil {
    95  			self.processBlock(block)
    96  		}
    97  	}
    98  
    99  }
   100  
   101  func (self *GasPriceOracle) listenLoop() {
   102  	events := self.eth.EventMux().Subscribe(core.ChainEvent{}, core.ChainSplitEvent{})
   103  	defer events.Unsubscribe()
   104  
   105  	for event := range events.Chan() {
   106  		switch event := event.Data.(type) {
   107  		case core.ChainEvent:
   108  			self.processBlock(event.Block)
   109  		case core.ChainSplitEvent:
   110  			self.processBlock(event.Block)
   111  		}
   112  	}
   113  }
   114  
   115  func (self *GasPriceOracle) processBlock(block *types.Block) {
   116  	i := block.NumberU64()
   117  	if i > self.lastProcessed {
   118  		self.lastProcessed = i
   119  	}
   120  
   121  	lastBase := self.minPrice
   122  	bpl := self.blocks[i-1]
   123  	if bpl != nil {
   124  		lastBase = bpl.baseGasPrice
   125  	}
   126  	if lastBase == nil {
   127  		return
   128  	}
   129  
   130  	var corr int
   131  	lp := self.lowestPrice(block)
   132  	if lp == nil {
   133  		return
   134  	}
   135  
   136  	if lastBase.Cmp(lp) < 0 {
   137  		corr = self.eth.GpobaseStepUp
   138  	} else {
   139  		corr = -self.eth.GpobaseStepDown
   140  	}
   141  
   142  	crand := int64(corr * (900 + rand.Intn(201)))
   143  	newBase := new(big.Int).Mul(lastBase, big.NewInt(1000000+crand))
   144  	newBase.Div(newBase, big.NewInt(1000000))
   145  
   146  	if newBase.Cmp(self.minBase) < 0 {
   147  		newBase = self.minBase
   148  	}
   149  
   150  	bpi := self.blocks[i]
   151  	if bpi == nil {
   152  		bpi = &blockPriceInfo{}
   153  		self.blocks[i] = bpi
   154  	}
   155  	bpi.baseGasPrice = newBase
   156  	self.lastBaseMutex.Lock()
   157  	self.lastBase = newBase
   158  	self.lastBaseMutex.Unlock()
   159  
   160  	glog.V(logger.Detail).Infof("Processed block #%v, base price is %v\n", block.NumberU64(), newBase.Int64())
   161  }
   162  
   163  // returns the lowers possible price with which a tx was or could have been included
   164  func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int {
   165  	gasUsed := big.NewInt(0)
   166  
   167  	receipts := core.GetBlockReceipts(self.eth.ChainDb(), block.Hash())
   168  	if len(receipts) > 0 {
   169  		if cgu := receipts[len(receipts)-1].CumulativeGasUsed; cgu != nil {
   170  			gasUsed = receipts[len(receipts)-1].CumulativeGasUsed
   171  		}
   172  	}
   173  
   174  	if new(big.Int).Mul(gasUsed, big.NewInt(100)).Cmp(new(big.Int).Mul(block.GasLimit(),
   175  		big.NewInt(int64(self.eth.GpoFullBlockRatio)))) < 0 {
   176  		// block is not full, could have posted a tx with MinGasPrice
   177  		return big.NewInt(0)
   178  	}
   179  
   180  	txs := block.Transactions()
   181  	if len(txs) == 0 {
   182  		return big.NewInt(0)
   183  	}
   184  	// block is full, find smallest gasPrice
   185  	minPrice := txs[0].GasPrice()
   186  	for i := 1; i < len(txs); i++ {
   187  		price := txs[i].GasPrice()
   188  		if price.Cmp(minPrice) < 0 {
   189  			minPrice = price
   190  		}
   191  	}
   192  	return minPrice
   193  }
   194  
   195  // SuggestPrice returns the recommended gas price.
   196  func (self *GasPriceOracle) SuggestPrice() *big.Int {
   197  	self.init()
   198  	self.lastBaseMutex.Lock()
   199  	price := new(big.Int).Set(self.lastBase)
   200  	self.lastBaseMutex.Unlock()
   201  
   202  	price.Mul(price, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
   203  	price.Div(price, big.NewInt(100))
   204  	if price.Cmp(self.minPrice) < 0 {
   205  		price.Set(self.minPrice)
   206  	} else if self.eth.GpoMaxGasPrice != nil && price.Cmp(self.eth.GpoMaxGasPrice) > 0 {
   207  		price.Set(self.eth.GpoMaxGasPrice)
   208  	}
   209  	return price
   210  }