@@ -18,6 +18,7 @@ package core
1818
1919import (
2020 "math/big"
21+ "runtime"
2122 "testing"
2223 "time"
2324
@@ -234,6 +235,118 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
234235 }
235236}
236237
238+ // Tests that concurrent header verification works, for both good and bad blocks.
239+ func TestHeaderConcurrentVerification2 (t * testing.T ) { testHeaderConcurrentVerification (t , 2 ) }
240+ func TestHeaderConcurrentVerification8 (t * testing.T ) { testHeaderConcurrentVerification (t , 8 ) }
241+ func TestHeaderConcurrentVerification32 (t * testing.T ) { testHeaderConcurrentVerification (t , 32 ) }
242+
243+ func testHeaderConcurrentVerification (t * testing.T , threads int ) {
244+ // Create a simple chain to verify
245+ var (
246+ gspec = & Genesis {Config : params .TestChainConfig }
247+ _ , blocks , _ = GenerateChainWithGenesis (gspec , ethash .NewFaker (), 8 , nil )
248+ )
249+ headers := make ([]* types.Header , len (blocks ))
250+ for i , block := range blocks {
251+ headers [i ] = block .Header ()
252+ }
253+ // Set the number of threads to verify on
254+ old := runtime .GOMAXPROCS (threads )
255+ defer runtime .GOMAXPROCS (old )
256+
257+ // Run the header checker for the entire block chain at once both for a valid and
258+ // also an invalid chain (enough if one arbitrary block is invalid).
259+ for i , valid := range []bool {true , false } {
260+ var results <- chan error
261+
262+ if valid {
263+ chain , _ := NewBlockChain (rawdb .NewMemoryDatabase (), nil , gspec , nil , ethash .NewFaker (), vm.Config {}, nil , nil )
264+ _ , results = chain .engine .VerifyHeaders (chain , headers )
265+ chain .Stop ()
266+ } else {
267+ chain , _ := NewBlockChain (rawdb .NewMemoryDatabase (), nil , gspec , nil , ethash .NewFakeFailer (uint64 (len (headers )- 1 )), vm.Config {}, nil , nil )
268+ _ , results = chain .engine .VerifyHeaders (chain , headers )
269+ chain .Stop ()
270+ }
271+ // Wait for all the verification results
272+ checks := make (map [int ]error )
273+ for j := 0 ; j < len (blocks ); j ++ {
274+ select {
275+ case result := <- results :
276+ checks [j ] = result
277+
278+ case <- time .After (time .Second ):
279+ t .Fatalf ("test %d.%d: verification timeout" , i , j )
280+ }
281+ }
282+ // Check nonce check validity
283+ for j := 0 ; j < len (blocks ); j ++ {
284+ want := valid || (j < len (blocks )- 2 ) // We chose the last-but-one nonce in the chain to fail
285+ if (checks [j ] == nil ) != want {
286+ t .Errorf ("test %d.%d: validity mismatch: have %v, want %v" , i , j , checks [j ], want )
287+ }
288+ if ! want {
289+ // A few blocks after the first error may pass verification due to concurrent
290+ // workers. We don't care about those in this test, just that the correct block
291+ // errors out.
292+ break
293+ }
294+ }
295+ // Make sure no more data is returned
296+ select {
297+ case result := <- results :
298+ t .Fatalf ("test %d: unexpected result returned: %v" , i , result )
299+ case <- time .After (25 * time .Millisecond ):
300+ }
301+ }
302+ }
303+
304+ // Tests that aborting a header validation indeed prevents further checks from being
305+ // run, as well as checks that no left-over goroutines are leaked.
306+ func TestHeaderConcurrentAbortion2 (t * testing.T ) { testHeaderConcurrentAbortion (t , 2 ) }
307+ func TestHeaderConcurrentAbortion8 (t * testing.T ) { testHeaderConcurrentAbortion (t , 8 ) }
308+ func TestHeaderConcurrentAbortion32 (t * testing.T ) { testHeaderConcurrentAbortion (t , 32 ) }
309+
310+ func testHeaderConcurrentAbortion (t * testing.T , threads int ) {
311+ // Create a simple chain to verify
312+ var (
313+ gspec = & Genesis {Config : params .TestChainConfig }
314+ _ , blocks , _ = GenerateChainWithGenesis (gspec , ethash .NewFaker (), 1024 , nil )
315+ )
316+ headers := make ([]* types.Header , len (blocks ))
317+ for i , block := range blocks {
318+ headers [i ] = block .Header ()
319+ }
320+ // Set the number of threads to verify on
321+ old := runtime .GOMAXPROCS (threads )
322+ defer runtime .GOMAXPROCS (old )
323+
324+ // Start the verifications and immediately abort
325+ chain , _ := NewBlockChain (rawdb .NewMemoryDatabase (), nil , gspec , nil , ethash .NewFakeDelayer (time .Millisecond ), vm.Config {}, nil , nil )
326+ defer chain .Stop ()
327+
328+ abort , results := chain .engine .VerifyHeaders (chain , headers )
329+ close (abort )
330+
331+ // Deplete the results channel
332+ verified := 0
333+ for depleted := false ; ! depleted ; {
334+ select {
335+ case result := <- results :
336+ if result != nil {
337+ t .Errorf ("header %d: validation failed: %v" , verified , result )
338+ }
339+ verified ++
340+ case <- time .After (50 * time .Millisecond ):
341+ depleted = true
342+ }
343+ }
344+ // Check that abortion was honored by not processing too many POWs
345+ if verified > 2 * threads {
346+ t .Errorf ("verification count too large: have %d, want below %d" , verified , 2 * threads )
347+ }
348+ }
349+
237350func TestCalcGasLimit (t * testing.T ) {
238351 for i , tc := range []struct {
239352 pGasLimit uint64
0 commit comments