consensus/ethash: start remote ggoroutine to handle remote mining

This commit is contained in:
rjl493456442 2018-01-12 14:50:45 +08:00
parent 16eaf2b158
commit 48d15eb679
2 changed files with 164 additions and 4 deletions

View file

@ -33,7 +33,9 @@ import (
"unsafe"
mmap "github.com/edsrzf/mmap-go"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rpc"
@ -389,6 +391,29 @@ type Config struct {
PowMode Mode
}
// remoteOpType defines all the types of operations related to remote sealer.
type remoteOpType uint
const (
getWork remoteOpType = iota
submitWork
)
// sealResult wraps the pow solution parameters for the specified block.
type sealResult struct {
nonce types.BlockNonce
mixDigest common.Hash
hash common.Hash
}
// remoteOp wraps a mining operation sent by remote miner.
type remoteOp struct {
typ remoteOpType
result sealResult
errCh chan error
workCh chan [3]string
}
// Ethash is a consensus engine based on proof-of-work implementing the ethash
// algorithm.
type Ethash struct {
@ -397,12 +422,19 @@ type Ethash struct {
caches *lru // In memory caches to avoid regenerating too often
datasets *lru // In memory datasets to avoid regenerating too often
exitCh chan struct{} // Notification channel to exiting backend threads
// Mining related fields
rand *rand.Rand // Properly seeded random source for nonces
threads int // Number of threads to mine on if mining
update chan struct{} // Notification channel to update mining parameters
hashrate metrics.Meter // Meter tracking the average hashrate
// Remote sealer related fields
workCh chan *types.Block // Notification channel to push new work to remote sealer
resultCh chan *types.Block // Channel used by mining threads to return result
remoteOp chan *remoteOp // Channel used to receive all mining operations from api
// The fields below are hooks for testing
shared *Ethash // Shared PoW verifier to avoid cache regeneration
fakeFail uint64 // Block number which fails PoW check even in fake mode
@ -423,13 +455,20 @@ func New(config Config) *Ethash {
if config.DatasetDir != "" && config.DatasetsOnDisk > 0 {
log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk)
}
return &Ethash{
ethash := &Ethash{
config: config,
caches: newlru("cache", config.CachesInMem, newCache),
datasets: newlru("dataset", config.DatasetsInMem, newDataset),
update: make(chan struct{}),
hashrate: metrics.NewMeter(),
exitCh: make(chan struct{}),
workCh: make(chan *types.Block),
resultCh: make(chan *types.Block),
remoteOp: make(chan *remoteOp),
}
go ethash.remote(ethash.resultCh)
runtime.SetFinalizer(ethash, (*Ethash).close)
return ethash
}
// NewTester creates a small sized ethash PoW scheme useful only for testing
@ -489,6 +528,12 @@ func NewShared() *Ethash {
return &Ethash{shared: sharedEthash}
}
// close closes the exit channel to notify all backend threads exiting.
func (ethash *Ethash) close() {
runtime.SetFinalizer(ethash, nil)
close(ethash.exitCh)
}
// cache tries to retrieve a verification cache for the specified block number
// by first checking against a list of in-memory caches, then against caches
// stored on disk, and finally generating one if none can be found.

View file

@ -24,12 +24,19 @@ import (
"runtime"
"sync"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
var (
errNoSealWork = errors.New("No work available yet, don't panic.")
errInvalidSealResult = errors.New("Invalid proof-of-work solution.")
errNonDefinedRemoteOp = errors.New("Non-defined remote mining operation.")
)
// 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, stop <-chan struct{}) (*types.Block, error) {
@ -43,9 +50,9 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
if ethash.shared != nil {
return ethash.shared.Seal(chain, block, stop)
}
// Create a runner and the multiple search threads it directs
abort := make(chan struct{})
found := make(chan *types.Block)
ethash.lock.Lock()
threads := ethash.threads
@ -64,12 +71,25 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
if threads < 0 {
threads = 0 // Allows disabling local mining without extra logic around local/remote
}
// Clear up result channel before new round mining start
for empty := false; !empty; {
select {
case <-ethash.resultCh:
default:
empty = true
}
}
// Push new work to remote sealer
ethash.workCh <- block
var pend sync.WaitGroup
for i := 0; i < threads; i++ {
pend.Add(1)
go func(id int, nonce uint64) {
defer pend.Done()
ethash.mine(block, id, nonce, abort, found)
ethash.mine(block, id, nonce, abort, ethash.resultCh)
}(i, uint64(ethash.rand.Int63()))
}
// Wait until sealing is terminated or a nonce is found
@ -78,7 +98,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
case <-stop:
// Outside abort, stop all miner threads
close(abort)
case result = <-found:
case result = <-ethash.resultCh:
// One of the threads found a block, abort all others
close(abort)
case <-ethash.update:
@ -150,3 +170,98 @@ search:
// during sealing so it's not unmapped while being read.
runtime.KeepAlive(dataset)
}
// remote starts a standalone goroutine to handle remote mining related stuff.
func (ethash *Ethash) remote(resultCh chan *types.Block) {
var (
works = make(map[common.Hash]*types.Block)
currentWork *types.Block
)
// makeWork returns a work package for external sealer. The work package consists of 3 strings
// result[0], 32 bytes hex encoded current block header pow-hash
// result[1], 32 bytes hex encoded seed hash used for DAG
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
makeWork := func() ([3]string, error) {
var res [3]string
if currentWork == nil {
return res, errNoSealWork
}
res[0] = currentWork.HashNoNonce().Hex()
res[1] = common.BytesToHash(SeedHash(currentWork.NumberU64())).Hex()
// Calculate the "target" to be returned to the external sealer
n := big.NewInt(1)
n.Lsh(n, 255)
n.Div(n, currentWork.Difficulty())
n.Lsh(n, 1)
res[2] = common.BytesToHash(n.Bytes()).Hex()
works[currentWork.HashNoNonce()] = currentWork
return res, nil
}
// verifyWork verifies the submitted pow solution, returning
// whether the solution was accepted or not (not can be both a bad pow as well as
// any other error, like no work pending).
verifyWork := func(result sealResult) bool {
// Make sure the work submitted is present
block := works[result.hash]
if block == nil {
log.Info("Work submitted but none pending", "hash", result.hash)
return false
}
// Make sure the Engine solutions is indeed valid
header := block.Header()
header.Nonce = result.nonce
header.MixDigest = result.mixDigest
if err := ethash.VerifySeal(nil, header); err != nil {
log.Warn("Invalid proof-of-work submitted", "hash", result.hash, "err", err)
return false
}
// Solutions seems to be valid, return to the miner and notify acceptance
resultCh <- block.WithSeal(header)
delete(works, result.hash)
return true
}
running:
for {
select {
case block := <-ethash.workCh:
if block.ParentHash() != currentWork.ParentHash() {
// Start new round mining, throw out all previous work.
works = make(map[common.Hash]*types.Block)
}
// Update current work with new received block
currentWork = block
case op := <-ethash.remoteOp:
switch op.typ {
case getWork:
// Push current mining work to return channel
work, err := makeWork()
if err != nil {
op.errCh <- err
} else {
op.workCh <- work
close(op.errCh)
}
case submitWork:
// Verify submitted PoW solution based on maintained mining blocks
if verifyWork(op.result) {
close(op.errCh)
} else {
op.errCh <- errInvalidSealResult
}
default:
op.errCh <- errNonDefinedRemoteOp
}
case <-ethash.exitCh:
// Exit remote loop if ethash object is cleared by GC
break running
}
}
log.Trace("Ethash remote sealer is exiting")
}