core: refactor block header validation tests to use test tables

This commit is contained in:
Matthew Halpern 2019-02-14 08:53:16 -08:00
parent 2a0e1bb32b
commit 67e279e815

View file

@ -28,34 +28,29 @@ import (
"github.com/ethereum/go-ethereum/params"
)
const height = 8
// Tests that simple header verification works, for both good and bad blocks.
func TestHeaderVerification(t *testing.T) {
// Create a simple chain to verify
var (
testdb = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig}
genesis = gspec.MustCommit(testdb)
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
headers[i] = block.Header()
}
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
db, blocks, _, _ := setupDatabaseAndBlocks(t)
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces.
chain, _ := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
defer chain.Stop()
for i := 0; i < len(blocks); i++ {
for i, block := range blocks {
for j, valid := range []bool{true, false} {
header := block.Header()
var results <-chan error
var engine *ethash.Ethash
if valid {
engine := ethash.NewFaker()
_, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true})
engine = ethash.NewFaker()
} else {
engine := ethash.NewFakeFailer(headers[i].Number.Uint64())
_, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true})
engine = ethash.NewFakeFailer(header.Number.Uint64())
}
_, results = engine.VerifyHeaders(chain, []*types.Header{header}, []bool{true})
// Wait for the verification result
select {
case result := <-results:
@ -72,64 +67,90 @@ func TestHeaderVerification(t *testing.T) {
case <-time.After(25 * time.Millisecond):
}
}
chain.InsertChain(blocks[i : i+1])
chain.InsertChain([]*types.Block{block})
}
}
// Tests that concurrent header verification works, for both good and bad blocks.
func TestHeaderConcurrentVerification2(t *testing.T) { testHeaderConcurrentVerification(t, 2) }
func TestHeaderConcurrentVerification8(t *testing.T) { testHeaderConcurrentVerification(t, 8) }
func TestHeaderConcurrentVerification32(t *testing.T) { testHeaderConcurrentVerification(t, 32) }
func testHeaderConcurrentVerification(t *testing.T, threads int) {
// Create a simple chain to verify
var (
testdb = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig}
genesis = gspec.MustCommit(testdb)
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
)
headers := make([]*types.Header, len(blocks))
seals := make([]bool, len(blocks))
for i, block := range blocks {
headers[i] = block.Header()
seals[i] = true
func TestConcurrentHeaderVerification(t *testing.T) {
tests := []struct {
name string
threads int
valid bool
}{
{
"2ThreadsSucceeds",
2,
true,
},
{
"2ThreadsFails",
2,
false,
},
{
"8ThreadsSucceeds",
8,
true,
},
{
"8ThreadsFails",
8,
false,
},
{
"32ThreadsSucceeds",
32,
true,
},
{
"32ThreadsFails",
32,
false,
},
}
for _, tc := range tests {
// Tests cannot be run in parallel due to modifying runtime.GOMAXPROCS.
t.Run(tc.name, func(t *testing.T) {
db, _, headers, seals := setupDatabaseAndBlocks(t)
// Set the number of threads to verify on
old := runtime.GOMAXPROCS(threads)
old := runtime.GOMAXPROCS(tc.threads)
defer runtime.GOMAXPROCS(old)
// Run the header checker for the entire block chain at once both for a valid and
// also an invalid chain (enough if one arbitrary block is invalid).
for i, valid := range []bool{true, false} {
var results <-chan error
if valid {
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
_, results = chain.engine.VerifyHeaders(chain, headers, seals)
chain.Stop()
var chain *BlockChain
var err error
if tc.valid {
chain, err = NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
} else {
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil)
chain, err = NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(height-1)), vm.Config{}, nil)
}
if err != nil {
t.Fatalf("Error creating blockchain: %v", err)
}
_, results = chain.engine.VerifyHeaders(chain, headers, seals)
chain.Stop()
}
// Wait for all the verification results
checks := make(map[int]error)
for j := 0; j < len(blocks); j++ {
for i := range headers {
select {
case result := <-results:
checks[j] = result
checks[i] = result
case <-time.After(time.Second):
t.Fatalf("test %d.%d: verification timeout", i, j)
t.Fatalf("Verification timed out after receiving %d results", i)
}
}
// Check nonce check validity
for j := 0; j < len(blocks); j++ {
want := valid || (j < len(blocks)-2) // We chose the last-but-one nonce in the chain to fail
if (checks[j] == nil) != want {
t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, checks[j], want)
for i := range headers {
want := tc.valid || (i < len(headers)-2) // We chose the last-but-one nonce in the chain to fail
if (checks[i] == nil) != want {
t.Errorf("Validity mismatch for result %d: got %v, want %v", i, checks[i], want)
}
if !want {
// A few blocks after the first error may pass verification due to concurrent
@ -141,39 +162,45 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
// Make sure no more data is returned
select {
case result := <-results:
t.Fatalf("test %d: unexpected result returned: %v", i, result)
t.Fatalf("Received unexpected result: %v", result)
case <-time.After(25 * time.Millisecond):
}
})
}
}
// Tests that aborting a header validation indeed prevents further checks from being
// run, as well as checks that no left-over goroutines are leaked.
func TestHeaderConcurrentAbortion2(t *testing.T) { testHeaderConcurrentAbortion(t, 2) }
func TestHeaderConcurrentAbortion8(t *testing.T) { testHeaderConcurrentAbortion(t, 8) }
func TestHeaderConcurrentAbortion32(t *testing.T) { testHeaderConcurrentAbortion(t, 32) }
func testHeaderConcurrentAbortion(t *testing.T, threads int) {
// Create a simple chain to verify
var (
testdb = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig}
genesis = gspec.MustCommit(testdb)
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil)
)
headers := make([]*types.Header, len(blocks))
seals := make([]bool, len(blocks))
for i, block := range blocks {
headers[i] = block.Header()
seals[i] = true
func TestConcurrentHeaderVerificationAbortion(t *testing.T) {
tests := []struct {
name string
threads int
}{
{
"2Threads",
2,
},
{
"8Threads",
8,
},
{
"32Threads",
32,
},
}
for _, tc := range tests {
// Tests cannot be run in parallel due to modifying runtime.GOMAXPROCS.
t.Run(tc.name, func(t *testing.T) {
db, _, headers, seals := setupDatabaseAndBlocks(t)
// Set the number of threads to verify on
old := runtime.GOMAXPROCS(threads)
old := runtime.GOMAXPROCS(tc.threads)
defer runtime.GOMAXPROCS(old)
// Start the verifications and immediately abort
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil)
chain, _ := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil)
defer chain.Stop()
abort, results := chain.engine.VerifyHeaders(chain, headers, seals)
@ -185,7 +212,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
select {
case result := <-results:
if result != nil {
t.Errorf("header %d: validation failed: %v", verified, result)
t.Errorf("Header %d validation failed: %v", verified, result)
}
verified++
case <-time.After(50 * time.Millisecond):
@ -193,7 +220,25 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
}
}
// Check that abortion was honored by not processing too many POWs
if verified > 2*threads {
t.Errorf("verification count too large: have %d, want below %d", verified, 2*threads)
if verified > 2*tc.threads {
t.Errorf("Verification count too large: got %d, want below %d", verified, 2*tc.threads)
}
})
}
}
func setupDatabaseAndBlocks(t *testing.T) (ethdb.Database, []*types.Block, []*types.Header, []bool) {
t.Helper()
db := ethdb.NewMemDatabase()
gspec := &Genesis{Config: params.TestChainConfig}
genesis := gspec.MustCommit(db)
blocks, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, height, nil)
headers := make([]*types.Header, 0)
seals := make([]bool, 0)
for _, block := range blocks {
headers = append(headers, block.Header())
seals = append(seals, true)
}
return db, blocks, headers, seals
}