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
// 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))
}

View file

@ -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
}

View file

@ -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)
)
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
}

View file

@ -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
}

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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