diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go
index b3e1709dbf4f..87943395de4e 100644
--- a/cmd/puppeth/genesis.go
+++ b/cmd/puppeth/genesis.go
@@ -239,6 +239,7 @@ type parityChainSpec struct {
DifficultyBombDelays map[string]string `json:"difficultyBombDelays"`
HomesteadTransition hexutil.Uint64 `json:"homesteadTransition"`
EIP100bTransition hexutil.Uint64 `json:"eip100bTransition"`
+ ProgpowTransition *hexutil.Uint64 `json:"progpowTransition,omitempty"`
} `json:"params"`
} `json:"Ethash"`
} `json:"engine"`
@@ -412,6 +413,11 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin
if num := genesis.Config.IstanbulBlock; num != nil {
spec.setIstanbul(num)
}
+ // Progpow
+ if num := genesis.Config.ProgpowBlock; num != nil {
+ hexnum := hexutil.Uint64(num.Uint64())
+ spec.Engine.Ethash.Params.ProgpowTransition = &hexnum
+ }
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
spec.Params.GasLimitBoundDivisor = (math2.HexOrDecimal64)(params.GasLimitBoundDivisor)
diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go
index be8b6ec60094..8f40dc8981ef 100644
--- a/cmd/puppeth/module_dashboard.go
+++ b/cmd/puppeth/module_dashboard.go
@@ -633,6 +633,7 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
"Byzantium": conf.Genesis.Config.ByzantiumBlock,
"Constantinople": conf.Genesis.Config.ConstantinopleBlock,
"ConstantinopleFix": conf.Genesis.Config.PetersburgBlock,
+ "ProgPoW": conf.Genesis.Config.ProgpowBlock,
})
files[filepath.Join(workdir, "index.html")] = indexfile.Bytes()
diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go
index 40327d25d226..62778dee4002 100644
--- a/cmd/puppeth/wizard_genesis.go
+++ b/cmd/puppeth/wizard_genesis.go
@@ -239,6 +239,12 @@ func (w *wizard) manageGenesis() {
fmt.Printf("Which block should YOLOv1 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV1Block)
w.conf.Genesis.Config.YoloV1Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV1Block)
+ if w.conf.Genesis.Config.Clique == nil {
+ fmt.Println()
+ fmt.Printf("Which block should ProgPow come into effect? (default = %v)\n", w.conf.Genesis.Config.ProgpowBlock)
+ w.conf.Genesis.Config.ProgpowBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ProgpowBlock)
+ }
+
out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ")
fmt.Printf("Chain configuration updated:\n\n%s\n", out)
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 55b751d62527..22d21f17ba13 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1822,6 +1822,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B
DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem,
DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk,
DatasetsLockMmap: eth.DefaultConfig.Ethash.DatasetsLockMmap,
+ ProgpowBlock: config.ProgpowBlock,
}, nil, false)
}
}
diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go
index d6c871092ed3..44cc2db8280a 100644
--- a/consensus/ethash/algorithm.go
+++ b/consensus/ethash/algorithm.go
@@ -207,6 +207,27 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) {
}
}
+// generateCDag generates the cDag used for progpow. If the 'cDag' is nil, this method is a no-op. Otherwise
+// it expects the cDag to be of size progpowCacheWords
+func generateCDag(cDag, cache []uint32, epoch uint64) {
+ if cDag == nil {
+ return
+ }
+ start := time.Now()
+ keccak512 := makeHasher(sha3.NewLegacyKeccak512())
+
+ for i := uint32(0); i < progpowCacheWords/16; i++ {
+ rawData := generateDatasetItem(cache, i, keccak512)
+ // 64 bytes in rawData -> 16 uint32
+ for j := uint32(0); j < 16; j++ {
+ cDag[i*16+j] = binary.LittleEndian.Uint32(rawData[4*j:])
+ }
+ }
+
+ elapsed := time.Since(start)
+ log.Info("Generated progpow cDag", "elapsed", common.PrettyDuration(elapsed), "epoch", epoch)
+}
+
// swap changes the byte order of the buffer assuming a uint32 representation.
func swap(buffer []byte) {
for i := 0; i < len(buffer); i += 4 {
diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go
index 51fb6b124de9..18ccf3a51531 100644
--- a/consensus/ethash/algorithm_test.go
+++ b/consensus/ethash/algorithm_test.go
@@ -729,7 +729,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
go func(idx int) {
defer pend.Done()
- ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false)
+ ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil, nil}, nil, false)
defer ethash.Close()
if err := ethash.VerifySeal(nil, block.Header()); err != nil {
t.Errorf("proc %d: block verification failed: %v", idx, err)
@@ -739,7 +739,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
pend.Wait()
}
-// Benchmarks the cache generation performance.
+// BenchmarkCacheGeneration benchmarks the cache generation performance.
func BenchmarkCacheGeneration(b *testing.B) {
for i := 0; i < b.N; i++ {
cache := make([]uint32, cacheSize(1)/4)
@@ -747,7 +747,7 @@ func BenchmarkCacheGeneration(b *testing.B) {
}
}
-// Benchmarks the dataset (small) generation performance.
+// BenchmarkSmallDatasetGeneration benchmarks the dataset (small) generation performance.
func BenchmarkSmallDatasetGeneration(b *testing.B) {
cache := make([]uint32, 65536/4)
generateCache(cache, 0, make([]byte, 32))
@@ -759,7 +759,7 @@ func BenchmarkSmallDatasetGeneration(b *testing.B) {
}
}
-// Benchmarks the light verification performance.
+// BenchmarkHashimotoLight benchmarks the light verification performance.
func BenchmarkHashimotoLight(b *testing.B) {
cache := make([]uint32, cacheSize(1)/4)
generateCache(cache, 0, make([]byte, 32))
@@ -772,7 +772,22 @@ func BenchmarkHashimotoLight(b *testing.B) {
}
}
-// Benchmarks the full (small) verification performance.
+// BenchmarkProgpowLight benchmarks the light verification performance (not counting cDag generation).
+func BenchmarkProgpowLight(b *testing.B) {
+ cache := make([]uint32, cacheSize(1)/4)
+ generateCache(cache, 0, make([]byte, 32))
+
+ hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
+ cDag := make([]uint32, progpowCacheWords)
+ generateCDag(cDag, cache, 0)
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ progpowLight(datasetSize(1), cache, hash, 0, 0, cDag)
+ }
+}
+
+// BenchmarkHashimotoFullSmall benchmarks the full (small) verification performance.
func BenchmarkHashimotoFullSmall(b *testing.B) {
cache := make([]uint32, 65536/4)
generateCache(cache, 0, make([]byte, 32))
@@ -812,3 +827,19 @@ func BenchmarkHashimotoFullMmap(b *testing.B) {
benchmarkHashimotoFullMmap(b, "WithLock", true)
benchmarkHashimotoFullMmap(b, "WithoutLock", false)
}
+
+// BenchmarkProgpowFullSmall benchmarks the full (small) verification performance.
+func BenchmarkProgpowFullSmall(b *testing.B) {
+ cache := make([]uint32, 65536/4)
+ generateCache(cache, 0, make([]byte, 32))
+
+ dataset := make([]uint32, 32*65536/4)
+ generateDataset(dataset, 0, cache)
+
+ hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ progpowFull(dataset, hash, 0, 0)
+ }
+}
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
index bdc02098afdf..722197c92174 100644
--- a/consensus/ethash/consensus.go
+++ b/consensus/ethash/consensus.go
@@ -515,14 +515,16 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *type
number := header.Number.Uint64()
var (
- digest []byte
- result []byte
+ digest []byte
+ result []byte
+ powLight = ethash.lightPow(header.Number)
+ powFull = ethash.fullPow(header.Number)
)
// If fast-but-heavy PoW verification was requested, use an ethash dataset
if fulldag {
dataset := ethash.dataset(number, true)
if dataset.generated() {
- digest, result = hashimotoFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
+ digest, result = powFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64(), number)
// 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.
@@ -540,7 +542,7 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *type
if ethash.config.PowMode == ModeTest {
size = 32 * 1024
}
- digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
+ digest, result = powLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64(), number)
// 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.
diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go
index 675737d9e1aa..d3dd07f4f33a 100644
--- a/consensus/ethash/consensus_test.go
+++ b/consensus/ethash/consensus_test.go
@@ -84,3 +84,81 @@ func TestCalcDifficulty(t *testing.T) {
}
}
}
+
+//func TestTransitionToProgpow(t *testing.T) {
+// fn := filepath.Join("..", "..", "tests", "hashi_to_pp_at_5.rlp.gz")
+// fh, err := os.Open(fn)
+// if err != nil {
+// t.Skip(err)
+// }
+// defer fh.Close()
+//
+// var reader io.Reader = fh
+// if strings.HasSuffix(fn, ".gz") {
+// if reader, err = gzip.NewReader(reader); err != nil {
+// t.Skip(err)
+// }
+// }
+// stream := rlp.NewStream(reader, 0)
+// config := ¶ms.ChainConfig{
+// HomesteadBlock: big.NewInt(1),
+// EIP150Block: big.NewInt(2),
+// EIP155Block: big.NewInt(3),
+// EIP158Block: big.NewInt(3),
+// ProgpowBlock: big.NewInt(5),
+// }
+// genesis := core.Genesis{Config: config,
+// GasLimit: 0x47b760,
+// Alloc: core.GenesisAlloc{},
+// Timestamp: 0x59a4e76d,
+// ExtraData: hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000000"),
+// }
+// db := ethdb.NewMemDatabase()
+// genesis.MustCommit(db)
+//
+// engine := New(Config{
+// CacheDir: "",
+// CachesInMem: 1,
+// CachesOnDisk: 1,
+// DatasetDir: "",
+// DatasetsInMem: 1,
+// DatasetsOnDisk: 1,
+// ProgpowBlock: config.ProgpowBlock,
+// }, nil, false)
+// bc, err := core.NewBlockChain(db, nil, config, engine, vm.Config{}, nil)
+// //fmt.Printf("Genesis hash %x\n", bc.Genesis().Hash())
+// if err != nil {
+// t.Skip(err)
+// }
+// blocks := make(types.Blocks, 100)
+// n := 0
+// for batch := 0; ; batch++ {
+// // Load a batch of RLP blocks.
+// i := 0
+// for ; i < 100; i++ {
+// var b types.Block
+// if err := stream.Decode(&b); err == io.EOF {
+// break
+// } else if err != nil {
+// t.Errorf("at block %d: %v", n, err)
+// }
+// // don't import first block
+// if b.NumberU64() == 0 {
+// i--
+// continue
+// }
+// blocks[i] = &b
+// n++
+// }
+// if i == 0 {
+// break
+// }
+// if _, err := bc.InsertChain(blocks[:i]); err != nil {
+// t.Fatalf("invalid block %d: %v", n, err)
+// }
+// }
+// if bc.CurrentBlock().Number().Cmp(big.NewInt(1054)) != 0 {
+// t.Errorf("Expected to import 1054 blocks, got %v", bc.CurrentBlock().Number())
+//
+// }
+//}
diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go
index aa3f002c0dac..8377f8dea933 100644
--- a/consensus/ethash/ethash.go
+++ b/consensus/ethash/ethash.go
@@ -18,6 +18,7 @@
package ethash
import (
+ "encoding/binary"
"errors"
"fmt"
"math"
@@ -47,8 +48,9 @@ var (
// two256 is a big integer representing 2^256
two256 = 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, false, "", 1, 0, false, ModeNormal, nil}, nil, false)
+ // sharedEngines contains ethash instances which are mapped by progpow blocknumber
+ sharedEngines map[uint64]*Ethash
+ ethashMu sync.Mutex // lock for modifying sharedEngines
// algorithmRevision is the data structure version used for file naming.
algorithmRevision = 23
@@ -210,6 +212,7 @@ type cache struct {
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)
+ cDag []uint32 // The cDag used by progpow. May be nil
once sync.Once // Ensures the cache is generated only once
}
@@ -231,6 +234,8 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) {
if dir == "" {
c.cache = make([]uint32, size/4)
generateCache(c.cache, c.epoch, seed)
+ c.cDag = make([]uint32, progpowCacheWords)
+ generateCDag(c.cDag, c.cache, c.epoch)
return
}
// Disk storage is needed, this will get fancy
@@ -250,6 +255,8 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) {
c.dump, c.mmap, c.cache, err = memoryMap(path, lock)
if err == nil {
logger.Debug("Loaded old ethash cache from disk")
+ c.cDag = make([]uint32, progpowCacheWords)
+ generateCDag(c.cDag, c.cache, c.epoch)
return
}
logger.Debug("Failed to load old ethash cache", "err", err)
@@ -262,6 +269,8 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) {
c.cache = make([]uint32, size/4)
generateCache(c.cache, c.epoch, seed)
}
+ c.cDag = make([]uint32, progpowCacheWords)
+ generateCDag(c.cDag, c.cache, c.epoch)
// Iterate over all previous instances and delete old ones
for ep := int(c.epoch) - limit; ep >= 0; ep-- {
seed := seedHash(uint64(ep)*epochLength + 1)
@@ -410,6 +419,7 @@ type Config struct {
DatasetsOnDisk int
DatasetsLockMmap bool
PowMode Mode
+ ProgpowBlock *big.Int // Block number at which to use progpow instead of hashimoto
Log log.Logger `toml:"-"`
}
@@ -531,8 +541,21 @@ func NewFullFaker() *Ethash {
// NewShared creates a full sized ethash PoW shared between all requesters running
// in the same process.
-func NewShared() *Ethash {
- return &Ethash{shared: sharedEthash}
+func NewShared(progpowNumber *big.Int) *Ethash {
+ ethashMu.Lock()
+ if progpowNumber == nil {
+ progpowNumber = new(big.Int).SetUint64(uint64(math.MaxUint64))
+ }
+ if sharedEngines == nil {
+ sharedEngines = make(map[uint64]*Ethash)
+ }
+ sharedEngine, exist := sharedEngines[progpowNumber.Uint64()]
+ if !exist {
+ sharedEngine = New(Config{"", 3, 0, false, "", 1, 0, false, ModeNormal, progpowNumber, nil}, nil, false)
+ sharedEngines[progpowNumber.Uint64()] = sharedEngine
+ }
+ ethashMu.Unlock()
+ return &Ethash{shared: sharedEngine}
}
// Close closes the exit channel to notify all backend threads exiting.
@@ -680,3 +703,51 @@ func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
func SeedHash(block uint64) []byte {
return seedHash(block)
}
+
+type powFull func(dataset []uint32, hash []byte, nonce, number uint64) ([]byte, []byte)
+type powLight func(size uint64, cache []uint32, hash []byte, nonce, number uint64) ([]byte, []byte)
+
+// fullPow returns either hashimoto or progpow full checker depending on number
+func (ethash *Ethash) fullPow(number *big.Int) powFull {
+ if progpowNumber := ethash.config.ProgpowBlock; progpowNumber != nil && progpowNumber.Cmp(number) <= 0 {
+ ethashCache := ethash.cache(number.Uint64())
+ if ethashCache.cDag == nil {
+ log.Warn("cDag is nil, suboptimal performance")
+ cDag := make([]uint32, progpowCacheWords)
+ generateCDag(cDag, ethashCache.cache, number.Uint64()/epochLength)
+ ethashCache.cDag = cDag
+ }
+ mix := make([]byte, hashBytes)
+ return func(dataset []uint32, hash []byte, nonce, number uint64) ([]byte, []byte) {
+ lookup := func(index uint32) []byte {
+ for i := uint32(0); i < hashWords; i++ {
+ binary.LittleEndian.PutUint32(mix[i*4:], dataset[index+i])
+ }
+ return mix
+ }
+ return progpow(hash, nonce, uint64(len(dataset))*4, number, ethashCache.cDag, lookup)
+ }
+ }
+ return func(dataset []uint32, hash []byte, nonce uint64, number uint64) ([]byte, []byte) {
+ return hashimotoFull(dataset, hash, nonce)
+ }
+}
+
+// lightPow returns either hashimoto or progpow depending on number
+func (ethash *Ethash) lightPow(number *big.Int) powLight {
+ if progpowNumber := ethash.config.ProgpowBlock; progpowNumber != nil && progpowNumber.Cmp(number) <= 0 {
+ return func(size uint64, cache []uint32, hash []byte, nonce uint64, blockNumber uint64) ([]byte, []byte) {
+ ethashCache := ethash.cache(blockNumber)
+ if ethashCache.cDag == nil {
+ log.Warn("cDag is nil, suboptimal performance")
+ cDag := make([]uint32, progpowCacheWords)
+ generateCDag(cDag, ethashCache.cache, blockNumber/epochLength)
+ ethashCache.cDag = cDag
+ }
+ return progpowLight(size, cache, hash, nonce, blockNumber, ethashCache.cDag)
+ }
+ }
+ return func(size uint64, cache []uint32, hash []byte, nonce uint64, blockNumber uint64) ([]byte, []byte) {
+ return hashimotoLight(size, cache, hash, nonce)
+ }
+}
diff --git a/consensus/ethash/progpow.go b/consensus/ethash/progpow.go
new file mode 100644
index 000000000000..49b7ed35de73
--- /dev/null
+++ b/consensus/ethash/progpow.go
@@ -0,0 +1,431 @@
+// Copyright 2019 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 .
+
+// Package ethash implements the ethash proof-of-work consensus engine.
+package ethash
+
+import (
+ "encoding/binary"
+ "math/bits"
+
+ "golang.org/x/crypto/sha3"
+)
+
+const (
+ progpowCacheBytes = 16 * 1024 // Total size 16*1024 bytes
+ progpowCacheWords = progpowCacheBytes / 4 // Total size 16*1024 bytes
+ progpowLanes = 16 // The number of parallel lanes that coordinate to calculate a single hash instance.
+ progpowRegs = 32 // The register file usage size
+ progpowDagLoads = 4 // Number of uint32 loads from the DAG per lane
+ progpowCntCache = 11
+ progpowCntMath = 18
+ progpowPeriodLength = 10 // Blocks per progpow epoch (N)
+ progpowCntDag = loopAccesses // Number of DAG accesses, same as ethash (64)
+ progpowMixBytes = 2 * mixBytes
+)
+
+func progpowLight(size uint64, cache []uint32, hash []byte, nonce uint64,
+ blockNumber uint64, cDag []uint32) ([]byte, []byte) {
+ keccak512 := makeHasher(sha3.NewLegacyKeccak512())
+ lookup := func(index uint32) []byte {
+ return generateDatasetItem(cache, index/16, keccak512)
+ }
+ return progpow(hash, nonce, size, blockNumber, cDag, lookup)
+}
+
+func progpowFull(dataset []uint32, hash []byte, nonce uint64, blockNumber uint64) ([]byte, []byte) {
+ lookup := func(index uint32) []byte {
+ mix := make([]byte, hashBytes)
+ for i := uint32(0); i < hashWords; i++ {
+ binary.LittleEndian.PutUint32(mix[i*4:], dataset[index+i])
+ }
+ return mix
+ }
+ cDag := make([]uint32, progpowCacheBytes/4)
+ for i := uint32(0); i < progpowCacheBytes/4; i += 2 {
+ cDag[i+0] = dataset[i+0]
+ cDag[i+1] = dataset[i+1]
+ }
+ return progpow(hash, nonce, uint64(len(dataset))*4, blockNumber, cDag, lookup)
+}
+
+func rotl32(x uint32, n uint32) uint32 {
+ return ((x) << (n % 32)) | ((x) >> (32 - (n % 32)))
+}
+
+func rotr32(x uint32, n uint32) uint32 {
+ return ((x) >> (n % 32)) | ((x) << (32 - (n % 32)))
+}
+
+func lower32(in uint64) uint32 {
+ return uint32(in)
+}
+
+func higher32(in uint64) uint32 {
+ return uint32(in >> 32)
+}
+
+var keccakfRNDC = [24]uint32{
+ 0x00000001, 0x00008082, 0x0000808a, 0x80008000, 0x0000808b, 0x80000001,
+ 0x80008081, 0x00008009, 0x0000008a, 0x00000088, 0x80008009, 0x8000000a,
+ 0x8000808b, 0x0000008b, 0x00008089, 0x00008003, 0x00008002, 0x00000080,
+ 0x0000800a, 0x8000000a, 0x80008081, 0x00008080, 0x80000001, 0x80008008}
+
+func keccakF800Round(st *[25]uint32, r int) {
+ var keccakfROTC = [24]uint32{1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2,
+ 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61,
+ 20, 44}
+ var keccakfPILN = [24]uint32{10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24,
+ 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9,
+ 6, 1}
+ bc := make([]uint32, 5)
+ // Theta
+ for i := 0; i < 5; i++ {
+ bc[i] = st[i] ^ st[i+5] ^ st[i+10] ^ st[i+15] ^ st[i+20]
+ }
+
+ for i := 0; i < 5; i++ {
+ t := bc[(i+4)%5] ^ rotl32(bc[(i+1)%5], 1)
+ for j := 0; j < 25; j += 5 {
+ st[j+i] ^= t
+ }
+ }
+
+ // Rho Pi
+ t := st[1]
+ for i, j := range keccakfPILN {
+ bc[0] = st[j]
+ st[j] = rotl32(t, keccakfROTC[i])
+ t = bc[0]
+ }
+
+ // Chi
+ for j := 0; j < 25; j += 5 {
+ bc[0] = st[j+0]
+ bc[1] = st[j+1]
+ bc[2] = st[j+2]
+ bc[3] = st[j+3]
+ bc[4] = st[j+4]
+ st[j+0] ^= ^bc[1] & bc[2]
+ st[j+1] ^= ^bc[2] & bc[3]
+ st[j+2] ^= ^bc[3] & bc[4]
+ st[j+3] ^= ^bc[4] & bc[0]
+ st[j+4] ^= ^bc[0] & bc[1]
+ }
+
+ // Iota
+ st[0] ^= keccakfRNDC[r]
+ //return st
+}
+
+func keccakF800Short(headerHash []byte, nonce uint64, result []uint32) uint64 {
+ var st [25]uint32
+
+ for i := 0; i < 8; i++ {
+ st[i] = (uint32(headerHash[4*i])) +
+ (uint32(headerHash[4*i+1]) << 8) +
+ (uint32(headerHash[4*i+2]) << 16) +
+ (uint32(headerHash[4*i+3]) << 24)
+ }
+
+ st[8] = lower32(nonce)
+ st[9] = higher32(nonce)
+ for i := 0; i < 8; i++ {
+ st[10+i] = result[i]
+ }
+
+ for r := 0; r < 21; r++ {
+ keccakF800Round(&st, r)
+ }
+ keccakF800Round(&st, 21)
+ ret := make([]byte, 8)
+ binary.BigEndian.PutUint32(ret[4:], st[0])
+ binary.BigEndian.PutUint32(ret, st[1])
+ return binary.LittleEndian.Uint64(ret)
+}
+
+func keccakF800Long(headerHash []byte, nonce uint64, result []uint32) []byte {
+ var st [25]uint32
+
+ for i := 0; i < 8; i++ {
+ st[i] = (uint32(headerHash[4*i])) +
+ (uint32(headerHash[4*i+1]) << 8) +
+ (uint32(headerHash[4*i+2]) << 16) +
+ (uint32(headerHash[4*i+3]) << 24)
+ }
+
+ st[8] = lower32(nonce)
+ st[9] = higher32(nonce)
+ for i := 0; i < 8; i++ {
+ st[10+i] = result[i]
+ }
+
+ for r := 0; r <= 21; r++ {
+ keccakF800Round(&st, r)
+ }
+ ret := make([]byte, 32)
+ for i := 0; i < 8; i++ {
+ binary.LittleEndian.PutUint32(ret[i*4:], st[i])
+ }
+ return ret
+}
+
+func fnv1a(h *uint32, d uint32) uint32 {
+ *h = (*h ^ d) * uint32(0x1000193)
+ return *h
+}
+
+type kiss99State struct {
+ z uint32
+ w uint32
+ jsr uint32
+ jcong uint32
+}
+
+func kiss99(st *kiss99State) uint32 {
+ var MWC uint32
+ st.z = 36969*(st.z&65535) + (st.z >> 16)
+ st.w = 18000*(st.w&65535) + (st.w >> 16)
+ MWC = ((st.z << 16) + st.w)
+ st.jsr ^= (st.jsr << 17)
+ st.jsr ^= (st.jsr >> 13)
+ st.jsr ^= (st.jsr << 5)
+ st.jcong = 69069*st.jcong + 1234567
+ return ((MWC ^ st.jcong) + st.jsr)
+}
+
+func fillMix(seed uint64, laneId uint32) [progpowRegs]uint32 {
+ var st kiss99State
+ var mix [progpowRegs]uint32
+
+ fnvHash := uint32(0x811c9dc5)
+
+ st.z = fnv1a(&fnvHash, lower32(seed))
+ st.w = fnv1a(&fnvHash, higher32(seed))
+ st.jsr = fnv1a(&fnvHash, laneId)
+ st.jcong = fnv1a(&fnvHash, laneId)
+
+ for i := 0; i < progpowRegs; i++ {
+ mix[i] = kiss99(&st)
+ }
+ return mix
+}
+
+// Merge new data from b into the value in a
+// Assuming A has high entropy only do ops that retain entropy
+// even if B is low entropy
+// (IE don't do A&B)
+func merge(a *uint32, b uint32, r uint32) {
+ switch r % 4 {
+ case 0:
+ *a = (*a * 33) + b
+ case 1:
+ *a = (*a ^ b) * 33
+ case 2:
+ *a = rotl32(*a, ((r>>16)%31)+1) ^ b
+ default:
+ *a = rotr32(*a, ((r>>16)%31)+1) ^ b
+ }
+}
+
+func progpowInit(seed uint64) (kiss99State, [progpowRegs]uint32, [progpowRegs]uint32) {
+ var randState kiss99State
+
+ fnvHash := uint32(0x811c9dc5)
+
+ randState.z = fnv1a(&fnvHash, lower32(seed))
+ randState.w = fnv1a(&fnvHash, higher32(seed))
+ randState.jsr = fnv1a(&fnvHash, lower32(seed))
+ randState.jcong = fnv1a(&fnvHash, higher32(seed))
+
+ // Create a random sequence of mix destinations for merge()
+ // and mix sources for cache reads
+ // guarantees every destination merged once
+ // guarantees no duplicate cache reads, which could be optimized away
+ // Uses Fisher-Yates shuffle
+ var dstSeq = [32]uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
+ var srcSeq = [32]uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
+
+ for i := uint32(progpowRegs - 1); i > 0; i-- {
+ j := kiss99(&randState) % (i + 1)
+ dstSeq[i], dstSeq[j] = dstSeq[j], dstSeq[i]
+ j = kiss99(&randState) % (i + 1)
+ srcSeq[i], srcSeq[j] = srcSeq[j], srcSeq[i]
+ }
+ return randState, dstSeq, srcSeq
+}
+
+// Random math between two input values
+func progpowMath(a uint32, b uint32, r uint32) uint32 {
+ switch r % 11 {
+ case 0:
+ return a + b
+ case 1:
+ return a * b
+ case 2:
+ return higher32(uint64(a) * uint64(b))
+ case 3:
+ if a < b {
+ return a
+ }
+ return b
+ case 4:
+ return rotl32(a, b)
+ case 5:
+ return rotr32(a, b)
+ case 6:
+ return a & b
+ case 7:
+ return a | b
+ case 8:
+ return a ^ b
+ case 9:
+ return uint32(bits.LeadingZeros32(a) + bits.LeadingZeros32(b))
+ case 10:
+ return uint32(bits.OnesCount32(a) + bits.OnesCount32(b))
+
+ default:
+ return 0
+ }
+}
+
+func progpowLoop(seed uint64, loop uint32, mix *[progpowLanes][progpowRegs]uint32,
+ lookup func(index uint32) []byte,
+ cDag []uint32, datasetSize uint32) {
+ // All lanes share a base address for the global load
+ // Global offset uses mix[0] to guarantee it depends on the load result
+ gOffset := mix[loop%progpowLanes][0] % (64 * datasetSize / (progpowLanes * progpowDagLoads))
+
+ var (
+ srcCounter uint32
+ dstCounter uint32
+ randState kiss99State
+ srcSeq [32]uint32
+ dstSeq [32]uint32
+ rnd = kiss99
+ index uint32
+ data_g []uint32 = make([]uint32, progpowDagLoads)
+ )
+ // 256 bytes of dag data
+ dag_item := make([]byte, 256)
+ // The lookup returns 64, so we'll fetch four items
+ copy(dag_item, lookup((gOffset*progpowLanes)*progpowDagLoads))
+ copy(dag_item[64:], lookup((gOffset*progpowLanes)*progpowDagLoads+16))
+ copy(dag_item[128:], lookup((gOffset*progpowLanes)*progpowDagLoads+32))
+ copy(dag_item[192:], lookup((gOffset*progpowLanes)*progpowDagLoads+48))
+
+ // Lanes can execute in parallel and will be convergent
+ for l := uint32(0); l < progpowLanes; l++ {
+
+ // initialize the seed and mix destination sequence
+ randState, dstSeq, srcSeq = progpowInit(seed)
+ srcCounter = uint32(0)
+ dstCounter = uint32(0)
+
+ //if progpowCntCache > progpowCntMath {
+ // iMax = progpowCntCache
+ //} else {
+ // iMax = progpowCntMath
+ //}
+
+ for i := uint32(0); i < progpowCntMath; i++ {
+ if i < progpowCntCache {
+ // Cached memory access
+ // lanes access random location
+
+ src := srcSeq[(srcCounter)%progpowRegs]
+ srcCounter++
+
+ offset := mix[l][src] % progpowCacheWords
+ data32 := cDag[offset]
+
+ dst := dstSeq[(dstCounter)%progpowRegs]
+ dstCounter++
+
+ r := kiss99(&randState)
+ merge(&mix[l][dst], data32, r)
+ }
+
+ //if i < progpowCntMath
+ {
+ // Random Math
+ srcRnd := rnd(&randState) % (progpowRegs * (progpowRegs - 1))
+ src1 := srcRnd % progpowRegs
+ src2 := srcRnd / progpowRegs
+ if src2 >= src1 {
+ src2++
+ }
+ data32 := progpowMath(mix[l][src1], mix[l][src2], rnd(&randState))
+
+ dst := dstSeq[(dstCounter)%progpowRegs]
+ dstCounter++
+
+ merge(&mix[l][dst], data32, rnd(&randState))
+ }
+ }
+ index = ((l ^ loop) % progpowLanes) * progpowDagLoads
+
+ data_g[0] = binary.LittleEndian.Uint32(dag_item[4*index:])
+ data_g[1] = binary.LittleEndian.Uint32(dag_item[4*(index+1):])
+ data_g[2] = binary.LittleEndian.Uint32(dag_item[4*(index+2):])
+ data_g[3] = binary.LittleEndian.Uint32(dag_item[4*(index+3):])
+
+ merge(&mix[l][0], data_g[0], rnd(&randState))
+
+ for i := 1; i < progpowDagLoads; i++ {
+ dst := dstSeq[(dstCounter)%progpowRegs]
+ dstCounter++
+ merge(&mix[l][dst], data_g[i], rnd(&randState))
+ }
+ }
+}
+
+func progpow(hash []byte, nonce uint64, size uint64, blockNumber uint64, cDag []uint32,
+ lookup func(index uint32) []byte) ([]byte, []byte) {
+ var (
+ mix [progpowLanes][progpowRegs]uint32
+ laneResults [progpowLanes]uint32
+ )
+ result := make([]uint32, 8)
+ seed := keccakF800Short(hash, nonce, result)
+ for lane := uint32(0); lane < progpowLanes; lane++ {
+ mix[lane] = fillMix(seed, lane)
+ }
+ period := (blockNumber / progpowPeriodLength)
+ for l := uint32(0); l < progpowCntDag; l++ {
+ progpowLoop(period, l, &mix, lookup, cDag, uint32(size/progpowMixBytes))
+ }
+
+ // Reduce mix data to a single per-lane result
+ for lane := uint32(0); lane < progpowLanes; lane++ {
+ laneResults[lane] = 0x811c9dc5
+ for i := uint32(0); i < progpowRegs; i++ {
+ fnv1a(&laneResults[lane], mix[lane][i])
+ }
+ }
+ for i := uint32(0); i < 8; i++ {
+ result[i] = 0x811c9dc5
+ }
+ for lane := uint32(0); lane < progpowLanes; lane++ {
+ fnv1a(&result[lane%8], laneResults[lane])
+ }
+ finalHash := keccakF800Long(hash, seed, result[:])
+ mixHash := make([]byte, 8*4)
+ for i := 0; i < 8; i++ {
+ binary.LittleEndian.PutUint32(mixHash[i*4:], result[i])
+ }
+ return mixHash[:], finalHash[:]
+}
diff --git a/consensus/ethash/progpow_test.go b/consensus/ethash/progpow_test.go
new file mode 100644
index 000000000000..926795ad478a
--- /dev/null
+++ b/consensus/ethash/progpow_test.go
@@ -0,0 +1,284 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package ethash
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "strconv"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/crypto/sha3"
+)
+
+func TestRandomMerge(t *testing.T) {
+
+ type test struct {
+ a uint32
+ b uint32
+ exp uint32
+ }
+ for i, tt := range []test{
+ {1000000, 101, 33000101},
+ {2000000, 102, 66003366},
+ {3000000, 103, 6000103},
+ {4000000, 104, 2000104},
+ {1000000, 0, 33000000},
+ {2000000, 0, 66000000},
+ {3000000, 0, 6000000},
+ {4000000, 0, 2000000},
+ } {
+ res := tt.a
+ merge(&res, tt.b, uint32(i))
+ if res != tt.exp {
+ t.Errorf("test %d, expected %d, got %d", i, tt.exp, res)
+ }
+ }
+
+}
+
+func TestProgpowChanges(t *testing.T) {
+ headerHash := common.HexToHash("ffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff")
+ nonce := uint64(0x123456789abcdef0)
+ blocknum := uint64(30000)
+ seed := seedHash(blocknum)
+ fmt.Printf("seedHash %x\n", seed)
+ //seed = common.FromHex("ee304846ddd0a47b")
+ expCdag0_to_15 := []uint32{
+ 0xb3e35467, 0xae7402e3, 0x8522a782, 0xa2d8353b,
+ 0xff4723bd, 0xbfbc05ee, 0xde6944de, 0xf0d2b5b8,
+ 0xc74cbad3, 0xb100f797, 0x05bc60be, 0x4f40840b,
+ 0x35e47268, 0x9cd6f993, 0x6a0e4659, 0xb838e46e,
+ }
+ expCdag4080_to_4095 := []uint32{
+ 0xbde0c650, 0x57cba482, 0x54877c9d, 0xf9fdc423,
+ 0xfb65141b, 0x55074ca4, 0xc7dd116e, 0xbc1737d1,
+ 0x126e8847, 0xb16983b2, 0xf80c058e, 0xe0ad53b5,
+ 0xd5f3e840, 0xff1bdd89, 0x35660a19, 0x73244193,
+ }
+ epoch := blocknum / epochLength
+ size := cacheSize(blocknum)
+ cache := make([]uint32, size/4)
+ generateCache(cache, epoch, seed)
+ cDag := make([]uint32, progpowCacheWords)
+ generateCDag(cDag, cache, epoch)
+
+ for i := 0; i < 15; i++ {
+ if exp := expCdag0_to_15[i]; exp != cDag[i] {
+ t.Errorf("test %d, exp %x != %x", i, exp, cDag[i])
+
+ }
+ if exp := expCdag4080_to_4095[i]; exp != cDag[4080+i] {
+ t.Errorf("test %d (+4080), exp %x != %x", i, exp, cDag[4080+i])
+ }
+ }
+ mixHash, finalHash, _ := hashForBlock(blocknum, nonce, headerHash)
+ fmt.Printf("mixHash %x\n", mixHash)
+ fmt.Printf("finalHash %x\n", finalHash)
+ expMix := common.FromHex("6018c151b0f9895ebe44a4ca6ce2829e5ba6ae1a68a4ccd05a67ac01219655c1")
+ expHash := common.FromHex("34d8436444aa5c61761ce0bcce0f11401df2eace77f5c14ba7039b86b5800c08")
+ if !bytes.Equal(expMix, mixHash) {
+ t.Errorf("mixhash err, expected %x, got %x", expMix, mixHash)
+ }
+ if !bytes.Equal(expHash, finalHash) {
+ t.Errorf("finhash err, expected %x, got %x", expHash, finalHash)
+ }
+ //digest: 7d9a5f6b1407796497f16b091e5dcbbcd711d025634b505fae496611c0d6f57d
+ //result (top 64 bits): 6cf196600abd663e
+}
+
+func TestCDag(t *testing.T) {
+ size := cacheSize(0)
+ cache := make([]uint32, size/4)
+ seed := seedHash(0)
+ generateCache(cache, 0, seed)
+ cDag := make([]uint32, progpowCacheWords)
+ generateCDag(cDag, cache, 0)
+ //fmt.Printf("Cdag: %d \n", cDag[:20])
+ expect := []uint32{690150178, 1181503948, 2248155602, 2118233073, 2193871115,
+ 1791778428, 1067701239, 724807309, 530799275, 3480325829, 3899029234,
+ 1998124059, 2541974622, 1100859971, 1297211151, 3268320000, 2217813733,
+ 2690422980, 3172863319, 2651064309}
+ for i, v := range cDag[:20] {
+ if expect[i] != v {
+ t.Errorf("cdag err, index %d, expected %d, got %d", i, expect[i], v)
+ }
+ }
+}
+
+func TestRandomMath(t *testing.T) {
+
+ type test struct {
+ a uint32
+ b uint32
+ exp uint32
+ }
+ for i, tt := range []test{
+ {20, 22, 42},
+ {70000, 80000, 1305032704},
+ {70000, 80000, 1},
+ {1, 2, 1},
+ {3, 10000, 196608},
+ {3, 0, 3},
+ {3, 6, 2},
+ {3, 6, 7},
+ {3, 6, 5},
+ {0, 0xffffffff, 32},
+ {3 << 13, 1 << 5, 3},
+ {22, 20, 42},
+ {80000, 70000, 1305032704},
+ {80000, 70000, 1},
+ {2, 1, 1},
+ {10000, 3, 80000},
+ {0, 3, 0},
+ {6, 3, 2},
+ {6, 3, 7},
+ {6, 3, 5},
+ {0, 0xffffffff, 32},
+ {3 << 13, 1 << 5, 3},
+ } {
+ res := progpowMath(tt.a, tt.b, uint32(i))
+ if res != tt.exp {
+ t.Errorf("test %d, expected %d, got %d", i, tt.exp, res)
+ }
+ }
+}
+
+func TestProgpowKeccak256(t *testing.T) {
+ result := make([]uint32, 8)
+ header := make([]byte, 32)
+ hash := keccakF800Long(header, 0, result)
+ exp := "5dd431e5fbc604f499bfa0232f45f8f142d0ff5178f539e5a7800bf0643697af"
+ if !bytes.Equal(hash, common.FromHex(exp)) {
+ t.Errorf("expected %s, got %x", exp, hash)
+ }
+}
+func TestProgpowKeccak64(t *testing.T) {
+ result := make([]uint32, 8)
+ header := make([]byte, 32)
+ hash := keccakF800Short(header, 0, result)
+ exp := uint64(0x5dd431e5fbc604f4)
+ if exp != hash {
+ t.Errorf("expected %x, got %x", exp, hash)
+ }
+}
+
+func hashForBlock(blocknum uint64, nonce uint64, headerHash common.Hash) ([]byte, []byte, error) {
+ return speedyHashForBlock(&periodContext{}, blocknum, nonce, headerHash)
+}
+
+type periodContext struct {
+ cDag []uint32
+ cache []uint32
+ datasetSize uint64
+ blockNum uint64
+}
+
+// speedyHashForBlock reuses the context, if possible
+func speedyHashForBlock(ctx *periodContext, blocknum uint64, nonce uint64, headerHash common.Hash) ([]byte, []byte, error) {
+ if blocknum == 0 || ctx.blockNum/epochLength != blocknum/epochLength {
+ size := cacheSize(blocknum)
+ cache := make([]uint32, size/4)
+ seed := seedHash(blocknum)
+ epoch := blocknum / epochLength
+ generateCache(cache, epoch, seed)
+ cDag := make([]uint32, progpowCacheWords)
+ generateCDag(cDag, cache, epoch)
+ ctx.cache = cache
+ ctx.cDag = cDag
+ ctx.datasetSize = datasetSize(blocknum)
+ ctx.blockNum = blocknum
+
+ }
+ keccak512 := makeHasher(sha3.NewLegacyKeccak512())
+ lookup := func(index uint32) []byte {
+ x := generateDatasetItem(ctx.cache, index/16, keccak512)
+ //fmt.Printf("lookup(%d) : %x\n", index/16, x)
+ return x
+ }
+ mixhash, final := progpow(headerHash.Bytes(), nonce, ctx.datasetSize, blocknum, ctx.cDag, lookup)
+ return mixhash, final, nil
+}
+
+func TestProgpowHash(t *testing.T) {
+ mixHash, finalHash, _ := hashForBlock(0, 0, common.Hash{})
+ expHash := common.FromHex("b3bad9ca6f7c566cf0377d1f8cce29d6516a96562c122d924626281ec948ef02")
+ expMix := common.FromHex("f4ac202715ded4136e72887c39e63a4738331c57fd9eb79f6ec421c281aa8743")
+ if !bytes.Equal(mixHash, expMix) {
+ t.Errorf("mixhash err, got %x expected %x", mixHash, expMix)
+ }
+ if !bytes.Equal(finalHash, expHash) {
+ t.Errorf("sealhash err, got %x expected %x", finalHash, expHash)
+ }
+}
+
+type progpowHashTestcase struct {
+ blockNum int
+ headerHash string
+ nonce string
+ mixHash string
+ finalHash string
+}
+
+func (n *progpowHashTestcase) UnmarshalJSON(buf []byte) error {
+ tmp := []interface{}{&n.blockNum, &n.headerHash, &n.nonce, &n.mixHash, &n.finalHash}
+ wantLen := len(tmp)
+ if err := json.Unmarshal(buf, &tmp); err != nil {
+ return err
+ }
+ if g, e := len(tmp), wantLen; g != e {
+ return fmt.Errorf("wrong number of fields in testcase: %d != %d", g, e)
+ }
+ return nil
+}
+func TestProgpowHashes(t *testing.T) {
+ data, err := ioutil.ReadFile(filepath.Join(".", "testdata", "progpow_testvectors.json"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var tests []progpowHashTestcase
+ if err = json.Unmarshal(data, &tests); err != nil {
+ t.Fatal(err)
+ }
+ var ctx periodContext
+ for i, tt := range tests {
+ nonce, err := strconv.ParseInt(tt.nonce, 16, 64)
+ if err != nil {
+ t.Errorf("test %d, nonce err: %v", i, err)
+ }
+ mixhash, final, err := speedyHashForBlock(&ctx,
+ uint64(tt.blockNum),
+ uint64(nonce),
+ common.BytesToHash(common.FromHex(tt.headerHash)))
+ if err != nil {
+ t.Errorf("test %d, err: %v", i, err)
+ }
+ expectFinalHash := common.FromHex(tt.finalHash)
+ expectMixHash := common.FromHex(tt.mixHash)
+ if !bytes.Equal(final, expectFinalHash) {
+ t.Errorf("test %d (blocknum %d), sealhash err, got %x expected %x", i, tt.blockNum, final, expectFinalHash)
+ }
+ if !bytes.Equal(mixhash, expectMixHash) {
+ t.Fatalf("test %d (blocknum %d), mixhash err, got %x expected %x", i, tt.blockNum, mixhash, expectMixHash)
+ }
+ //fmt.Printf("test %d ok!\n", i)
+ }
+}
diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go
index 205353402852..4f0f412a45dd 100644
--- a/consensus/ethash/sealer.go
+++ b/consensus/ethash/sealer.go
@@ -137,6 +137,7 @@ func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan s
target = new(big.Int).Div(two256, header.Difficulty)
number = header.Number.Uint64()
dataset = ethash.dataset(number, false)
+ powFull = ethash.fullPow(header.Number)
)
// Start generating random nonces until we abort or find a good one
var (
@@ -162,7 +163,7 @@ search:
attempts = 0
}
// Compute the PoW value of this nonce
- digest, result := hashimotoFull(dataset.dataset, hash, nonce)
+ digest, result := powFull(dataset.dataset, hash, nonce, number)
if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
// Correct nonce found, create a new header with it
header = types.CopyHeader(header)
diff --git a/consensus/ethash/testdata/progpow_testvectors.json b/consensus/ethash/testdata/progpow_testvectors.json
new file mode 100644
index 000000000000..ea9640fe2bea
--- /dev/null
+++ b/consensus/ethash/testdata/progpow_testvectors.json
@@ -0,0 +1,38 @@
+[
+ [0, "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000",
+ "f4ac202715ded4136e72887c39e63a4738331c57fd9eb79f6ec421c281aa8743",
+ "b3bad9ca6f7c566cf0377d1f8cce29d6516a96562c122d924626281ec948ef02"],
+ [49, "b3bad9ca6f7c566cf0377d1f8cce29d6516a96562c122d924626281ec948ef02", "0000000006ff2c47",
+ "7730596f128f675ef9a6bb7281f268e4077d302f2b9078da1ece4349248561dd",
+ "0b9ed0c11157f1365143e329a6e1cea4248d9d6cb44b9c6daf492c7a076654a4"],
+ [50, "0b9ed0c11157f1365143e329a6e1cea4248d9d6cb44b9c6daf492c7a076654a4", "00000000076e482e",
+ "829136d4a704eb8d06da773f1a90466e7b5ed12119c44526f045bbff4475d891",
+ "e2e881c5b893c2f1ef06b96a10cfcbcf7255b307f0818e7d30eb12b2edfc237b"],
+ [99, "e2e881c5b893c2f1ef06b96a10cfcbcf7255b307f0818e7d30eb12b2edfc237b", "000000003917afab",
+ "deb3d8b45bdc596c56aa37a5eba456f478c82e60e5c028ce95f2e654e4bb7b57",
+ "9bdc2ad2286eaa051d6ca1f5196d2dd1c9a039f1d7ce3e1c856b793deed01778"],
+ [29950, "9bdc2ad2286eaa051d6ca1f5196d2dd1c9a039f1d7ce3e1c856b793deed01778", "005d409dbc23a62a",
+ "c01e6d339cc687c77f653b81c74cb9de8b595554f2c5db671a7dde3846d2fa01",
+ "de0d693e597cf2fd70a4cfaa73f6baafc29e1eee695a81295b278c1116580b72"],
+ [29999, "de0d693e597cf2fd70a4cfaa73f6baafc29e1eee695a81295b278c1116580b72", "005db5fa4c2a3d03",
+ "8b664cdbf396a7a185446c93dddd6611f5a736b11097381ae6bea45e802cec16",
+ "21ec5d1984a4fd4394b042aa96365085225d964727a45def245ceab326e28128"],
+ [30000, "21ec5d1984a4fd4394b042aa96365085225d964727a45def245ceab326e28128", "005db8607994ff30",
+ "276951d89c1ed262bcac00df4fb9bf7af36991532744a2e287b0b758a56e15aa",
+ "dc070b76cc311cd82267f98936acbbbd3ec1c1ab25b55e2c885af6474e1e6841"],
+ [30049, "dc070b76cc311cd82267f98936acbbbd3ec1c1ab25b55e2c885af6474e1e6841", "005e2e215a8ca2e7",
+ "6248ba0157d0f0592dacfe2963337948fffb37f67e7451a6862c1321d894cebe",
+ "6fdecf719e2547f585a6ee807d8237db8e9489f63d3f259ab5236451eaded433"],
+ [30050, "6fdecf719e2547f585a6ee807d8237db8e9489f63d3f259ab5236451eaded433", "005e30899481055e",
+ "512d8f2bb0441fcfa1764c67e8dbed2afcbe9141de4bbebc5b51e0661dede550",
+ "cb1587a1c372642cbd9ce4c1ba2f433985d44c571a676a032bc1e8c1ad066e24"],
+ [30099, "cb1587a1c372642cbd9ce4c1ba2f433985d44c571a676a032bc1e8c1ad066e24", "005ea6aef136f88b",
+ "be0e7d6afa6edd483ccc304afa9bf0abaca5e0f037a4f05bf5550b9309d1d12c",
+ "78be18f20569a834d839dad48e0e51d6df6b6537575f0ad29898c7cf357f12cb"],
+ [59950, "78be18f20569a834d839dad48e0e51d6df6b6537575f0ad29898c7cf357f12cb", "02ebe0503bd7b1da",
+ "b85be51fce670aa437f28c02ea4fd7995fa8b6ac224e959b8dbfb5bdbc6f77ce",
+ "a68a620ba17e0cf2817bc4397cf4b85f5770983aa7b7931319a7f61bd6f905b1"],
+ [59999, "a68a620ba17e0cf2817bc4397cf4b85f5770983aa7b7931319a7f61bd6f905b1", "02edb6275bd221e3",
+ "ffe745a932c21c0704291bb416fe8bffec76621cd3434861885beab42cec1734",
+ "9e6667a151ac6f5186a05cb20877a2b3df02317046256a762cb8ec2d96aa34f0"]
+]
diff --git a/eth/backend.go b/eth/backend.go
index 3fd027137c7f..60d51294144b 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -255,7 +255,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co
return ethash.NewTester(nil, noverify)
case ethash.ModeShared:
log.Warn("Ethash used in shared mode")
- return ethash.NewShared()
+ return ethash.NewShared(chainConfig.ProgpowBlock)
default:
engine := ethash.New(ethash.Config{
CacheDir: stack.ResolvePath(config.CacheDir),
@@ -266,6 +266,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co
DatasetsInMem: config.DatasetsInMem,
DatasetsOnDisk: config.DatasetsOnDisk,
DatasetsLockMmap: config.DatasetsLockMmap,
+ ProgpowBlock: chainConfig.ProgpowBlock,
}, notify, noverify)
engine.SetThreads(-1) // Disable CPU mining
return engine
diff --git a/params/config.go b/params/config.go
index a69d513080a6..56ed2d4f0a7f 100644
--- a/params/config.go
+++ b/params/config.go
@@ -239,16 +239,16 @@ var (
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
- AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
+ AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
- AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
+ AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
- TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
+ TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int))
)
@@ -320,8 +320,9 @@ type ChainConfig struct {
IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul)
MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
- YoloV1Block *big.Int `json:"yoloV1Block,omitempty"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet)
- EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
+ YoloV1Block *big.Int `json:"yoloV1Block,omitempty"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet)
+ EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
+ ProgpowBlock *big.Int `json:"progpowBlock,omitempty"` // Progpow switch block (nil = not active, 0 = already activated)
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
@@ -358,7 +359,7 @@ func (c *ChainConfig) String() string {
default:
engine = "unknown"
}
- return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Engine: %v}",
+ return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Progpow: %v, Engine: %v}",
c.ChainID,
c.HomesteadBlock,
c.DAOForkBlock,
@@ -372,6 +373,7 @@ func (c *ChainConfig) String() string {
c.IstanbulBlock,
c.MuirGlacierBlock,
c.YoloV1Block,
+ c.ProgpowBlock,
engine,
)
}
@@ -477,6 +479,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{name: "istanbulBlock", block: c.IstanbulBlock},
{name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true},
{name: "yoloV1Block", block: c.YoloV1Block},
+ // We don't check progpowblock here, that one can be applied at any point
} {
if lastFork.name != "" {
// Next one must be higher number
diff --git a/tests/block_test_util.go b/tests/block_test_util.go
index be9cdb70cd6e..4c8b09837f5a 100644
--- a/tests/block_test_util.go
+++ b/tests/block_test_util.go
@@ -117,7 +117,7 @@ func (t *BlockTest) Run(snapshotter bool) error {
if t.json.SealEngine == "NoProof" {
engine = ethash.NewFaker()
} else {
- engine = ethash.NewShared()
+ engine = ethash.NewShared(nil)
}
cache := &core.CacheConfig{TrieCleanLimit: 0}
if snapshotter {