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.

117 lines
3.1 KiB

  1. package consensus
  2. import (
  3. "sync"
  4. "testing"
  5. "github.com/stretchr/testify/require"
  6. "github.com/tendermint/tendermint/libs/bytes"
  7. tmrand "github.com/tendermint/tendermint/libs/rand"
  8. "github.com/tendermint/tendermint/p2p"
  9. tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus"
  10. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  11. "github.com/tendermint/tendermint/types"
  12. )
  13. func TestReactorInvalidPrecommit(t *testing.T) {
  14. configSetup(t)
  15. n := 4
  16. states, cleanup := randConsensusState(n, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
  17. t.Cleanup(cleanup)
  18. for i := 0; i < 4; i++ {
  19. ticker := NewTimeoutTicker()
  20. ticker.SetLogger(states[i].Logger)
  21. states[i].SetTimeoutTicker(ticker)
  22. }
  23. rts := setup(t, n, states, 100) // buffer must be large enough to not deadlock
  24. for _, reactor := range rts.reactors {
  25. state := reactor.state.GetState()
  26. reactor.SwitchToConsensus(state, false)
  27. }
  28. // this val sends a random precommit at each height
  29. node := rts.network.RandomNode()
  30. byzState := rts.states[node.NodeID]
  31. byzReactor := rts.reactors[node.NodeID]
  32. // Update the doPrevote function to just send a valid precommit for a random
  33. // block and otherwise disable the priv validator.
  34. byzState.mtx.Lock()
  35. privVal := byzState.privValidator
  36. byzState.doPrevote = func(height int64, round int32) {
  37. invalidDoPrevoteFunc(t, height, round, byzState, byzReactor, privVal)
  38. }
  39. byzState.mtx.Unlock()
  40. // wait for a bunch of blocks
  41. //
  42. // TODO: Make this tighter by ensuring the halt happens by block 2.
  43. var wg sync.WaitGroup
  44. for i := 0; i < 10; i++ {
  45. for _, sub := range rts.subs {
  46. wg.Add(1)
  47. go func(s types.Subscription) {
  48. <-s.Out()
  49. wg.Done()
  50. }(sub)
  51. }
  52. }
  53. wg.Wait()
  54. }
  55. func invalidDoPrevoteFunc(t *testing.T, height int64, round int32, cs *State, r *Reactor, pv types.PrivValidator) {
  56. // routine to:
  57. // - precommit for a random block
  58. // - send precommit to all peers
  59. // - disable privValidator (so we don't do normal precommits)
  60. go func() {
  61. cs.mtx.Lock()
  62. cs.privValidator = pv
  63. pubKey, err := cs.privValidator.GetPubKey()
  64. require.NoError(t, err)
  65. addr := pubKey.Address()
  66. valIndex, _ := cs.Validators.GetByAddress(addr)
  67. // precommit a random block
  68. blockHash := bytes.HexBytes(tmrand.Bytes(32))
  69. precommit := &types.Vote{
  70. ValidatorAddress: addr,
  71. ValidatorIndex: valIndex,
  72. Height: cs.Height,
  73. Round: cs.Round,
  74. Timestamp: cs.voteTime(),
  75. Type: tmproto.PrecommitType,
  76. BlockID: types.BlockID{
  77. Hash: blockHash,
  78. PartSetHeader: types.PartSetHeader{Total: 1, Hash: tmrand.Bytes(32)}},
  79. }
  80. p := precommit.ToProto()
  81. err = cs.privValidator.SignVote(cs.state.ChainID, p)
  82. require.NoError(t, err)
  83. precommit.Signature = p.Signature
  84. cs.privValidator = nil // disable priv val so we don't do normal votes
  85. cs.mtx.Unlock()
  86. for _, ps := range r.peers {
  87. cs.Logger.Info("sending bad vote", "block", blockHash, "peer", ps.peerID)
  88. r.voteCh.Out <- p2p.Envelope{
  89. To: ps.peerID,
  90. Message: &tmcons.Vote{
  91. Vote: precommit.ToProto(),
  92. },
  93. }
  94. }
  95. }()
  96. }