cmd, eth, miner: experimental zero-diff miner for dev/hack networks

This commit is contained in:
Péter Szilágyi 2016-12-21 13:03:02 +02:00
parent 6d15d00ac4
commit 6001b04de0
No known key found for this signature in database
GPG key ID: 119A76381CCB7DD2
7 changed files with 184 additions and 9 deletions

View file

@ -106,6 +106,9 @@ func init() {
utils.MiningEnabledFlag,
utils.AutoDAGFlag,
utils.TargetGasLimitFlag,
utils.StrategyZerodiffEnabledFlag,
utils.StrategyZerodiffInstantTxsFlag,
utils.StrategyZerodiffMinEtherFlag,
utils.NATFlag,
utils.NatspecEnabledFlag,
utils.NoDiscoverFlag,

View file

@ -139,6 +139,14 @@ var AppHelpFlagGroups = []flagGroup{
utils.ExtraDataFlag,
},
},
{
Name: "MINER STRATEGY",
Flags: []cli.Flag{
utils.StrategyZerodiffEnabledFlag,
utils.StrategyZerodiffInstantTxsFlag,
utils.StrategyZerodiffMinEtherFlag,
},
},
{
Name: "GAS PRICE ORACLE",
Flags: []cli.Flag{

View file

@ -43,6 +43,7 @@ import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discv5"
@ -210,6 +211,19 @@ var (
Name: "extradata",
Usage: "Block extra data set by the miner (default = client version)",
}
StrategyZerodiffEnabledFlag = cli.BoolFlag{
Name: "miner.zerodiff",
Usage: "Enables the base zero-diff miner strategy (private networks)",
}
StrategyZerodiffInstantTxsFlag = cli.BoolFlag{
Name: "miner.zerodiff.instanttxs",
Usage: "Ignores zero-diff delays if block contains transactions (private networks)",
}
StrategyZerodiffMinEtherFlag = cli.IntFlag{
Name: "miner.zerodiff.minether",
Usage: "Ignores zero-diff delays if miner balance is below threshold (private networks)",
Value: 0,
}
// Account settings
UnlockedAccountFlag = cli.StringFlag{
Name: "unlock",
@ -636,6 +650,24 @@ func MakeMinerExtra(extra []byte, ctx *cli.Context) []byte {
return extra
}
// MakeMinerStrategies assembles any miner strategies from the set command line
// flags.
func MakeMinerStrategies(ctx *cli.Context) []*miner.Strategy {
var strategies []*miner.Strategy
// If zero-diff mining is requested, assemble it
var (
zerodiffBaseline = ctx.GlobalBool(StrategyZerodiffEnabledFlag.Name)
zerodiffInstanttxs = ctx.GlobalBool(StrategyZerodiffInstantTxsFlag.Name)
zerodiffThreshold = ctx.GlobalInt(StrategyZerodiffMinEtherFlag.Name)
)
if zerodiffBaseline || zerodiffInstanttxs || zerodiffThreshold > 0 {
threshold := new(big.Int).Mul(big.NewInt(int64(zerodiffThreshold)), common.Ether)
strategies = append(strategies, miner.NewZeroDiffStrategy(zerodiffInstanttxs, threshold))
}
return strategies
}
// MakePasswordList reads password lines from the file specified by --password.
func MakePasswordList(ctx *cli.Context) []string {
path := ctx.GlobalString(PasswordFileFlag.Name)
@ -737,6 +769,7 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
DatabaseHandles: MakeDatabaseHandles(),
NetworkId: ctx.GlobalInt(NetworkIdFlag.Name),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
MinerStrategies: MakeMinerStrategies(ctx),
ExtraData: MakeMinerExtra(extra, ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
DocRoot: ctx.GlobalString(DocRootFlag.Name),

View file

@ -83,10 +83,11 @@ type Config struct {
PowShared bool
ExtraData []byte
Etherbase common.Address
GasPrice *big.Int
MinerThreads int
SolcPath string
Etherbase common.Address
GasPrice *big.Int
MinerThreads int
MinerStrategies []*miner.Strategy
SolcPath string
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
@ -242,7 +243,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.FastSync, config.NetworkId, maxPeers, eth.eventMux, eth.txPool, eth.pow, eth.blockchain, chainDb); err != nil {
return nil, err
}
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.pow)
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.pow, config.MinerStrategies)
eth.miner.SetGasPrice(config.GasPrice)
eth.miner.SetExtra(config.ExtraData)

View file

@ -60,12 +60,12 @@ type Miner struct {
shouldStart int32 // should start indicates whether we should start after sync
}
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, pow pow.PoW) *Miner {
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, pow pow.PoW, strategies []*Strategy) *Miner {
miner := &Miner{
eth: eth,
mux: mux,
pow: pow,
worker: newWorker(config, common.Address{}, eth, mux),
worker: newWorker(config, common.Address{}, eth, mux, strategies),
canStart: 1,
}
go miner.update()

112
miner/strategy.go Normal file
View file

@ -0,0 +1,112 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package miner
import (
"errors"
"math/big"
"math/rand"
"time"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// Strategy is a collection of optional callback methods that miner strategies
// may implement to influence the behavior of the miner.
type Strategy struct {
Name string // Name of the strategy for logging purposes
// OnMinedBlock is invoked when a new block is mined by the miner.
OnMinedBlock func(emux *event.TypeMux, chain *core.BlockChain, pool *core.TxPool, block *types.Block) error
}
// NewZeroDiffStrategy creates a mining strategy that aims to push the difficulty
// of the blocks down to zero. It tries to achive this by delaying found blocks
// to always be above the configured block time limit, pushing the difficulty
// down a bit after every block.
//
// The strategy has a few nice properties that is orthogonal both to multiple
// zero-diff miners as well as other plain miners:
//
// * The zero-diff miner may be ran with arbitrarilly many threads: if a block
// is found faster than the target block time, it will be delayed anyway, so
// the mining threads will go idle. If on the other hand the difficulty is
// pushed up by a rouge miner and abandoned, the multiple threads will allow
// pulling the difficulty down faster until it reaches sub-target times.
// * The zero-diff miner can play along nicely with non zero-diff miners, since
// it will either delay it's block to above-target times, or outright discard
// its own block if another is found, thereby ensuring it only ever reduces
// the difficulty, never increases.
// * The zero-diff miner can also play along nicely with other zero-diff miners
// by simulating block times at random between [target, 1.1*target], allowing
// multiple zero-diff miners to co-exist and share blocks, without racing each
// other for blocks and leading to a high uncle rate.
//
// Note, this strategy is only meaningful in trusted private networks where the
// goal of mining is not to secure the network, rather to provide a stable but
// resource-light testbed.
func NewZeroDiffStrategy(instantTxs bool, minBalance *big.Int) *Strategy {
return &Strategy{
Name: "zero-diff",
OnMinedBlock: func(emux *event.TypeMux, chain *core.BlockChain, pool *core.TxPool, block *types.Block) error {
// A new block was mined, calculate the required delay
elapsed := time.Since(time.Unix(block.Time().Int64(), 0))
delay := float64(15*time.Second)*(1.0+rand.Float64()/10.0) - float64(elapsed)
// If the delay is negative, block times are way over the target already, release
if delay <= 0 {
return nil
}
// If instant transactions are enabled and the block contains something, release
if instantTxs && len(block.Transactions()) > 0 {
return nil
}
// If minimum balance requirements are set but not met, release
if minBalance != nil {
if state, _ := chain.State(); state.GetBalance(block.Coinbase()).Cmp(minBalance) < 0 {
return nil
}
}
// Otherwise monitor head and transaction events
head := emux.Subscribe(core.ChainHeadEvent{})
defer head.Unsubscribe()
txs := emux.Subscribe(core.TxPreEvent{})
defer txs.Unsubscribe()
// Double check for events that happened before subscriptions
if chain.CurrentHeader().Hash() != block.ParentHash() {
return errors.New("stale block") // Another block arrived already, drop this
}
if pend, _ := pool.Stats(); pend > 0 {
return nil // Transaction pending in the pool, release this and start processing them
}
// Wait for some event to occur that releases or bins the block
select {
case <-time.After(time.Duration(delay)):
return nil // Timeout for difficulty reduction reached, release
case <-head.Chan():
return errors.New("concurrent block") // Alternate block arrived, drop this
case <-txs.Chan():
return nil // Transaction arrived, include it as fast as possible
}
},
}
}

View file

@ -117,7 +117,8 @@ type worker struct {
txQueueMu sync.Mutex
txQueue map[common.Hash]*types.Transaction
unconfirmed *unconfirmedBlocks // set of locally mined blocks pending canonicalness confirmations
unconfirmed *unconfirmedBlocks // Set of locally mined blocks pending confirmation
strategies []*Strategy // Miner strategies currently ran by the miner
// atomic status counters
mining int32
@ -126,7 +127,7 @@ type worker struct {
fullValidation bool
}
func newWorker(config *params.ChainConfig, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
func newWorker(config *params.ChainConfig, coinbase common.Address, eth Backend, mux *event.TypeMux, strategies []*Strategy) *worker {
worker := &worker{
config: config,
eth: eth,
@ -141,6 +142,7 @@ func newWorker(config *params.ChainConfig, coinbase common.Address, eth Backend,
txQueue: make(map[common.Hash]*types.Transaction),
agents: make(map[Agent]struct{}),
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), 5),
strategies: strategies,
fullValidation: false,
}
worker.events = worker.mux.Subscribe(core.ChainHeadEvent{}, core.ChainSideEvent{}, core.TxPreEvent{})
@ -278,6 +280,22 @@ func (self *worker) wait() {
block := result.Block
work := result.Work
// Run any miner strategies interested in block mine results and ensure none denies this block
denied := false
for _, strat := range self.strategies {
if strat.OnMinedBlock != nil {
glog.V(logger.Debug).Infof("Passing mined block #%d [%x…] to strategy %s", block.Number(), block.Hash().Bytes()[:4], strat.Name)
if err := strat.OnMinedBlock(self.mux, self.chain, self.eth.TxPool(), block); err != nil {
glog.V(logger.Debug).Infof("Mined block #%d [%x…] denied by strategy %s: %v", block.Number(), block.Hash().Bytes()[:4], strat.Name, err)
denied = true
break
}
glog.V(logger.Debug).Infof("Mined block #%d [%x…] accepted by strategy %s", block.Number(), block.Hash().Bytes()[:4], strat.Name)
}
}
if denied {
continue
}
if self.fullValidation {
if _, err := self.chain.InsertChain(types.Blocks{block}); err != nil {
glog.V(logger.Error).Infoln("mining err", err)