github.com/klaytn/klaytn@v1.12.1/consensus/istanbul/core/handler_test.go (about) 1 package core 2 3 import ( 4 "crypto/ecdsa" 5 "fmt" 6 "io" 7 "math/big" 8 "math/rand" 9 "os" 10 "testing" 11 "time" 12 13 "github.com/golang/mock/gomock" 14 "github.com/klaytn/klaytn/blockchain" 15 "github.com/klaytn/klaytn/blockchain/types" 16 "github.com/klaytn/klaytn/common" 17 "github.com/klaytn/klaytn/consensus/istanbul" 18 mock_istanbul "github.com/klaytn/klaytn/consensus/istanbul/mocks" 19 "github.com/klaytn/klaytn/consensus/istanbul/validator" 20 "github.com/klaytn/klaytn/crypto" 21 "github.com/klaytn/klaytn/crypto/sha3" 22 "github.com/klaytn/klaytn/event" 23 "github.com/klaytn/klaytn/fork" 24 "github.com/klaytn/klaytn/log" 25 "github.com/klaytn/klaytn/log/term" 26 "github.com/klaytn/klaytn/params" 27 "github.com/klaytn/klaytn/rlp" 28 "github.com/mattn/go-colorable" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 ) 32 33 // newMockBackend create a mock-backend initialized with default values 34 func newMockBackend(t *testing.T, validatorAddrs []common.Address) (*mock_istanbul.MockBackend, *gomock.Controller) { 35 committeeSize := uint64(len(validatorAddrs) / 3) 36 37 istExtra := &types.IstanbulExtra{ 38 Validators: validatorAddrs, 39 Seal: []byte{}, 40 CommittedSeal: [][]byte{}, 41 } 42 extra, err := rlp.EncodeToBytes(istExtra) 43 if err != nil { 44 t.Fatal(err) 45 } 46 47 initBlock := types.NewBlockWithHeader(&types.Header{ 48 ParentHash: common.Hash{}, 49 Number: common.Big0, 50 GasUsed: 0, 51 Extra: append(make([]byte, types.IstanbulExtraVanity), extra...), 52 Time: new(big.Int).SetUint64(1234), 53 BlockScore: common.Big0, 54 }) 55 56 eventMux := new(event.TypeMux) 57 validatorSet := validator.NewWeightedCouncil(validatorAddrs, nil, validatorAddrs, nil, nil, 58 istanbul.WeightedRandom, committeeSize, 0, 0, &blockchain.BlockChain{}) 59 60 mockCtrl := gomock.NewController(t) 61 mockBackend := mock_istanbul.NewMockBackend(mockCtrl) 62 63 // Consider the last proposal is "initBlock" and the owner of mockBackend is validatorAddrs[0] 64 mockBackend.EXPECT().Address().Return(validatorAddrs[0]).AnyTimes() 65 mockBackend.EXPECT().LastProposal().Return(initBlock, validatorAddrs[0]).AnyTimes() 66 mockBackend.EXPECT().Validators(initBlock).Return(validatorSet).AnyTimes() 67 mockBackend.EXPECT().NodeType().Return(common.CONSENSUSNODE).AnyTimes() 68 69 // Set an eventMux in which istanbul core will subscribe istanbul events 70 mockBackend.EXPECT().EventMux().Return(eventMux).AnyTimes() 71 72 // Just for bypassing an unused function 73 mockBackend.EXPECT().SetCurrentView(gomock.Any()).Return().AnyTimes() 74 75 // Always return nil for broadcasting related functions 76 mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).AnyTimes() 77 mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 78 mockBackend.EXPECT().GossipSubPeer(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 79 80 // Verify checks whether the proposal of the preprepare message is a valid block. Consider it valid. 81 mockBackend.EXPECT().Verify(gomock.Any()).Return(time.Duration(0), nil).AnyTimes() 82 83 return mockBackend, mockCtrl 84 } 85 86 // genValidators returns a set of addresses and corresponding keys used for generating a validator set 87 func genValidators(n int) ([]common.Address, map[common.Address]*ecdsa.PrivateKey) { 88 addrs := make([]common.Address, n) 89 keyMap := make(map[common.Address]*ecdsa.PrivateKey, n) 90 91 for i := 0; i < n; i++ { 92 key, _ := crypto.GenerateKey() 93 addrs[i] = crypto.PubkeyToAddress(key.PublicKey) 94 keyMap[addrs[i]] = key 95 } 96 return addrs, keyMap 97 } 98 99 // getRandomValidator selects a validator in the given validator set. 100 // `isCommittee` determines whether it returns a committee or a non-committee. 101 func getRandomValidator(isCommittee bool, valSet istanbul.ValidatorSet, prevHash common.Hash, view *istanbul.View) istanbul.Validator { 102 committee := valSet.SubList(prevHash, view) 103 104 if isCommittee { 105 return committee[rand.Int()%(len(committee)-1)] 106 } 107 108 for _, val := range valSet.List() { 109 for _, com := range committee { 110 if val.Address() == com.Address() { 111 isCommittee = true 112 } 113 } 114 if !isCommittee { 115 return val 116 } 117 isCommittee = false 118 } 119 120 // it should not be happened 121 return nil 122 } 123 124 // signBlock signs the given block with the given private key 125 func signBlock(block *types.Block, privateKey *ecdsa.PrivateKey) (*types.Block, error) { 126 var hash common.Hash 127 header := block.Header() 128 hasher := sha3.NewKeccak256() 129 130 // Clean seal is required for calculating proposer seal 131 rlp.Encode(hasher, types.IstanbulFilteredHeader(header, false)) 132 hasher.Sum(hash[:0]) 133 134 seal, err := crypto.Sign(crypto.Keccak256([]byte(hash.Bytes())), privateKey) 135 if err != nil { 136 return nil, err 137 } 138 139 istanbulExtra, err := types.ExtractIstanbulExtra(header) 140 if err != nil { 141 return nil, err 142 } 143 istanbulExtra.Seal = seal 144 145 payload, err := rlp.EncodeToBytes(&istanbulExtra) 146 if err != nil { 147 return nil, err 148 } 149 150 header.Extra = append(header.Extra[:types.IstanbulExtraVanity], payload...) 151 return block.WithSeal(header), nil 152 } 153 154 // genBlock generates a signed block indicating prevBlock with ParentHash 155 func genBlock(prevBlock *types.Block, signerKey *ecdsa.PrivateKey) (*types.Block, error) { 156 block := types.NewBlockWithHeader(&types.Header{ 157 ParentHash: prevBlock.Hash(), 158 Number: new(big.Int).Add(prevBlock.Number(), common.Big1), 159 GasUsed: 0, 160 Extra: prevBlock.Extra(), 161 Time: new(big.Int).Add(prevBlock.Time(), common.Big1), 162 BlockScore: new(big.Int).Add(prevBlock.BlockScore(), common.Big1), 163 }) 164 return signBlock(block, signerKey) 165 } 166 167 // genBlockParams generates a signed block indicating prevBlock with ParentHash with additional parameters. 168 func genBlockParams(prevBlock *types.Block, signerKey *ecdsa.PrivateKey, gasUsed uint64, time int64, blockScore int64) (*types.Block, error) { 169 block := types.NewBlockWithHeader(&types.Header{ 170 ParentHash: prevBlock.Hash(), 171 Number: new(big.Int).Add(prevBlock.Number(), common.Big1), 172 GasUsed: gasUsed, 173 Extra: prevBlock.Extra(), 174 Time: new(big.Int).Add(prevBlock.Time(), big.NewInt(time)), 175 BlockScore: new(big.Int).Add(prevBlock.BlockScore(), big.NewInt(blockScore)), 176 }) 177 return signBlock(block, signerKey) 178 } 179 180 // genIstanbulMsg generates an istanbul message with given values 181 func genIstanbulMsg(msgType uint64, prevHash common.Hash, proposal *types.Block, signerAddr common.Address, signerKey *ecdsa.PrivateKey) (istanbul.MessageEvent, error) { 182 var subject interface{} 183 184 if msgType == msgPreprepare { 185 subject = &istanbul.Preprepare{ 186 View: &istanbul.View{ 187 Round: big.NewInt(0), 188 Sequence: proposal.Number(), 189 }, 190 Proposal: proposal, 191 } 192 } else { 193 subject = &istanbul.Subject{ 194 View: &istanbul.View{ 195 Round: big.NewInt(0), 196 Sequence: proposal.Number(), 197 }, 198 Digest: proposal.Hash(), 199 PrevHash: prevHash, 200 } 201 } 202 203 encodedSubject, err := Encode(subject) 204 if err != nil { 205 return istanbul.MessageEvent{}, err 206 } 207 208 msg := &message{ 209 Hash: prevHash, 210 Code: msgType, 211 Msg: encodedSubject, 212 Address: signerAddr, 213 } 214 215 data, err := msg.PayloadNoSig() 216 if err != nil { 217 return istanbul.MessageEvent{}, err 218 } 219 220 msg.Signature, err = crypto.Sign(crypto.Keccak256([]byte(data)), signerKey) 221 if err != nil { 222 return istanbul.MessageEvent{}, err 223 } 224 225 encodedPayload, err := msg.Payload() 226 if err != nil { 227 return istanbul.MessageEvent{}, err 228 } 229 230 istMsg := istanbul.MessageEvent{ 231 Hash: msg.Hash, 232 Payload: encodedPayload, 233 } 234 235 return istMsg, nil 236 } 237 238 // TestCore_handleEvents_scenario_invalidSender tests `handleEvents` function of `istanbul.core` with a scenario. 239 // It posts an invalid message and a valid message of each istanbul message type. 240 func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { 241 fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{}) 242 defer fork.ClearHardForkBlockNumberConfig() 243 244 validatorAddrs, validatorKeyMap := genValidators(30) 245 mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) 246 defer mockCtrl.Finish() 247 248 istConfig := istanbul.DefaultConfig 249 istConfig.ProposerPolicy = istanbul.WeightedRandom 250 251 // When the istanbul core started, a message handling loop in `handleEvents()` waits istanbul messages 252 istCore := New(mockBackend, istConfig).(*core) 253 if err := istCore.Start(); err != nil { 254 t.Fatal(err) 255 } 256 defer istCore.Stop() 257 258 // Get variables initialized on `newMockBackend()` 259 eventMux := mockBackend.EventMux() 260 lastProposal, _ := mockBackend.LastProposal() 261 lastBlock := lastProposal.(*types.Block) 262 validators := mockBackend.Validators(lastBlock) 263 264 // Preprepare message originated from invalid sender 265 { 266 msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) 267 msgSenderKey := validatorKeyMap[msgSender.Address()] 268 269 newProposal, err := genBlock(lastBlock, msgSenderKey) 270 if err != nil { 271 t.Fatal(err) 272 } 273 274 istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastProposal.Hash(), newProposal, msgSender.Address(), msgSenderKey) 275 if err != nil { 276 t.Fatal(err) 277 } 278 279 if err := eventMux.Post(istanbulMsg); err != nil { 280 t.Fatal(err) 281 } 282 283 time.Sleep(time.Second) 284 assert.Nil(t, istCore.current.Preprepare) 285 } 286 287 // Preprepare message originated from valid sender and set a new proposal in the istanbul core 288 { 289 msgSender := validators.GetProposer() 290 msgSenderKey := validatorKeyMap[msgSender.Address()] 291 292 newProposal, err := genBlock(lastBlock, msgSenderKey) 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, msgSender.Address(), msgSenderKey) 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 if err := eventMux.Post(istanbulMsg); err != nil { 303 t.Fatal(err) 304 } 305 306 time.Sleep(time.Second) 307 assert.Equal(t, istCore.current.Preprepare.Proposal.Header().String(), newProposal.Header().String()) 308 } 309 310 // Prepare message originated from invalid sender 311 { 312 msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) 313 msgSenderKey := validatorKeyMap[msgSender.Address()] 314 315 istanbulMsg, err := genIstanbulMsg(msgPrepare, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) 316 if err != nil { 317 t.Fatal(err) 318 } 319 320 if err := eventMux.Post(istanbulMsg); err != nil { 321 t.Fatal(err) 322 } 323 324 time.Sleep(time.Second) 325 assert.Equal(t, 0, len(istCore.current.Prepares.messages)) 326 } 327 328 // Prepare message originated from valid sender 329 { 330 msgSender := getRandomValidator(true, validators, lastBlock.Hash(), istCore.currentView()) 331 msgSenderKey := validatorKeyMap[msgSender.Address()] 332 333 istanbulMsg, err := genIstanbulMsg(msgPrepare, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) 334 if err != nil { 335 t.Fatal(err) 336 } 337 338 if err := eventMux.Post(istanbulMsg); err != nil { 339 t.Fatal(err) 340 } 341 342 time.Sleep(time.Second) 343 assert.Equal(t, 1, len(istCore.current.Prepares.messages)) 344 } 345 346 // Commit message originated from invalid sender 347 { 348 msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) 349 msgSenderKey := validatorKeyMap[msgSender.Address()] 350 351 istanbulMsg, err := genIstanbulMsg(msgCommit, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) 352 if err != nil { 353 t.Fatal(err) 354 } 355 356 if err := eventMux.Post(istanbulMsg); err != nil { 357 t.Fatal(err) 358 } 359 360 time.Sleep(time.Second) 361 assert.Equal(t, 0, len(istCore.current.Commits.messages)) 362 } 363 364 // Commit message originated from valid sender 365 { 366 msgSender := getRandomValidator(true, validators, lastBlock.Hash(), istCore.currentView()) 367 msgSenderKey := validatorKeyMap[msgSender.Address()] 368 369 istanbulMsg, err := genIstanbulMsg(msgCommit, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) 370 if err != nil { 371 t.Fatal(err) 372 } 373 374 if err := eventMux.Post(istanbulMsg); err != nil { 375 t.Fatal(err) 376 } 377 378 time.Sleep(time.Second) 379 assert.Equal(t, 1, len(istCore.current.Commits.messages)) 380 } 381 382 //// RoundChange message originated from invalid sender 383 //{ 384 // msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) 385 // msgSenderKey := validatorKeyMap[msgSender.Address()] 386 // 387 // istanbulMsg, err := genIstanbulMsg(msgRoundChange, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) 388 // if err != nil { 389 // t.Fatal(err) 390 // } 391 // 392 // if err := eventMux.Post(istanbulMsg); err != nil { 393 // t.Fatal(err) 394 // } 395 // 396 // time.Sleep(time.Second) 397 // assert.Nil(t, istCore.roundChangeSet.roundChanges[0]) // round is set to 0 in this test 398 //} 399 400 // RoundChange message originated from valid sender 401 { 402 msgSender := getRandomValidator(true, validators, lastBlock.Hash(), istCore.currentView()) 403 msgSenderKey := validatorKeyMap[msgSender.Address()] 404 405 istanbulMsg, err := genIstanbulMsg(msgRoundChange, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) 406 if err != nil { 407 t.Fatal(err) 408 } 409 410 if err := eventMux.Post(istanbulMsg); err != nil { 411 t.Fatal(err) 412 } 413 414 time.Sleep(time.Second) 415 assert.Equal(t, 1, len(istCore.roundChangeSet.roundChanges[0].messages)) // round is set to 0 in this test 416 } 417 } 418 419 func TestCore_handlerMsg(t *testing.T) { 420 fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{}) 421 defer fork.ClearHardForkBlockNumberConfig() 422 423 validatorAddrs, validatorKeyMap := genValidators(10) 424 mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) 425 defer mockCtrl.Finish() 426 427 istConfig := istanbul.DefaultConfig 428 istConfig.ProposerPolicy = istanbul.WeightedRandom 429 430 istCore := New(mockBackend, istConfig).(*core) 431 if err := istCore.Start(); err != nil { 432 t.Fatal(err) 433 } 434 defer istCore.Stop() 435 436 lastProposal, _ := mockBackend.LastProposal() 437 lastBlock := lastProposal.(*types.Block) 438 validators := mockBackend.Validators(lastBlock) 439 440 // invalid format 441 { 442 invalidMsg := []byte{0x1, 0x2, 0x3, 0x4} 443 err := istCore.handleMsg(invalidMsg) 444 assert.NotNil(t, err) 445 } 446 447 // invali sender (non-validator) 448 { 449 newAddr, keyMap := genValidators(1) 450 nonValidatorAddr := newAddr[0] 451 nonValidatorKey := keyMap[nonValidatorAddr] 452 453 newProposal, err := genBlock(lastBlock, nonValidatorKey) 454 if err != nil { 455 t.Fatal(err) 456 } 457 458 istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, nonValidatorAddr, nonValidatorKey) 459 if err != nil { 460 t.Fatal(err) 461 } 462 463 err = istCore.handleMsg(istanbulMsg.Payload) 464 assert.NotNil(t, err) 465 } 466 467 // valid message 468 { 469 msgSender := validators.GetProposer() 470 msgSenderKey := validatorKeyMap[msgSender.Address()] 471 472 newProposal, err := genBlock(lastBlock, msgSenderKey) 473 if err != nil { 474 t.Fatal(err) 475 } 476 477 istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, msgSender.Address(), msgSenderKey) 478 if err != nil { 479 t.Fatal(err) 480 } 481 482 err = istCore.handleMsg(istanbulMsg.Payload) 483 assert.Nil(t, err) 484 } 485 } 486 487 // TODO-Klaytn: To enable logging in the test code, we can use the following function. 488 // This function will be moved to somewhere utility functions are located. 489 func enableLog() { 490 usecolor := term.IsTty(os.Stderr.Fd()) && os.Getenv("TERM") != "dumb" 491 output := io.Writer(os.Stderr) 492 if usecolor { 493 output = colorable.NewColorableStderr() 494 } 495 glogger := log.NewGlogHandler(log.StreamHandler(output, log.TerminalFormat(usecolor))) 496 log.PrintOrigins(true) 497 log.ChangeGlobalLogLevel(glogger, log.Lvl(3)) 498 glogger.Vmodule("") 499 glogger.BacktraceAt("") 500 log.Root().SetHandler(glogger) 501 } 502 503 // splitSubList splits a committee into two groups w/o proposer 504 // one for n nodes, the other for len(committee) - n - 1 nodes 505 func splitSubList(committee []istanbul.Validator, n int, proposerAddr common.Address) ([]istanbul.Validator, []istanbul.Validator) { 506 var subCN, remainingCN []istanbul.Validator 507 508 for _, val := range committee { 509 if val.Address() == proposerAddr { 510 // proposer is not included in any group 511 continue 512 } 513 if len(subCN) < n { 514 subCN = append(subCN, val) 515 } else { 516 remainingCN = append(remainingCN, val) 517 } 518 } 519 return subCN, remainingCN 520 } 521 522 // Simulate a proposer that receives messages from disagreeing groups of CNs. 523 func simulateMaliciousCN(t *testing.T, numValidators int, numMalicious int) State { 524 if testing.Verbose() { 525 enableLog() 526 } 527 528 fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{}) 529 defer fork.ClearHardForkBlockNumberConfig() 530 531 // Note that genValidators(n) will generate n/3 validators. 532 // We want n validators, thus calling genValidators(3n). 533 validatorAddrs, validatorKeyMap := genValidators(numValidators * 3) 534 535 // Add more EXPECT()s to remove unexpected call error 536 mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) 537 mockBackend.EXPECT().Commit(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 538 mockBackend.EXPECT().HasBadProposal(gomock.Any()).Return(true).AnyTimes() 539 defer mockCtrl.Finish() 540 541 var ( 542 // it creates two pre-defined blocks: one for benign CNs, the other for the malicious 543 // newProposal is a block which the proposer has created 544 // malProposal is an incorrect block that malicious CNs use to try stop consensus 545 lastProposal, _ = mockBackend.LastProposal() 546 lastBlock = lastProposal.(*types.Block) 547 validators = mockBackend.Validators(lastBlock) 548 proposer = validators.GetProposer() 549 proposerKey = validatorKeyMap[proposer.Address()] 550 // the proposer generates a block as newProposal 551 // malicious CNs does not accept the proposer's block and use malProposal's hash value for consensus 552 newProposal, _ = genBlockParams(lastBlock, proposerKey, 0, 1, 1) 553 malProposal, _ = genBlockParams(lastBlock, proposerKey, 0, 0, 0) 554 ) 555 556 // Start istanbul core 557 istConfig := istanbul.DefaultConfig 558 istConfig.ProposerPolicy = istanbul.WeightedRandom 559 istCore := New(mockBackend, istConfig).(*core) 560 err := istCore.Start() 561 require.Nil(t, err) 562 defer istCore.Stop() 563 564 // Step 1 - Pre-prepare with correct message 565 566 // Create pre-prepare message 567 istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, proposer.Address(), proposerKey) 568 require.Nil(t, err) 569 570 // Handle pre-prepare message 571 err = istCore.handleMsg(istanbulMsg.Payload) 572 require.Nil(t, err) 573 574 // splitSubList split current committee into benign CNs and malicious CNs 575 subList := validators.SubList(lastBlock.Hash(), istCore.currentView()) 576 maliciousCNs, benignCNs := splitSubList(subList, numMalicious, proposer.Address()) 577 benignCNs = append(benignCNs, proposer) 578 579 // Shortcut for sending consensus message to everyone in `CNList` 580 sendMessages := func(state uint64, proposal *types.Block, CNList []istanbul.Validator) { 581 for _, val := range CNList { 582 valKey := validatorKeyMap[val.Address()] 583 istanbulMsg, err := genIstanbulMsg(state, lastBlock.Hash(), proposal, val.Address(), valKey) 584 assert.Nil(t, err) 585 err = istCore.handleMsg(istanbulMsg.Payload) 586 // assert.Nil(t, err) 587 } 588 } 589 590 // Step 2 - Receive disagreeing prepare messages 591 592 sendMessages(msgPrepare, newProposal, benignCNs) 593 sendMessages(msgPrepare, malProposal, maliciousCNs) 594 595 if istCore.state.Cmp(StatePreprepared) == 0 { 596 t.Logf("State stuck at preprepared") 597 return istCore.state 598 } 599 600 // Step 3 - Receive disagreeing commit messages 601 602 sendMessages(msgCommit, newProposal, benignCNs) 603 sendMessages(msgCommit, malProposal, maliciousCNs) 604 return istCore.state 605 } 606 607 // TestCore_MalCN tests whether the proposer can commit when malicious CNs exist. 608 func TestCore_malCN(t *testing.T) { 609 // If there are less than 'f' malicious CNs, proposer can commit. 610 state := simulateMaliciousCN(t, 4, 1) 611 assert.Equal(t, StateCommitted, state) 612 613 // If there are more than 'f' malicious CNs, the proposer cannot commit, stuck at preprepared state. 614 state = simulateMaliciousCN(t, 4, 3) 615 assert.Equal(t, StatePreprepared, state) 616 } 617 618 // Simulate chain split depending on the number of numValidators 619 func simulateChainSplit(t *testing.T, numValidators int) (State, State) { 620 if testing.Verbose() { 621 enableLog() 622 } 623 624 fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{}) 625 defer fork.ClearHardForkBlockNumberConfig() 626 627 // Note that genValidators(n) will generate n/3 validators. 628 // We want n validators, thus calling genValidators(3n). 629 validatorAddrs, validatorKeyMap := genValidators(numValidators * 3) 630 631 // Add more EXPECT()s to remove unexpected call error 632 mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) 633 mockBackend.EXPECT().Commit(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 634 mockBackend.EXPECT().HasBadProposal(gomock.Any()).Return(true).AnyTimes() 635 defer mockCtrl.Finish() 636 637 var ( 638 lastProposal, _ = mockBackend.LastProposal() 639 lastBlock = lastProposal.(*types.Block) 640 validators = mockBackend.Validators(lastBlock) 641 proposer = validators.GetProposer() 642 proposerKey = validatorKeyMap[proposer.Address()] 643 ) 644 645 // Start istanbul core 646 istConfig := istanbul.DefaultConfig 647 istConfig.ProposerPolicy = istanbul.WeightedRandom 648 coreProposer := New(mockBackend, istConfig).(*core) 649 coreA := New(mockBackend, istConfig).(*core) 650 coreB := New(mockBackend, istConfig).(*core) 651 require.Nil(t, 652 coreProposer.Start(), 653 coreA.Start(), 654 coreB.Start()) 655 defer coreProposer.Stop() 656 defer coreA.Stop() 657 defer coreB.Stop() 658 659 // make two groups 660 // the number of group size is (numValidators-1/2) + 1 661 // groupA consists of proposer, coreA, unnamed node(s) 662 // groupB consists of proposer, coreB, unnamed node(s) 663 subList := validators.SubList(lastBlock.Hash(), coreProposer.currentView()) 664 groupA, groupB := splitSubList(subList, (numValidators-1)/2, proposer.Address()) 665 groupA = append(groupA, proposer) 666 groupB = append(groupB, proposer) 667 668 // Step 1 - the malicious proposer generates two blocks 669 proposalA, err := genBlockParams(lastBlock, proposerKey, 0, 0, 1) 670 assert.Nil(t, err) 671 672 proposalB, err := genBlockParams(lastBlock, proposerKey, 1000, 10, 1) 673 assert.Nil(t, err) 674 675 // Shortcut for sending message `proposal` to core `c` 676 sendMessages := func(state uint64, proposal *types.Block, CNList []istanbul.Validator, c *core) { 677 for _, val := range CNList { 678 valKey := validatorKeyMap[val.Address()] 679 if state == msgPreprepare { 680 istanbulMsg, _ := genIstanbulMsg(state, lastBlock.Hash(), proposal, proposer.Address(), valKey) 681 err = c.handleMsg(istanbulMsg.Payload) 682 } else { 683 istanbulMsg, _ := genIstanbulMsg(state, lastBlock.Hash(), proposal, val.Address(), valKey) 684 err = c.handleMsg(istanbulMsg.Payload) 685 } 686 if err != nil { 687 t.Logf("handleMsg error: %s", err) 688 } 689 } 690 } 691 // Step 2 - exchange consensus messages inside each group 692 693 // the proposer sends two different blocks to each group 694 // each group receives a block and handles the message 695 // when chain split occurs, their states become StateCommitted 696 // otherwise, their states stay StatePreprepared 697 sendMessages(msgPreprepare, proposalA, groupA, coreA) 698 sendMessages(msgPrepare, proposalA, groupA, coreA) 699 if coreA.state.Cmp(StatePrepared) == 0 { 700 sendMessages(msgCommit, proposalA, groupA, coreA) 701 } 702 703 sendMessages(msgPreprepare, proposalB, groupB, coreB) 704 sendMessages(msgPrepare, proposalB, groupB, coreB) 705 if coreB.state.Cmp(StatePrepared) == 0 { 706 sendMessages(msgCommit, proposalB, groupB, coreB) 707 } 708 709 return coreA.state, coreB.state 710 } 711 712 // TestCore_chainSplit tests whether a chain split occurs in a certain conditions: 713 // 1. the number of validators does not consist of 3f+1; 714 // e.g. if the number of validator is 5, it consists of 3f+2 (f=1) 715 // 2. the proposer is malicious; it sends two different blocks to each group 716 // 717 // After Ceil(2N/3) quorum calculation, the chain should not be split 718 func TestCore_chainSplit(t *testing.T) { 719 // Even though the number of validators is not 3f+1, the chain is not split. 720 stateA, stateB := simulateChainSplit(t, 5) 721 assert.Equal(t, StatePreprepared, stateA) 722 assert.Equal(t, StatePreprepared, stateB) 723 724 // If the number of validators is 3f+1, the chain cannot be split. 725 stateA, stateB = simulateChainSplit(t, 7) 726 fmt.Println(stateA, stateB) 727 assert.Equal(t, StatePreprepared, stateA) 728 assert.Equal(t, StatePreprepared, stateB) 729 } 730 731 // TestCore_handleTimeoutMsg_race tests a race condition between round change triggers. 732 // There should be no race condition when round change message and timeout event are handled simultaneously. 733 func TestCore_handleTimeoutMsg_race(t *testing.T) { 734 fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{}) 735 defer fork.ClearHardForkBlockNumberConfig() 736 737 // important variables to construct test cases 738 const sleepTime = 200 * time.Millisecond 739 const processingTime = 400 * time.Millisecond 740 741 type testCase struct { 742 name string 743 timeoutTime time.Duration 744 messageRound int64 745 expectedRound int64 746 } 747 testCases := []testCase{ 748 { 749 // if timeoutTime < sleepTime, 750 // timeout event will be posted and then round change message will be processed 751 name: "timeout before processing the (2f+1)th round change message", 752 timeoutTime: 50 * time.Millisecond, 753 messageRound: 10, 754 expectedRound: 10, 755 }, 756 { 757 // if timeoutTime > sleepTime && timeoutTime < (processingTime + sleepTime), 758 // timeout event will be posted during the processing of (2f+1)th round change message 759 name: "timeout during processing the (2f+1)th round change message", 760 timeoutTime: 300 * time.Millisecond, 761 messageRound: 20, 762 expectedRound: 20, 763 }, 764 } 765 766 validatorAddrs, _ := genValidators(10) 767 mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) 768 defer mockCtrl.Finish() 769 770 istConfig := istanbul.DefaultConfig 771 istConfig.ProposerPolicy = istanbul.WeightedRandom 772 773 istCore := New(mockBackend, istConfig).(*core) 774 if err := istCore.Start(); err != nil { 775 t.Fatal(err) 776 } 777 defer istCore.Stop() 778 779 eventMux := mockBackend.EventMux() 780 lastProposal, _ := mockBackend.LastProposal() 781 sequence := istCore.current.sequence.Int64() 782 783 for _, tc := range testCases { 784 handler := func(t *testing.T) { 785 roundChangeTimer := istCore.roundChangeTimer.Load().(*time.Timer) 786 787 // reset timeout timer of this round and wait some time 788 roundChangeTimer.Reset(tc.timeoutTime) 789 time.Sleep(sleepTime) 790 791 // `istCore.validateFn` will be executed on processing a istanbul message 792 istCore.validateFn = func(arg1 []byte, arg2 []byte) (common.Address, error) { 793 // postpones the processing of a istanbul message 794 time.Sleep(processingTime) 795 return common.Address{}, nil 796 } 797 798 // prepare a round change message payload 799 payload := makeRCMsgPayload(tc.messageRound, sequence, lastProposal.Hash(), validatorAddrs[0]) 800 if payload == nil { 801 t.Fatal("failed to make a round change message payload") 802 } 803 804 // one round change message changes the round because the committee size of mockBackend is 3 805 err := eventMux.Post(istanbul.MessageEvent{ 806 Hash: lastProposal.Hash(), 807 Payload: payload, 808 }) 809 if err != nil { 810 t.Fatal(err) 811 } 812 813 // wait until the istanbul message have processed 814 time.Sleep(processingTime + sleepTime) 815 roundChangeTimer.Stop() 816 817 // check the result 818 assert.Equal(t, tc.expectedRound, istCore.current.round.Int64()) 819 } 820 t.Run(tc.name, handler) 821 } 822 } 823 824 // makeRCMsgPayload makes a payload of round change message. 825 func makeRCMsgPayload(round int64, sequence int64, prevHash common.Hash, senderAddr common.Address) []byte { 826 subject, err := Encode(&istanbul.Subject{ 827 View: &istanbul.View{ 828 Round: big.NewInt(round), 829 Sequence: big.NewInt(sequence), 830 }, 831 Digest: common.Hash{}, 832 PrevHash: prevHash, 833 }) 834 if err != nil { 835 return nil 836 } 837 838 msg := &message{ 839 Hash: prevHash, 840 Code: msgRoundChange, 841 Msg: subject, 842 Address: senderAddr, 843 } 844 845 payload, err := msg.Payload() 846 if err != nil { 847 return nil 848 } 849 850 return payload 851 }