github.com/ConsenSys/Quorum@v20.10.0+incompatible/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  	"bytes"
    21  	"math/big"
    22  	"testing"
    23  
    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/crypto"
    28  )
    29  
    30  func TestHandleCommit(t *testing.T) {
    31  	N := uint64(4)
    32  	F := uint64(1)
    33  
    34  	proposal := newTestProposal()
    35  	expectedSubject := &istanbul.Subject{
    36  		View: &istanbul.View{
    37  			Round:    big.NewInt(0),
    38  			Sequence: proposal.Number(),
    39  		},
    40  		Digest: proposal.Hash(),
    41  	}
    42  
    43  	testCases := []struct {
    44  		system      *testSystem
    45  		expectedErr error
    46  	}{
    47  		{
    48  			// normal case
    49  			func() *testSystem {
    50  				sys := NewTestSystemWithBackend(N, F)
    51  
    52  				for i, backend := range sys.backends {
    53  					c := backend.engine.(*core)
    54  					c.valSet = backend.peers
    55  					c.current = newTestRoundState(
    56  						&istanbul.View{
    57  							Round:    big.NewInt(0),
    58  							Sequence: big.NewInt(1),
    59  						},
    60  						c.valSet,
    61  					)
    62  
    63  					if i == 0 {
    64  						// replica 0 is the proposer
    65  						c.state = StatePrepared
    66  					}
    67  				}
    68  				return sys
    69  			}(),
    70  			nil,
    71  		},
    72  		{
    73  			// future message
    74  			func() *testSystem {
    75  				sys := NewTestSystemWithBackend(N, F)
    76  
    77  				for i, backend := range sys.backends {
    78  					c := backend.engine.(*core)
    79  					c.valSet = backend.peers
    80  					if i == 0 {
    81  						// replica 0 is the proposer
    82  						c.current = newTestRoundState(
    83  							expectedSubject.View,
    84  							c.valSet,
    85  						)
    86  						c.state = StatePreprepared
    87  					} else {
    88  						c.current = newTestRoundState(
    89  							&istanbul.View{
    90  								Round:    big.NewInt(2),
    91  								Sequence: big.NewInt(3),
    92  							},
    93  							c.valSet,
    94  						)
    95  					}
    96  				}
    97  				return sys
    98  			}(),
    99  			errFutureMessage,
   100  		},
   101  		{
   102  			// subject not match
   103  			func() *testSystem {
   104  				sys := NewTestSystemWithBackend(N, F)
   105  
   106  				for i, backend := range sys.backends {
   107  					c := backend.engine.(*core)
   108  					c.valSet = backend.peers
   109  					if i == 0 {
   110  						// replica 0 is the proposer
   111  						c.current = newTestRoundState(
   112  							expectedSubject.View,
   113  							c.valSet,
   114  						)
   115  						c.state = StatePreprepared
   116  					} else {
   117  						c.current = newTestRoundState(
   118  							&istanbul.View{
   119  								Round:    big.NewInt(0),
   120  								Sequence: big.NewInt(0),
   121  							},
   122  							c.valSet,
   123  						)
   124  					}
   125  				}
   126  				return sys
   127  			}(),
   128  			errOldMessage,
   129  		},
   130  		{
   131  			// jump state
   132  			func() *testSystem {
   133  				sys := NewTestSystemWithBackend(N, F)
   134  
   135  				for i, backend := range sys.backends {
   136  					c := backend.engine.(*core)
   137  					c.valSet = backend.peers
   138  					c.current = newTestRoundState(
   139  						&istanbul.View{
   140  							Round:    big.NewInt(0),
   141  							Sequence: proposal.Number(),
   142  						},
   143  						c.valSet,
   144  					)
   145  
   146  					// only replica0 stays at StatePreprepared
   147  					// other replicas are at StatePrepared
   148  					if i != 0 {
   149  						c.state = StatePrepared
   150  					} else {
   151  						c.state = StatePreprepared
   152  					}
   153  				}
   154  				return sys
   155  			}(),
   156  			nil,
   157  		},
   158  		// TODO: double send message
   159  	}
   160  
   161  OUTER:
   162  	for _, test := range testCases {
   163  		test.system.Run(false)
   164  
   165  		v0 := test.system.backends[0]
   166  		r0 := v0.engine.(*core)
   167  
   168  		for i, v := range test.system.backends {
   169  			validator := r0.valSet.GetByIndex(uint64(i))
   170  			m, _ := Encode(v.engine.(*core).current.Subject())
   171  			if err := r0.handleCommit(&message{
   172  				Code:          msgCommit,
   173  				Msg:           m,
   174  				Address:       validator.Address(),
   175  				Signature:     []byte{},
   176  				CommittedSeal: validator.Address().Bytes(), // small hack
   177  			}, validator); err != nil {
   178  				if err != test.expectedErr {
   179  					t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
   180  				}
   181  				if r0.current.IsHashLocked() {
   182  					t.Errorf("block should not be locked")
   183  				}
   184  				continue OUTER
   185  			}
   186  		}
   187  
   188  		// prepared is normal case
   189  		if r0.state != StateCommitted {
   190  			// There are not enough commit messages in core
   191  			if r0.state != StatePrepared {
   192  				t.Errorf("state mismatch: have %v, want %v", r0.state, StatePrepared)
   193  			}
   194  			if r0.current.Commits.Size() >= r0.QuorumSize() {
   195  				t.Errorf("the size of commit messages should be less than %v", r0.QuorumSize())
   196  			}
   197  			if r0.current.IsHashLocked() {
   198  				t.Errorf("block should not be locked")
   199  			}
   200  			continue
   201  		}
   202  
   203  		// core should have 2F+1 before Ceil2Nby3Block or Ceil(2N/3) prepare messages
   204  		if r0.current.Commits.Size() < r0.QuorumSize() {
   205  			t.Errorf("the size of commit messages should be larger than 2F+1 or Ceil(2N/3): size %v", r0.QuorumSize())
   206  		}
   207  
   208  		// check signatures large than F
   209  		signedCount := 0
   210  		committedSeals := v0.committedMsgs[0].committedSeals
   211  		for _, validator := range r0.valSet.List() {
   212  			for _, seal := range committedSeals {
   213  				if bytes.Equal(validator.Address().Bytes(), seal[:common.AddressLength]) {
   214  					signedCount++
   215  					break
   216  				}
   217  			}
   218  		}
   219  		if signedCount <= r0.valSet.F() {
   220  			t.Errorf("the expected signed count should be larger than %v, but got %v", r0.valSet.F(), signedCount)
   221  		}
   222  		if !r0.current.IsHashLocked() {
   223  			t.Errorf("block should be locked")
   224  		}
   225  	}
   226  }
   227  
   228  // round is not checked for now
   229  func TestVerifyCommit(t *testing.T) {
   230  	// for log purpose
   231  	privateKey, _ := crypto.GenerateKey()
   232  	peer := validator.New(getPublicKeyAddress(privateKey))
   233  	valSet := validator.NewSet([]common.Address{peer.Address()}, istanbul.RoundRobin)
   234  
   235  	sys := NewTestSystemWithBackend(uint64(1), uint64(0))
   236  
   237  	testCases := []struct {
   238  		expected   error
   239  		commit     *istanbul.Subject
   240  		roundState *roundState
   241  	}{
   242  		{
   243  			// normal case
   244  			expected: nil,
   245  			commit: &istanbul.Subject{
   246  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   247  				Digest: newTestProposal().Hash(),
   248  			},
   249  			roundState: newTestRoundState(
   250  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   251  				valSet,
   252  			),
   253  		},
   254  		{
   255  			// old message
   256  			expected: errInconsistentSubject,
   257  			commit: &istanbul.Subject{
   258  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   259  				Digest: newTestProposal().Hash(),
   260  			},
   261  			roundState: newTestRoundState(
   262  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   263  				valSet,
   264  			),
   265  		},
   266  		{
   267  			// different digest
   268  			expected: errInconsistentSubject,
   269  			commit: &istanbul.Subject{
   270  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   271  				Digest: common.StringToHash("1234567890"),
   272  			},
   273  			roundState: newTestRoundState(
   274  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   275  				valSet,
   276  			),
   277  		},
   278  		{
   279  			// malicious package(lack of sequence)
   280  			expected: errInconsistentSubject,
   281  			commit: &istanbul.Subject{
   282  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: nil},
   283  				Digest: newTestProposal().Hash(),
   284  			},
   285  			roundState: newTestRoundState(
   286  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   287  				valSet,
   288  			),
   289  		},
   290  		{
   291  			// wrong prepare message with same sequence but different round
   292  			expected: errInconsistentSubject,
   293  			commit: &istanbul.Subject{
   294  				View:   &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(0)},
   295  				Digest: newTestProposal().Hash(),
   296  			},
   297  			roundState: newTestRoundState(
   298  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   299  				valSet,
   300  			),
   301  		},
   302  		{
   303  			// wrong prepare message with same round but different sequence
   304  			expected: errInconsistentSubject,
   305  			commit: &istanbul.Subject{
   306  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(1)},
   307  				Digest: newTestProposal().Hash(),
   308  			},
   309  			roundState: newTestRoundState(
   310  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   311  				valSet,
   312  			),
   313  		},
   314  	}
   315  	for i, test := range testCases {
   316  		c := sys.backends[0].engine.(*core)
   317  		c.current = test.roundState
   318  
   319  		if err := c.verifyCommit(test.commit, peer); err != nil {
   320  			if err != test.expected {
   321  				t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected)
   322  			}
   323  		}
   324  	}
   325  }