You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

100 lines
2.9 KiB

  1. package consensus
  2. import (
  3. "testing"
  4. "github.com/tendermint/tendermint/libs/bytes"
  5. "github.com/tendermint/tendermint/libs/log"
  6. tmrand "github.com/tendermint/tendermint/libs/rand"
  7. "github.com/tendermint/tendermint/p2p"
  8. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  9. "github.com/tendermint/tendermint/types"
  10. )
  11. //----------------------------------------------
  12. // byzantine failures
  13. // one byz val sends a precommit for a random block at each height
  14. // Ensure a testnet makes blocks
  15. func TestReactorInvalidPrecommit(t *testing.T) {
  16. N := 4
  17. css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
  18. t.Cleanup(cleanup)
  19. for i := 0; i < 4; i++ {
  20. ticker := NewTimeoutTicker()
  21. ticker.SetLogger(css[i].Logger)
  22. css[i].SetTimeoutTicker(ticker)
  23. }
  24. reactors, blocksSubs, eventBuses := startConsensusNet(t, css, N)
  25. // this val sends a random precommit at each height
  26. byzValIdx := 0
  27. byzVal := css[byzValIdx]
  28. byzR := reactors[byzValIdx]
  29. // update the doPrevote function to just send a valid precommit for a random block
  30. // and otherwise disable the priv validator
  31. byzVal.mtx.Lock()
  32. pv := byzVal.privValidator
  33. byzVal.doPrevote = func(height int64, round int32) {
  34. invalidDoPrevoteFunc(t, height, round, byzVal, byzR.Switch, pv)
  35. }
  36. byzVal.mtx.Unlock()
  37. t.Cleanup(func() { stopConsensusNet(log.TestingLogger(), reactors, eventBuses) })
  38. // wait for a bunch of blocks
  39. // TODO: make this tighter by ensuring the halt happens by block 2
  40. for i := 0; i < 10; i++ {
  41. timeoutWaitGroup(t, N, func(j int) {
  42. <-blocksSubs[j].Out()
  43. }, css)
  44. }
  45. }
  46. func invalidDoPrevoteFunc(t *testing.T, height int64, round int32, cs *State, sw *p2p.Switch, pv types.PrivValidator) {
  47. // routine to:
  48. // - precommit for a random block
  49. // - send precommit to all peers
  50. // - disable privValidator (so we don't do normal precommits)
  51. go func() {
  52. cs.mtx.Lock()
  53. cs.privValidator = pv
  54. pubKey, err := cs.privValidator.GetPubKey()
  55. if err != nil {
  56. panic(err)
  57. }
  58. addr := pubKey.Address()
  59. valIndex, _ := cs.Validators.GetByAddress(addr)
  60. // precommit a random block
  61. blockHash := bytes.HexBytes(tmrand.Bytes(32))
  62. precommit := &types.Vote{
  63. ValidatorAddress: addr,
  64. ValidatorIndex: valIndex,
  65. Height: cs.Height,
  66. Round: cs.Round,
  67. Timestamp: cs.voteTime(),
  68. Type: tmproto.PrecommitType,
  69. BlockID: types.BlockID{
  70. Hash: blockHash,
  71. PartSetHeader: types.PartSetHeader{Total: 1, Hash: tmrand.Bytes(32)}},
  72. }
  73. p := precommit.ToProto()
  74. err = cs.privValidator.SignVote(cs.state.ChainID, p)
  75. if err != nil {
  76. t.Error(err)
  77. }
  78. precommit.Signature = p.Signature
  79. cs.privValidator = nil // disable priv val so we don't do normal votes
  80. cs.mtx.Unlock()
  81. peers := sw.Peers().List()
  82. for _, peer := range peers {
  83. cs.Logger.Info("Sending bad vote", "block", blockHash, "peer", peer)
  84. peer.Send(VoteChannel, MustEncode(&VoteMessage{precommit}))
  85. }
  86. }()
  87. }