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

     1  package core
     2  
     3  import (
     4  	"bytes"
     5  	"math/big"
     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 TestHandleCommit(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 = StatePrepared
    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  			// jump state
   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  					c.current = newTestRoundState(
   123  						&bft.View{
   124  							Round:    big.NewInt(0),
   125  							Sequence: proposal.Number(),
   126  						},
   127  						c.valSet,
   128  					)
   129  
   130  					// only replica0 stays at StatePreprepared
   131  					// other replicas are at StatePrepared
   132  					if i != 0 {
   133  						c.state = StatePrepared
   134  					} else {
   135  						c.state = StatePreprepared
   136  					}
   137  				}
   138  				return sys
   139  			}(),
   140  			nil,
   141  		},
   142  		// TODO: double send message
   143  	}
   144  
   145  OUTER:
   146  	for _, test := range testCases {
   147  		test.system.Run(false)
   148  
   149  		v0 := test.system.backends[0]
   150  		r0 := v0.engine.(*core)
   151  
   152  		for i, v := range test.system.backends {
   153  			validator := r0.valSet.GetByIndex(uint64(i))
   154  			m, _ := Encode(v.engine.(*core).current.Subject())
   155  			if err := r0.handleCommit(&message{
   156  				Code:          msgCommit,
   157  				Msg:           m,
   158  				Address:       validator.Address(),
   159  				Signature:     []byte{},
   160  				CommittedSeal: validator.Address().Bytes(), // small hack
   161  			}, validator); err != nil {
   162  				if err != test.expectedErr {
   163  					t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
   164  				}
   165  				if r0.current.IsHashLocked() {
   166  					t.Errorf("block should not be locked")
   167  				}
   168  				continue OUTER
   169  			}
   170  		}
   171  
   172  		// prepared is normal case
   173  		if r0.state != StateCommitted {
   174  			// There are not enough commit messages in core
   175  			if r0.state != StatePrepared {
   176  				t.Errorf("state mismatch: have %v, want %v", r0.state, StatePrepared)
   177  			}
   178  			if r0.current.Commits.Size() > 2*r0.valSet.F() {
   179  				t.Errorf("the size of commit messages should be less than %v", 2*r0.valSet.F()+1)
   180  			}
   181  			if r0.current.IsHashLocked() {
   182  				t.Errorf("block should not be locked")
   183  			}
   184  			continue
   185  		}
   186  
   187  		// core should have 2F+1 prepare messages
   188  		if r0.current.Commits.Size() <= 2*r0.valSet.F() {
   189  			t.Errorf("the size of commit messages should be larger than 2F+1: size %v", r0.current.Commits.Size())
   190  		}
   191  
   192  		// check signatures large than 2F+1
   193  		signedCount := 0
   194  		committedSeals := v0.committedMsgs[0].committedSeals
   195  		for _, validator := range r0.valSet.List() {
   196  			for _, seal := range committedSeals {
   197  				if bytes.Compare(validator.Address().Bytes(), seal[:common.AddressLength]) == 0 {
   198  					signedCount++
   199  					break
   200  				}
   201  			}
   202  		}
   203  		if signedCount <= 2*r0.valSet.F() {
   204  			t.Errorf("the expected signed count should be larger than %v, but got %v", 2*r0.valSet.F(), signedCount)
   205  		}
   206  		if !r0.current.IsHashLocked() {
   207  			t.Errorf("block should be locked")
   208  		}
   209  	}
   210  }
   211  
   212  // round is not checked for now
   213  func TestVerifyCommit(t *testing.T) {
   214  	// for log purpose
   215  	privateKey, _ := crypto.GenerateKey()
   216  	peer := validator.New(getPublicKeyAddress(privateKey))
   217  	valSet := validator.NewSet([]common.Address{peer.Address()}, bft.RoundRobin)
   218  
   219  	sys := NewTestSystemWithBackend(uint64(1), uint64(0))
   220  
   221  	testCases := []struct {
   222  		expected   error
   223  		commit     *bft.Subject
   224  		roundState *roundState
   225  	}{
   226  		{
   227  			// normal case
   228  			expected: nil,
   229  			commit: &bft.Subject{
   230  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   231  				Digest: newTestProposal().Hash(),
   232  			},
   233  			roundState: newTestRoundState(
   234  				&bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   235  				valSet,
   236  			),
   237  		},
   238  		{
   239  			// old message
   240  			expected: errInconsistentSubject,
   241  			commit: &bft.Subject{
   242  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   243  				Digest: newTestProposal().Hash(),
   244  			},
   245  			roundState: newTestRoundState(
   246  				&bft.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   247  				valSet,
   248  			),
   249  		},
   250  		{
   251  			// different digest
   252  			expected: errInconsistentSubject,
   253  			commit: &bft.Subject{
   254  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   255  				Digest: common.StringToHash("1234567890"),
   256  			},
   257  			roundState: newTestRoundState(
   258  				&bft.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   259  				valSet,
   260  			),
   261  		},
   262  		{
   263  			// malicious package(lack of sequence)
   264  			expected: errInconsistentSubject,
   265  			commit: &bft.Subject{
   266  				View:   &bft.View{Round: big.NewInt(0), Sequence: nil},
   267  				Digest: newTestProposal().Hash(),
   268  			},
   269  			roundState: newTestRoundState(
   270  				&bft.View{Round: big.NewInt(1), Sequence: big.NewInt(1)},
   271  				valSet,
   272  			),
   273  		},
   274  		{
   275  			// wrong prepare message with same sequence but different round
   276  			expected: errInconsistentSubject,
   277  			commit: &bft.Subject{
   278  				View:   &bft.View{Round: big.NewInt(1), Sequence: big.NewInt(0)},
   279  				Digest: newTestProposal().Hash(),
   280  			},
   281  			roundState: newTestRoundState(
   282  				&bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   283  				valSet,
   284  			),
   285  		},
   286  		{
   287  			// wrong prepare message with same round but different sequence
   288  			expected: errInconsistentSubject,
   289  			commit: &bft.Subject{
   290  				View:   &bft.View{Round: big.NewInt(0), Sequence: big.NewInt(1)},
   291  				Digest: newTestProposal().Hash(),
   292  			},
   293  			roundState: newTestRoundState(
   294  				&bft.View{Round: big.NewInt(0), Sequence: big.NewInt(0)},
   295  				valSet,
   296  			),
   297  		},
   298  	}
   299  	for i, test := range testCases {
   300  		c := sys.backends[0].engine.(*core)
   301  		c.current = test.roundState
   302  
   303  		if err := c.verifyCommit(test.commit, peer); err != nil {
   304  			if err != test.expected {
   305  				t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected)
   306  			}
   307  		}
   308  	}
   309  }