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.

127 lines
3.4 KiB

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