Merge pull request #17405 from karalabe/miner-remote-dag
consensus/ethash: use DAGs for remote mining, generate async
This commit is contained in:
commit
9bf6bb8f63
@ -461,6 +461,13 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
|
|||||||
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
||||||
// the PoW difficulty requirements.
|
// the PoW difficulty requirements.
|
||||||
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||||
|
return ethash.verifySeal(chain, header, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifySeal checks whether a block satisfies the PoW difficulty requirements,
|
||||||
|
// either using the usual ethash cache for it, or alternatively using a full DAG
|
||||||
|
// to make remote mining fast.
|
||||||
|
func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error {
|
||||||
// If we're running a fake PoW, accept any seal as valid
|
// If we're running a fake PoW, accept any seal as valid
|
||||||
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
||||||
time.Sleep(ethash.fakeDelay)
|
time.Sleep(ethash.fakeDelay)
|
||||||
@ -471,25 +478,48 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head
|
|||||||
}
|
}
|
||||||
// If we're running a shared PoW, delegate verification to it
|
// If we're running a shared PoW, delegate verification to it
|
||||||
if ethash.shared != nil {
|
if ethash.shared != nil {
|
||||||
return ethash.shared.VerifySeal(chain, header)
|
return ethash.shared.verifySeal(chain, header, fulldag)
|
||||||
}
|
}
|
||||||
// Ensure that we have a valid difficulty for the block
|
// Ensure that we have a valid difficulty for the block
|
||||||
if header.Difficulty.Sign() <= 0 {
|
if header.Difficulty.Sign() <= 0 {
|
||||||
return errInvalidDifficulty
|
return errInvalidDifficulty
|
||||||
}
|
}
|
||||||
// Recompute the digest and PoW value and verify against the header
|
// Recompute the digest and PoW values
|
||||||
number := header.Number.Uint64()
|
number := header.Number.Uint64()
|
||||||
|
|
||||||
cache := ethash.cache(number)
|
var (
|
||||||
size := datasetSize(number)
|
digest []byte
|
||||||
if ethash.config.PowMode == ModeTest {
|
result []byte
|
||||||
size = 32 * 1024
|
)
|
||||||
}
|
// If fast-but-heavy PoW verification was requested, use an ethash dataset
|
||||||
digest, result := hashimotoLight(size, cache.cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64())
|
if fulldag {
|
||||||
// Caches are unmapped in a finalizer. Ensure that the cache stays live
|
dataset := ethash.dataset(number, true)
|
||||||
// until after the call to hashimotoLight so it's not unmapped while being used.
|
if dataset.generated() {
|
||||||
runtime.KeepAlive(cache)
|
digest, result = hashimotoFull(dataset.dataset, header.HashNoNonce().Bytes(), header.Nonce.Uint64())
|
||||||
|
|
||||||
|
// Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
|
||||||
|
// until after the call to hashimotoFull so it's not unmapped while being used.
|
||||||
|
runtime.KeepAlive(dataset)
|
||||||
|
} else {
|
||||||
|
// Dataset not yet generated, don't hang, use a cache instead
|
||||||
|
fulldag = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If slow-but-light PoW verification was requested (or DAG not yet ready), use an ethash cache
|
||||||
|
if !fulldag {
|
||||||
|
cache := ethash.cache(number)
|
||||||
|
|
||||||
|
size := datasetSize(number)
|
||||||
|
if ethash.config.PowMode == ModeTest {
|
||||||
|
size = 32 * 1024
|
||||||
|
}
|
||||||
|
digest, result = hashimotoLight(size, cache.cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64())
|
||||||
|
|
||||||
|
// Caches are unmapped in a finalizer. Ensure that the cache stays alive
|
||||||
|
// until after the call to hashimotoLight so it's not unmapped while being used.
|
||||||
|
runtime.KeepAlive(cache)
|
||||||
|
}
|
||||||
|
// Verify the calculated values against the ones provided in the header
|
||||||
if !bytes.Equal(header.MixDigest[:], digest) {
|
if !bytes.Equal(header.MixDigest[:], digest) {
|
||||||
return errInvalidMixDigest
|
return errInvalidMixDigest
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -281,6 +282,7 @@ type dataset struct {
|
|||||||
mmap mmap.MMap // Memory map itself to unmap before releasing
|
mmap mmap.MMap // Memory map itself to unmap before releasing
|
||||||
dataset []uint32 // The actual cache data content
|
dataset []uint32 // The actual cache data content
|
||||||
once sync.Once // Ensures the cache is generated only once
|
once sync.Once // Ensures the cache is generated only once
|
||||||
|
done uint32 // Atomic flag to determine generation status
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDataset creates a new ethash mining dataset and returns it as a plain Go
|
// newDataset creates a new ethash mining dataset and returns it as a plain Go
|
||||||
@ -292,6 +294,9 @@ func newDataset(epoch uint64) interface{} {
|
|||||||
// generate ensures that the dataset content is generated before use.
|
// generate ensures that the dataset content is generated before use.
|
||||||
func (d *dataset) generate(dir string, limit int, test bool) {
|
func (d *dataset) generate(dir string, limit int, test bool) {
|
||||||
d.once.Do(func() {
|
d.once.Do(func() {
|
||||||
|
// Mark the dataset generated after we're done. This is needed for remote
|
||||||
|
defer atomic.StoreUint32(&d.done, 1)
|
||||||
|
|
||||||
csize := cacheSize(d.epoch*epochLength + 1)
|
csize := cacheSize(d.epoch*epochLength + 1)
|
||||||
dsize := datasetSize(d.epoch*epochLength + 1)
|
dsize := datasetSize(d.epoch*epochLength + 1)
|
||||||
seed := seedHash(d.epoch*epochLength + 1)
|
seed := seedHash(d.epoch*epochLength + 1)
|
||||||
@ -306,6 +311,8 @@ func (d *dataset) generate(dir string, limit int, test bool) {
|
|||||||
|
|
||||||
d.dataset = make([]uint32, dsize/4)
|
d.dataset = make([]uint32, dsize/4)
|
||||||
generateDataset(d.dataset, d.epoch, cache)
|
generateDataset(d.dataset, d.epoch, cache)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// Disk storage is needed, this will get fancy
|
// Disk storage is needed, this will get fancy
|
||||||
var endian string
|
var endian string
|
||||||
@ -348,6 +355,13 @@ func (d *dataset) generate(dir string, limit int, test bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generated returns whether this particular dataset finished generating already
|
||||||
|
// or not (it may not have been started at all). This is useful for remote miners
|
||||||
|
// to default to verification caches instead of blocking on DAG generations.
|
||||||
|
func (d *dataset) generated() bool {
|
||||||
|
return atomic.LoadUint32(&d.done) == 1
|
||||||
|
}
|
||||||
|
|
||||||
// finalizer closes any file handlers and memory maps open.
|
// finalizer closes any file handlers and memory maps open.
|
||||||
func (d *dataset) finalizer() {
|
func (d *dataset) finalizer() {
|
||||||
if d.mmap != nil {
|
if d.mmap != nil {
|
||||||
@ -589,20 +603,34 @@ func (ethash *Ethash) cache(block uint64) *cache {
|
|||||||
// dataset tries to retrieve a mining dataset for the specified block number
|
// dataset tries to retrieve a mining dataset for the specified block number
|
||||||
// by first checking against a list of in-memory datasets, then against DAGs
|
// by first checking against a list of in-memory datasets, then against DAGs
|
||||||
// stored on disk, and finally generating one if none can be found.
|
// stored on disk, and finally generating one if none can be found.
|
||||||
func (ethash *Ethash) dataset(block uint64) *dataset {
|
//
|
||||||
|
// If async is specified, not only the future but the current DAG is also
|
||||||
|
// generates on a background thread.
|
||||||
|
func (ethash *Ethash) dataset(block uint64, async bool) *dataset {
|
||||||
|
// Retrieve the requested ethash dataset
|
||||||
epoch := block / epochLength
|
epoch := block / epochLength
|
||||||
currentI, futureI := ethash.datasets.get(epoch)
|
currentI, futureI := ethash.datasets.get(epoch)
|
||||||
current := currentI.(*dataset)
|
current := currentI.(*dataset)
|
||||||
|
|
||||||
// Wait for generation finish.
|
// If async is specified, generate everything in a background thread
|
||||||
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
if async && !current.generated() {
|
||||||
|
go func() {
|
||||||
|
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
|
|
||||||
// If we need a new future dataset, now's a good time to regenerate it.
|
if futureI != nil {
|
||||||
if futureI != nil {
|
future := futureI.(*dataset)
|
||||||
future := futureI.(*dataset)
|
future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
// Either blocking generation was requested, or already done
|
||||||
|
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
|
|
||||||
|
if futureI != nil {
|
||||||
|
future := futureI.(*dataset)
|
||||||
|
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan s
|
|||||||
hash = header.HashNoNonce().Bytes()
|
hash = header.HashNoNonce().Bytes()
|
||||||
target = new(big.Int).Div(two256, header.Difficulty)
|
target = new(big.Int).Div(two256, header.Difficulty)
|
||||||
number = header.Number.Uint64()
|
number = header.Number.Uint64()
|
||||||
dataset = ethash.dataset(number)
|
dataset = ethash.dataset(number, false)
|
||||||
)
|
)
|
||||||
// Start generating random nonces until we abort or find a good one
|
// Start generating random nonces until we abort or find a good one
|
||||||
var (
|
var (
|
||||||
@ -233,21 +233,22 @@ func (ethash *Ethash) remote(notify []string) {
|
|||||||
log.Info("Work submitted but none pending", "hash", hash)
|
log.Info("Work submitted but none pending", "hash", hash)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the correctness of submitted result.
|
// Verify the correctness of submitted result.
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
header.Nonce = nonce
|
header.Nonce = nonce
|
||||||
header.MixDigest = mixDigest
|
header.MixDigest = mixDigest
|
||||||
if err := ethash.VerifySeal(nil, header); err != nil {
|
|
||||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
start := time.Now()
|
||||||
|
if err := ethash.verifySeal(nil, header, true); err != nil {
|
||||||
|
log.Warn("Invalid proof-of-work submitted", "hash", hash, "elapsed", time.Since(start), "err", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the result channel is created.
|
// Make sure the result channel is created.
|
||||||
if ethash.resultCh == nil {
|
if ethash.resultCh == nil {
|
||||||
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
log.Trace("Verified correct proof-of-work", "hash", hash, "elapsed", time.Since(start))
|
||||||
|
|
||||||
// Solutions seems to be valid, return to the miner and notify acceptance.
|
// Solutions seems to be valid, return to the miner and notify acceptance.
|
||||||
select {
|
select {
|
||||||
|
Loading…
Reference in New Issue
Block a user