github.com/pfcoder/quorum@v2.0.3-0.20180501191142-d4a1b0958135+incompatible/consensus/istanbul/core/prepare_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  	"reflect"
    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 TestHandlePrepare(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 = StatePreprepared
    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  			// subject not match
   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  					if i == 0 {
   139  						// replica 0 is the proposer
   140  						c.current = newTestRoundState(
   141  							expectedSubject.View,
   142  							c.valSet,
   143  						)
   144  						c.state = StatePreprepared
   145  					} else {
   146  						c.current = newTestRoundState(
   147  							&istanbul.View{
   148  								Round:    big.NewInt(0),
   149  								Sequence: big.NewInt(1)},
   150  							c.valSet,
   151  						)
   152  					}
   153  				}
   154  				return sys
   155  			}(),
   156  			errInconsistentSubject,
   157  		},
   158  		{
   159  			// less than 2F+1
   160  			func() *testSystem {
   161  				sys := NewTestSystemWithBackend(N, F)
   162  
   163  				// save less than 2*F+1 replica
   164  				sys.backends = sys.backends[2*int(F)+1:]
   165  
   166  				for i, backend := range sys.backends {
   167  					c := backend.engine.(*core)
   168  					c.valSet = backend.peers
   169  					c.current = newTestRoundState(
   170  						expectedSubject.View,
   171  						c.valSet,
   172  					)
   173  
   174  					if i == 0 {
   175  						// replica 0 is the proposer
   176  						c.state = StatePreprepared
   177  					}
   178  				}
   179  				return sys
   180  			}(),
   181  			nil,
   182  		},
   183  		// TODO: double send message
   184  	}
   185  
   186  OUTER:
   187  	for _, test := range testCases {
   188  		test.system.Run(false)
   189  
   190  		v0 := test.system.backends[0]
   191  		r0 := v0.engine.(*core)
   192  
   193  		for i, v := range test.system.backends {
   194  			validator := r0.valSet.GetByIndex(uint64(i))
   195  			m, _ := Encode(v.engine.(*core).current.Subject())
   196  			if err := r0.handlePrepare(&message{
   197  				Code:    msgPrepare,
   198  				Msg:     m,
   199  				Address: validator.Address(),
   200  			}, validator); err != nil {
   201  				if err != test.expectedErr {
   202  					t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
   203  				}
   204  				if r0.current.IsHashLocked() {
   205  					t.Errorf("block should not be locked")
   206  				}
   207  				continue OUTER
   208  			}
   209  		}
   210  
   211  		// prepared is normal case
   212  		if r0.state != StatePrepared {
   213  			// There are not enough PREPARE messages in core
   214  			if r0.state != StatePreprepared {
   215  				t.Errorf("state mismatch: have %v, want %v", r0.state, StatePreprepared)
   216  			}
   217  			if r0.current.Prepares.Size() > 2*r0.valSet.F() {
   218  				t.Errorf("the size of PREPARE messages should be less than %v", 2*r0.valSet.F()+1)
   219  			}
   220  			if r0.current.IsHashLocked() {
   221  				t.Errorf("block should not be locked")
   222  			}
   223  
   224  			continue
   225  		}
   226  
   227  		// core should have 2F+1 PREPARE messages
   228  		if r0.current.Prepares.Size() <= 2*r0.valSet.F() {
   229  			t.Errorf("the size of PREPARE messages should be larger than 2F+1: size %v", r0.current.Commits.Size())
   230  		}
   231  
   232  		// a message will be delivered to backend if 2F+1
   233  		if int64(len(v0.sentMsgs)) != 1 {
   234  			t.Errorf("the Send() should be called once: times %v", len(test.system.backends[0].sentMsgs))
   235  		}
   236  
   237  		// verify COMMIT messages
   238  		decodedMsg := new(message)
   239  		err := decodedMsg.FromPayload(v0.sentMsgs[0], nil)
   240  		if err != nil {
   241  			t.Errorf("error mismatch: have %v, want nil", err)
   242  		}
   243  
   244  		if decodedMsg.Code != msgCommit {
   245  			t.Errorf("message code mismatch: have %v, want %v", decodedMsg.Code, msgCommit)
   246  		}
   247  		var m *istanbul.Subject
   248  		err = decodedMsg.Decode(&m)
   249  		if err != nil {
   250  			t.Errorf("error mismatch: have %v, want nil", err)
   251  		}
   252  		if !reflect.DeepEqual(m, expectedSubject) {
   253  			t.Errorf("subject mismatch: have %v, want %v", m, expectedSubject)
   254  		}
   255  		if !r0.current.IsHashLocked() {
   256  			t.Errorf("block should be locked")
   257  		}
   258  	}
   259  }
   260  
   261  // round is not checked for now
   262  func TestVerifyPrepare(t *testing.T) {
   263  	// for log purpose
   264  	privateKey, _ := crypto.GenerateKey()
   265  	peer := validator.New(getPublicKeyAddress(privateKey))
   266  	valSet := validator.NewSet([]common.Address{peer.Address()}, istanbul.RoundRobin)
   267  
   268  	sys := NewTestSystemWithBackend(uint64(1), uint64(0))
   269  
   270  	testCases := []struct {
   271  		expected error
   272  
   273  		prepare    *istanbul.Subject
   274  		roundState *roundState
   275  	}{
   276  		{
   277  			// normal case
   278  			expected: nil,
   279  			prepare: &istanbul.Subject{
   280  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   281  				Digest: newTestProposal().Hash(),
   282  			},
   283  			roundState: newTestRoundState(
   284  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   285  				valSet,
   286  			),
   287  		},
   288  		{
   289  			// old message
   290  			expected: errInconsistentSubject,
   291  			prepare: &istanbul.Subject{
   292  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   293  				Digest: newTestProposal().Hash(),
   294  			},
   295  			roundState: newTestRoundState(
   296  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   297  				valSet,
   298  			),
   299  		},
   300  		{
   301  			// different digest
   302  			expected: errInconsistentSubject,
   303  			prepare: &istanbul.Subject{
   304  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   305  				Digest: common.StringToHash("1234567890"),
   306  			},
   307  			roundState: newTestRoundState(
   308  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   309  				valSet,
   310  			),
   311  		},
   312  		{
   313  			// malicious package(lack of sequence)
   314  			expected: errInconsistentSubject,
   315  			prepare: &istanbul.Subject{
   316  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: nil},
   317  				Digest: newTestProposal().Hash(),
   318  			},
   319  			roundState: newTestRoundState(
   320  				&istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   321  				valSet,
   322  			),
   323  		},
   324  		{
   325  			// wrong PREPARE message with same sequence but different round
   326  			expected: errInconsistentSubject,
   327  			prepare: &istanbul.Subject{
   328  				View:   &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(0)},
   329  				Digest: newTestProposal().Hash(),
   330  			},
   331  			roundState: newTestRoundState(
   332  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   333  				valSet,
   334  			),
   335  		},
   336  		{
   337  			// wrong PREPARE message with same round but different sequence
   338  			expected: errInconsistentSubject,
   339  			prepare: &istanbul.Subject{
   340  				View:   &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(1)},
   341  				Digest: newTestProposal().Hash(),
   342  			},
   343  			roundState: newTestRoundState(
   344  				&istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   345  				valSet,
   346  			),
   347  		},
   348  	}
   349  	for i, test := range testCases {
   350  		c := sys.backends[0].engine.(*core)
   351  		c.current = test.roundState
   352  
   353  		if err := c.verifyPrepare(test.prepare, peer); err != nil {
   354  			if err != test.expected {
   355  				t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected)
   356  			}
   357  		}
   358  	}
   359  }