go-ethereum/consensus/ethash/consensus_test.go
Martin Holst Swende efe6dd2904
consensus/ethash: implement faster difficulty calculators (#21976)
This PR adds re-written difficulty calculators, which are based on uint256. It also adds a fuzzer + oss-fuzz integration for the new fuzzer. It does differential fuzzing between the new and old calculators.

Note: this PR does not actually enable the new calculators.
2020-12-11 11:06:44 +01:00

189 lines
5.3 KiB
Go

// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ethash
import (
"encoding/binary"
"encoding/json"
"math/big"
"math/rand"
"os"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)
type diffTest struct {
ParentTimestamp uint64
ParentDifficulty *big.Int
CurrentTimestamp uint64
CurrentBlocknumber *big.Int
CurrentDifficulty *big.Int
}
func (d *diffTest) UnmarshalJSON(b []byte) (err error) {
var ext struct {
ParentTimestamp string
ParentDifficulty string
CurrentTimestamp string
CurrentBlocknumber string
CurrentDifficulty string
}
if err := json.Unmarshal(b, &ext); err != nil {
return err
}
d.ParentTimestamp = math.MustParseUint64(ext.ParentTimestamp)
d.ParentDifficulty = math.MustParseBig256(ext.ParentDifficulty)
d.CurrentTimestamp = math.MustParseUint64(ext.CurrentTimestamp)
d.CurrentBlocknumber = math.MustParseBig256(ext.CurrentBlocknumber)
d.CurrentDifficulty = math.MustParseBig256(ext.CurrentDifficulty)
return nil
}
func TestCalcDifficulty(t *testing.T) {
file, err := os.Open(filepath.Join("..", "..", "tests", "testdata", "BasicTests", "difficulty.json"))
if err != nil {
t.Skip(err)
}
defer file.Close()
tests := make(map[string]diffTest)
err = json.NewDecoder(file).Decode(&tests)
if err != nil {
t.Fatal(err)
}
config := &params.ChainConfig{HomesteadBlock: big.NewInt(1150000)}
for name, test := range tests {
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{
Number: number,
Time: test.ParentTimestamp,
Difficulty: test.ParentDifficulty,
})
if diff.Cmp(test.CurrentDifficulty) != 0 {
t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff)
}
}
}
func randSlice(min, max uint32) []byte {
var b = make([]byte, 4)
rand.Read(b)
a := binary.LittleEndian.Uint32(b)
size := min + a%(max-min)
out := make([]byte, size)
rand.Read(out)
return out
}
func TestDifficultyCalculators(t *testing.T) {
rand.Seed(2)
for i := 0; i < 5000; i++ {
// 1 to 300 seconds diff
var timeDelta = uint64(1 + rand.Uint32()%3000)
diffBig := big.NewInt(0).SetBytes(randSlice(2, 10))
if diffBig.Cmp(params.MinimumDifficulty) < 0 {
diffBig.Set(params.MinimumDifficulty)
}
//rand.Read(difficulty)
header := &types.Header{
Difficulty: diffBig,
Number: new(big.Int).SetUint64(rand.Uint64() % 50_000_000),
Time: rand.Uint64() - timeDelta,
}
if rand.Uint32()&1 == 0 {
header.UncleHash = types.EmptyUncleHash
}
bombDelay := new(big.Int).SetUint64(rand.Uint64() % 50_000_000)
for i, pair := range []struct {
bigFn func(time uint64, parent *types.Header) *big.Int
u256Fn func(time uint64, parent *types.Header) *big.Int
}{
{FrontierDifficultyCalulator, CalcDifficultyFrontierU256},
{HomesteadDifficultyCalulator, CalcDifficultyHomesteadU256},
{DynamicDifficultyCalculator(bombDelay), MakeDifficultyCalculatorU256(bombDelay)},
} {
time := header.Time + timeDelta
want := pair.bigFn(time, header)
have := pair.u256Fn(time, header)
if want.BitLen() > 256 {
continue
}
if want.Cmp(have) != 0 {
t.Fatalf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have,
header.Number, header.Time, time, bombDelay)
}
}
}
}
func BenchmarkDifficultyCalculator(b *testing.B) {
x1 := makeDifficultyCalculator(big.NewInt(1000000))
x2 := MakeDifficultyCalculatorU256(big.NewInt(1000000))
h := &types.Header{
ParentHash: common.Hash{},
UncleHash: types.EmptyUncleHash,
Difficulty: big.NewInt(0xffffff),
Number: big.NewInt(500000),
Time: 1000000,
}
b.Run("big-frontier", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
calcDifficultyFrontier(1000014, h)
}
})
b.Run("u256-frontier", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
CalcDifficultyFrontierU256(1000014, h)
}
})
b.Run("big-homestead", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
calcDifficultyHomestead(1000014, h)
}
})
b.Run("u256-homestead", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
CalcDifficultyHomesteadU256(1000014, h)
}
})
b.Run("big-generic", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
x1(1000014, h)
}
})
b.Run("u256-generic", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
x2(1000014, h)
}
})
}