github.com/ConsenSys/Quorum@v20.10.0+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" 21 "math/big" 22 "reflect" 23 "testing" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/consensus/istanbul" 27 "github.com/ethereum/go-ethereum/consensus/istanbul/validator" 28 "github.com/ethereum/go-ethereum/crypto" 29 ) 30 31 func TestHandlePrepare(t *testing.T) { 32 N := uint64(4) 33 F := uint64(1) 34 35 proposal := newTestProposal() 36 expectedSubject := &istanbul.Subject{ 37 View: &istanbul.View{ 38 Round: big.NewInt(0), 39 Sequence: proposal.Number(), 40 }, 41 Digest: proposal.Hash(), 42 } 43 44 testCases := []struct { 45 system *testSystem 46 expectedErr error 47 }{ 48 { 49 // normal case 50 func() *testSystem { 51 sys := NewTestSystemWithBackend(N, F) 52 53 for i, backend := range sys.backends { 54 c := backend.engine.(*core) 55 c.valSet = backend.peers 56 c.current = newTestRoundState( 57 &istanbul.View{ 58 Round: big.NewInt(0), 59 Sequence: big.NewInt(1), 60 }, 61 c.valSet, 62 ) 63 64 if i == 0 { 65 // replica 0 is the proposer 66 c.state = StatePreprepared 67 } 68 } 69 return sys 70 }(), 71 nil, 72 }, 73 { 74 // future message 75 func() *testSystem { 76 sys := NewTestSystemWithBackend(N, F) 77 78 for i, backend := range sys.backends { 79 c := backend.engine.(*core) 80 c.valSet = backend.peers 81 if i == 0 { 82 // replica 0 is the proposer 83 c.current = newTestRoundState( 84 expectedSubject.View, 85 c.valSet, 86 ) 87 c.state = StatePreprepared 88 } else { 89 c.current = newTestRoundState( 90 &istanbul.View{ 91 Round: big.NewInt(2), 92 Sequence: big.NewInt(3), 93 }, 94 c.valSet, 95 ) 96 } 97 } 98 return sys 99 }(), 100 errFutureMessage, 101 }, 102 { 103 // subject not match 104 func() *testSystem { 105 sys := NewTestSystemWithBackend(N, F) 106 107 for i, backend := range sys.backends { 108 c := backend.engine.(*core) 109 c.valSet = backend.peers 110 if i == 0 { 111 // replica 0 is the proposer 112 c.current = newTestRoundState( 113 expectedSubject.View, 114 c.valSet, 115 ) 116 c.state = StatePreprepared 117 } else { 118 c.current = newTestRoundState( 119 &istanbul.View{ 120 Round: big.NewInt(0), 121 Sequence: big.NewInt(0), 122 }, 123 c.valSet, 124 ) 125 } 126 } 127 return sys 128 }(), 129 errOldMessage, 130 }, 131 { 132 // subject not match 133 func() *testSystem { 134 sys := NewTestSystemWithBackend(N, F) 135 136 for i, backend := range sys.backends { 137 c := backend.engine.(*core) 138 c.valSet = backend.peers 139 if i == 0 { 140 // replica 0 is the proposer 141 c.current = newTestRoundState( 142 expectedSubject.View, 143 c.valSet, 144 ) 145 c.state = StatePreprepared 146 } else { 147 c.current = newTestRoundState( 148 &istanbul.View{ 149 Round: big.NewInt(0), 150 Sequence: big.NewInt(1)}, 151 c.valSet, 152 ) 153 } 154 } 155 return sys 156 }(), 157 errInconsistentSubject, 158 }, 159 { 160 func() *testSystem { 161 sys := NewTestSystemWithBackend(N, F) 162 163 // save less than Ceil(2*N/3) replica 164 sys.backends = sys.backends[int(math.Ceil(float64(2*N)/3)):] 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() >= r0.QuorumSize() { 218 t.Errorf("the size of PREPARE messages should be less than %v", r0.QuorumSize()) 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 before Ceil2Nby3Block and Ceil(2N/3) after Ceil2Nby3Block PREPARE messages 228 if r0.current.Prepares.Size() < r0.QuorumSize() { 229 t.Errorf("the size of PREPARE messages should be larger than 2F+1 or ceil(2N/3): size %v", r0.current.Commits.Size()) 230 } 231 232 // a message will be delivered to backend if ceil(2N/3) 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 }