github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/consensus/dbft/core/prepare_test.go (about)

     1  package core
     2  
     3  import (
     4  	"math/big"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/quickchainproject/quickchain/common"
     9  	"github.com/quickchainproject/quickchain/consensus/dbft"
    10  	"github.com/quickchainproject/quickchain/consensus/dbft/validator"
    11  	"github.com/quickchainproject/quickchain/crypto"
    12  )
    13  
    14  func TestHandlePrepare(t *testing.T) {
    15  	N := uint64(4)
    16  	F := uint64(1)
    17  
    18  	proposal := newTestProposal()
    19  	expectedSubject := &bft.Subject{
    20  		View: &bft.View{
    21  			Round:    big.NewInt(0),
    22  			Sequence: proposal.Number(),
    23  		},
    24  		Digest: proposal.Hash(),
    25  	}
    26  
    27  	testCases := []struct {
    28  		system      *testSystem
    29  		expectedErr error
    30  	}{
    31  		{
    32  			// normal case
    33  			func() *testSystem {
    34  				sys := NewTestSystemWithBackend(N, F)
    35  
    36  				for i, backend := range sys.backends {
    37  					c := backend.engine.(*core)
    38  					c.valSet = backend.peers
    39  					c.current = newTestRoundState(
    40  						&bft.View{
    41  							Round:    big.NewInt(0),
    42  							Sequence: big.NewInt(1),
    43  						},
    44  						c.valSet,
    45  					)
    46  
    47  					if i == 0 {
    48  						// replica 0 is the proposer
    49  						c.state = StatePreprepared
    50  					}
    51  				}
    52  				return sys
    53  			}(),
    54  			nil,
    55  		},
    56  		{
    57  			// future message
    58  			func() *testSystem {
    59  				sys := NewTestSystemWithBackend(N, F)
    60  
    61  				for i, backend := range sys.backends {
    62  					c := backend.engine.(*core)
    63  					c.valSet = backend.peers
    64  					if i == 0 {
    65  						// replica 0 is the proposer
    66  						c.current = newTestRoundState(
    67  							expectedSubject.View,
    68  							c.valSet,
    69  						)
    70  						c.state = StatePreprepared
    71  					} else {
    72  						c.current = newTestRoundState(
    73  							&bft.View{
    74  								Round:    big.NewInt(2),
    75  								Sequence: big.NewInt(3),
    76  							},
    77  							c.valSet,
    78  						)
    79  					}
    80  				}
    81  				return sys
    82  			}(),
    83  			errFutureMessage,
    84  		},
    85  		{
    86  			// subject not match
    87  			func() *testSystem {
    88  				sys := NewTestSystemWithBackend(N, F)
    89  
    90  				for i, backend := range sys.backends {
    91  					c := backend.engine.(*core)
    92  					c.valSet = backend.peers
    93  					if i == 0 {
    94  						// replica 0 is the proposer
    95  						c.current = newTestRoundState(
    96  							expectedSubject.View,
    97  							c.valSet,
    98  						)
    99  						c.state = StatePreprepared
   100  					} else {
   101  						c.current = newTestRoundState(
   102  							&bft.View{
   103  								Round:    big.NewInt(0),
   104  								Sequence: big.NewInt(0),
   105  							},
   106  							c.valSet,
   107  						)
   108  					}
   109  				}
   110  				return sys
   111  			}(),
   112  			errOldMessage,
   113  		},
   114  		{
   115  			// subject not match
   116  			func() *testSystem {
   117  				sys := NewTestSystemWithBackend(N, F)
   118  
   119  				for i, backend := range sys.backends {
   120  					c := backend.engine.(*core)
   121  					c.valSet = backend.peers
   122  					if i == 0 {
   123  						// replica 0 is the proposer
   124  						c.current = newTestRoundState(
   125  							expectedSubject.View,
   126  							c.valSet,
   127  						)
   128  						c.state = StatePreprepared
   129  					} else {
   130  						c.current = newTestRoundState(
   131  							&bft.View{
   132  								Round:    big.NewInt(0),
   133  								Sequence: big.NewInt(1)},
   134  							c.valSet,
   135  						)
   136  					}
   137  				}
   138  				return sys
   139  			}(),
   140  			errInconsistentSubject,
   141  		},
   142  		{
   143  			// less than 2F+1
   144  			func() *testSystem {
   145  				sys := NewTestSystemWithBackend(N, F)
   146  
   147  				// save less than 2*F+1 replica
   148  				sys.backends = sys.backends[2*int(F)+1:]
   149  
   150  				for i, backend := range sys.backends {
   151  					c := backend.engine.(*core)
   152  					c.valSet = backend.peers
   153  					c.current = newTestRoundState(
   154  						expectedSubject.View,
   155  						c.valSet,
   156  					)
   157  
   158  					if i == 0 {
   159  						// replica 0 is the proposer
   160  						c.state = StatePreprepared
   161  					}
   162  				}
   163  				return sys
   164  			}(),
   165  			nil,
   166  		},
   167  		// TODO: double send message
   168  	}
   169  
   170  OUTER:
   171  	for _, test := range testCases {
   172  		test.system.Run(false)
   173  
   174  		v0 := test.system.backends[0]
   175  		r0 := v0.engine.(*core)
   176  
   177  		for i, v := range test.system.backends {
   178  			validator := r0.valSet.GetByIndex(uint64(i))
   179  			m, _ := Encode(v.engine.(*core).current.Subject())
   180  			if err := r0.handlePrepare(&message{
   181  				Code:    msgPrepare,
   182  				Msg:     m,
   183  				Address: validator.Address(),
   184  			}, validator); err != nil {
   185  				if err != test.expectedErr {
   186  					t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
   187  				}
   188  				if r0.current.IsHashLocked() {
   189  					t.Errorf("block should not be locked")
   190  				}
   191  				continue OUTER
   192  			}
   193  		}
   194  
   195  		// prepared is normal case
   196  		if r0.state != StatePrepared {
   197  			// There are not enough PREPARE messages in core
   198  			if r0.state != StatePreprepared {
   199  				t.Errorf("state mismatch: have %v, want %v", r0.state, StatePreprepared)
   200  			}
   201  			if r0.current.Prepares.Size() > 2*r0.valSet.F() {
   202  				t.Errorf("the size of PREPARE messages should be less than %v", 2*r0.valSet.F()+1)
   203  			}
   204  			if r0.current.IsHashLocked() {
   205  				t.Errorf("block should not be locked")
   206  			}
   207  
   208  			continue
   209  		}
   210  
   211  		// core should have 2F+1 PREPARE messages
   212  		if r0.current.Prepares.Size() <= 2*r0.valSet.F() {
   213  			t.Errorf("the size of PREPARE messages should be larger than 2F+1: size %v", r0.current.Commits.Size())
   214  		}
   215  
   216  		// a message will be delivered to backend if 2F+1
   217  		if int64(len(v0.sentMsgs)) != 1 {
   218  			t.Errorf("the Send() should be called once: times %v", len(test.system.backends[0].sentMsgs))
   219  		}
   220  
   221  		// verify COMMIT messages
   222  		decodedMsg := new(message)
   223  		err := decodedMsg.FromPayload(v0.sentMsgs[0], nil)
   224  		if err != nil {
   225  			t.Errorf("error mismatch: have %v, want nil", err)
   226  		}
   227  
   228  		if decodedMsg.Code != msgCommit {
   229  			t.Errorf("message code mismatch: have %v, want %v", decodedMsg.Code, msgCommit)
   230  		}
   231  		var m *bft.Subject
   232  		err = decodedMsg.Decode(&m)
   233  		if err != nil {
   234  			t.Errorf("error mismatch: have %v, want nil", err)
   235  		}
   236  		if !reflect.DeepEqual(m, expectedSubject) {
   237  			t.Errorf("subject mismatch: have %v, want %v", m, expectedSubject)
   238  		}
   239  		if !r0.current.IsHashLocked() {
   240  			t.Errorf("block should be locked")
   241  		}
   242  	}
   243  }
   244  
   245  // round is not checked for now
   246  func TestVerifyPrepare(t *testing.T) {
   247  	// for log purpose
   248  	privateKey, _ := crypto.GenerateKey()
   249  	peer := validator.New(getPublicKeyAddress(privateKey))
   250  	valSet := validator.NewSet([]common.Address{peer.Address()}, bft.RoundRobin)
   251  
   252  	sys := NewTestSystemWithBackend(uint64(1), uint64(0))
   253  
   254  	testCases := []struct {
   255  		expected error
   256  
   257  		prepare    *bft.Subject
   258  		roundState *roundState
   259  	}{
   260  		{
   261  			// normal case
   262  			expected: nil,
   263  			prepare: &bft.Subject{
   264  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   265  				Digest: newTestProposal().Hash(),
   266  			},
   267  			roundState: newTestRoundState(
   268  				&bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   269  				valSet,
   270  			),
   271  		},
   272  		{
   273  			// old message
   274  			expected: errInconsistentSubject,
   275  			prepare: &bft.Subject{
   276  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   277  				Digest: newTestProposal().Hash(),
   278  			},
   279  			roundState: newTestRoundState(
   280  				&bft.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   281  				valSet,
   282  			),
   283  		},
   284  		{
   285  			// different digest
   286  			expected: errInconsistentSubject,
   287  			prepare: &bft.Subject{
   288  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   289  				Digest: common.StringToHash("1234567890"),
   290  			},
   291  			roundState: newTestRoundState(
   292  				&bft.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   293  				valSet,
   294  			),
   295  		},
   296  		{
   297  			// malicious package(lack of sequence)
   298  			expected: errInconsistentSubject,
   299  			prepare: &bft.Subject{
   300  				View:   &bft.View{Round: big.NewInt(0), Sequence: nil},
   301  				Digest: newTestProposal().Hash(),
   302  			},
   303  			roundState: newTestRoundState(
   304  				&bft.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   305  				valSet,
   306  			),
   307  		},
   308  		{
   309  			// wrong PREPARE message with same sequence but different round
   310  			expected: errInconsistentSubject,
   311  			prepare: &bft.Subject{
   312  				View:   &bft.View{Round: big.NewInt(1), Sequence: big.NewInt(0)},
   313  				Digest: newTestProposal().Hash(),
   314  			},
   315  			roundState: newTestRoundState(
   316  				&bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   317  				valSet,
   318  			),
   319  		},
   320  		{
   321  			// wrong PREPARE message with same round but different sequence
   322  			expected: errInconsistentSubject,
   323  			prepare: &bft.Subject{
   324  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(1)},
   325  				Digest: newTestProposal().Hash(),
   326  			},
   327  			roundState: newTestRoundState(
   328  				&bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   329  				valSet,
   330  			),
   331  		},
   332  	}
   333  	for i, test := range testCases {
   334  		c := sys.backends[0].engine.(*core)
   335  		c.current = test.roundState
   336  
   337  		if err := c.verifyPrepare(test.prepare, peer); err != nil {
   338  			if err != test.expected {
   339  				t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected)
   340  			}
   341  		}
   342  	}
   343  }