mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-26 16:36:17 +00:00
This change includes a lot of things, listed below.
The interfaces have been split up into one write-interface and one read-interface, with `Snapshot` being the gateway from write to read. This simplifies the semantics _a lot_.
Example of splitting up an interface into one readonly 'snapshot' part, and one updatable writeonly part:
```golang
type MeterSnapshot interface {
Count() int64
Rate1() float64
Rate5() float64
Rate15() float64
RateMean() float64
}
// Meters count events to produce exponentially-weighted moving average rates
// at one-, five-, and fifteen-minutes and a mean rate.
type Meter interface {
Mark(int64)
Snapshot() MeterSnapshot
Stop()
}
```
This PR makes the concurrency model clearer. We have actual meters and snapshot of meters. The `meter` is the thing which can be accessed from the registry, and updates can be made to it.
- For all `meters`, (`Gauge`, `Timer` etc), it is assumed that they are accessed by different threads, making updates. Therefore, all `meters` update-methods (`Inc`, `Add`, `Update`, `Clear` etc) need to be concurrency-safe.
- All `meters` have a `Snapshot()` method. This method is _usually_ called from one thread, a backend-exporter. But it's fully possible to have several exporters simultaneously: therefore this method should also be concurrency-safe.
TLDR: `meter`s are accessible via registry, all their methods must be concurrency-safe.
For all `Snapshot`s, it is assumed that an individual exporter-thread has obtained a `meter` from the registry, and called the `Snapshot` method to obtain a readonly snapshot. This snapshot is _not_ guaranteed to be concurrency-safe. There's no need for a snapshot to be concurrency-safe, since exporters should not share snapshots.
Note, though: that by happenstance a lot of the snapshots _are_ concurrency-safe, being unmutable minimal representations of a value. Only the more complex ones are _not_ threadsafe, those that lazily calculate things like `Variance()`, `Mean()`.
Example of how a background exporter typically works, obtaining the snapshot and sequentially accessing the non-threadsafe methods in it:
```golang
ms := metric.Snapshot()
...
fields := map[string]interface{}{
"count": ms.Count(),
"max": ms.Max(),
"mean": ms.Mean(),
"min": ms.Min(),
"stddev": ms.StdDev(),
"variance": ms.Variance(),
```
TLDR: `snapshots` are not guaranteed to be concurrency-safe (but often are).
I also changed the `Sample` type: previously, it iterated the samples fully every time `Mean()`,`Sum()`, `Min()` or `Max()` was invoked. Since we now have readonly base data, we can just iterate it once, in the constructor, and set all four values at once.
The same thing has been done for runtimehistogram.
Back when ResettingTImer was implemented, as part of https://github.com/ethereum/go-ethereum/pull/15910, Anton implemented a `Percentiles` on the new type. However, the method did not conform to the other existing types which also had a `Percentiles`.
1. The existing ones, on input, took `0.5` to mean `50%`. Anton used `50` to mean `50%`.
2. The existing ones returned `float64` outputs, thus interpolating between values. A value-set of `0, 10`, at `50%` would return `5`, whereas Anton's would return either `0` or `10`.
This PR removes the 'new' version, and uses only the 'legacy' percentiles, also for the ResettingTimer type.
The resetting timer snapshot was also defined so that it would expose the internal values. This has been removed, and getters for `Max, Min, Mean` have been added instead.
A lot of types were exported, but do not need to be. This PR unexports quite a lot of them.
metrics: refactor metrics (28035)
578 lines
18 KiB
Go
578 lines
18 KiB
Go
// Copyright 2017 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Package ethash implements the ethash proof-of-work consensus engine.
|
|
package ethash
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
"github.com/XinFinOrg/XDPoSChain/metrics"
|
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
|
mmap "github.com/edsrzf/mmap-go"
|
|
"github.com/hashicorp/golang-lru/simplelru"
|
|
)
|
|
|
|
var ErrInvalidDumpMagic = errors.New("invalid dump magic")
|
|
|
|
var (
|
|
// maxUint256 is a big integer representing 2^256-1
|
|
maxUint256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
|
|
|
|
// sharedEthash is a full instance that can be shared between multiple users.
|
|
sharedEthash = New(Config{"", 3, 0, "", 1, 0, ModeNormal})
|
|
|
|
// algorithmRevision is the data structure version used for file naming.
|
|
algorithmRevision = 23
|
|
|
|
// dumpMagic is a dataset dump header to sanity check a data dump.
|
|
dumpMagic = []uint32{0xbaddcafe, 0xfee1dead}
|
|
)
|
|
|
|
// isLittleEndian returns whether the local system is running in little or big
|
|
// endian byte order.
|
|
func isLittleEndian() bool {
|
|
n := uint32(0x01020304)
|
|
return *(*byte)(unsafe.Pointer(&n)) == 0x04
|
|
}
|
|
|
|
// memoryMap tries to memory map a file of uint32s for read only access.
|
|
func memoryMap(path string) (*os.File, mmap.MMap, []uint32, error) {
|
|
file, err := os.OpenFile(path, os.O_RDONLY, 0644)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
mem, buffer, err := memoryMapFile(file, false)
|
|
if err != nil {
|
|
file.Close()
|
|
return nil, nil, nil, err
|
|
}
|
|
for i, magic := range dumpMagic {
|
|
if buffer[i] != magic {
|
|
mem.Unmap()
|
|
file.Close()
|
|
return nil, nil, nil, ErrInvalidDumpMagic
|
|
}
|
|
}
|
|
return file, mem, buffer[len(dumpMagic):], err
|
|
}
|
|
|
|
// memoryMapFile tries to memory map an already opened file descriptor.
|
|
func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) {
|
|
// Try to memory map the file
|
|
flag := mmap.RDONLY
|
|
if write {
|
|
flag = mmap.RDWR
|
|
}
|
|
mem, err := mmap.Map(file, flag, 0)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// Yay, we managed to memory map the file, here be dragons
|
|
header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem))
|
|
header.Len /= 4
|
|
header.Cap /= 4
|
|
|
|
return mem, *(*[]uint32)(unsafe.Pointer(&header)), nil
|
|
}
|
|
|
|
// memoryMapAndGenerate tries to memory map a temporary file of uint32s for write
|
|
// access, fill it with the data from a generator and then move it into the final
|
|
// path requested.
|
|
func memoryMapAndGenerate(path string, size uint64, generator func(buffer []uint32)) (*os.File, mmap.MMap, []uint32, error) {
|
|
// Ensure the data folder exists
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
// Create a huge temporary empty file to fill with data
|
|
temp := path + "." + strconv.Itoa(rand.Int())
|
|
|
|
dump, err := os.Create(temp)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if err = dump.Truncate(int64(len(dumpMagic))*4 + int64(size)); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
// Memory map the file for writing and fill it with the generator
|
|
mem, buffer, err := memoryMapFile(dump, true)
|
|
if err != nil {
|
|
dump.Close()
|
|
return nil, nil, nil, err
|
|
}
|
|
copy(buffer, dumpMagic)
|
|
|
|
data := buffer[len(dumpMagic):]
|
|
generator(data)
|
|
|
|
if err := mem.Unmap(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if err := dump.Close(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if err := os.Rename(temp, path); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return memoryMap(path)
|
|
}
|
|
|
|
// lru tracks caches or datasets by their last use time, keeping at most N of them.
|
|
type lru struct {
|
|
what string
|
|
new func(epoch uint64) interface{}
|
|
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
|
|
future uint64
|
|
futureItem interface{}
|
|
}
|
|
|
|
// newlru create a new least-recently-used cache for ither the verification caches
|
|
// or the mining datasets.
|
|
func newlru(what string, maxItems int, new func(epoch uint64) interface{}) *lru {
|
|
if maxItems <= 0 {
|
|
maxItems = 1
|
|
}
|
|
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{}) {
|
|
lru.mu.Lock()
|
|
defer lru.mu.Unlock()
|
|
|
|
// Get or create the item for the requested epoch.
|
|
item, ok := lru.cache.Get(epoch)
|
|
if !ok {
|
|
if lru.future > 0 && lru.future == epoch {
|
|
item = lru.futureItem
|
|
} else {
|
|
log.Trace("Requiring new ethash "+lru.what, "epoch", epoch)
|
|
item = lru.new(epoch)
|
|
}
|
|
lru.cache.Add(epoch, item)
|
|
}
|
|
// Update the 'future item' if epoch is larger than previously seen.
|
|
if epoch < maxEpoch-1 && lru.future < epoch+1 {
|
|
log.Trace("Requiring new future ethash "+lru.what, "epoch", epoch+1)
|
|
future = lru.new(epoch + 1)
|
|
lru.future = epoch + 1
|
|
lru.futureItem = future
|
|
}
|
|
return item, future
|
|
}
|
|
|
|
// cache wraps an ethash cache with some metadata to allow easier concurrent use.
|
|
type cache struct {
|
|
epoch uint64 // Epoch for which this cache is relevant
|
|
dump *os.File // File descriptor of the memory mapped cache
|
|
mmap mmap.MMap // Memory map itself to unmap before releasing
|
|
cache []uint32 // The actual cache data content (may be memory mapped)
|
|
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{} {
|
|
return &cache{epoch: epoch}
|
|
}
|
|
|
|
// generate ensures that the cache content is generated before use.
|
|
func (c *cache) generate(dir string, limit int, test bool) {
|
|
c.once.Do(func() {
|
|
size := cacheSize(c.epoch*epochLength + 1)
|
|
seed := seedHash(c.epoch*epochLength + 1)
|
|
if test {
|
|
size = 1024
|
|
}
|
|
// If we don't store anything on disk, generate and return.
|
|
if dir == "" {
|
|
c.cache = make([]uint32, size/4)
|
|
generateCache(c.cache, c.epoch, seed)
|
|
return
|
|
}
|
|
// Disk storage is needed, this will get fancy
|
|
var endian string
|
|
if !isLittleEndian() {
|
|
endian = ".be"
|
|
}
|
|
path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
|
|
logger := log.New("epoch", c.epoch)
|
|
|
|
// We're about to mmap the file, ensure that the mapping is cleaned up when the
|
|
// cache becomes unused.
|
|
runtime.SetFinalizer(c, (*cache).finalizer)
|
|
|
|
// Try to load the file from disk and memory map it
|
|
var err error
|
|
c.dump, c.mmap, c.cache, err = memoryMap(path)
|
|
if err == nil {
|
|
logger.Debug("Loaded old ethash cache from disk")
|
|
return
|
|
}
|
|
logger.Debug("Failed to load old ethash cache", "err", err)
|
|
|
|
// No previous cache available, create a new cache file to fill
|
|
c.dump, c.mmap, c.cache, err = memoryMapAndGenerate(path, size, func(buffer []uint32) { generateCache(buffer, c.epoch, seed) })
|
|
if err != nil {
|
|
logger.Error("Failed to generate mapped ethash cache", "err", err)
|
|
|
|
c.cache = make([]uint32, size/4)
|
|
generateCache(c.cache, c.epoch, seed)
|
|
}
|
|
// Iterate over all previous instances and delete old ones
|
|
for ep := int(c.epoch) - limit; ep >= 0; ep-- {
|
|
seed := seedHash(uint64(ep)*epochLength + 1)
|
|
path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
|
|
os.Remove(path)
|
|
}
|
|
})
|
|
}
|
|
|
|
// finalizer unmaps the memory and closes the file.
|
|
func (c *cache) finalizer() {
|
|
if c.mmap != nil {
|
|
c.mmap.Unmap()
|
|
c.dump.Close()
|
|
c.mmap, c.dump = nil, nil
|
|
}
|
|
}
|
|
|
|
// dataset wraps an ethash dataset with some metadata to allow easier concurrent use.
|
|
type dataset struct {
|
|
epoch uint64 // Epoch for which this cache is relevant
|
|
dump *os.File // File descriptor of the memory mapped cache
|
|
mmap mmap.MMap // Memory map itself to unmap before releasing
|
|
dataset []uint32 // The actual cache data content
|
|
once sync.Once // Ensures the cache is generated only once
|
|
}
|
|
|
|
// 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{} {
|
|
return &dataset{epoch: epoch}
|
|
}
|
|
|
|
// generate ensures that the dataset content is generated before use.
|
|
func (d *dataset) generate(dir string, limit int, test bool) {
|
|
d.once.Do(func() {
|
|
csize := cacheSize(d.epoch*epochLength + 1)
|
|
dsize := datasetSize(d.epoch*epochLength + 1)
|
|
seed := seedHash(d.epoch*epochLength + 1)
|
|
if test {
|
|
csize = 1024
|
|
dsize = 32 * 1024
|
|
}
|
|
// If we don't store anything on disk, generate and return
|
|
if dir == "" {
|
|
cache := make([]uint32, csize/4)
|
|
generateCache(cache, d.epoch, seed)
|
|
|
|
d.dataset = make([]uint32, dsize/4)
|
|
generateDataset(d.dataset, d.epoch, cache)
|
|
}
|
|
// Disk storage is needed, this will get fancy
|
|
var endian string
|
|
if !isLittleEndian() {
|
|
endian = ".be"
|
|
}
|
|
path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian))
|
|
logger := log.New("epoch", d.epoch)
|
|
|
|
// We're about to mmap the file, ensure that the mapping is cleaned up when the
|
|
// cache becomes unused.
|
|
runtime.SetFinalizer(d, (*dataset).finalizer)
|
|
|
|
// Try to load the file from disk and memory map it
|
|
var err error
|
|
d.dump, d.mmap, d.dataset, err = memoryMap(path)
|
|
if err == nil {
|
|
logger.Debug("Loaded old ethash dataset from disk")
|
|
return
|
|
}
|
|
logger.Debug("Failed to load old ethash dataset", "err", err)
|
|
|
|
// No previous dataset available, create a new dataset file to fill
|
|
cache := make([]uint32, csize/4)
|
|
generateCache(cache, d.epoch, seed)
|
|
|
|
d.dump, d.mmap, d.dataset, err = memoryMapAndGenerate(path, dsize, func(buffer []uint32) { generateDataset(buffer, d.epoch, cache) })
|
|
if err != nil {
|
|
logger.Error("Failed to generate mapped ethash dataset", "err", err)
|
|
|
|
d.dataset = make([]uint32, dsize/2)
|
|
generateDataset(d.dataset, d.epoch, cache)
|
|
}
|
|
// Iterate over all previous instances and delete old ones
|
|
for ep := int(d.epoch) - limit; ep >= 0; ep-- {
|
|
seed := seedHash(uint64(ep)*epochLength + 1)
|
|
path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian))
|
|
os.Remove(path)
|
|
}
|
|
})
|
|
}
|
|
|
|
// finalizer closes any file handlers and memory maps open.
|
|
func (d *dataset) finalizer() {
|
|
if d.mmap != nil {
|
|
d.mmap.Unmap()
|
|
d.dump.Close()
|
|
d.mmap, d.dump = nil, nil
|
|
}
|
|
}
|
|
|
|
// MakeCache generates a new ethash cache and optionally stores it to disk.
|
|
func MakeCache(block uint64, dir string) {
|
|
c := cache{epoch: block / epochLength}
|
|
c.generate(dir, math.MaxInt32, false)
|
|
}
|
|
|
|
// MakeDataset generates a new ethash dataset and optionally stores it to disk.
|
|
func MakeDataset(block uint64, dir string) {
|
|
d := dataset{epoch: block / epochLength}
|
|
d.generate(dir, math.MaxInt32, false)
|
|
}
|
|
|
|
// Mode defines the type and amount of PoW verification an ethash engine makes.
|
|
type Mode uint
|
|
|
|
const (
|
|
ModeNormal Mode = iota
|
|
ModeShared
|
|
ModeTest
|
|
ModeFake
|
|
ModeFullFake
|
|
)
|
|
|
|
// Config are the configuration parameters of the ethash.
|
|
type Config struct {
|
|
CacheDir string
|
|
CachesInMem int
|
|
CachesOnDisk int
|
|
DatasetDir string
|
|
DatasetsInMem int
|
|
DatasetsOnDisk int
|
|
PowMode Mode
|
|
}
|
|
|
|
// Ethash is a consensus engine based on proot-of-work implementing the ethash
|
|
// algorithm.
|
|
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
|
|
|
|
// 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
|
|
|
|
// 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
|
|
fakeDelay time.Duration // Time delay to sleep for before returning from verify
|
|
|
|
lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
|
|
}
|
|
|
|
// New creates a full sized ethash PoW scheme.
|
|
func New(config Config) *Ethash {
|
|
if config.CachesInMem <= 0 {
|
|
log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem)
|
|
config.CachesInMem = 1
|
|
}
|
|
if config.CacheDir != "" && config.CachesOnDisk > 0 {
|
|
log.Info("Disk storage enabled for ethash caches", "dir", config.CacheDir, "count", config.CachesOnDisk)
|
|
}
|
|
if config.DatasetDir != "" && config.DatasetsOnDisk > 0 {
|
|
log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk)
|
|
}
|
|
return &Ethash{
|
|
config: config,
|
|
caches: newlru("cache", config.CachesInMem, newCache),
|
|
datasets: newlru("dataset", config.DatasetsInMem, newDataset),
|
|
update: make(chan struct{}),
|
|
hashrate: metrics.NewMeter(),
|
|
}
|
|
}
|
|
|
|
// NewTester creates a small sized ethash PoW scheme useful only for testing
|
|
// purposes.
|
|
func NewTester() *Ethash {
|
|
return New(Config{CachesInMem: 1, PowMode: ModeTest})
|
|
}
|
|
|
|
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
|
|
// all blocks' seal as valid, though they still have to conform to the Ethereum
|
|
// consensus rules.
|
|
func NewFaker() *Ethash {
|
|
return &Ethash{
|
|
config: Config{
|
|
PowMode: ModeFake,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewFakeFailer creates a ethash consensus engine with a fake PoW scheme that
|
|
// accepts all blocks as valid apart from the single one specified, though they
|
|
// still have to conform to the Ethereum consensus rules.
|
|
func NewFakeFailer(fail uint64) *Ethash {
|
|
return &Ethash{
|
|
config: Config{
|
|
PowMode: ModeFake,
|
|
},
|
|
fakeFail: fail,
|
|
}
|
|
}
|
|
|
|
// NewFakeDelayer creates a ethash consensus engine with a fake PoW scheme that
|
|
// accepts all blocks as valid, but delays verifications by some time, though
|
|
// they still have to conform to the Ethereum consensus rules.
|
|
func NewFakeDelayer(delay time.Duration) *Ethash {
|
|
return &Ethash{
|
|
config: Config{
|
|
PowMode: ModeFake,
|
|
},
|
|
fakeDelay: delay,
|
|
}
|
|
}
|
|
|
|
// NewFullFaker creates an ethash consensus engine with a full fake scheme that
|
|
// accepts all blocks as valid, without checking any consensus rules whatsoever.
|
|
func NewFullFaker() *Ethash {
|
|
return &Ethash{
|
|
config: Config{
|
|
PowMode: ModeFullFake,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewShared creates a full sized ethash PoW shared between all requesters running
|
|
// in the same process.
|
|
func NewShared() *Ethash {
|
|
return &Ethash{shared: sharedEthash}
|
|
}
|
|
|
|
// 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.
|
|
func (ethash *Ethash) cache(block uint64) *cache {
|
|
epoch := block / epochLength
|
|
currentI, futureI := ethash.caches.get(epoch)
|
|
current := currentI.(*cache)
|
|
|
|
// Wait for generation finish.
|
|
current.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, 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)
|
|
go future.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.PowMode == ModeTest)
|
|
}
|
|
return current
|
|
}
|
|
|
|
// 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
|
|
// stored on disk, and finally generating one if none can be found.
|
|
func (ethash *Ethash) dataset(block uint64) *dataset {
|
|
epoch := block / epochLength
|
|
currentI, futureI := ethash.datasets.get(epoch)
|
|
current := currentI.(*dataset)
|
|
|
|
// Wait for generation finish.
|
|
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 {
|
|
future := futureI.(*dataset)
|
|
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
|
}
|
|
|
|
return current
|
|
}
|
|
|
|
// Threads returns the number of mining threads currently enabled. This doesn't
|
|
// necessarily mean that mining is running!
|
|
func (ethash *Ethash) Threads() int {
|
|
ethash.lock.Lock()
|
|
defer ethash.lock.Unlock()
|
|
|
|
return ethash.threads
|
|
}
|
|
|
|
// SetThreads updates the number of mining threads currently enabled. Calling
|
|
// this method does not start mining, only sets the thread count. If zero is
|
|
// specified, the miner will use all cores of the machine. Setting a thread
|
|
// count below zero is allowed and will cause the miner to idle, without any
|
|
// work being done.
|
|
func (ethash *Ethash) SetThreads(threads int) {
|
|
ethash.lock.Lock()
|
|
defer ethash.lock.Unlock()
|
|
|
|
// If we're running a shared PoW, set the thread count on that instead
|
|
if ethash.shared != nil {
|
|
ethash.shared.SetThreads(threads)
|
|
return
|
|
}
|
|
// Update the threads and ping any running seal to pull in any changes
|
|
ethash.threads = threads
|
|
select {
|
|
case ethash.update <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// Hashrate implements PoW, returning the measured rate of the search invocations
|
|
// per second over the last minute.
|
|
func (ethash *Ethash) Hashrate() float64 {
|
|
return ethash.hashrate.Snapshot().Rate1()
|
|
}
|
|
|
|
// APIs implements consensus.Engine, returning the user facing RPC APIs. Currently
|
|
// that is empty.
|
|
func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API {
|
|
return nil
|
|
}
|
|
|
|
// SeedHash is the seed to use for generating a verification cache and the mining
|
|
// dataset.
|
|
func SeedHash(block uint64) []byte {
|
|
return seedHash(block)
|
|
}
|