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.

151 lines
3.8 KiB

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