mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22: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"
|
"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.
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue