mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
consensus, miner: track original sealhash in ethash remote sealer
This commit is contained in:
parent
05d9cd1560
commit
012aee25f6
8 changed files with 81 additions and 59 deletions
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue