|
|
|
@ -34,11 +34,11 @@ import ( |
|
|
|
|
"unsafe" |
|
|
|
|
|
|
|
|
|
"github.com/edsrzf/mmap-go" |
|
|
|
|
lrupkg "github.com/ethereum/go-ethereum/common/lru" |
|
|
|
|
"github.com/ethereum/go-ethereum/consensus" |
|
|
|
|
"github.com/ethereum/go-ethereum/log" |
|
|
|
|
"github.com/ethereum/go-ethereum/metrics" |
|
|
|
|
"github.com/ethereum/go-ethereum/rpc" |
|
|
|
|
"github.com/hashicorp/golang-lru/simplelru" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ErrInvalidDumpMagic = errors.New("invalid dump magic") |
|
|
|
@ -165,34 +165,45 @@ func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(bu |
|
|
|
|
return memoryMap(path, lock) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type cacheOrDataset interface { |
|
|
|
|
*cache | *dataset |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// lru tracks caches or datasets by their last use time, keeping at most N of them.
|
|
|
|
|
type lru struct { |
|
|
|
|
type lru[T cacheOrDataset] struct { |
|
|
|
|
what string |
|
|
|
|
new func(epoch uint64) interface{} |
|
|
|
|
new func(epoch uint64) T |
|
|
|
|
mu sync.Mutex |
|
|
|
|
// Items are kept in a LRU cache, but there is a special case:
|
|
|
|
|
// We always keep an item for (highest seen epoch) + 1 as the 'future item'.
|
|
|
|
|
cache *simplelru.LRU |
|
|
|
|
cache lrupkg.BasicLRU[uint64, T] |
|
|
|
|
future uint64 |
|
|
|
|
futureItem interface{} |
|
|
|
|
futureItem T |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// newlru create a new least-recently-used cache for either the verification caches
|
|
|
|
|
// or the mining datasets.
|
|
|
|
|
func newlru(what string, maxItems int, new func(epoch uint64) interface{}) *lru { |
|
|
|
|
if maxItems <= 0 { |
|
|
|
|
maxItems = 1 |
|
|
|
|
func newlru[T cacheOrDataset](maxItems int, new func(epoch uint64) T) *lru[T] { |
|
|
|
|
var what string |
|
|
|
|
switch any(T(nil)).(type) { |
|
|
|
|
case *cache: |
|
|
|
|
what = "cache" |
|
|
|
|
case *dataset: |
|
|
|
|
what = "dataset" |
|
|
|
|
default: |
|
|
|
|
panic("unknown type") |
|
|
|
|
} |
|
|
|
|
return &lru[T]{ |
|
|
|
|
what: what, |
|
|
|
|
new: new, |
|
|
|
|
cache: lrupkg.NewBasicLRU[uint64, T](maxItems), |
|
|
|
|
} |
|
|
|
|
cache, _ := simplelru.NewLRU(maxItems, func(key, value interface{}) { |
|
|
|
|
log.Trace("Evicted ethash "+what, "epoch", key) |
|
|
|
|
}) |
|
|
|
|
return &lru{what: what, new: new, cache: cache} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// get retrieves or creates an item for the given epoch. The first return value is always
|
|
|
|
|
// non-nil. The second return value is non-nil if lru thinks that an item will be useful in
|
|
|
|
|
// the near future.
|
|
|
|
|
func (lru *lru) get(epoch uint64) (item, future interface{}) { |
|
|
|
|
func (lru *lru[T]) get(epoch uint64) (item, future T) { |
|
|
|
|
lru.mu.Lock() |
|
|
|
|
defer lru.mu.Unlock() |
|
|
|
|
|
|
|
|
@ -226,9 +237,8 @@ type cache struct { |
|
|
|
|
once sync.Once // Ensures the cache is generated only once
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// newCache creates a new ethash verification cache and returns it as a plain Go
|
|
|
|
|
// interface to be usable in an LRU cache.
|
|
|
|
|
func newCache(epoch uint64) interface{} { |
|
|
|
|
// newCache creates a new ethash verification cache.
|
|
|
|
|
func newCache(epoch uint64) *cache { |
|
|
|
|
return &cache{epoch: epoch} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -308,7 +318,7 @@ type dataset struct { |
|
|
|
|
|
|
|
|
|
// newDataset creates a new ethash mining dataset and returns it as a plain Go
|
|
|
|
|
// interface to be usable in an LRU cache.
|
|
|
|
|
func newDataset(epoch uint64) interface{} { |
|
|
|
|
func newDataset(epoch uint64) *dataset { |
|
|
|
|
return &dataset{epoch: epoch} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -439,8 +449,8 @@ type Config struct { |
|
|
|
|
type Ethash struct { |
|
|
|
|
config Config |
|
|
|
|
|
|
|
|
|
caches *lru // In memory caches to avoid regenerating too often
|
|
|
|
|
datasets *lru // In memory datasets to avoid regenerating too often
|
|
|
|
|
caches *lru[*cache] // In memory caches to avoid regenerating too often
|
|
|
|
|
datasets *lru[*dataset] // In memory datasets to avoid regenerating too often
|
|
|
|
|
|
|
|
|
|
// Mining related fields
|
|
|
|
|
rand *rand.Rand // Properly seeded random source for nonces
|
|
|
|
@ -477,8 +487,8 @@ func New(config Config, notify []string, noverify bool) *Ethash { |
|
|
|
|
} |
|
|
|
|
ethash := &Ethash{ |
|
|
|
|
config: config, |
|
|
|
|
caches: newlru("cache", config.CachesInMem, newCache), |
|
|
|
|
datasets: newlru("dataset", config.DatasetsInMem, newDataset), |
|
|
|
|
caches: newlru(config.CachesInMem, newCache), |
|
|
|
|
datasets: newlru(config.DatasetsInMem, newDataset), |
|
|
|
|
update: make(chan struct{}), |
|
|
|
|
hashrate: metrics.NewMeterForced(), |
|
|
|
|
} |
|
|
|
@ -573,15 +583,13 @@ func (ethash *Ethash) StopRemoteSealer() error { |
|
|
|
|
// stored on disk, and finally generating one if none can be found.
|
|
|
|
|
func (ethash *Ethash) cache(block uint64) *cache { |
|
|
|
|
epoch := block / epochLength |
|
|
|
|
currentI, futureI := ethash.caches.get(epoch) |
|
|
|
|
current := currentI.(*cache) |
|
|
|
|
current, future := ethash.caches.get(epoch) |
|
|
|
|
|
|
|
|
|
// Wait for generation finish.
|
|
|
|
|
current.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.CachesLockMmap, ethash.config.PowMode == ModeTest) |
|
|
|
|
|
|
|
|
|
// If we need a new future cache, now's a good time to regenerate it.
|
|
|
|
|
if futureI != nil { |
|
|
|
|
future := futureI.(*cache) |
|
|
|
|
if future != nil { |
|
|
|
|
go future.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.CachesLockMmap, ethash.config.PowMode == ModeTest) |
|
|
|
|
} |
|
|
|
|
return current |
|
|
|
@ -596,25 +604,20 @@ func (ethash *Ethash) cache(block uint64) *cache { |
|
|
|
|
func (ethash *Ethash) dataset(block uint64, async bool) *dataset { |
|
|
|
|
// Retrieve the requested ethash dataset
|
|
|
|
|
epoch := block / epochLength |
|
|
|
|
currentI, futureI := ethash.datasets.get(epoch) |
|
|
|
|
current := currentI.(*dataset) |
|
|
|
|
current, future := ethash.datasets.get(epoch) |
|
|
|
|
|
|
|
|
|
// If async is specified, generate everything in a background thread
|
|
|
|
|
if async && !current.generated() { |
|
|
|
|
go func() { |
|
|
|
|
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) |
|
|
|
|
|
|
|
|
|
if futureI != nil { |
|
|
|
|
future := futureI.(*dataset) |
|
|
|
|
if future != nil { |
|
|
|
|
future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
} else { |
|
|
|
|
// Either blocking generation was requested, or already done
|
|
|
|
|
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) |
|
|
|
|
|
|
|
|
|
if futureI != nil { |
|
|
|
|
future := futureI.(*dataset) |
|
|
|
|
if future != nil { |
|
|
|
|
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|