Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion consensus/ethash/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ func generateDataset(dest []uint32, epoch uint64, epochLength uint64, cache []ui
if elapsed > 3*time.Second {
logFn = logger.Info
}
logFn("Generated ethash verification cache", "epochLength", epochLength, "elapsed", common.PrettyDuration(elapsed))
logFn("Generated ethash verification dataset", "epochLength", epochLength, "elapsed", common.PrettyDuration(elapsed))
}()

// Figure out whether the bytes need to be swapped for the machine
Expand Down
53 changes: 42 additions & 11 deletions consensus/ethash/ethash.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,31 +226,34 @@ func (lru *lru) get(epoch uint64, epochLength uint64, ecip1099FBlock *uint64) (i
lru.mu.Lock()
defer lru.mu.Unlock()

cacheKey := fmt.Sprintf("%d-%d", epoch, epochLength)
// Get or create the item for the requested epoch.
item, ok := lru.cache.Get(epoch)
item, ok := lru.cache.Get(cacheKey)
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, epochLength)
}
lru.cache.Add(epoch, item)
lru.cache.Add(cacheKey, item)
}

// Ensure pre-generation handles ecip-1099 changeover correctly
var nextEpoch = epoch + 1
var nextEpochLength = epochLength
if ecip1099FBlock != nil {
nextEpochBlock := nextEpoch * epochLength
if nextEpochBlock == *ecip1099FBlock && epochLength == epochLengthDefault {
if nextEpochBlock >= *ecip1099FBlock && epochLength == epochLengthDefault {
nextEpoch = nextEpoch / 2
nextEpochLength = epochLengthECIP1099
}
}

// Update the 'future item' if epoch is larger than previously seen.
if epoch < maxEpoch-1 && lru.future < nextEpoch {
// Last conditional clause ('lru.future > nextEpoch') handles the ECIP1099 case where
// the next epoch is expected to be LESSER THAN that of the previous state's future epoch number.
if epoch < maxEpoch-1 && (lru.future < nextEpoch || lru.future > nextEpoch) {
log.Trace("Requiring new future ethash "+lru.what, "epoch", nextEpoch)
future = lru.new(nextEpoch, nextEpochLength)
lru.future = nextEpoch
Expand Down Expand Up @@ -337,8 +340,13 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) {
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)
// The file path naming scheme was changed to include epoch values in the filename,
// which enables a filepath glob with scan to identify out-of-bounds caches and remove them.
// The legacy path declaration is provided below as a comment for reference.
//
// path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian)) // LEGACY
path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%d-%x%s", algorithmRevision, c.epoch, seed[:8], endian)) // CURRENT
logger := log.New("epoch", c.epoch, "epochLength", c.epochLength)

// We're about to mmap the file, ensure that the mapping is cleaned up when the
// cache becomes unused.
Expand Down Expand Up @@ -367,11 +375,34 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) {
c.cache = make([]uint32, size/4)
generateCache(c.cache, c.epoch, c.epochLength, seed)
}
// Iterate over all previous instances and delete old ones
for ep := int(c.epoch) - limit; ep >= 0; ep-- {
seed := seedHash(uint64(ep), c.epochLength)
path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
os.Remove(path)

// Iterate over all cache file instances, deleting any out of bounds (where epoch is below lower limit, or above upper limit).
matches, _ := filepath.Glob(filepath.Join(dir, fmt.Sprintf("cache-R%d*", algorithmRevision)))
for _, file := range matches {
var ar int // algorithm revision
var e uint64 // epoch
var s string // seed
if _, err := fmt.Sscanf(filepath.Base(file), "cache-R%d-%d-%s"+endian, &ar, &e, &s); err != nil {
// There is an unrecognized file in this directory.
// See if the name matches the expected pattern of the legacy naming scheme.
if _, err := fmt.Sscanf(filepath.Base(file), "cache-R%d-%s"+endian, &ar, &s); err == nil {
// This file matches the previous generation naming pattern (sans epoch).
if err := os.Remove(file); err != nil {
logger.Error("Failed to remove legacy ethash cache file", "file", file, "err", err)
} else {
logger.Warn("Deleted legacy ethash cache file", "path", file)
}
}
// Else the file is unrecognized (unknown name format), leave it alone.
continue
}
if e <= c.epoch-uint64(limit) || e > c.epoch+1 {
if err := os.Remove(file); err == nil {
logger.Debug("Deleted ethash cache file", "target.epoch", e, "file", file)
} else {
logger.Error("Failed to delete ethash cache file", "target.epoch", e, "file", file, "err", err)
}
}
}
})
}
Expand Down
90 changes: 90 additions & 0 deletions consensus/ethash/ethash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,105 @@ import (
"math/big"
"math/rand"
"os"
"path/filepath"
"sync"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)

func verboseLogging() {
glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(99))
log.Root().SetHandler(glogger)
}

func TestEthashCaches(t *testing.T) {
verboseLogging()

// Make a copy of the default config.
conf := Config{
CacheDir: filepath.Join(os.TempDir(), "ethash-cache-test-cachedir"),
CachesInMem: 2,
CachesOnDisk: 3,
CachesLockMmap: false,
DatasetsInMem: 1,
DatasetsOnDisk: 2,
DatasetsLockMmap: false,
DatasetDir: filepath.Join(os.TempDir(), "ethash-cache-test-datadir"),
PowMode: ModeNormal,
}

// Clean up ahead of ourselves.
os.RemoveAll(conf.CacheDir)
os.RemoveAll(conf.DatasetDir)

// And after ourselves.
defer os.RemoveAll(conf.CacheDir)
defer os.RemoveAll(conf.DatasetDir)

// Use some "big" arbitrary multiple to make sure that simulate real life adequately.
testIterationMultiple := 6
ecip1099Block := uint64(epochLengthDefault * conf.CachesInMem * testIterationMultiple)
conf.ECIP1099Block = &ecip1099Block

// Construct our Ethash
e := New(conf, nil, false)

trialMax := ecip1099Block * uint64(testIterationMultiple) * 2
latestIteratedEpoch := uint64(math.MaxInt64)
for n := uint64(0); n < trialMax; n += epochLengthDefault / 300 {
// Calculate the epoch number independently to use for logging and debugging.
epochLength := epochLengthDefault
if n >= ecip1099Block {
epochLength = epochLengthECIP1099
}
ep := calcEpoch(n, uint64(epochLength))
epl := calcEpochLength(n, conf.ECIP1099Block)

if ep != latestIteratedEpoch {
t.Logf("block=%d epoch=%d epoch.len=%d ECIP1099=/%d (%0.1f%%) RANGE=/%d (%0.1f%%)",
n,
ep, epl,
ecip1099Block, float64(n)/float64(ecip1099Block)*100,
trialMax, float64(n)/float64(trialMax)*100,
)
latestIteratedEpoch = ep
}

// This is the tested function.
c := e.cache(n)
c.finalizer()

// Do we get the right epoch length?
if n >= ecip1099Block && c.epochLength != epochLengthECIP1099 {
// Give the future epoch routine a chance to finish.
time.Sleep(1 * time.Second)

// current status
t.Logf("block=%d epoch=%d epoch.len=%d ECIP1099=/%d (%0.1f%%) RANGE=/%d (%0.1f%%)",
n,
ep, epl,
ecip1099Block, float64(n)/float64(ecip1099Block)*100,
trialMax, float64(n)/float64(trialMax)*100,
)

// ls -l /tmp/ethash-cache-test-cachedir
entries, _ := os.ReadDir(conf.CacheDir)
t.Log("cachedir", conf.CacheDir)
for _, entry := range entries {
t.Logf(` - %s\n`, entry.Name())
}

t.Fatalf("Unexpected epoch length: %d", c.epochLength)
}
}
}

// Tests caches get sets correct future
func TestCachesGet(t *testing.T) {
ethashA := NewTester(nil, false)
Expand Down