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 {