github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/core/chain_pow_test.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core
    18  
    19  import (
    20  	"math/big"
    21  	"runtime"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/ethereumproject/go-ethereum/common"
    26  	"github.com/ethereumproject/go-ethereum/core/types"
    27  	"github.com/ethereumproject/go-ethereum/ethdb"
    28  	"github.com/ethereumproject/go-ethereum/pow"
    29  )
    30  
    31  // failPow is a non-validating proof of work implementation, that returns true
    32  // from Verify for all but one block.
    33  type failPow struct {
    34  	failing uint64
    35  }
    36  
    37  func (pow failPow) Search(pow.Block, <-chan struct{}, int) (uint64, []byte) {
    38  	return 0, nil
    39  }
    40  func (pow failPow) Verify(block pow.Block) bool { return block.NumberU64() != pow.failing }
    41  func (pow failPow) GetHashrate() int64          { return 0 }
    42  func (pow failPow) Turbo(bool)                  {}
    43  
    44  // delayedPow is a non-validating proof of work implementation, that returns true
    45  // from Verify for all blocks, but delays them the configured amount of time.
    46  type delayedPow struct {
    47  	delay time.Duration
    48  }
    49  
    50  func (pow delayedPow) Search(pow.Block, <-chan struct{}, int) (uint64, []byte) {
    51  	return 0, nil
    52  }
    53  func (pow delayedPow) Verify(block pow.Block) bool { time.Sleep(pow.delay); return true }
    54  func (pow delayedPow) GetHashrate() int64          { return 0 }
    55  func (pow delayedPow) Turbo(bool)                  {}
    56  
    57  // Tests that simple POW verification works, for both good and bad blocks.
    58  func TestPowVerification(t *testing.T) {
    59  	// Create a simple chain to verify
    60  	var (
    61  		testdb, _ = ethdb.NewMemDatabase()
    62  		genesis   = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
    63  		blocks, _ = GenerateChain(testChainConfig(), genesis, testdb, 8, nil)
    64  	)
    65  	headers := make([]*types.Header, len(blocks))
    66  	for i, block := range blocks {
    67  		headers[i] = block.Header()
    68  	}
    69  	// Run the POW checker for blocks one-by-one, checking for both valid and invalid nonces
    70  	for i := 0; i < len(blocks); i++ {
    71  		for j, full := range []bool{true, false} {
    72  			for k, valid := range []bool{true, false} {
    73  				var results <-chan nonceCheckResult
    74  
    75  				switch {
    76  				case full && valid:
    77  					_, results = verifyNoncesFromBlocks(FakePow{}, []*types.Block{blocks[i]})
    78  				case full && !valid:
    79  					_, results = verifyNoncesFromBlocks(failPow{blocks[i].NumberU64()}, []*types.Block{blocks[i]})
    80  				case !full && valid:
    81  					_, results = verifyNoncesFromHeaders(FakePow{}, []*types.Header{headers[i]})
    82  				case !full && !valid:
    83  					_, results = verifyNoncesFromHeaders(failPow{headers[i].Number.Uint64()}, []*types.Header{headers[i]})
    84  				}
    85  				// Wait for the verification result
    86  				select {
    87  				case result := <-results:
    88  					if result.index != 0 {
    89  						t.Errorf("test %d.%d.%d: invalid index: have %d, want 0", i, j, k, result.index)
    90  					}
    91  					if result.valid != valid {
    92  						t.Errorf("test %d.%d.%d: validity mismatch: have %v, want %v", i, j, k, result.valid, valid)
    93  					}
    94  				case <-time.After(time.Second):
    95  					t.Fatalf("test %d.%d.%d: verification timeout", i, j, k)
    96  				}
    97  				// Make sure no more data is returned
    98  				select {
    99  				case result := <-results:
   100  					t.Fatalf("test %d.%d.%d: unexpected result returned: %v", i, j, k, result)
   101  				case <-time.After(25 * time.Millisecond):
   102  				}
   103  			}
   104  		}
   105  	}
   106  }
   107  
   108  // Tests that concurrent POW verification works, for both good and bad blocks.
   109  func TestPowConcurrentVerification2(t *testing.T)  { testPowConcurrentVerification(t, 2) }
   110  func TestPowConcurrentVerification8(t *testing.T)  { testPowConcurrentVerification(t, 8) }
   111  func TestPowConcurrentVerification32(t *testing.T) { testPowConcurrentVerification(t, 32) }
   112  
   113  func testPowConcurrentVerification(t *testing.T, threads int) {
   114  	// Create a simple chain to verify
   115  	var (
   116  		testdb, _ = ethdb.NewMemDatabase()
   117  		genesis   = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
   118  		blocks, _ = GenerateChain(testChainConfig(), genesis, testdb, 8, nil)
   119  	)
   120  	headers := make([]*types.Header, len(blocks))
   121  	for i, block := range blocks {
   122  		headers[i] = block.Header()
   123  	}
   124  	// Set the number of threads to verify on
   125  	old := runtime.GOMAXPROCS(threads)
   126  	defer runtime.GOMAXPROCS(old)
   127  
   128  	// Run the POW checker for the entire block chain at once both for a valid and
   129  	// also an invalid chain (enough if one is invalid, last but one (arbitrary)).
   130  	for i, full := range []bool{true, false} {
   131  		for j, valid := range []bool{true, false} {
   132  			var results <-chan nonceCheckResult
   133  
   134  			switch {
   135  			case full && valid:
   136  				_, results = verifyNoncesFromBlocks(FakePow{}, blocks)
   137  			case full && !valid:
   138  				_, results = verifyNoncesFromBlocks(failPow{uint64(len(blocks) - 1)}, blocks)
   139  			case !full && valid:
   140  				_, results = verifyNoncesFromHeaders(FakePow{}, headers)
   141  			case !full && !valid:
   142  				_, results = verifyNoncesFromHeaders(failPow{uint64(len(headers) - 1)}, headers)
   143  			}
   144  			// Wait for all the verification results
   145  			checks := make(map[int]bool)
   146  			for k := 0; k < len(blocks); k++ {
   147  				select {
   148  				case result := <-results:
   149  					if _, ok := checks[result.index]; ok {
   150  						t.Fatalf("test %d.%d.%d: duplicate results for %d", i, j, k, result.index)
   151  					}
   152  					if result.index < 0 || result.index >= len(blocks) {
   153  						t.Fatalf("test %d.%d.%d: result %d out of bounds [%d, %d]", i, j, k, result.index, 0, len(blocks)-1)
   154  					}
   155  					checks[result.index] = result.valid
   156  
   157  				case <-time.After(time.Second):
   158  					t.Fatalf("test %d.%d.%d: verification timeout", i, j, k)
   159  				}
   160  			}
   161  			// Check nonce check validity
   162  			for k := 0; k < len(blocks); k++ {
   163  				want := valid || (k != len(blocks)-2) // We chose the last but one nonce in the chain to fail
   164  				if checks[k] != want {
   165  					t.Errorf("test %d.%d.%d: validity mismatch: have %v, want %v", i, j, k, checks[k], want)
   166  				}
   167  			}
   168  			// Make sure no more data is returned
   169  			select {
   170  			case result := <-results:
   171  				t.Fatalf("test %d.%d: unexpected result returned: %v", i, j, result)
   172  			case <-time.After(25 * time.Millisecond):
   173  			}
   174  		}
   175  	}
   176  }
   177  
   178  // Tests that aborting a POW validation indeed prevents further checks from being
   179  // run, as well as checks that no left-over goroutines are leaked.
   180  func TestPowConcurrentAbortion2(t *testing.T)  { testPowConcurrentAbortion(t, 2) }
   181  func TestPowConcurrentAbortion8(t *testing.T)  { testPowConcurrentAbortion(t, 8) }
   182  func TestPowConcurrentAbortion32(t *testing.T) { testPowConcurrentAbortion(t, 32) }
   183  
   184  func testPowConcurrentAbortion(t *testing.T, threads int) {
   185  	// Create a simple chain to verify
   186  	var (
   187  		testdb, _ = ethdb.NewMemDatabase()
   188  		genesis   = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
   189  		blocks, _ = GenerateChain(testChainConfig(), genesis, testdb, 1024, nil)
   190  	)
   191  	headers := make([]*types.Header, len(blocks))
   192  	for i, block := range blocks {
   193  		headers[i] = block.Header()
   194  	}
   195  	// Set the number of threads to verify on
   196  	old := runtime.GOMAXPROCS(threads)
   197  	defer runtime.GOMAXPROCS(old)
   198  
   199  	// Run the POW checker for the entire block chain at once
   200  	for i, full := range []bool{true, false} {
   201  		var abort chan<- struct{}
   202  		var results <-chan nonceCheckResult
   203  
   204  		// Start the verifications and immediately abort
   205  		if full {
   206  			abort, results = verifyNoncesFromBlocks(delayedPow{time.Millisecond}, blocks)
   207  		} else {
   208  			abort, results = verifyNoncesFromHeaders(delayedPow{time.Millisecond}, headers)
   209  		}
   210  		close(abort)
   211  
   212  		// Deplete the results channel
   213  		verified := make(map[int]struct{})
   214  		for depleted := false; !depleted; {
   215  			select {
   216  			case result := <-results:
   217  				verified[result.index] = struct{}{}
   218  			case <-time.After(50 * time.Millisecond):
   219  				depleted = true
   220  			}
   221  		}
   222  		// Check that abortion was honored by not processing too many POWs
   223  		if len(verified) > 2*threads {
   224  			t.Errorf("test %d: verification count too large: have %d, want below %d", i, len(verified), 2*threads)
   225  		}
   226  		// Check that there are no gaps in the results
   227  		for j := 0; j < len(verified); j++ {
   228  			if _, ok := verified[j]; !ok {
   229  				t.Errorf("test %d.%d: gap found in verification results", i, j)
   230  			}
   231  		}
   232  	}
   233  }