github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/core/justification_test.go (about) 1 package core 2 3 import ( 4 "fmt" 5 "math/big" 6 "math/rand" 7 "testing" 8 "time" 9 10 "github.com/electroneum/electroneum-sc/common" 11 "github.com/electroneum/electroneum-sc/consensus/istanbul" 12 qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types" 13 "github.com/electroneum/electroneum-sc/consensus/istanbul/validator" 14 "github.com/electroneum/electroneum-sc/core/types" 15 "github.com/electroneum/electroneum-sc/crypto" 16 ) 17 18 // Tests combinations of justifications that evaluate to true. 19 func TestJustifyTrue(t *testing.T) { 20 for quorumSize := 3; quorumSize <= 10; quorumSize++ { 21 // All ROUND-CHANGE messages have pr/pb nil 22 testParameterizedCase(t, quorumSize, quorumSize, 0, 0, 0, 0, 0, true) 23 24 // Some ROUND-CHANGE message has pr/pb not nil 25 for equal := 1; equal <= quorumSize; equal++ { 26 for less := 0; less <= quorumSize-equal; less++ { 27 nil := quorumSize - equal - less 28 testParameterizedCase(t, quorumSize, nil, equal, less, 0, quorumSize, 0, true) 29 } 30 } 31 } 32 } 33 34 // Tests combinations of justifications that evaluate to false. 35 func TestJustifyFalse(t *testing.T) { 36 for quorumSize := 3; quorumSize <= 10; quorumSize++ { 37 // Total ROUND-CHANGE messages less than quorumSize 38 // all have pr/pb nil 39 for totalRoundChange := 0; totalRoundChange < quorumSize; totalRoundChange++ { 40 testParameterizedCase(t, quorumSize, totalRoundChange, 0, 0, 0, 0, 0, false) 41 } 42 // some has pr/pb not nil 43 for totalRoundChange := 0; totalRoundChange < quorumSize; totalRoundChange++ { 44 for equal := 1; equal <= totalRoundChange; equal++ { 45 for less := 0; less <= totalRoundChange-equal; less++ { 46 nil := totalRoundChange - equal - less 47 testParameterizedCase(t, quorumSize, nil, equal, less, 0, quorumSize, 0, false) 48 } 49 } 50 } 51 52 // Total ROUND-CHANGE messages equal to quorumSize 53 for equal := 1; equal <= quorumSize; equal++ { 54 for less := 0; less <= quorumSize-equal; less++ { 55 nil := quorumSize - equal - less 56 57 // Total PREPARE messages less than quorumSize 58 for total := 0; total < quorumSize; total++ { 59 testParameterizedCase(t, quorumSize, nil, equal, less, 0, total, quorumSize-total, false) 60 } 61 62 // Total PREPARE messages equal to quorumSize and some PREPARE message has round different than others 63 for different := 1; different <= quorumSize; different++ { 64 testParameterizedCase(t, quorumSize, nil, equal, less, 0, quorumSize-different, different, false) 65 } 66 } 67 } 68 } 69 } 70 71 func testParameterizedCase( 72 t *testing.T, 73 quorumSize int, 74 rcForNil int, 75 rcEqualToTargetRound int, 76 rcLowerThanTargetRound int, 77 rcHigherThanTargetRound int, 78 preparesForTargetRound int, 79 preparesNotForTargetRound int, 80 messageJustified bool) { 81 pp := istanbul.NewRoundRobinProposerPolicy() 82 pp.Use(istanbul.ValidatorSortByByte()) 83 validatorSet := validator.NewSet(generateValidators(quorumSize), pp) 84 block := makeBlock(1) 85 var round int64 = 10 86 var targetPreparedRound int64 = 5 87 88 rng := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) 89 90 if rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound+rcHigherThanTargetRound > quorumSize { 91 t.Errorf("rcForNil (%v) + rcEqualToTargetRound (%v) + rcLowerThanTargetRound (%v) + rcHigherThanTargetRound (%v) > quorumSize (%v)", 92 rcForNil, rcEqualToTargetRound, rcLowerThanTargetRound, rcHigherThanTargetRound, quorumSize) 93 } 94 95 if preparesForTargetRound+preparesNotForTargetRound > quorumSize { 96 t.Errorf("preparesForTargetRound (%v) + preparesNotForTargetRound (%v) > quorumSize (%v)", preparesForTargetRound, preparesNotForTargetRound, quorumSize) 97 } 98 99 // ROUND-CHANGE messages 100 roundChangeMessages := make([]*qbfttypes.SignedRoundChangePayload, 0) 101 for index, validator := range validatorSet.List() { 102 var m *qbfttypes.SignedRoundChangePayload 103 if index < rcForNil { 104 m = createRoundChangeMessage(validator.Address(), round, 0, nil) 105 } else if index >= rcForNil && index < rcForNil+rcEqualToTargetRound { 106 m = createRoundChangeMessage(validator.Address(), round, targetPreparedRound, block) 107 } else if index >= rcForNil+rcEqualToTargetRound && index < rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound { 108 m = createRoundChangeMessage(validator.Address(), round, int64(rng.Intn(int(targetPreparedRound)-1)+1), block) 109 } else if index >= rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound && index < rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound+rcHigherThanTargetRound { 110 m = createRoundChangeMessage(validator.Address(), round, int64(rng.Intn(int(targetPreparedRound))+int(targetPreparedRound)+1), block) 111 } else { 112 break 113 } 114 roundChangeMessages = append(roundChangeMessages, m) 115 } 116 117 // PREPARE messages 118 prepareMessages := make([]*qbfttypes.Prepare, 0) 119 for index, validator := range validatorSet.List() { 120 var m *qbfttypes.Prepare 121 if index < preparesForTargetRound { 122 m = createPrepareMessage(validator.Address(), targetPreparedRound, block) 123 } else if index >= preparesForTargetRound && index < preparesForTargetRound+preparesNotForTargetRound { 124 notTargetPreparedRound := targetPreparedRound 125 for notTargetPreparedRound == targetPreparedRound { 126 notTargetPreparedRound = rng.Int63() 127 } 128 m = createPrepareMessage(validator.Address(), notTargetPreparedRound, block) 129 } else { 130 break 131 } 132 prepareMessages = append(prepareMessages, m) 133 } 134 135 for _, m := range roundChangeMessages { 136 fmt.Printf("RC %v\n", m) 137 } 138 for _, m := range prepareMessages { 139 fmt.Printf("PR %v\n", m) 140 } 141 fmt.Println("roundChangeMessages", roundChangeMessages, len(roundChangeMessages)) 142 if err := isJustified(block, roundChangeMessages, prepareMessages, quorumSize); err == nil && !messageJustified { 143 t.Errorf("quorumSize = %v, rcForNil = %v, rcEqualToTargetRound = %v, rcLowerThanTargetRound = %v, rcHigherThanTargetRound = %v, preparesForTargetRound = %v, preparesNotForTargetRound = %v (Expected: %v, Actual: %v)", 144 quorumSize, rcForNil, rcEqualToTargetRound, rcLowerThanTargetRound, rcHigherThanTargetRound, preparesForTargetRound, preparesNotForTargetRound, err == nil, !messageJustified) 145 } 146 } 147 148 func createRoundChangeMessage(from common.Address, round int64, preparedRound int64, preparedBlock istanbul.Proposal) *qbfttypes.SignedRoundChangePayload { 149 m := qbfttypes.NewRoundChange(big.NewInt(1), big.NewInt(1), big.NewInt(preparedRound), preparedBlock, false) 150 m.SetSource(from) 151 return &m.SignedRoundChangePayload 152 } 153 154 func createPrepareMessage(from common.Address, round int64, preparedBlock istanbul.Proposal) *qbfttypes.Prepare { 155 return qbfttypes.NewPrepareWithSigAndSource(big.NewInt(1), big.NewInt(round), preparedBlock.Hash(), nil, from) 156 } 157 158 func generateValidators(n int) []common.Address { 159 vals := make([]common.Address, 0) 160 for i := 0; i < n; i++ { 161 privateKey, _ := crypto.GenerateKey() 162 vals = append(vals, crypto.PubkeyToAddress(privateKey.PublicKey)) 163 } 164 return vals 165 } 166 167 func makeBlock(number int64) *types.Block { 168 header := &types.Header{ 169 Difficulty: big.NewInt(0), 170 Number: big.NewInt(number), 171 GasLimit: 0, 172 GasUsed: 0, 173 Time: 0, 174 } 175 block := &types.Block{} 176 return block.WithSeal(header) 177 }