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.

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