From d53486746fe2859ae850d63f221cd80fe06de1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 22 Dec 2016 11:18:36 +0200 Subject: [PATCH] miner: zero-diff polishes + suspend if difficulty increases --- miner/strategy.go | 51 ++++++++++++++++++++++++++++++++++++++++++++--- miner/worker.go | 15 +++++++++++++- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/miner/strategy.go b/miner/strategy.go index c8094c9886..de0aa410af 100644 --- a/miner/strategy.go +++ b/miner/strategy.go @@ -27,11 +27,19 @@ import ( "github.com/ethereum/go-ethereum/event" ) +// zerodiffBlocktime block time for the zero-diff miner to target. Note, this +// needs to be in sync with the difficulty adjustment algorithm so chaning this +// or making it configurable doesn't make sense. +const zerodiffBlocktime = 15 * time.Second + // 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 + // OnNewWork is invoked when a new block is starting to be mined by the miner. + OnNewWork func(emux *event.TypeMux, chain *core.BlockChain, block *types.Block) error + // 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 } @@ -39,7 +47,8 @@ type Strategy struct { // 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. +// down a bit after every block. Similarly if it notices the difficulty going up, +// it aborts mining altogether. // // The strategy has a few nice properties that is orthogonal both to multiple // zero-diff miners as well as other plain miners: @@ -50,7 +59,7 @@ type Strategy struct { // 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 +// it will either delay its 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 @@ -58,6 +67,16 @@ type Strategy struct { // multiple zero-diff miners to co-exist and share blocks, without racing each // other for blocks and leading to a high uncle rate. // +// There are two short-circuits built in that can cause the zero-diff miner to +// temporarilly turn off and mine at full capacity: +// +// * If instant-transactions are requested, then blocks containing transactions +// will not be delayed at all; furthermore empty blocks will be fast-tracked +// too if the transaction pool contains pending transactions ready to be added +// to the next block. +// * If a minimum balance threshold is set for the miner, no delays will come +// into effect until that minimum threshold is reached. +// // 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. @@ -65,10 +84,36 @@ func NewZeroDiffStrategy(instantTxs bool, minBalance *big.Int) *Strategy { return &Strategy{ Name: "zero-diff", + OnNewWork: func(emux *event.TypeMux, chain *core.BlockChain, block *types.Block) error { + // If the difficulty is increasing, give a change for others to waste resources + if parent := chain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent.Difficulty().Cmp(block.Difficulty()) < 0 { + // Before bailing out, do check if balance threshold needs to be reached + if minBalance != nil { + if state, _ := chain.State(); state.GetBalance(block.Coinbase()).Cmp(minBalance) < 0 { + return nil + } + } + // Someone seems to be mining, listen for events though to avoid hangs if the other leaves + head := emux.Subscribe(core.ChainHeadEvent{}) + defer head.Unsubscribe() + + if chain.CurrentHeader().Hash() != block.ParentHash() { + return errors.New("stale work") // Another block arrived already, drop this + } + select { + case <-time.After(zerodiffBlocktime): + return nil // Ok, nobody else seems to be mining, fire up the CPU + case <-head.Chan(): + return errors.New("concurrent miner") // Meh, someone's mining, let them burn + } + } + return nil + }, + 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) + delay := float64(zerodiffBlocktime)*(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 { diff --git a/miner/worker.go b/miner/worker.go index 0c04f32dcf..d76ded2baa 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -370,9 +370,22 @@ func (self *worker) wait() { // push sends a new work task to currently live miner agents. func (self *worker) push(work *Work) { + // If there are no agents mining, bail out if atomic.LoadInt32(&self.mining) != 1 { return } + // Iterate over all miner strategies and ensure none denies this block + for _, strat := range self.strategies { + if strat.OnMinedBlock != nil { + glog.V(logger.Debug).Infof("Passing mining request #%d [%x…] to strategy %s", work.Block.Number(), work.Block.Hash().Bytes()[:4], strat.Name) + if err := strat.OnNewWork(self.mux, self.chain, work.Block); err != nil { + glog.V(logger.Debug).Infof("Mining request #%d [%x…] denied by strategy %s: %v", work.Block.Number(), work.Block.Hash().Bytes()[:4], strat.Name, err) + return + } + glog.V(logger.Debug).Infof("Mining request #%d [%x…] accepted by strategy %s", work.Block.Number(), work.Block.Hash().Bytes()[:4], strat.Name) + } + } + // Block accepted for mining, proceed for agent := range self.agents { atomic.AddInt32(&self.atWork, 1) if ch := agent.Work(); ch != nil { @@ -534,7 +547,7 @@ func (self *worker) commitNewWork() { glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles. Took %v\n", work.Block.Number(), work.tcount, len(uncles), time.Since(tstart)) self.unconfirmed.Shift(work.Block.NumberU64() - 1) } - self.push(work) + go self.push(work) } func (self *worker) commitUncle(work *Work, uncle *types.Header) error {