diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 0ff72e55c6..03b4a17557 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -590,7 +590,7 @@ func (c *Clique) Authorize(signer common.Address, signFn SignerFn) { // Seal implements consensus.Engine, attempting to create a sealed block using // the local signing credentials. -func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { +func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *consensus.SealResult, stop <-chan struct{}) error { header := block.Header() // Sealing the genesis block is not supported @@ -635,7 +635,8 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) } // Sign all the things! - sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) + sealhash := sigHash(header) + sighash, err := signFn(accounts.Account{Address: signer}, sealhash.Bytes()) if err != nil { return err } @@ -650,7 +651,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c } select { - case results <- block.WithSeal(header): + case results <- &consensus.SealResult{SealHash: sealhash, Block: block.WithSeal(header)}: default: log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header)) } diff --git a/consensus/consensus.go b/consensus/consensus.go index 487b07be77..1f4aa6a8f5 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -91,7 +91,7 @@ type Engine interface { // // Note, the method returns immediately and will send the result async. More // than one result may also be returned depending on the consensus algorithm. - Seal(chain ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error + Seal(chain ChainReader, block *types.Block, results chan<- *SealResult, stop <-chan struct{}) error // SealHash returns the hash of a block prior to it being sealed. SealHash(header *types.Header) common.Hash @@ -114,3 +114,14 @@ type PoW interface { // Hashrate returns the current mining hashrate of a PoW consensus engine. Hashrate() float64 } + +// SealResult contains a sealed block and the corresponding sealhash. +// +// Note for ethash, the sealhash in struct may not match the result of SealHash +// calculation with the block because we allow remote miner to customize `extra` +// field for the mining block. The sealhash in struct is the unique id +// for miner tracked mining work. +type SealResult struct { + SealHash common.Hash + Block *types.Block +} diff --git a/consensus/ethash/api.go b/consensus/ethash/api.go index fa60aa1780..5b820088dd 100644 --- a/consensus/ethash/api.go +++ b/consensus/ethash/api.go @@ -46,17 +46,12 @@ func (api *API) GetWork(extra *string) ([3]string, error) { } var ( - extraData string - workCh = make(chan [3]string, 1) - errc = make(chan error, 1) + workCh = make(chan [3]string, 1) + errc = make(chan error, 1) ) - if extra != nil { - extraData = *extra - } - select { - case api.ethash.fetchWorkCh <- &sealWork{extra: extraData, errc: errc, res: workCh}: + case api.ethash.fetchWorkCh <- &sealWork{extra: extra, errc: errc, res: workCh}: case <-api.ethash.exitCh: return [3]string{}, errEthashStopped } diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 34e95e158a..cc118ecafd 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -408,7 +408,7 @@ type Config struct { // sealTask wraps a seal block with relative result channel for remote sealer thread. type sealTask struct { block *types.Block - results chan<- *types.Block + results chan<- *consensus.SealResult } // mineResult wraps the pow solution parameters for the specified block. @@ -431,7 +431,7 @@ type hashrate struct { // sealWork wraps a seal work package for remote sealer. type sealWork struct { - extra string // The specified string used to fill the block extra field + extra *string // The specified string used to fill the block extra field errc chan error res chan [3]string } diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index fc5aa1a524..3c11e80604 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" ) @@ -37,15 +38,15 @@ func TestTestMode(t *testing.T) { ethash := NewTester(nil, false) defer ethash.Close() - results := make(chan *types.Block) + results := make(chan *consensus.SealResult) err := ethash.Seal(nil, types.NewBlockWithHeader(header), results, nil) if err != nil { t.Fatalf("failed to seal block: %v", err) } select { - case block := <-results: - header.Nonce = types.EncodeNonce(block.Nonce()) - header.MixDigest = block.MixDigest() + case result := <-results: + header.Nonce = types.EncodeNonce(result.Block.Nonce()) + header.MixDigest = result.Block.MixDigest() if err := ethash.VerifySeal(nil, header); err != nil { t.Fatalf("unexpected verification error: %v", err) } @@ -103,7 +104,7 @@ func TestRemoteSealer(t *testing.T) { sealhash := ethash.SealHash(header) // Push new work. - results := make(chan *types.Block) + results := make(chan *consensus.SealResult) ethash.Seal(nil, block, results, nil) var ( @@ -138,7 +139,7 @@ func TestCustomizedWork(t *testing.T) { block := types.NewBlockWithHeader(header) // Push new work. - results := make(chan *types.Block, 1) + results := make(chan *consensus.SealResult, 1) ethash.Seal(nil, block, results, nil) // Get customized mining work. diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go index 9f856c8843..d6c70c46f0 100644 --- a/consensus/ethash/sealer.go +++ b/consensus/ethash/sealer.go @@ -47,13 +47,13 @@ var ( // Seal implements consensus.Engine, attempting to find a nonce that satisfies // the block's difficulty requirements. -func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { +func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *consensus.SealResult, stop <-chan struct{}) error { // If we're running a fake PoW, simply return a 0 nonce immediately if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { header := block.Header() header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} select { - case results <- block.WithSeal(header): + case results <- &consensus.SealResult{SealHash: ethash.SealHash(header), Block: block.WithSeal(header)}: default: log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header())) } @@ -107,10 +107,11 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, resu close(abort) case result = <-locals: // One of the threads found a block, abort all others + sealhash := ethash.SealHash(block.Header()) select { - case results <- result: + case results <- &consensus.SealResult{SealHash: sealhash, Block: result}: default: - log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", ethash.SealHash(block.Header())) + log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", sealhash) } close(abort) case <-ethash.update: @@ -187,13 +188,21 @@ search: // remote is a standalone goroutine to handle remote mining related stuff. func (ethash *Ethash) remote(notify []string, noverify bool) { + // miningWork wraps a mining block fetched by remote miner and + // an unique id tracked by miner. + type miningWork struct { + sealHash common.Hash + block *types.Block + } + var ( - works = make(map[common.Hash]*types.Block) + works = make(map[common.Hash]*miningWork) rates = make(map[common.Hash]hashrate) - results chan<- *types.Block + results chan<- *consensus.SealResult currentBlock *types.Block currentWork [3]string + currentHash common.Hash notifyTransport = &http.Transport{} notifyClient = &http.Client{ @@ -243,7 +252,9 @@ func (ethash *Ethash) remote(notify []string, noverify bool) { // Trace the seal work fetched by remote sealer. currentBlock = block - works[hash] = block + works[hash] = &miningWork{block: block, sealHash: hash} + currentHash = hash + log.Trace("Create mining work", "sealhash", hash, "number", currentBlock.NumberU64()) } // customizeWork creates a work package for external miner // with customized extra data. @@ -260,7 +271,8 @@ func (ethash *Ethash) remote(notify []string, noverify bool) { work[2] = currentWork[2] // Trace the seal work fetched by remote sealer. - works[hash] = newBlock + log.Trace("Create customized work", "sealhash", hash, "original", currentHash, "number", currentBlock.NumberU64()) + works[hash] = &miningWork{block: newBlock, sealHash: currentHash} return work } // submitWork verifies the submitted pow solution, returning @@ -272,13 +284,13 @@ func (ethash *Ethash) remote(notify []string, noverify bool) { return false } // Make sure the work submitted is present - block := works[sealhash] - if block == nil { + work := works[sealhash] + if work == nil { log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64()) return false } // Verify the correctness of submitted result. - header := block.Header() + header := work.block.Header() header.Nonce = nonce header.MixDigest = mixDigest @@ -297,12 +309,12 @@ func (ethash *Ethash) remote(notify []string, noverify bool) { log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", time.Since(start)) // Solutions seems to be valid, return to the miner and notify acceptance. - solution := block.WithSeal(header) + solution := work.block.WithSeal(header) // The submitted solution is within the scope of acceptance. if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() { select { - case results <- solution: + case results <- &consensus.SealResult{SealHash: work.sealHash, Block: solution}: log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) return true default: @@ -334,13 +346,13 @@ func (ethash *Ethash) remote(notify []string, noverify bool) { if currentBlock == nil { // Return error message if there is no available mining work yet. work.errc <- errNoMiningWork - } else if work.extra == "" { + } else if work.extra == nil { // Return the default current mining work if no extra customized data // specified. work.res <- currentWork } else { // Return the customized mining work. - work.res <- customizeWork(work.extra) + work.res <- customizeWork(*work.extra) } case result := <-ethash.submitWorkCh: @@ -374,8 +386,8 @@ func (ethash *Ethash) remote(notify []string, noverify bool) { } // Clear stale pending blocks if currentBlock != nil { - for hash, block := range works { - if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() { + for hash, work := range works { + if work.block.NumberU64()+staleThreshold <= currentBlock.NumberU64() { delete(works, hash) } } diff --git a/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go index 436359af7c..1bdcaf9faa 100644 --- a/consensus/ethash/sealer_test.go +++ b/consensus/ethash/sealer_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" ) @@ -175,7 +176,7 @@ func TestStaleSubmission(t *testing.T) { false, }, } - results := make(chan *types.Block, 16) + results := make(chan *consensus.SealResult, 16) for id, c := range testcases { for _, h := range c.headers { @@ -189,20 +190,20 @@ func TestStaleSubmission(t *testing.T) { } select { case res := <-results: - if res.Header().Nonce != fakeNonce { - t.Errorf("case %d block nonce mismatch, want %s, get %s", id+1, fakeNonce, res.Header().Nonce) + if res.Block.Header().Nonce != fakeNonce { + t.Errorf("case %d block nonce mismatch, want %s, get %s", id+1, fakeNonce, res.Block.Header().Nonce) } - if res.Header().MixDigest != fakeDigest { - t.Errorf("case %d block digest mismatch, want %s, get %s", id+1, fakeDigest, res.Header().MixDigest) + if res.Block.Header().MixDigest != fakeDigest { + t.Errorf("case %d block digest mismatch, want %s, get %s", id+1, fakeDigest, res.Block.Header().MixDigest) } - if res.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() { - t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Header().Difficulty) + if res.Block.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() { + t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Block.Header().Difficulty) } - if res.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() { - t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Header().Number.Uint64()) + if res.Block.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() { + t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Block.Header().Number.Uint64()) } - if res.Header().ParentHash != c.headers[c.submitIndex].ParentHash { - t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Header().ParentHash.Hex()) + if res.Block.Header().ParentHash != c.headers[c.submitIndex].ParentHash { + t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Block.Header().ParentHash.Hex()) } case <-time.NewTimer(time.Second).C: t.Errorf("case %d fetch ethash result timeout", id+1) diff --git a/miner/worker.go b/miner/worker.go index 3500ca4c23..cbe6b8f1ff 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -143,7 +143,7 @@ type worker struct { // Channels newWorkCh chan *newWorkReq taskCh chan *task - resultCh chan *types.Block + resultCh chan *consensus.SealResult startCh chan struct{} exitCh chan struct{} resubmitIntervalCh chan time.Duration @@ -192,7 +192,7 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), taskCh: make(chan *task), - resultCh: make(chan *types.Block, resultQueueSize), + resultCh: make(chan *consensus.SealResult, resultQueueSize), exitCh: make(chan struct{}), startCh: make(chan struct{}, 1), resubmitIntervalCh: make(chan time.Duration), @@ -524,24 +524,25 @@ func (w *worker) taskLoop() { func (w *worker) resultLoop() { for { select { - case block := <-w.resultCh: + case result := <-w.resultCh: // Short circuit when receiving empty result. - if block == nil { + if result == nil { continue } + var ( + block = result.Block + hash = result.Block.Hash() + ) // Short circuit when receiving duplicate result caused by resubmitting. if w.chain.HasBlock(block.Hash(), block.NumberU64()) { continue } - var ( - sealhash = w.engine.SealHash(block.Header()) - hash = block.Hash() - ) + w.pendingMu.RLock() - task, exist := w.pendingTasks[sealhash] + task, exist := w.pendingTasks[result.SealHash] w.pendingMu.RUnlock() if !exist { - log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) + log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", result.SealHash, "hash", hash) continue } // Different block could share same sealhash, deep copy here to prevent write-write conflict. @@ -565,7 +566,7 @@ func (w *worker) resultLoop() { log.Error("Failed writing block to chain", "err", err) continue } - log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, + log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", result.SealHash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) // Broadcast the block and announce chain insertion event