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" "unsafe"
mmap "github.com/edsrzf/mmap-go" mmap "github.com/edsrzf/mmap-go"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -389,6 +391,29 @@ type Config struct {
PowMode Mode 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 // Ethash is a consensus engine based on proof-of-work implementing the ethash
// algorithm. // algorithm.
type Ethash struct { type Ethash struct {
@ -397,12 +422,19 @@ type Ethash struct {
caches *lru // In memory caches to avoid regenerating too often caches *lru // In memory caches to avoid regenerating too often
datasets *lru // In memory datasets 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 // Mining related fields
rand *rand.Rand // Properly seeded random source for nonces rand *rand.Rand // Properly seeded random source for nonces
threads int // Number of threads to mine on if mining threads int // Number of threads to mine on if mining
update chan struct{} // Notification channel to update mining parameters update chan struct{} // Notification channel to update mining parameters
hashrate metrics.Meter // Meter tracking the average hashrate 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 // The fields below are hooks for testing
shared *Ethash // Shared PoW verifier to avoid cache regeneration shared *Ethash // Shared PoW verifier to avoid cache regeneration
fakeFail uint64 // Block number which fails PoW check even in fake mode 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 { if config.DatasetDir != "" && config.DatasetsOnDisk > 0 {
log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk) log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk)
} }
return &Ethash{ ethash := &Ethash{
config: config, config: config,
caches: newlru("cache", config.CachesInMem, newCache), caches: newlru("cache", config.CachesInMem, newCache),
datasets: newlru("dataset", config.DatasetsInMem, newDataset), datasets: newlru("dataset", config.DatasetsInMem, newDataset),
update: make(chan struct{}), update: make(chan struct{}),
hashrate: metrics.NewMeter(), 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 // NewTester creates a small sized ethash PoW scheme useful only for testing
@ -489,6 +528,12 @@ func NewShared() *Ethash {
return &Ethash{shared: sharedEthash} 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 // 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 // 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. // stored on disk, and finally generating one if none can be found.

View file

@ -24,12 +24,19 @@ import (
"runtime" "runtime"
"sync" "sync"
"errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "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 // 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, stop <-chan struct{}) (*types.Block, error) { 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 { if ethash.shared != nil {
return ethash.shared.Seal(chain, block, stop) return ethash.shared.Seal(chain, block, stop)
} }
// Create a runner and the multiple search threads it directs // Create a runner and the multiple search threads it directs
abort := make(chan struct{}) abort := make(chan struct{})
found := make(chan *types.Block)
ethash.lock.Lock() ethash.lock.Lock()
threads := ethash.threads threads := ethash.threads
@ -64,12 +71,25 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
if threads < 0 { if threads < 0 {
threads = 0 // Allows disabling local mining without extra logic around local/remote 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 var pend sync.WaitGroup
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
pend.Add(1) pend.Add(1)
go func(id int, nonce uint64) { go func(id int, nonce uint64) {
defer pend.Done() defer pend.Done()
ethash.mine(block, id, nonce, abort, found) ethash.mine(block, id, nonce, abort, ethash.resultCh)
}(i, uint64(ethash.rand.Int63())) }(i, uint64(ethash.rand.Int63()))
} }
// Wait until sealing is terminated or a nonce is found // 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: case <-stop:
// Outside abort, stop all miner threads // Outside abort, stop all miner threads
close(abort) close(abort)
case result = <-found: case result = <-ethash.resultCh:
// One of the threads found a block, abort all others // One of the threads found a block, abort all others
close(abort) close(abort)
case <-ethash.update: case <-ethash.update:
@ -150,3 +170,98 @@ search:
// during sealing so it's not unmapped while being read. // during sealing so it's not unmapped while being read.
runtime.KeepAlive(dataset) 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")
}