github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/backend/engine_test.go (about)

     1  // Copyright 2017 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 backend
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/ecdsa"
    22  	"math/big"
    23  	"reflect"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/electroneum/electroneum-sc/common"
    28  	"github.com/electroneum/electroneum-sc/consensus"
    29  	"github.com/electroneum/electroneum-sc/consensus/istanbul"
    30  	istanbulcommon "github.com/electroneum/electroneum-sc/consensus/istanbul/common"
    31  	"github.com/electroneum/electroneum-sc/consensus/istanbul/testutils"
    32  	"github.com/electroneum/electroneum-sc/core"
    33  	"github.com/electroneum/electroneum-sc/core/rawdb"
    34  	"github.com/electroneum/electroneum-sc/core/types"
    35  	"github.com/electroneum/electroneum-sc/core/vm"
    36  	"github.com/electroneum/electroneum-sc/crypto"
    37  )
    38  
    39  func newBlockchainFromConfig(genesis *core.Genesis, nodeKeys []*ecdsa.PrivateKey, cfg istanbul.Config) (*core.BlockChain, *Backend) {
    40  	memDB := rawdb.NewMemoryDatabase()
    41  
    42  	// Use the first key as private key
    43  	backend := New(cfg, nodeKeys[0], memDB)
    44  
    45  	genesis.MustCommit(memDB)
    46  
    47  	blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, backend, vm.Config{}, nil, nil)
    48  	if err != nil {
    49  		panic(err)
    50  	}
    51  
    52  	backend.Start(blockchain, blockchain.CurrentBlock, rawdb.HasBadBlock)
    53  
    54  	snap, err := backend.snapshot(blockchain, 0, common.Hash{}, nil)
    55  	if err != nil {
    56  		panic(err)
    57  	}
    58  	if snap == nil {
    59  		panic("failed to get snapshot")
    60  	}
    61  	proposerAddr := snap.ValSet.GetProposer().Address()
    62  
    63  	// find proposer key
    64  	for _, key := range nodeKeys {
    65  		addr := crypto.PubkeyToAddress(key.PublicKey)
    66  		if addr.String() == proposerAddr.String() {
    67  			backend.privateKey = key
    68  			backend.address = addr
    69  		}
    70  	}
    71  
    72  	return blockchain, backend
    73  }
    74  
    75  // in this test, we can set n to 1, and it means we can process Istanbul and commit a
    76  // block by one node. Otherwise, if n is larger than 1, we have to generate
    77  // other fake events to process Istanbul.
    78  func newBlockChain(n int) (*core.BlockChain, *Backend) {
    79  	genesis, nodeKeys := testutils.GenesisAndKeys(n)
    80  	config := copyConfig(istanbul.DefaultConfig)
    81  
    82  	return newBlockchainFromConfig(genesis, nodeKeys, config)
    83  }
    84  
    85  // copyConfig create a copy of istanbul.Config, so that changing it does not update the original
    86  func copyConfig(config *istanbul.Config) istanbul.Config {
    87  	cpy := *config
    88  	return cpy
    89  }
    90  
    91  func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header {
    92  	header := &types.Header{
    93  		ParentHash: parent.Hash(),
    94  		Number:     parent.Number().Add(parent.Number(), common.Big1),
    95  		GasLimit:   core.CalcGasLimit(parent.GasLimit(), parent.GasLimit()),
    96  		GasUsed:    0,
    97  		Time:       parent.Time() + config.BlockPeriod,
    98  		Difficulty: istanbulcommon.DefaultDifficulty,
    99  	}
   100  	return header
   101  }
   102  
   103  func makeBlock(chain *core.BlockChain, engine *Backend, parent *types.Block) *types.Block {
   104  	block := makeBlockWithoutSeal(chain, engine, parent, true)
   105  	stopCh := make(chan struct{})
   106  	resultCh := make(chan *types.Block, 10)
   107  	go engine.Seal(chain, block, resultCh, stopCh)
   108  	blk := <-resultCh
   109  	return blk
   110  }
   111  
   112  func makeBlockWithoutSeal(chain *core.BlockChain, engine *Backend, parent *types.Block, includeCoinbase bool) *types.Block {
   113  	header := makeHeader(parent, engine.config)
   114  	if includeCoinbase {
   115  		header.Coinbase = engine.Address()
   116  	} else {
   117  		header.Coinbase = common.Address{}
   118  	}
   119  
   120  	engine.Prepare(chain, header)
   121  	state, _ := chain.StateAt(parent.Root())
   122  	block, _ := engine.FinalizeAndAssemble(chain, header, state, nil, nil, nil)
   123  	return block
   124  }
   125  
   126  func TestIBFTPrepare(t *testing.T) {
   127  	chain, engine := newBlockChain(1)
   128  	defer engine.Stop()
   129  	header := makeHeader(chain.Genesis(), engine.config)
   130  	err := engine.Prepare(chain, header)
   131  	if err != nil {
   132  		t.Errorf("error mismatch: have %v, want nil", err)
   133  	}
   134  	header.ParentHash = common.StringToHash("1234567890")
   135  	err = engine.Prepare(chain, header)
   136  	if err != consensus.ErrUnknownAncestor {
   137  		t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor)
   138  	}
   139  }
   140  
   141  func TestQBFTPrepare(t *testing.T) {
   142  	chain, engine := newBlockChain(1)
   143  	defer engine.Stop()
   144  	header := makeHeader(chain.Genesis(), engine.config)
   145  	err := engine.Prepare(chain, header)
   146  	if err != nil {
   147  		t.Errorf("error mismatch: have %v, want nil", err)
   148  	}
   149  
   150  	header.ParentHash = common.StringToHash("1234567890")
   151  	err = engine.Prepare(chain, header)
   152  	if err != consensus.ErrUnknownAncestor {
   153  		t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor)
   154  	}
   155  }
   156  
   157  func TestSealStopChannel(t *testing.T) {
   158  	chain, engine := newBlockChain(1)
   159  	defer engine.Stop()
   160  	block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   161  	stop := make(chan struct{}, 1)
   162  	eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{})
   163  	eventLoop := func() {
   164  		ev := <-eventSub.Chan()
   165  		_, ok := ev.Data.(istanbul.RequestEvent)
   166  		if !ok {
   167  			t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data))
   168  		}
   169  		stop <- struct{}{}
   170  		eventSub.Unsubscribe()
   171  	}
   172  	resultCh := make(chan *types.Block, 10)
   173  	go func() {
   174  		err := engine.Seal(chain, block, resultCh, stop)
   175  		if err != nil {
   176  			t.Errorf("error mismatch: have %v, want nil", err)
   177  		}
   178  	}()
   179  	go eventLoop()
   180  
   181  	finalBlock := <-resultCh
   182  	if finalBlock != nil {
   183  		t.Errorf("block mismatch: have %v, want nil", finalBlock)
   184  	}
   185  }
   186  
   187  func TestSealCommittedOtherHash(t *testing.T) {
   188  	chain, engine := newBlockChain(1)
   189  	defer engine.Stop()
   190  	block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), false)
   191  	otherBlock := makeBlockWithoutSeal(chain, engine, block, false)
   192  	expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...)
   193  
   194  	eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{})
   195  	blockOutputChannel := make(chan *types.Block)
   196  	stopChannel := make(chan struct{})
   197  
   198  	go func() {
   199  		ev := <-eventSub.Chan()
   200  		if _, ok := ev.Data.(istanbul.RequestEvent); !ok {
   201  			t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data))
   202  		}
   203  		if err := engine.Commit(otherBlock, [][]byte{expectedCommittedSeal}, big.NewInt(0)); err != nil {
   204  			t.Error(err.Error())
   205  		}
   206  		eventSub.Unsubscribe()
   207  	}()
   208  
   209  	go func() {
   210  		if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil {
   211  			t.Error(err.Error())
   212  		}
   213  	}()
   214  
   215  	select {
   216  	case <-blockOutputChannel:
   217  		t.Error("Wrong block found!")
   218  	default:
   219  		//no block found, stop the sealing
   220  		close(stopChannel)
   221  	}
   222  
   223  	output := <-blockOutputChannel
   224  	if output != nil {
   225  		t.Error("Block not nil!")
   226  	}
   227  }
   228  
   229  func updateQBFTBlock(block *types.Block, addr common.Address) *types.Block {
   230  	header := block.Header()
   231  	header.Coinbase = addr
   232  	return block.WithSeal(header)
   233  }
   234  
   235  func TestSealCommitted(t *testing.T) {
   236  	chain, engine := newBlockChain(1)
   237  	defer engine.Stop()
   238  	block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   239  	expectedBlock := updateQBFTBlock(block, engine.Address())
   240  
   241  	resultCh := make(chan *types.Block, 10)
   242  	go func() {
   243  		err := engine.Seal(chain, block, resultCh, make(chan struct{}))
   244  
   245  		if err != nil {
   246  			t.Errorf("error mismatch: have %v, want %v", err, expectedBlock)
   247  		}
   248  	}()
   249  
   250  	finalBlock := <-resultCh
   251  	if finalBlock.Hash() != expectedBlock.Hash() {
   252  		t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash())
   253  	}
   254  }
   255  
   256  func TestVerifyHeader(t *testing.T) {
   257  	chain, engine := newBlockChain(1)
   258  	defer engine.Stop()
   259  
   260  	// istanbulcommon.ErrEmptyCommittedSeals case
   261  	block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   262  	block = updateQBFTBlock(block, engine.Address())
   263  	err := engine.VerifyHeader(chain, block.Header(), false)
   264  	if err != istanbulcommon.ErrEmptyCommittedSeals {
   265  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrEmptyCommittedSeals)
   266  	}
   267  
   268  	// short extra data
   269  	header := block.Header()
   270  	header.Extra = []byte{}
   271  	err = engine.VerifyHeader(chain, header, false)
   272  	if err != istanbulcommon.ErrInvalidExtraDataFormat {
   273  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidExtraDataFormat)
   274  	}
   275  	// incorrect extra format
   276  	header.Extra = []byte("0000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000000")
   277  	err = engine.VerifyHeader(chain, header, false)
   278  	if err != istanbulcommon.ErrInvalidExtraDataFormat {
   279  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidExtraDataFormat)
   280  	}
   281  
   282  	// non zero MixDigest
   283  	block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   284  	header = block.Header()
   285  	header.MixDigest = common.StringToHash("123456789")
   286  	err = engine.VerifyHeader(chain, header, false)
   287  	if err != istanbulcommon.ErrInvalidMixDigest {
   288  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidMixDigest)
   289  	}
   290  
   291  	// invalid uncles hash
   292  	block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   293  	header = block.Header()
   294  	header.UncleHash = common.StringToHash("123456789")
   295  	err = engine.VerifyHeader(chain, header, false)
   296  	if err != istanbulcommon.ErrInvalidUncleHash {
   297  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidUncleHash)
   298  	}
   299  
   300  	// invalid difficulty
   301  	block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   302  	header = block.Header()
   303  	header.Difficulty = big.NewInt(2)
   304  	err = engine.VerifyHeader(chain, header, false)
   305  	if err != istanbulcommon.ErrInvalidDifficulty {
   306  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidDifficulty)
   307  	}
   308  
   309  	// invalid timestamp
   310  	block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   311  	header = block.Header()
   312  	header.Time = chain.Genesis().Time() + (engine.config.BlockPeriod - 1)
   313  	err = engine.VerifyHeader(chain, header, false)
   314  	if err != istanbulcommon.ErrInvalidTimestamp {
   315  		t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidTimestamp)
   316  	}
   317  
   318  	// future block
   319  	block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   320  	header = block.Header()
   321  	header.Time = uint64(time.Now().Unix() + 10)
   322  	err = engine.VerifyHeader(chain, header, false)
   323  	if err != consensus.ErrFutureBlock {
   324  		t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrFutureBlock)
   325  	}
   326  
   327  	// future block which is within AllowedFutureBlockTime
   328  	block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true)
   329  	header = block.Header()
   330  	header.Time = new(big.Int).Add(big.NewInt(time.Now().Unix()), new(big.Int).SetUint64(10)).Uint64()
   331  	priorValue := engine.config.AllowedFutureBlockTime
   332  	engine.config.AllowedFutureBlockTime = 10
   333  	err = engine.VerifyHeader(chain, header, false)
   334  	engine.config.AllowedFutureBlockTime = priorValue //restore changed value
   335  	if err == consensus.ErrFutureBlock {
   336  		t.Errorf("error mismatch: have %v, want nil", err)
   337  	}
   338  
   339  	// TODO This test does not make sense anymore as validate vote type is not stored in nonce
   340  	// invalid nonce
   341  	/*block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
   342  	header = block.Header()
   343  	copy(header.Nonce[:], hexutil.MustDecode("0x111111111111"))
   344  	header.Number = big.NewInt(int64(engine.config.Epoch))
   345  	err = engine.VerifyHeader(chain, header, false)
   346  	if err != errInvalidNonce {
   347  		t.Errorf("error mismatch: have %v, want %v", err, errInvalidNonce)
   348  	}*/
   349  }
   350  
   351  func TestVerifyHeaders(t *testing.T) {
   352  	chain, engine := newBlockChain(1)
   353  	defer engine.Stop()
   354  	genesis := chain.Genesis()
   355  
   356  	// success case
   357  	headers := []*types.Header{}
   358  	blocks := []*types.Block{}
   359  	size := 100
   360  
   361  	for i := 0; i < size; i++ {
   362  		var b *types.Block
   363  		if i == 0 {
   364  			b = makeBlockWithoutSeal(chain, engine, genesis, false)
   365  			b = updateQBFTBlock(b, engine.Address())
   366  		} else {
   367  			b = makeBlockWithoutSeal(chain, engine, blocks[i-1], false)
   368  			b = updateQBFTBlock(b, engine.Address())
   369  		}
   370  		blocks = append(blocks, b)
   371  		headers = append(headers, blocks[i].Header())
   372  	}
   373  	// now = func() time.Time {
   374  	// 	return time.Unix(int64(headers[size-1].Time), 0)
   375  	// }
   376  	_, results := engine.VerifyHeaders(chain, headers, nil)
   377  	const timeoutDura = 2 * time.Second
   378  	timeout := time.NewTimer(timeoutDura)
   379  	index := 0
   380  OUT1:
   381  	for {
   382  		select {
   383  		case err := <-results:
   384  			if err != nil {
   385  				if err != istanbulcommon.ErrEmptyCommittedSeals && err != istanbulcommon.ErrInvalidCommittedSeals && err != consensus.ErrUnknownAncestor {
   386  					t.Errorf("error mismatch: have %v, want istanbulcommon.ErrEmptyCommittedSeals|istanbulcommon.ErrInvalidCommittedSeals|ErrUnknownAncestor", err)
   387  					break OUT1
   388  				}
   389  			}
   390  			index++
   391  			if index == size {
   392  				break OUT1
   393  			}
   394  		case <-timeout.C:
   395  			break OUT1
   396  		}
   397  	}
   398  	_, results = engine.VerifyHeaders(chain, headers, nil)
   399  	timeout = time.NewTimer(timeoutDura)
   400  OUT2:
   401  	for {
   402  		select {
   403  		case err := <-results:
   404  			if err != nil {
   405  				if err != istanbulcommon.ErrEmptyCommittedSeals && err != istanbulcommon.ErrInvalidCommittedSeals && err != consensus.ErrUnknownAncestor {
   406  					t.Errorf("error mismatch: have %v, want istanbulcommon.ErrEmptyCommittedSeals|istanbulcommon.ErrInvalidCommittedSeals|ErrUnknownAncestor", err)
   407  					break OUT2
   408  				}
   409  			}
   410  		case <-timeout.C:
   411  			break OUT2
   412  		}
   413  	}
   414  	// error header cases
   415  	headers[2].Number = big.NewInt(100)
   416  	_, results = engine.VerifyHeaders(chain, headers, nil)
   417  	timeout = time.NewTimer(timeoutDura)
   418  	index = 0
   419  	errors := 0
   420  	expectedErrors := 0
   421  OUT3:
   422  	for {
   423  		select {
   424  		case err := <-results:
   425  			if err != nil {
   426  				if err != istanbulcommon.ErrEmptyCommittedSeals && err != istanbulcommon.ErrInvalidCommittedSeals && err != consensus.ErrUnknownAncestor {
   427  					errors++
   428  				}
   429  			}
   430  			index++
   431  			if index == size {
   432  				if errors != expectedErrors {
   433  					t.Errorf("error mismatch: have %v, want %v", errors, expectedErrors)
   434  				}
   435  				break OUT3
   436  			}
   437  		case <-timeout.C:
   438  			break OUT3
   439  		}
   440  	}
   441  }