github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/consensus/istanbul/core/commit_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 core
    18  
    19  import (
    20  	"math/big"
    21  	"testing"
    22  
    23  	bls "github.com/celo-org/bls-zexe/go"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/consensus/istanbul"
    26  	"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
    27  	"github.com/ethereum/go-ethereum/core/types"
    28  	"github.com/ethereum/go-ethereum/crypto"
    29  	blscrypto "github.com/ethereum/go-ethereum/crypto/bls"
    30  )
    31  
    32  func TestHandleCommit(t *testing.T) {
    33  	N := uint64(4)
    34  	F := uint64(1)
    35  
    36  	// create block 4
    37  	proposal := newTestProposalWithNum(4)
    38  	expectedSubject := &istanbul.Subject{
    39  		View: &istanbul.View{
    40  			Round:    big.NewInt(0),
    41  			Sequence: proposal.Number(),
    42  		},
    43  		Digest: proposal.Hash(),
    44  	}
    45  
    46  	testCases := []struct {
    47  		system             *testSystem
    48  		expectedErr        error
    49  		checkParentCommits bool
    50  	}{
    51  		{
    52  			// normal case
    53  			func() *testSystem {
    54  				sys := NewTestSystemWithBackend(N, F)
    55  
    56  				for i, backend := range sys.backends {
    57  					c := backend.engine.(*core)
    58  					// same view as the expected one to everyone
    59  					c.current = newTestRoundState(
    60  						expectedSubject.View,
    61  						backend.peers,
    62  					)
    63  
    64  					if i == 0 {
    65  						// replica 0 is the proposer
    66  						c.current.(*roundStateImpl).state = StatePrepared
    67  					}
    68  				}
    69  				return sys
    70  			}(),
    71  			nil,
    72  			false,
    73  		},
    74  		{
    75  			// future message
    76  			func() *testSystem {
    77  				sys := NewTestSystemWithBackend(N, F)
    78  
    79  				for i, backend := range sys.backends {
    80  					c := backend.engine.(*core)
    81  					if i == 0 {
    82  						// replica 0 is the proposer
    83  						c.current = newTestRoundState(
    84  							expectedSubject.View,
    85  							backend.peers,
    86  						)
    87  						c.current.(*roundStateImpl).state = StatePreprepared
    88  					} else {
    89  						c.current = newTestRoundState(
    90  							&istanbul.View{
    91  								Round: big.NewInt(0),
    92  								// proposal from 1 round in the future
    93  								Sequence: big.NewInt(0).Add(proposal.Number(), common.Big1),
    94  							},
    95  							backend.peers,
    96  						)
    97  					}
    98  				}
    99  				return sys
   100  			}(),
   101  			errFutureMessage,
   102  			false,
   103  		},
   104  		{
   105  			// past message
   106  			func() *testSystem {
   107  				sys := NewTestSystemWithBackend(N, F)
   108  
   109  				for i, backend := range sys.backends {
   110  					c := backend.engine.(*core)
   111  
   112  					if i == 0 {
   113  						// replica 0 is the proposer
   114  						c.current = newTestRoundState(
   115  							expectedSubject.View,
   116  							backend.peers,
   117  						)
   118  						c.current.(*roundStateImpl).state = StatePreprepared
   119  					} else {
   120  						c.current = newTestRoundState(
   121  							&istanbul.View{
   122  								Round: big.NewInt(0),
   123  								// we're 2 blocks before so this is indeed a
   124  								// very old proposal and will error as expected
   125  								// with an old error message
   126  								Sequence: big.NewInt(0).Sub(proposal.Number(), common.Big2),
   127  							},
   128  							backend.peers,
   129  						)
   130  					}
   131  				}
   132  				return sys
   133  			}(),
   134  			errOldMessage,
   135  			false,
   136  		},
   137  		{
   138  			// jump state
   139  			func() *testSystem {
   140  				sys := NewTestSystemWithBackend(N, F)
   141  
   142  				for i, backend := range sys.backends {
   143  					c := backend.engine.(*core)
   144  					c.current = newTestRoundState(
   145  						&istanbul.View{
   146  							Round:    big.NewInt(0),
   147  							Sequence: proposal.Number(),
   148  						},
   149  						backend.peers,
   150  					)
   151  
   152  					// only replica0 stays at StatePreprepared
   153  					// other replicas are at StatePrepared
   154  					if i != 0 {
   155  						c.current.(*roundStateImpl).state = StatePrepared
   156  					} else {
   157  						c.current.(*roundStateImpl).state = StatePreprepared
   158  					}
   159  				}
   160  				return sys
   161  			}(),
   162  			nil,
   163  			false,
   164  		},
   165  		{
   166  			// message from previous sequence and round matching last proposal
   167  			// this should pass the message check, but will return an error in
   168  			// handleCheckedCommitForPreviousSequence, because the proposal hashes won't match.
   169  			func() *testSystem {
   170  				sys := NewTestSystemWithBackend(N, F)
   171  
   172  				for i, backend := range sys.backends {
   173  					backend.Commit(newTestProposalWithNum(3), types.IstanbulAggregatedSeal{}, types.IstanbulEpochValidatorSetSeal{})
   174  					c := backend.engine.(*core)
   175  					if i == 0 {
   176  						// replica 0 is the proposer
   177  						c.current = newTestRoundState(
   178  							expectedSubject.View,
   179  							backend.peers,
   180  						)
   181  						c.current.(*roundStateImpl).state = StatePrepared
   182  					} else {
   183  						c.current = newTestRoundState(
   184  							&istanbul.View{
   185  								Round:    big.NewInt(1),
   186  								Sequence: big.NewInt(0).Sub(proposal.Number(), common.Big1),
   187  							},
   188  							backend.peers,
   189  						)
   190  					}
   191  				}
   192  				return sys
   193  			}(),
   194  			errInconsistentSubject,
   195  			true,
   196  		},
   197  		// TODO: double send message
   198  	}
   199  
   200  OUTER:
   201  	for _, test := range testCases {
   202  		test.system.Run(false)
   203  
   204  		v0 := test.system.backends[0]
   205  		r0 := v0.engine.(*core)
   206  
   207  		for i, v := range test.system.backends {
   208  			validator := r0.current.ValidatorSet().GetByIndex(uint64(i))
   209  			privateKey, _ := bls.DeserializePrivateKey(test.system.validatorsKeys[i])
   210  			defer privateKey.Destroy()
   211  
   212  			hash := PrepareCommittedSeal(v.engine.(*core).current.Proposal().Hash(), v.engine.(*core).current.Round())
   213  			signature, _ := privateKey.SignMessage(hash, []byte{}, false)
   214  			defer signature.Destroy()
   215  			signatureBytes, _ := signature.Serialize()
   216  			committedSubject := &istanbul.CommittedSubject{
   217  				Subject:       v.engine.(*core).current.Subject(),
   218  				CommittedSeal: signatureBytes,
   219  			}
   220  			m, _ := Encode(committedSubject)
   221  			if err := r0.handleCommit(&istanbul.Message{
   222  				Code:      istanbul.MsgCommit,
   223  				Msg:       m,
   224  				Address:   validator.Address(),
   225  				Signature: []byte{},
   226  			}); err != nil {
   227  				if err != test.expectedErr {
   228  					t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
   229  				}
   230  				continue OUTER
   231  			}
   232  		}
   233  
   234  		// core should have received a parent seal from each of its neighbours
   235  		// how can we add our signature to the ParentCommit? Broadcast to ourselve
   236  		// does not make much sense
   237  		if test.checkParentCommits {
   238  			if r0.current.ParentCommits().Size() != r0.current.ValidatorSet().Size()-1 { // TODO: Maybe remove the -1?
   239  				t.Errorf("parent seals mismatch: have %v, want %v", r0.current.ParentCommits().Size(), r0.current.ValidatorSet().Size()-1)
   240  			}
   241  		}
   242  
   243  		// prepared is normal case
   244  		if r0.current.State() != StateCommitted {
   245  			// There are not enough commit messages in core
   246  			if r0.current.State() != StatePrepared {
   247  				t.Errorf("state mismatch: have %v, want %v", r0.current.State(), StatePrepared)
   248  			}
   249  			if r0.current.Commits().Size() > r0.current.ValidatorSet().MinQuorumSize() {
   250  				t.Errorf("the size of commit messages should be less than %v", r0.current.ValidatorSet().MinQuorumSize())
   251  			}
   252  			continue
   253  		}
   254  
   255  		// core should have min quorum size prepare messages
   256  		if r0.current.Commits().Size() < r0.current.ValidatorSet().MinQuorumSize() {
   257  			t.Errorf("the size of commit messages should be greater than or equal to minQuorumSize: size %v", r0.current.Commits().Size())
   258  		}
   259  
   260  		// check signatures large than MinQuorumSize
   261  		signedCount := 0
   262  		for i := 0; i < r0.current.ValidatorSet().Size(); i++ {
   263  			if v0.committedMsgs[0].aggregatedSeal.Bitmap.Bit(i) == 1 {
   264  				signedCount++
   265  			}
   266  		}
   267  		if signedCount < r0.current.ValidatorSet().MinQuorumSize() {
   268  			t.Errorf("the expected signed count should be greater than or equal to %v, but got %v", r0.current.ValidatorSet().MinQuorumSize(), signedCount)
   269  		}
   270  	}
   271  }
   272  
   273  // round is not checked for now
   274  func TestVerifyCommit(t *testing.T) {
   275  	// for log purpose
   276  	privateKey, _ := crypto.GenerateKey()
   277  	blsPrivateKey, _ := blscrypto.ECDSAToBLS(privateKey)
   278  	blsPublicKey, _ := blscrypto.PrivateToPublic(blsPrivateKey)
   279  	peer := validator.New(getPublicKeyAddress(privateKey), blsPublicKey)
   280  	valSet := validator.NewSet([]istanbul.ValidatorData{
   281  		{
   282  			peer.Address(),
   283  			blsPublicKey,
   284  		},
   285  	})
   286  	// }, istanbul.RoundRobin)
   287  
   288  	sys := NewTestSystemWithBackend(uint64(1), uint64(0))
   289  
   290  	testCases := []struct {
   291  		expected   error
   292  		commit     *istanbul.CommittedSubject
   293  		roundState RoundState
   294  	}{
   295  		{
   296  			// normal case
   297  			expected: nil,
   298  			commit: &istanbul.CommittedSubject{
   299  				Subject: &istanbul.Subject{
   300  					View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   301  					Digest: newTestProposal().Hash(),
   302  				},
   303  			},
   304  			roundState: newTestRoundState(
   305  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   306  				valSet,
   307  			),
   308  		},
   309  		{
   310  			// old message
   311  			expected: errInconsistentSubject,
   312  			commit: &istanbul.CommittedSubject{
   313  				Subject: &istanbul.Subject{
   314  					View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   315  					Digest: newTestProposal().Hash(),
   316  				},
   317  			},
   318  			roundState: newTestRoundState(
   319  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   320  				valSet,
   321  			),
   322  		},
   323  		{
   324  			// different digest
   325  			expected: errInconsistentSubject,
   326  			commit: &istanbul.CommittedSubject{
   327  				Subject: &istanbul.Subject{
   328  					View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   329  					Digest: common.BytesToHash([]byte("1234567890")),
   330  				},
   331  			},
   332  			roundState: newTestRoundState(
   333  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   334  				valSet,
   335  			),
   336  		},
   337  		{
   338  			// malicious package(lack of sequence)
   339  			expected: errInconsistentSubject,
   340  			commit: &istanbul.CommittedSubject{
   341  				Subject: &istanbul.Subject{
   342  					View:   &istanbul.View{Round: big.NewInt(0), Sequence: nil},
   343  					Digest: newTestProposal().Hash(),
   344  				},
   345  			},
   346  			roundState: newTestRoundState(
   347  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   348  				valSet,
   349  			),
   350  		},
   351  		{
   352  			// wrong prepare message with same sequence but different round
   353  			expected: errInconsistentSubject,
   354  			commit: &istanbul.CommittedSubject{
   355  				Subject: &istanbul.Subject{
   356  					View:   &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(0)},
   357  					Digest: newTestProposal().Hash(),
   358  				},
   359  			},
   360  			roundState: newTestRoundState(
   361  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   362  				valSet,
   363  			),
   364  		},
   365  		{
   366  			// wrong prepare message with same round but different sequence
   367  			expected: errInconsistentSubject,
   368  			commit: &istanbul.CommittedSubject{
   369  				Subject: &istanbul.Subject{
   370  					View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(1)},
   371  					Digest: newTestProposal().Hash(),
   372  				},
   373  			},
   374  			roundState: newTestRoundState(
   375  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   376  				valSet,
   377  			),
   378  		},
   379  	}
   380  	for i, test := range testCases {
   381  		c := sys.backends[0].engine.(*core)
   382  		c.current = test.roundState
   383  
   384  		if err := c.verifyCommit(test.commit); err != nil {
   385  			if err != test.expected {
   386  				t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected)
   387  			}
   388  		}
   389  	}
   390  }