github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/consensus/ethash/sealer_test.go (about)

     1  // Copyright 2018 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 ethash
    18  
    19  import (
    20  	"encoding/json"
    21  	"io/ioutil"
    22  	"math/big"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"strconv"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/common"
    30  	"github.com/ethereum/go-ethereum/core/types"
    31  	"github.com/ethereum/go-ethereum/internal/testlog"
    32  	"github.com/ethereum/go-ethereum/log"
    33  )
    34  
    35  // Tests whether remote HTTP servers are correctly notified of new work.
    36  func TestRemoteNotify(t *testing.T) {
    37  	// Start a simple web server to capture notifications.
    38  	sink := make(chan [3]string)
    39  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    40  		blob, err := ioutil.ReadAll(req.Body)
    41  		if err != nil {
    42  			t.Errorf("failed to read miner notification: %v", err)
    43  		}
    44  		var work [3]string
    45  		if err := json.Unmarshal(blob, &work); err != nil {
    46  			t.Errorf("failed to unmarshal miner notification: %v", err)
    47  		}
    48  		sink <- work
    49  	}))
    50  	defer server.Close()
    51  
    52  	// Create the custom ethash engine.
    53  	ethash := NewTester([]string{server.URL}, false)
    54  	defer ethash.Close()
    55  
    56  	// Stream a work task and ensure the notification bubbles out.
    57  	header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
    58  	block := types.NewBlockWithHeader(header)
    59  
    60  	ethash.Seal(nil, block, nil, nil)
    61  	select {
    62  	case work := <-sink:
    63  		if want := ethash.SealHash(header).Hex(); work[0] != want {
    64  			t.Errorf("work packet hash mismatch: have %s, want %s", work[0], want)
    65  		}
    66  		if want := common.BytesToHash(SeedHash(header.Number.Uint64())).Hex(); work[1] != want {
    67  			t.Errorf("work packet seed mismatch: have %s, want %s", work[1], want)
    68  		}
    69  		target := new(big.Int).Div(new(big.Int).Lsh(big.NewInt(1), 256), header.Difficulty)
    70  		if want := common.BytesToHash(target.Bytes()).Hex(); work[2] != want {
    71  			t.Errorf("work packet target mismatch: have %s, want %s", work[2], want)
    72  		}
    73  	case <-time.After(3 * time.Second):
    74  		t.Fatalf("notification timed out")
    75  	}
    76  }
    77  
    78  // Tests whether remote HTTP servers are correctly notified of new work. (Full pending block body / --miner.notify.full)
    79  func TestRemoteNotifyFull(t *testing.T) {
    80  	// Start a simple web server to capture notifications.
    81  	sink := make(chan map[string]interface{})
    82  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    83  		blob, err := ioutil.ReadAll(req.Body)
    84  		if err != nil {
    85  			t.Errorf("failed to read miner notification: %v", err)
    86  		}
    87  		var work map[string]interface{}
    88  		if err := json.Unmarshal(blob, &work); err != nil {
    89  			t.Errorf("failed to unmarshal miner notification: %v", err)
    90  		}
    91  		sink <- work
    92  	}))
    93  	defer server.Close()
    94  
    95  	// Create the custom ethash engine.
    96  	config := Config{
    97  		PowMode:    ModeTest,
    98  		NotifyFull: true,
    99  		Log:        testlog.Logger(t, log.LvlWarn),
   100  	}
   101  	ethash := New(config, []string{server.URL}, false)
   102  	defer ethash.Close()
   103  
   104  	// Stream a work task and ensure the notification bubbles out.
   105  	header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
   106  	block := types.NewBlockWithHeader(header)
   107  
   108  	ethash.Seal(nil, block, nil, nil)
   109  	select {
   110  	case work := <-sink:
   111  		if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want {
   112  			t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want)
   113  		}
   114  		if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want {
   115  			t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want)
   116  		}
   117  	case <-time.After(3 * time.Second):
   118  		t.Fatalf("notification timed out")
   119  	}
   120  }
   121  
   122  // Tests that pushing work packages fast to the miner doesn't cause any data race
   123  // issues in the notifications.
   124  func TestRemoteMultiNotify(t *testing.T) {
   125  	// Start a simple web server to capture notifications.
   126  	sink := make(chan [3]string, 64)
   127  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   128  		blob, err := ioutil.ReadAll(req.Body)
   129  		if err != nil {
   130  			t.Errorf("failed to read miner notification: %v", err)
   131  		}
   132  		var work [3]string
   133  		if err := json.Unmarshal(blob, &work); err != nil {
   134  			t.Errorf("failed to unmarshal miner notification: %v", err)
   135  		}
   136  		sink <- work
   137  	}))
   138  	defer server.Close()
   139  
   140  	// Create the custom ethash engine.
   141  	ethash := NewTester([]string{server.URL}, false)
   142  	ethash.config.Log = testlog.Logger(t, log.LvlWarn)
   143  	defer ethash.Close()
   144  
   145  	// Provide a results reader.
   146  	// Otherwise the unread results will be logged asynchronously
   147  	// and this can happen after the test is finished, causing a panic.
   148  	results := make(chan *types.Block, cap(sink))
   149  
   150  	// Stream a lot of work task and ensure all the notifications bubble out.
   151  	for i := 0; i < cap(sink); i++ {
   152  		header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
   153  		block := types.NewBlockWithHeader(header)
   154  		ethash.Seal(nil, block, results, nil)
   155  	}
   156  
   157  	for i := 0; i < cap(sink); i++ {
   158  		select {
   159  		case <-sink:
   160  			<-results
   161  		case <-time.After(10 * time.Second):
   162  			t.Fatalf("notification %d timed out", i)
   163  		}
   164  	}
   165  }
   166  
   167  // Tests that pushing work packages fast to the miner doesn't cause any data race
   168  // issues in the notifications. Full pending block body / --miner.notify.full)
   169  func TestRemoteMultiNotifyFull(t *testing.T) {
   170  	// Start a simple web server to capture notifications.
   171  	sink := make(chan map[string]interface{}, 64)
   172  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   173  		blob, err := ioutil.ReadAll(req.Body)
   174  		if err != nil {
   175  			t.Errorf("failed to read miner notification: %v", err)
   176  		}
   177  		var work map[string]interface{}
   178  		if err := json.Unmarshal(blob, &work); err != nil {
   179  			t.Errorf("failed to unmarshal miner notification: %v", err)
   180  		}
   181  		sink <- work
   182  	}))
   183  	defer server.Close()
   184  
   185  	// Create the custom ethash engine.
   186  	config := Config{
   187  		PowMode:    ModeTest,
   188  		NotifyFull: true,
   189  		Log:        testlog.Logger(t, log.LvlWarn),
   190  	}
   191  	ethash := New(config, []string{server.URL}, false)
   192  	defer ethash.Close()
   193  
   194  	// Provide a results reader.
   195  	// Otherwise the unread results will be logged asynchronously
   196  	// and this can happen after the test is finished, causing a panic.
   197  	results := make(chan *types.Block, cap(sink))
   198  
   199  	// Stream a lot of work task and ensure all the notifications bubble out.
   200  	for i := 0; i < cap(sink); i++ {
   201  		header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
   202  		block := types.NewBlockWithHeader(header)
   203  		ethash.Seal(nil, block, results, nil)
   204  	}
   205  
   206  	for i := 0; i < cap(sink); i++ {
   207  		select {
   208  		case <-sink:
   209  			<-results
   210  		case <-time.After(10 * time.Second):
   211  			t.Fatalf("notification %d timed out", i)
   212  		}
   213  	}
   214  }
   215  
   216  // Tests whether stale solutions are correctly processed.
   217  func TestStaleSubmission(t *testing.T) {
   218  	ethash := NewTester(nil, true)
   219  	defer ethash.Close()
   220  	api := &API{ethash}
   221  
   222  	fakeNonce, fakeDigest := types.BlockNonce{0x01, 0x02, 0x03}, common.HexToHash("deadbeef")
   223  
   224  	testcases := []struct {
   225  		headers     []*types.Header
   226  		submitIndex int
   227  		submitRes   bool
   228  	}{
   229  		// Case1: submit solution for the latest mining package
   230  		{
   231  			[]*types.Header{
   232  				{ParentHash: common.BytesToHash([]byte{0xa}), Number: big.NewInt(1), Difficulty: big.NewInt(100000000)},
   233  			},
   234  			0,
   235  			true,
   236  		},
   237  		// Case2: submit solution for the previous package but have same parent.
   238  		{
   239  			[]*types.Header{
   240  				{ParentHash: common.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000000)},
   241  				{ParentHash: common.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000001)},
   242  			},
   243  			0,
   244  			true,
   245  		},
   246  		// Case3: submit stale but acceptable solution
   247  		{
   248  			[]*types.Header{
   249  				{ParentHash: common.BytesToHash([]byte{0xc}), Number: big.NewInt(3), Difficulty: big.NewInt(100000000)},
   250  				{ParentHash: common.BytesToHash([]byte{0xd}), Number: big.NewInt(9), Difficulty: big.NewInt(100000000)},
   251  			},
   252  			0,
   253  			true,
   254  		},
   255  		// Case4: submit very old solution
   256  		{
   257  			[]*types.Header{
   258  				{ParentHash: common.BytesToHash([]byte{0xe}), Number: big.NewInt(10), Difficulty: big.NewInt(100000000)},
   259  				{ParentHash: common.BytesToHash([]byte{0xf}), Number: big.NewInt(17), Difficulty: big.NewInt(100000000)},
   260  			},
   261  			0,
   262  			false,
   263  		},
   264  	}
   265  	results := make(chan *types.Block, 16)
   266  
   267  	for id, c := range testcases {
   268  		for _, h := range c.headers {
   269  			ethash.Seal(nil, types.NewBlockWithHeader(h), results, nil)
   270  		}
   271  		if res := api.SubmitWork(fakeNonce, ethash.SealHash(c.headers[c.submitIndex]), fakeDigest); res != c.submitRes {
   272  			t.Errorf("case %d submit result mismatch, want %t, get %t", id+1, c.submitRes, res)
   273  		}
   274  		if !c.submitRes {
   275  			continue
   276  		}
   277  		select {
   278  		case res := <-results:
   279  			if res.Header().Nonce != fakeNonce {
   280  				t.Errorf("case %d block nonce mismatch, want %x, get %x", id+1, fakeNonce, res.Header().Nonce)
   281  			}
   282  			if res.Header().MixDigest != fakeDigest {
   283  				t.Errorf("case %d block digest mismatch, want %x, get %x", id+1, fakeDigest, res.Header().MixDigest)
   284  			}
   285  			if res.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() {
   286  				t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Header().Difficulty)
   287  			}
   288  			if res.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() {
   289  				t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Header().Number.Uint64())
   290  			}
   291  			if res.Header().ParentHash != c.headers[c.submitIndex].ParentHash {
   292  				t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Header().ParentHash.Hex())
   293  			}
   294  		case <-time.NewTimer(time.Second).C:
   295  			t.Errorf("case %d fetch ethash result timeout", id+1)
   296  		}
   297  	}
   298  }