aboutsummaryrefslogblamecommitdiffstats
path: root/eth/gasprice.go
blob: cd5293691eb9d34f71dca19b18efc24991138d92 (plain) (tree)
































































































































































                                                                                                                  


                                              


                                                                                                                
 
                                                                                               











                                                      
package eth

import (
    "math/big"
    "math/rand"
    "sync"

    "github.com/ethereum/go-ethereum/core"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/event"
    "github.com/ethereum/go-ethereum/logger"
    "github.com/ethereum/go-ethereum/logger/glog"
)

const gpoProcessPastBlocks = 100

type blockPriceInfo struct {
    baseGasPrice *big.Int
}

type GasPriceOracle struct {
    eth                           *Ethereum
    chain                         *core.ChainManager
    pool                          *core.TxPool
    events                        event.Subscription
    blocks                        map[uint64]*blockPriceInfo
    firstProcessed, lastProcessed uint64
    lastBaseMutex                 sync.Mutex
    lastBase                      *big.Int
}

func NewGasPriceOracle(eth *Ethereum) (self *GasPriceOracle) {
    self = &GasPriceOracle{}
    self.blocks = make(map[uint64]*blockPriceInfo)
    self.eth = eth
    self.chain = eth.chainManager
    self.pool = eth.txPool
    self.events = eth.EventMux().Subscribe(
        core.ChainEvent{},
        core.ChainSplitEvent{},
        core.TxPreEvent{},
        core.TxPostEvent{},
    )
    self.processPastBlocks()
    go self.listenLoop()
    return
}

func (self *GasPriceOracle) processPastBlocks() {
    last := self.chain.CurrentBlock().NumberU64()
    first := uint64(0)
    if last > gpoProcessPastBlocks {
        first = last - gpoProcessPastBlocks
    }
    self.firstProcessed = first
    for i := first; i <= last; i++ {
        self.processBlock(self.chain.GetBlockByNumber(i))
    }

}

func (self *GasPriceOracle) listenLoop() {
    for {
        ev, isopen := <-self.events.Chan()
        if !isopen {
            break
        }
        switch ev := ev.(type) {
        case core.ChainEvent:
            self.processBlock(ev.Block)
        case core.ChainSplitEvent:
            self.processBlock(ev.Block)
        case core.TxPreEvent:
        case core.TxPostEvent:
        }
    }
    self.events.Unsubscribe()
}

func (self *GasPriceOracle) processBlock(block *types.Block) {
    i := block.NumberU64()
    if i > self.lastProcessed {
        self.lastProcessed = i
    }

    lastBase := self.eth.GpoMinGasPrice
    bpl := self.blocks[i-1]
    if bpl != nil {
        lastBase = bpl.baseGasPrice
    }
    if lastBase == nil {
        return
    }

    var corr int
    lp := self.lowestPrice(block)
    if lp == nil {
        return
    }

    if lastBase.Cmp(lp) < 0 {
        corr = self.eth.GpobaseStepUp
    } else {
        corr = -self.eth.GpobaseStepDown
    }

    crand := int64(corr * (900 + rand.Intn(201)))
    newBase := new(big.Int).Mul(lastBase, big.NewInt(1000000+crand))
    newBase.Div(newBase, big.NewInt(1000000))

    bpi := self.blocks[i]
    if bpi == nil {
        bpi = &blockPriceInfo{}
        self.blocks[i] = bpi
    }
    bpi.baseGasPrice = newBase
    self.lastBaseMutex.Lock()
    self.lastBase = newBase
    self.lastBaseMutex.Unlock()

    glog.V(logger.Detail).Infof("Processed block #%v, base price is %v\n", block.NumberU64(), newBase.Int64())
}

// returns the lowers possible price with which a tx was or could have been included
func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int {
    gasUsed := new(big.Int)
    recepits, err := self.eth.BlockProcessor().GetBlockReceipts(block.Hash())
    if err != nil {
        return self.eth.GpoMinGasPrice
    }

    if len(recepits) > 0 {
        gasUsed = recepits[len(recepits)-1].CumulativeGasUsed
    }

    if new(big.Int).Mul(gasUsed, big.NewInt(100)).Cmp(new(big.Int).Mul(block.Header().GasLimit,
        big.NewInt(int64(self.eth.GpoFullBlockRatio)))) < 0 {
        // block is not full, could have posted a tx with MinGasPrice
        return self.eth.GpoMinGasPrice
    }

    if len(block.Transactions()) < 1 {
        return self.eth.GpoMinGasPrice
    }

    // block is full, find smallest gasPrice
    minPrice := block.Transactions()[0].GasPrice()
    for i := 1; i < len(block.Transactions()); i++ {
        price := block.Transactions()[i].GasPrice()
        if price.Cmp(minPrice) < 0 {
            minPrice = price
        }
    }
    return minPrice
}

func (self *GasPriceOracle) SuggestPrice() *big.Int {
    self.lastBaseMutex.Lock()
    base := self.lastBase
    self.lastBaseMutex.Unlock()

    if base == nil {
        base = self.eth.GpoMinGasPrice
    }
    if base == nil {
        return big.NewInt(10000000000000) // apparently MinGasPrice is not initialized during some tests
    }

    baseCorr := new(big.Int).Mul(base, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
    baseCorr.Div(baseCorr, big.NewInt(100))

    if baseCorr.Cmp(self.eth.GpoMinGasPrice) < 0 {
        return self.eth.GpoMinGasPrice
    }

    if baseCorr.Cmp(self.eth.GpoMaxGasPrice) > 0 {
        return self.eth.GpoMaxGasPrice
    }

    return baseCorr
}