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