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
commit f2a2164184
8 changed files with 319 additions and 59 deletions

View File

@ -256,6 +256,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.PProfEanbledFlag, utils.PProfEanbledFlag,
utils.PProfPortFlag, utils.PProfPortFlag,
utils.SolcPathFlag, utils.SolcPathFlag,
utils.GpoMinGasPriceFlag,
utils.GpoMaxGasPriceFlag,
utils.GpoFullBlockRatioFlag,
utils.GpobaseStepDownFlag,
utils.GpobaseStepUpFlag,
utils.GpobaseCorrectionFactorFlag,
} }
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
utils.SetupLogger(ctx) utils.SetupLogger(ctx)

View File

@ -23,10 +23,10 @@ import (
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/xeth"
"github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/xeth"
) )
func init() { func init() {
@ -132,7 +132,7 @@ var (
GasPriceFlag = cli.StringFlag{ GasPriceFlag = cli.StringFlag{
Name: "gasprice", Name: "gasprice",
Usage: "Sets the minimal gasprice when mining transactions", Usage: "Sets the minimal gasprice when mining transactions",
Value: new(big.Int).Mul(big.NewInt(10), common.Szabo).String(), Value: new(big.Int).Mul(big.NewInt(1), common.Szabo).String(),
} }
UnlockedAccountFlag = cli.StringFlag{ UnlockedAccountFlag = cli.StringFlag{
@ -276,6 +276,36 @@ var (
Usage: "solidity compiler to be used", Usage: "solidity compiler to be used",
Value: "solc", Value: "solc",
} }
GpoMinGasPriceFlag = cli.StringFlag{
Name: "gpomin",
Usage: "Minimum suggested gas price",
Value: new(big.Int).Mul(big.NewInt(1), common.Szabo).String(),
}
GpoMaxGasPriceFlag = cli.StringFlag{
Name: "gpomax",
Usage: "Maximum suggested gas price",
Value: new(big.Int).Mul(big.NewInt(100), common.Szabo).String(),
}
GpoFullBlockRatioFlag = cli.IntFlag{
Name: "gpofull",
Usage: "Full block threshold for gas price calculation (%)",
Value: 80,
}
GpobaseStepDownFlag = cli.IntFlag{
Name: "gpobasedown",
Usage: "Suggested gas price base step down ratio (1/1000)",
Value: 10,
}
GpobaseStepUpFlag = cli.IntFlag{
Name: "gpobaseup",
Usage: "Suggested gas price base step up ratio (1/1000)",
Value: 100,
}
GpobaseCorrectionFactorFlag = cli.IntFlag{
Name: "gpobasecf",
Usage: "Suggested gas price base correction factor (%)",
Value: 110,
}
) )
// MakeNAT creates a port mapper from set command line flags. // MakeNAT creates a port mapper from set command line flags.
@ -338,6 +368,12 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
Dial: true, Dial: true,
BootNodes: ctx.GlobalString(BootnodesFlag.Name), BootNodes: ctx.GlobalString(BootnodesFlag.Name),
GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)),
GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)),
GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)),
GpoFullBlockRatio: ctx.GlobalInt(GpoFullBlockRatioFlag.Name),
GpobaseStepDown: ctx.GlobalInt(GpobaseStepDownFlag.Name),
GpobaseStepUp: ctx.GlobalInt(GpobaseStepUpFlag.Name),
GpobaseCorrectionFactor: ctx.GlobalInt(GpobaseCorrectionFactorFlag.Name),
SolcPath: ctx.GlobalString(SolcPathFlag.Name), SolcPath: ctx.GlobalString(SolcPathFlag.Name),
AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name), AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name),
} }

View File

@ -260,6 +260,12 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st
putTx(sm.extraDb, tx, block, uint64(i)) putTx(sm.extraDb, tx, block, uint64(i))
} }
receiptsRlp := receipts.RlpEncode()
/*if len(receipts) > 0 {
glog.V(logger.Info).Infof("Saving %v receipts, rlp len is %v\n", len(receipts), len(receiptsRlp))
}*/
sm.extraDb.Put(append(receiptsPre, block.Hash().Bytes()...), receiptsRlp)
return state.Logs(), nil return state.Logs(), nil
} }
@ -404,6 +410,8 @@ func getBlockReceipts(db common.Database, bhash common.Hash) (receipts types.Rec
if err == nil { if err == nil {
err = rlp.DecodeBytes(rdata, &receipts) err = rlp.DecodeBytes(rdata, &receipts)
} else {
glog.V(logger.Detail).Infof("getBlockReceipts error %v\n", err)
} }
return return
} }

View File

@ -93,6 +93,13 @@ type Config struct {
AccountManager *accounts.Manager AccountManager *accounts.Manager
SolcPath string SolcPath string
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
GpoFullBlockRatio int
GpobaseStepDown int
GpobaseStepUp int
GpobaseCorrectionFactor int
// NewDB is used to create databases. // NewDB is used to create databases.
// If nil, the default is to create leveldb databases on disk. // If nil, the default is to create leveldb databases on disk.
NewDB func(path string) (common.Database, error) NewDB func(path string) (common.Database, error)
@ -196,6 +203,13 @@ type Ethereum struct {
SolcPath string SolcPath string
solc *compiler.Solidity solc *compiler.Solidity
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
GpoFullBlockRatio int
GpobaseStepDown int
GpobaseStepUp int
GpobaseCorrectionFactor int
net *p2p.Server net *p2p.Server
eventMux *event.TypeMux eventMux *event.TypeMux
miner *miner.Miner miner *miner.Miner
@ -281,6 +295,12 @@ func New(config *Config) (*Ethereum, error) {
MinerThreads: config.MinerThreads, MinerThreads: config.MinerThreads,
SolcPath: config.SolcPath, SolcPath: config.SolcPath,
AutoDAG: config.AutoDAG, 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() 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
}

View File

@ -59,7 +59,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
case "eth_mining": case "eth_mining":
*reply = api.xeth().IsMining() *reply = api.xeth().IsMining()
case "eth_gasPrice": case "eth_gasPrice":
v := xeth.DefaultGasPrice() v := api.xeth().DefaultGasPrice()
*reply = newHexNum(v.Bytes()) *reply = newHexNum(v.Bytes())
case "eth_accounts": case "eth_accounts":
*reply = api.xeth().Accounts() *reply = api.xeth().Accounts()

View File

@ -139,7 +139,7 @@ func (self *ethApi) IsMining(req *shared.Request) (interface{}, error) {
} }
func (self *ethApi) GasPrice(req *shared.Request) (interface{}, error) { func (self *ethApi) GasPrice(req *shared.Request) (interface{}, error) {
return newHexNum(xeth.DefaultGasPrice().Bytes()), nil return newHexNum(self.xeth.DefaultGasPrice().Bytes()), nil
} }
func (self *ethApi) GetStorage(req *shared.Request) (interface{}, error) { func (self *ethApi) GetStorage(req *shared.Request) (interface{}, error) {

View File

@ -40,7 +40,13 @@ const (
) )
func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) } func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) }
func DefaultGasPrice() *big.Int { return new(big.Int).Set(defaultGasPrice) }
func (self *XEth) DefaultGasPrice() *big.Int {
if self.gpo == nil {
self.gpo = eth.NewGasPriceOracle(self.backend)
}
return self.gpo.SuggestPrice()
}
type XEth struct { type XEth struct {
backend *eth.Ethereum backend *eth.Ethereum
@ -68,6 +74,8 @@ type XEth struct {
// register map[string][]*interface{} // TODO improve return type // register map[string][]*interface{} // TODO improve return type
agent *miner.RemoteAgent agent *miner.RemoteAgent
gpo *eth.GasPriceOracle
} }
func NewTest(eth *eth.Ethereum, frontend Frontend) *XEth { func NewTest(eth *eth.Ethereum, frontend Frontend) *XEth {
@ -80,22 +88,22 @@ func NewTest(eth *eth.Ethereum, frontend Frontend) *XEth {
// New creates an XEth that uses the given frontend. // New creates an XEth that uses the given frontend.
// If a nil Frontend is provided, a default frontend which // If a nil Frontend is provided, a default frontend which
// confirms all transactions will be used. // confirms all transactions will be used.
func New(eth *eth.Ethereum, frontend Frontend) *XEth { func New(ethereum *eth.Ethereum, frontend Frontend) *XEth {
xeth := &XEth{ xeth := &XEth{
backend: eth, backend: ethereum,
frontend: frontend, frontend: frontend,
quit: make(chan struct{}), quit: make(chan struct{}),
filterManager: filter.NewFilterManager(eth.EventMux()), filterManager: filter.NewFilterManager(ethereum.EventMux()),
logQueue: make(map[int]*logQueue), logQueue: make(map[int]*logQueue),
blockQueue: make(map[int]*hashQueue), blockQueue: make(map[int]*hashQueue),
transactionQueue: make(map[int]*hashQueue), transactionQueue: make(map[int]*hashQueue),
messages: make(map[int]*whisperFilter), messages: make(map[int]*whisperFilter),
agent: miner.NewRemoteAgent(), agent: miner.NewRemoteAgent(),
} }
if eth.Whisper() != nil { if ethereum.Whisper() != nil {
xeth.whisper = NewWhisper(eth.Whisper()) xeth.whisper = NewWhisper(ethereum.Whisper())
} }
eth.Miner().Register(xeth.agent) ethereum.Miner().Register(xeth.agent)
if frontend == nil { if frontend == nil {
xeth.frontend = dummyFrontend{} xeth.frontend = dummyFrontend{}
} }
@ -227,6 +235,7 @@ func (self *XEth) WithState(statedb *state.StateDB) *XEth {
xeth := &XEth{ xeth := &XEth{
backend: self.backend, backend: self.backend,
frontend: self.frontend, frontend: self.frontend,
gpo: self.gpo,
} }
xeth.state = NewState(xeth, statedb) xeth.state = NewState(xeth, statedb)
@ -829,7 +838,7 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
} }
if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { if msg.gasPrice.Cmp(big.NewInt(0)) == 0 {
msg.gasPrice = DefaultGasPrice() msg.gasPrice = self.DefaultGasPrice()
} }
block := self.CurrentBlock() block := self.CurrentBlock()
@ -898,7 +907,7 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS
} }
if len(gasPriceStr) == 0 { if len(gasPriceStr) == 0 {
price = DefaultGasPrice() price = self.DefaultGasPrice()
} else { } else {
price = common.Big(gasPriceStr) price = common.Big(gasPriceStr)
} }