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.

174 lines
4.3 KiB

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