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