github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/consensus/istanbul/core/commit_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/big" 21 "testing" 22 23 bls "github.com/celo-org/bls-zexe/go" 24 "github.com/ethereum/go-ethereum/common" 25 "github.com/ethereum/go-ethereum/consensus/istanbul" 26 "github.com/ethereum/go-ethereum/consensus/istanbul/validator" 27 "github.com/ethereum/go-ethereum/core/types" 28 "github.com/ethereum/go-ethereum/crypto" 29 blscrypto "github.com/ethereum/go-ethereum/crypto/bls" 30 ) 31 32 func TestHandleCommit(t *testing.T) { 33 N := uint64(4) 34 F := uint64(1) 35 36 // create block 4 37 proposal := newTestProposalWithNum(4) 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 checkParentCommits bool 50 }{ 51 { 52 // normal case 53 func() *testSystem { 54 sys := NewTestSystemWithBackend(N, F) 55 56 for i, backend := range sys.backends { 57 c := backend.engine.(*core) 58 // same view as the expected one to everyone 59 c.current = newTestRoundState( 60 expectedSubject.View, 61 backend.peers, 62 ) 63 64 if i == 0 { 65 // replica 0 is the proposer 66 c.current.(*roundStateImpl).state = StatePrepared 67 } 68 } 69 return sys 70 }(), 71 nil, 72 false, 73 }, 74 { 75 // future message 76 func() *testSystem { 77 sys := NewTestSystemWithBackend(N, F) 78 79 for i, backend := range sys.backends { 80 c := backend.engine.(*core) 81 if i == 0 { 82 // replica 0 is the proposer 83 c.current = newTestRoundState( 84 expectedSubject.View, 85 backend.peers, 86 ) 87 c.current.(*roundStateImpl).state = StatePreprepared 88 } else { 89 c.current = newTestRoundState( 90 &istanbul.View{ 91 Round: big.NewInt(0), 92 // proposal from 1 round in the future 93 Sequence: big.NewInt(0).Add(proposal.Number(), common.Big1), 94 }, 95 backend.peers, 96 ) 97 } 98 } 99 return sys 100 }(), 101 errFutureMessage, 102 false, 103 }, 104 { 105 // past message 106 func() *testSystem { 107 sys := NewTestSystemWithBackend(N, F) 108 109 for i, backend := range sys.backends { 110 c := backend.engine.(*core) 111 112 if i == 0 { 113 // replica 0 is the proposer 114 c.current = newTestRoundState( 115 expectedSubject.View, 116 backend.peers, 117 ) 118 c.current.(*roundStateImpl).state = StatePreprepared 119 } else { 120 c.current = newTestRoundState( 121 &istanbul.View{ 122 Round: big.NewInt(0), 123 // we're 2 blocks before so this is indeed a 124 // very old proposal and will error as expected 125 // with an old error message 126 Sequence: big.NewInt(0).Sub(proposal.Number(), common.Big2), 127 }, 128 backend.peers, 129 ) 130 } 131 } 132 return sys 133 }(), 134 errOldMessage, 135 false, 136 }, 137 { 138 // jump state 139 func() *testSystem { 140 sys := NewTestSystemWithBackend(N, F) 141 142 for i, backend := range sys.backends { 143 c := backend.engine.(*core) 144 c.current = newTestRoundState( 145 &istanbul.View{ 146 Round: big.NewInt(0), 147 Sequence: proposal.Number(), 148 }, 149 backend.peers, 150 ) 151 152 // only replica0 stays at StatePreprepared 153 // other replicas are at StatePrepared 154 if i != 0 { 155 c.current.(*roundStateImpl).state = StatePrepared 156 } else { 157 c.current.(*roundStateImpl).state = StatePreprepared 158 } 159 } 160 return sys 161 }(), 162 nil, 163 false, 164 }, 165 { 166 // message from previous sequence and round matching last proposal 167 // this should pass the message check, but will return an error in 168 // handleCheckedCommitForPreviousSequence, because the proposal hashes won't match. 169 func() *testSystem { 170 sys := NewTestSystemWithBackend(N, F) 171 172 for i, backend := range sys.backends { 173 backend.Commit(newTestProposalWithNum(3), types.IstanbulAggregatedSeal{}, types.IstanbulEpochValidatorSetSeal{}) 174 c := backend.engine.(*core) 175 if i == 0 { 176 // replica 0 is the proposer 177 c.current = newTestRoundState( 178 expectedSubject.View, 179 backend.peers, 180 ) 181 c.current.(*roundStateImpl).state = StatePrepared 182 } else { 183 c.current = newTestRoundState( 184 &istanbul.View{ 185 Round: big.NewInt(1), 186 Sequence: big.NewInt(0).Sub(proposal.Number(), common.Big1), 187 }, 188 backend.peers, 189 ) 190 } 191 } 192 return sys 193 }(), 194 errInconsistentSubject, 195 true, 196 }, 197 // TODO: double send message 198 } 199 200 OUTER: 201 for _, test := range testCases { 202 test.system.Run(false) 203 204 v0 := test.system.backends[0] 205 r0 := v0.engine.(*core) 206 207 for i, v := range test.system.backends { 208 validator := r0.current.ValidatorSet().GetByIndex(uint64(i)) 209 privateKey, _ := bls.DeserializePrivateKey(test.system.validatorsKeys[i]) 210 defer privateKey.Destroy() 211 212 hash := PrepareCommittedSeal(v.engine.(*core).current.Proposal().Hash(), v.engine.(*core).current.Round()) 213 signature, _ := privateKey.SignMessage(hash, []byte{}, false) 214 defer signature.Destroy() 215 signatureBytes, _ := signature.Serialize() 216 committedSubject := &istanbul.CommittedSubject{ 217 Subject: v.engine.(*core).current.Subject(), 218 CommittedSeal: signatureBytes, 219 } 220 m, _ := Encode(committedSubject) 221 if err := r0.handleCommit(&istanbul.Message{ 222 Code: istanbul.MsgCommit, 223 Msg: m, 224 Address: validator.Address(), 225 Signature: []byte{}, 226 }); err != nil { 227 if err != test.expectedErr { 228 t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) 229 } 230 continue OUTER 231 } 232 } 233 234 // core should have received a parent seal from each of its neighbours 235 // how can we add our signature to the ParentCommit? Broadcast to ourselve 236 // does not make much sense 237 if test.checkParentCommits { 238 if r0.current.ParentCommits().Size() != r0.current.ValidatorSet().Size()-1 { // TODO: Maybe remove the -1? 239 t.Errorf("parent seals mismatch: have %v, want %v", r0.current.ParentCommits().Size(), r0.current.ValidatorSet().Size()-1) 240 } 241 } 242 243 // prepared is normal case 244 if r0.current.State() != StateCommitted { 245 // There are not enough commit messages in core 246 if r0.current.State() != StatePrepared { 247 t.Errorf("state mismatch: have %v, want %v", r0.current.State(), StatePrepared) 248 } 249 if r0.current.Commits().Size() > r0.current.ValidatorSet().MinQuorumSize() { 250 t.Errorf("the size of commit messages should be less than %v", r0.current.ValidatorSet().MinQuorumSize()) 251 } 252 continue 253 } 254 255 // core should have min quorum size prepare messages 256 if r0.current.Commits().Size() < r0.current.ValidatorSet().MinQuorumSize() { 257 t.Errorf("the size of commit messages should be greater than or equal to minQuorumSize: size %v", r0.current.Commits().Size()) 258 } 259 260 // check signatures large than MinQuorumSize 261 signedCount := 0 262 for i := 0; i < r0.current.ValidatorSet().Size(); i++ { 263 if v0.committedMsgs[0].aggregatedSeal.Bitmap.Bit(i) == 1 { 264 signedCount++ 265 } 266 } 267 if signedCount < r0.current.ValidatorSet().MinQuorumSize() { 268 t.Errorf("the expected signed count should be greater than or equal to %v, but got %v", r0.current.ValidatorSet().MinQuorumSize(), signedCount) 269 } 270 } 271 } 272 273 // round is not checked for now 274 func TestVerifyCommit(t *testing.T) { 275 // for log purpose 276 privateKey, _ := crypto.GenerateKey() 277 blsPrivateKey, _ := blscrypto.ECDSAToBLS(privateKey) 278 blsPublicKey, _ := blscrypto.PrivateToPublic(blsPrivateKey) 279 peer := validator.New(getPublicKeyAddress(privateKey), blsPublicKey) 280 valSet := validator.NewSet([]istanbul.ValidatorData{ 281 { 282 peer.Address(), 283 blsPublicKey, 284 }, 285 }) 286 // }, istanbul.RoundRobin) 287 288 sys := NewTestSystemWithBackend(uint64(1), uint64(0)) 289 290 testCases := []struct { 291 expected error 292 commit *istanbul.CommittedSubject 293 roundState RoundState 294 }{ 295 { 296 // normal case 297 expected: nil, 298 commit: &istanbul.CommittedSubject{ 299 Subject: &istanbul.Subject{ 300 View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, 301 Digest: newTestProposal().Hash(), 302 }, 303 }, 304 roundState: newTestRoundState( 305 &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, 306 valSet, 307 ), 308 }, 309 { 310 // old message 311 expected: errInconsistentSubject, 312 commit: &istanbul.CommittedSubject{ 313 Subject: &istanbul.Subject{ 314 View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, 315 Digest: newTestProposal().Hash(), 316 }, 317 }, 318 roundState: newTestRoundState( 319 &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, 320 valSet, 321 ), 322 }, 323 { 324 // different digest 325 expected: errInconsistentSubject, 326 commit: &istanbul.CommittedSubject{ 327 Subject: &istanbul.Subject{ 328 View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, 329 Digest: common.BytesToHash([]byte("1234567890")), 330 }, 331 }, 332 roundState: newTestRoundState( 333 &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, 334 valSet, 335 ), 336 }, 337 { 338 // malicious package(lack of sequence) 339 expected: errInconsistentSubject, 340 commit: &istanbul.CommittedSubject{ 341 Subject: &istanbul.Subject{ 342 View: &istanbul.View{Round: big.NewInt(0), Sequence: nil}, 343 Digest: newTestProposal().Hash(), 344 }, 345 }, 346 roundState: newTestRoundState( 347 &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, 348 valSet, 349 ), 350 }, 351 { 352 // wrong prepare message with same sequence but different round 353 expected: errInconsistentSubject, 354 commit: &istanbul.CommittedSubject{ 355 Subject: &istanbul.Subject{ 356 View: &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(0)}, 357 Digest: newTestProposal().Hash(), 358 }, 359 }, 360 roundState: newTestRoundState( 361 &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, 362 valSet, 363 ), 364 }, 365 { 366 // wrong prepare message with same round but different sequence 367 expected: errInconsistentSubject, 368 commit: &istanbul.CommittedSubject{ 369 Subject: &istanbul.Subject{ 370 View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(1)}, 371 Digest: newTestProposal().Hash(), 372 }, 373 }, 374 roundState: newTestRoundState( 375 &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, 376 valSet, 377 ), 378 }, 379 } 380 for i, test := range testCases { 381 c := sys.backends[0].engine.(*core) 382 c.current = test.roundState 383 384 if err := c.verifyCommit(test.commit); err != nil { 385 if err != test.expected { 386 t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected) 387 } 388 } 389 } 390 }