Merge pull request #990 from zsfelfoldi/gasprice

eth: add GasPriceOracle
This commit is contained in:
Jeffrey Wilcke
2015-06-15 08:44:25 -07:00
8 changed files with 319 additions and 59 deletions

View File

@ -93,6 +93,13 @@ type Config struct {
AccountManager *accounts.Manager
SolcPath string
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
GpoFullBlockRatio int
GpobaseStepDown int
GpobaseStepUp int
GpobaseCorrectionFactor int
// NewDB is used to create databases.
// If nil, the default is to create leveldb databases on disk.
NewDB func(path string) (common.Database, error)
@ -196,6 +203,13 @@ type Ethereum struct {
SolcPath string
solc *compiler.Solidity
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
GpoFullBlockRatio int
GpobaseStepDown int
GpobaseStepUp int
GpobaseCorrectionFactor int
net *p2p.Server
eventMux *event.TypeMux
miner *miner.Miner
@ -265,22 +279,28 @@ func New(config *Config) (*Ethereum, error) {
glog.V(logger.Info).Infof("Blockchain DB Version: %d", config.BlockChainVersion)
eth := &Ethereum{
shutdownChan: make(chan bool),
databasesClosed: make(chan bool),
blockDb: blockDb,
stateDb: stateDb,
extraDb: extraDb,
eventMux: &event.TypeMux{},
accountManager: config.AccountManager,
DataDir: config.DataDir,
etherbase: common.HexToAddress(config.Etherbase),
clientVersion: config.Name, // TODO should separate from Name
ethVersionId: config.ProtocolVersion,
netVersionId: config.NetworkId,
NatSpec: config.NatSpec,
MinerThreads: config.MinerThreads,
SolcPath: config.SolcPath,
AutoDAG: config.AutoDAG,
shutdownChan: make(chan bool),
databasesClosed: make(chan bool),
blockDb: blockDb,
stateDb: stateDb,
extraDb: extraDb,
eventMux: &event.TypeMux{},
accountManager: config.AccountManager,
DataDir: config.DataDir,
etherbase: common.HexToAddress(config.Etherbase),
clientVersion: config.Name, // TODO should separate from Name
ethVersionId: config.ProtocolVersion,
netVersionId: config.NetworkId,
NatSpec: config.NatSpec,
MinerThreads: config.MinerThreads,
SolcPath: config.SolcPath,
AutoDAG: config.AutoDAG,
GpoMinGasPrice: config.GpoMinGasPrice,
GpoMaxGasPrice: config.GpoMaxGasPrice,
GpoFullBlockRatio: config.GpoFullBlockRatio,
GpobaseStepDown: config.GpobaseStepDown,
GpobaseStepUp: config.GpobaseStepUp,
GpobaseCorrectionFactor: config.GpobaseCorrectionFactor,
}
eth.pow = ethash.New()

181
eth/gasprice.go Normal file
View File

@ -0,0 +1,181 @@
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
}