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