diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0f2438cfd..1739fbc6b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -256,6 +256,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.PProfEanbledFlag, utils.PProfPortFlag, utils.SolcPathFlag, + utils.GpoMinGasPriceFlag, + utils.GpoMaxGasPriceFlag, + utils.GpoFullBlockRatioFlag, + utils.GpobaseStepDownFlag, + utils.GpobaseStepUpFlag, + utils.GpobaseCorrectionFactorFlag, } app.Before = func(ctx *cli.Context) error { utils.SetupLogger(ctx) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ec29598fb..696dbd142 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,10 +23,10 @@ import ( "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p/nat" "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/comms" "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/ethereum/go-ethereum/xeth" ) func init() { @@ -132,7 +132,7 @@ var ( GasPriceFlag = cli.StringFlag{ Name: "gasprice", 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{ @@ -276,6 +276,36 @@ var ( Usage: "solidity compiler to be used", 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. @@ -313,33 +343,39 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { clientID += "/" + customName } return ð.Config{ - Name: common.MakeName(clientID, version), - DataDir: ctx.GlobalString(DataDirFlag.Name), - ProtocolVersion: ctx.GlobalInt(ProtocolVersionFlag.Name), - GenesisNonce: ctx.GlobalInt(GenesisNonceFlag.Name), - BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name), - SkipBcVersionCheck: false, - NetworkId: ctx.GlobalInt(NetworkIdFlag.Name), - LogFile: ctx.GlobalString(LogFileFlag.Name), - Verbosity: ctx.GlobalInt(VerbosityFlag.Name), - LogJSON: ctx.GlobalString(LogJSONFlag.Name), - Etherbase: ctx.GlobalString(EtherbaseFlag.Name), - MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), - AccountManager: MakeAccountManager(ctx), - VmDebug: ctx.GlobalBool(VMDebugFlag.Name), - MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), - MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), - Port: ctx.GlobalString(ListenPortFlag.Name), - NAT: MakeNAT(ctx), - NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), - Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name), - NodeKey: MakeNodeKey(ctx), - Shh: ctx.GlobalBool(WhisperEnabledFlag.Name), - Dial: true, - BootNodes: ctx.GlobalString(BootnodesFlag.Name), - GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), - SolcPath: ctx.GlobalString(SolcPathFlag.Name), - AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name), + Name: common.MakeName(clientID, version), + DataDir: ctx.GlobalString(DataDirFlag.Name), + ProtocolVersion: ctx.GlobalInt(ProtocolVersionFlag.Name), + GenesisNonce: ctx.GlobalInt(GenesisNonceFlag.Name), + BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name), + SkipBcVersionCheck: false, + NetworkId: ctx.GlobalInt(NetworkIdFlag.Name), + LogFile: ctx.GlobalString(LogFileFlag.Name), + Verbosity: ctx.GlobalInt(VerbosityFlag.Name), + LogJSON: ctx.GlobalString(LogJSONFlag.Name), + Etherbase: ctx.GlobalString(EtherbaseFlag.Name), + MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), + AccountManager: MakeAccountManager(ctx), + VmDebug: ctx.GlobalBool(VMDebugFlag.Name), + MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), + MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), + Port: ctx.GlobalString(ListenPortFlag.Name), + NAT: MakeNAT(ctx), + NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), + Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name), + NodeKey: MakeNodeKey(ctx), + Shh: ctx.GlobalBool(WhisperEnabledFlag.Name), + Dial: true, + BootNodes: ctx.GlobalString(BootnodesFlag.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), + AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name), } } @@ -396,7 +432,7 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) { if ctx.GlobalString(IPCPathFlag.Name) != common.DefaultIpcPath() { ipcpath = ctx.GlobalString(IPCPathFlag.Name) } else if ctx.GlobalString(DataDirFlag.Name) != "" && - ctx.GlobalString(DataDirFlag.Name) != common.DefaultDataDir() { + ctx.GlobalString(DataDirFlag.Name) != common.DefaultDataDir() { ipcpath = filepath.Join(ctx.GlobalString(DataDirFlag.Name), "geth.ipc") } } diff --git a/core/block_processor.go b/core/block_processor.go index 54378b2b9..c01b110be 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -260,6 +260,12 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st 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 } @@ -404,6 +410,8 @@ func getBlockReceipts(db common.Database, bhash common.Hash) (receipts types.Rec if err == nil { err = rlp.DecodeBytes(rdata, &receipts) + } else { + glog.V(logger.Detail).Infof("getBlockReceipts error %v\n", err) } return } diff --git a/eth/backend.go b/eth/backend.go index 4ebf21811..17447751d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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() diff --git a/eth/gasprice.go b/eth/gasprice.go new file mode 100644 index 000000000..cd5293691 --- /dev/null +++ b/eth/gasprice.go @@ -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 +} diff --git a/rpc/api.go b/rpc/api.go index e35395734..8b9a080f8 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -59,7 +59,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err case "eth_mining": *reply = api.xeth().IsMining() case "eth_gasPrice": - v := xeth.DefaultGasPrice() + v := api.xeth().DefaultGasPrice() *reply = newHexNum(v.Bytes()) case "eth_accounts": *reply = api.xeth().Accounts() diff --git a/rpc/api/eth.go b/rpc/api/eth.go index a0b9dad86..943f19b90 100644 --- a/rpc/api/eth.go +++ b/rpc/api/eth.go @@ -139,7 +139,7 @@ func (self *ethApi) IsMining(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) { diff --git a/xeth/xeth.go b/xeth/xeth.go index d2f992084..71753d6bd 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -39,8 +39,14 @@ const ( LogFilterTy ) -func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) } -func DefaultGasPrice() *big.Int { return new(big.Int).Set(defaultGasPrice) } +func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) } + +func (self *XEth) DefaultGasPrice() *big.Int { + if self.gpo == nil { + self.gpo = eth.NewGasPriceOracle(self.backend) + } + return self.gpo.SuggestPrice() +} type XEth struct { backend *eth.Ethereum @@ -68,6 +74,8 @@ type XEth struct { // register map[string][]*interface{} // TODO improve return type agent *miner.RemoteAgent + + gpo *eth.GasPriceOracle } 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. // If a nil Frontend is provided, a default frontend which // confirms all transactions will be used. -func New(eth *eth.Ethereum, frontend Frontend) *XEth { +func New(ethereum *eth.Ethereum, frontend Frontend) *XEth { xeth := &XEth{ - backend: eth, + backend: ethereum, frontend: frontend, quit: make(chan struct{}), - filterManager: filter.NewFilterManager(eth.EventMux()), + filterManager: filter.NewFilterManager(ethereum.EventMux()), logQueue: make(map[int]*logQueue), blockQueue: make(map[int]*hashQueue), transactionQueue: make(map[int]*hashQueue), messages: make(map[int]*whisperFilter), agent: miner.NewRemoteAgent(), } - if eth.Whisper() != nil { - xeth.whisper = NewWhisper(eth.Whisper()) + if ethereum.Whisper() != nil { + xeth.whisper = NewWhisper(ethereum.Whisper()) } - eth.Miner().Register(xeth.agent) + ethereum.Miner().Register(xeth.agent) if frontend == nil { xeth.frontend = dummyFrontend{} } @@ -227,6 +235,7 @@ func (self *XEth) WithState(statedb *state.StateDB) *XEth { xeth := &XEth{ backend: self.backend, frontend: self.frontend, + gpo: self.gpo, } 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 { - msg.gasPrice = DefaultGasPrice() + msg.gasPrice = self.DefaultGasPrice() } block := self.CurrentBlock() @@ -898,7 +907,7 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS } if len(gasPriceStr) == 0 { - price = DefaultGasPrice() + price = self.DefaultGasPrice() } else { price = common.Big(gasPriceStr) }