consensus, miner: track original sealhash in ethash remote sealer

This commit is contained in:
rjl493456442 2018-09-18 13:10:14 +08:00
parent 05d9cd1560
commit 012aee25f6
8 changed files with 81 additions and 59 deletions

View file

@ -590,7 +590,7 @@ func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
// Seal implements consensus.Engine, attempting to create a sealed block using // Seal implements consensus.Engine, attempting to create a sealed block using
// the local signing credentials. // 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() header := block.Header()
// Sealing the genesis block is not supported // 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)) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
} }
// Sign all the things! // 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 { if err != nil {
return err return err
} }
@ -650,7 +651,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
} }
select { select {
case results <- block.WithSeal(header): case results <- &consensus.SealResult{SealHash: sealhash, Block: block.WithSeal(header)}:
default: default:
log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header)) log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header))
} }

View file

@ -91,7 +91,7 @@ type Engine interface {
// //
// Note, the method returns immediately and will send the result async. More // Note, the method returns immediately and will send the result async. More
// than one result may also be returned depending on the consensus algorithm. // 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 returns the hash of a block prior to it being sealed.
SealHash(header *types.Header) common.Hash 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 returns the current mining hashrate of a PoW consensus engine.
Hashrate() float64 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
}

View file

@ -46,17 +46,12 @@ func (api *API) GetWork(extra *string) ([3]string, error) {
} }
var ( var (
extraData string workCh = make(chan [3]string, 1)
workCh = make(chan [3]string, 1) errc = make(chan error, 1)
errc = make(chan error, 1)
) )
if extra != nil {
extraData = *extra
}
select { 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: case <-api.ethash.exitCh:
return [3]string{}, errEthashStopped return [3]string{}, errEthashStopped
} }

View file

@ -408,7 +408,7 @@ type Config struct {
// sealTask wraps a seal block with relative result channel for remote sealer thread. // sealTask wraps a seal block with relative result channel for remote sealer thread.
type sealTask struct { type sealTask struct {
block *types.Block block *types.Block
results chan<- *types.Block results chan<- *consensus.SealResult
} }
// mineResult wraps the pow solution parameters for the specified block. // 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. // sealWork wraps a seal work package for remote sealer.
type sealWork struct { 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 errc chan error
res chan [3]string res chan [3]string
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
@ -37,15 +38,15 @@ func TestTestMode(t *testing.T) {
ethash := NewTester(nil, false) ethash := NewTester(nil, false)
defer ethash.Close() defer ethash.Close()
results := make(chan *types.Block) results := make(chan *consensus.SealResult)
err := ethash.Seal(nil, types.NewBlockWithHeader(header), results, nil) err := ethash.Seal(nil, types.NewBlockWithHeader(header), results, nil)
if err != nil { if err != nil {
t.Fatalf("failed to seal block: %v", err) t.Fatalf("failed to seal block: %v", err)
} }
select { select {
case block := <-results: case result := <-results:
header.Nonce = types.EncodeNonce(block.Nonce()) header.Nonce = types.EncodeNonce(result.Block.Nonce())
header.MixDigest = block.MixDigest() header.MixDigest = result.Block.MixDigest()
if err := ethash.VerifySeal(nil, header); err != nil { if err := ethash.VerifySeal(nil, header); err != nil {
t.Fatalf("unexpected verification error: %v", err) t.Fatalf("unexpected verification error: %v", err)
} }
@ -103,7 +104,7 @@ func TestRemoteSealer(t *testing.T) {
sealhash := ethash.SealHash(header) sealhash := ethash.SealHash(header)
// Push new work. // Push new work.
results := make(chan *types.Block) results := make(chan *consensus.SealResult)
ethash.Seal(nil, block, results, nil) ethash.Seal(nil, block, results, nil)
var ( var (
@ -138,7 +139,7 @@ func TestCustomizedWork(t *testing.T) {
block := types.NewBlockWithHeader(header) block := types.NewBlockWithHeader(header)
// Push new work. // Push new work.
results := make(chan *types.Block, 1) results := make(chan *consensus.SealResult, 1)
ethash.Seal(nil, block, results, nil) ethash.Seal(nil, block, results, nil)
// Get customized mining work. // Get customized mining work.

View file

@ -47,13 +47,13 @@ var (
// Seal implements consensus.Engine, attempting to find a nonce that satisfies // Seal implements consensus.Engine, attempting to find a nonce that satisfies
// the block's difficulty requirements. // 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 we're running a fake PoW, simply return a 0 nonce immediately
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
header := block.Header() header := block.Header()
header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
select { select {
case results <- block.WithSeal(header): case results <- &consensus.SealResult{SealHash: ethash.SealHash(header), Block: block.WithSeal(header)}:
default: default:
log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header())) 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) close(abort)
case result = <-locals: case result = <-locals:
// One of the threads found a block, abort all others // One of the threads found a block, abort all others
sealhash := ethash.SealHash(block.Header())
select { select {
case results <- result: case results <- &consensus.SealResult{SealHash: sealhash, Block: result}:
default: 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) close(abort)
case <-ethash.update: case <-ethash.update:
@ -187,13 +188,21 @@ search:
// remote is a standalone goroutine to handle remote mining related stuff. // remote is a standalone goroutine to handle remote mining related stuff.
func (ethash *Ethash) remote(notify []string, noverify bool) { 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 ( var (
works = make(map[common.Hash]*types.Block) works = make(map[common.Hash]*miningWork)
rates = make(map[common.Hash]hashrate) rates = make(map[common.Hash]hashrate)
results chan<- *types.Block results chan<- *consensus.SealResult
currentBlock *types.Block currentBlock *types.Block
currentWork [3]string currentWork [3]string
currentHash common.Hash
notifyTransport = &http.Transport{} notifyTransport = &http.Transport{}
notifyClient = &http.Client{ notifyClient = &http.Client{
@ -243,7 +252,9 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
// Trace the seal work fetched by remote sealer. // Trace the seal work fetched by remote sealer.
currentBlock = block 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 // customizeWork creates a work package for external miner
// with customized extra data. // with customized extra data.
@ -260,7 +271,8 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
work[2] = currentWork[2] work[2] = currentWork[2]
// Trace the seal work fetched by remote sealer. // 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 return work
} }
// submitWork verifies the submitted pow solution, returning // submitWork verifies the submitted pow solution, returning
@ -272,13 +284,13 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
return false return false
} }
// Make sure the work submitted is present // Make sure the work submitted is present
block := works[sealhash] work := works[sealhash]
if block == nil { if work == nil {
log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64()) log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64())
return false return false
} }
// Verify the correctness of submitted result. // Verify the correctness of submitted result.
header := block.Header() header := work.block.Header()
header.Nonce = nonce header.Nonce = nonce
header.MixDigest = mixDigest 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)) 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. // 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. // The submitted solution is within the scope of acceptance.
if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() { if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() {
select { 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()) log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
return true return true
default: default:
@ -334,13 +346,13 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
if currentBlock == nil { if currentBlock == nil {
// Return error message if there is no available mining work yet. // Return error message if there is no available mining work yet.
work.errc <- errNoMiningWork work.errc <- errNoMiningWork
} else if work.extra == "" { } else if work.extra == nil {
// Return the default current mining work if no extra customized data // Return the default current mining work if no extra customized data
// specified. // specified.
work.res <- currentWork work.res <- currentWork
} else { } else {
// Return the customized mining work. // Return the customized mining work.
work.res <- customizeWork(work.extra) work.res <- customizeWork(*work.extra)
} }
case result := <-ethash.submitWorkCh: case result := <-ethash.submitWorkCh:
@ -374,8 +386,8 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
} }
// Clear stale pending blocks // Clear stale pending blocks
if currentBlock != nil { if currentBlock != nil {
for hash, block := range works { for hash, work := range works {
if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() { if work.block.NumberU64()+staleThreshold <= currentBlock.NumberU64() {
delete(works, hash) delete(works, hash)
} }
} }

View file

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
@ -175,7 +176,7 @@ func TestStaleSubmission(t *testing.T) {
false, false,
}, },
} }
results := make(chan *types.Block, 16) results := make(chan *consensus.SealResult, 16)
for id, c := range testcases { for id, c := range testcases {
for _, h := range c.headers { for _, h := range c.headers {
@ -189,20 +190,20 @@ func TestStaleSubmission(t *testing.T) {
} }
select { select {
case res := <-results: case res := <-results:
if res.Header().Nonce != fakeNonce { if res.Block.Header().Nonce != fakeNonce {
t.Errorf("case %d block nonce mismatch, want %s, get %s", id+1, fakeNonce, res.Header().Nonce) t.Errorf("case %d block nonce mismatch, want %s, get %s", id+1, fakeNonce, res.Block.Header().Nonce)
} }
if res.Header().MixDigest != fakeDigest { if res.Block.Header().MixDigest != fakeDigest {
t.Errorf("case %d block digest mismatch, want %s, get %s", id+1, fakeDigest, res.Header().MixDigest) 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() { 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.Header().Difficulty) 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() { 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.Header().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 { 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.Header().ParentHash.Hex()) 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: case <-time.NewTimer(time.Second).C:
t.Errorf("case %d fetch ethash result timeout", id+1) t.Errorf("case %d fetch ethash result timeout", id+1)

View file

@ -143,7 +143,7 @@ type worker struct {
// Channels // Channels
newWorkCh chan *newWorkReq newWorkCh chan *newWorkReq
taskCh chan *task taskCh chan *task
resultCh chan *types.Block resultCh chan *consensus.SealResult
startCh chan struct{} startCh chan struct{}
exitCh chan struct{} exitCh chan struct{}
resubmitIntervalCh chan time.Duration resubmitIntervalCh chan time.Duration
@ -192,7 +192,7 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend,
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
newWorkCh: make(chan *newWorkReq), newWorkCh: make(chan *newWorkReq),
taskCh: make(chan *task), taskCh: make(chan *task),
resultCh: make(chan *types.Block, resultQueueSize), resultCh: make(chan *consensus.SealResult, resultQueueSize),
exitCh: make(chan struct{}), exitCh: make(chan struct{}),
startCh: make(chan struct{}, 1), startCh: make(chan struct{}, 1),
resubmitIntervalCh: make(chan time.Duration), resubmitIntervalCh: make(chan time.Duration),
@ -524,24 +524,25 @@ func (w *worker) taskLoop() {
func (w *worker) resultLoop() { func (w *worker) resultLoop() {
for { for {
select { select {
case block := <-w.resultCh: case result := <-w.resultCh:
// Short circuit when receiving empty result. // Short circuit when receiving empty result.
if block == nil { if result == nil {
continue continue
} }
var (
block = result.Block
hash = result.Block.Hash()
)
// Short circuit when receiving duplicate result caused by resubmitting. // Short circuit when receiving duplicate result caused by resubmitting.
if w.chain.HasBlock(block.Hash(), block.NumberU64()) { if w.chain.HasBlock(block.Hash(), block.NumberU64()) {
continue continue
} }
var (
sealhash = w.engine.SealHash(block.Header())
hash = block.Hash()
)
w.pendingMu.RLock() w.pendingMu.RLock()
task, exist := w.pendingTasks[sealhash] task, exist := w.pendingTasks[result.SealHash]
w.pendingMu.RUnlock() w.pendingMu.RUnlock()
if !exist { 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 continue
} }
// Different block could share same sealhash, deep copy here to prevent write-write conflict. // 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) log.Error("Failed writing block to chain", "err", err)
continue 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))) "elapsed", common.PrettyDuration(time.Since(task.createdAt)))
// Broadcast the block and announce chain insertion event // Broadcast the block and announce chain insertion event