mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 02:42:27 +00:00
core: refactor block header validation tests to use test tables
This commit is contained in:
parent
2a0e1bb32b
commit
67e279e815
1 changed files with 165 additions and 120 deletions
|
|
@ -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,128 +67,178 @@ 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,
|
||||
},
|
||||
}
|
||||
// Set the number of threads to verify on
|
||||
old := runtime.GOMAXPROCS(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
|
||||
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)
|
||||
|
||||
if valid {
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
|
||||
// Set the number of threads to verify on
|
||||
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).
|
||||
var results <-chan error
|
||||
|
||||
var chain *BlockChain
|
||||
var err error
|
||||
if tc.valid {
|
||||
chain, err = NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
|
||||
} else {
|
||||
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()
|
||||
} else {
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil)
|
||||
_, 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++ {
|
||||
|
||||
// Wait for all the verification results
|
||||
checks := make(map[int]error)
|
||||
for i := range headers {
|
||||
select {
|
||||
case result := <-results:
|
||||
checks[i] = result
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("Verification timed out after receiving %d results", i)
|
||||
}
|
||||
}
|
||||
// Check nonce check validity
|
||||
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
|
||||
// workers. We don't care about those in this test, just that the correct block
|
||||
// errors out.
|
||||
break
|
||||
}
|
||||
}
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
checks[j] = result
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d.%d: verification timeout", i, j)
|
||||
t.Fatalf("Received unexpected result: %v", result)
|
||||
case <-time.After(25 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
if !want {
|
||||
// A few blocks after the first error may pass verification due to concurrent
|
||||
// workers. We don't care about those in this test, just that the correct block
|
||||
// errors out.
|
||||
break
|
||||
}
|
||||
}
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
t.Fatalf("test %d: unexpected result returned: %v", i, 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,
|
||||
},
|
||||
}
|
||||
// Set the number of threads to verify on
|
||||
old := runtime.GOMAXPROCS(threads)
|
||||
defer runtime.GOMAXPROCS(old)
|
||||
|
||||
// Start the verifications and immediately abort
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil)
|
||||
defer chain.Stop()
|
||||
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)
|
||||
|
||||
abort, results := chain.engine.VerifyHeaders(chain, headers, seals)
|
||||
close(abort)
|
||||
// Set the number of threads to verify on
|
||||
old := runtime.GOMAXPROCS(tc.threads)
|
||||
defer runtime.GOMAXPROCS(old)
|
||||
|
||||
// Deplete the results channel
|
||||
verified := 0
|
||||
for depleted := false; !depleted; {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("header %d: validation failed: %v", verified, result)
|
||||
// Start the verifications and immediately abort
|
||||
chain, _ := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil)
|
||||
defer chain.Stop()
|
||||
|
||||
abort, results := chain.engine.VerifyHeaders(chain, headers, seals)
|
||||
close(abort)
|
||||
|
||||
// Deplete the results channel
|
||||
verified := 0
|
||||
for depleted := false; !depleted; {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("Header %d validation failed: %v", verified, result)
|
||||
}
|
||||
verified++
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
depleted = true
|
||||
}
|
||||
}
|
||||
verified++
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
depleted = true
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
// Check that abortion was honored by not processing too many POWs
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue