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