mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
consensus/ethash: start remote ggoroutine to handle remote mining
This commit is contained in:
parent
16eaf2b158
commit
48d15eb679
2 changed files with 164 additions and 4 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue