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.

162 lines
4.1 KiB

3 years ago
3 years ago
  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. n := 2
  24. states, cleanup := makeConsensusState(ctx, t,
  25. config, n, "consensus_reactor_test",
  26. newMockTickerFunc(true))
  27. t.Cleanup(cleanup)
  28. rts := setup(ctx, t, n, states, 100) // buffer must be large enough to not deadlock
  29. for _, reactor := range rts.reactors {
  30. reactor.SwitchToConsensus(ctx, reactor.state.state, false)
  31. }
  32. // this val sends a random precommit at each height
  33. node := rts.network.RandomNode()
  34. byzState := rts.states[node.NodeID]
  35. byzReactor := rts.reactors[node.NodeID]
  36. signal := make(chan struct{})
  37. // Update the doPrevote function to just send a valid precommit for a random
  38. // block and otherwise disable the priv validator.
  39. privVal := byzState.privValidator
  40. byzState.mtx.Lock()
  41. byzState.doPrevote = func(ctx context.Context, height int64, round int32) {
  42. defer close(signal)
  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 ctx.Err() != nil {
  57. return
  58. }
  59. if !assert.NoError(t, err) {
  60. cancel() // cancel other subscribers on failure
  61. }
  62. }(sub)
  63. }
  64. }
  65. wait := make(chan struct{})
  66. go func() { defer close(wait); wg.Wait() }()
  67. select {
  68. case <-wait:
  69. t.Error("test condition did not fire")
  70. case <-ctx.Done():
  71. t.Error("test condition did not fire after timeout")
  72. case <-signal:
  73. // test passed
  74. }
  75. }
  76. func invalidDoPrevoteFunc(
  77. ctx context.Context,
  78. t *testing.T,
  79. height int64,
  80. round int32,
  81. cs *State,
  82. r *Reactor,
  83. pv types.PrivValidator,
  84. ) {
  85. // routine to:
  86. // - precommit for a random block
  87. // - send precommit to all peers
  88. // - disable privValidator (so we don't do normal precommits)
  89. go func() {
  90. cs.mtx.Lock()
  91. cs.privValidator = pv
  92. pubKey, err := cs.privValidator.GetPubKey(ctx)
  93. require.NoError(t, err)
  94. addr := pubKey.Address()
  95. valIndex, _ := cs.Validators.GetByAddress(addr)
  96. // precommit a random block
  97. blockHash := bytes.HexBytes(tmrand.Bytes(32))
  98. precommit := &types.Vote{
  99. ValidatorAddress: addr,
  100. ValidatorIndex: valIndex,
  101. Height: cs.Height,
  102. Round: cs.Round,
  103. Timestamp: tmtime.Now(),
  104. Type: tmproto.PrecommitType,
  105. BlockID: types.BlockID{
  106. Hash: blockHash,
  107. PartSetHeader: types.PartSetHeader{Total: 1, Hash: tmrand.Bytes(32)}},
  108. }
  109. p := precommit.ToProto()
  110. err = cs.privValidator.SignVote(ctx, cs.state.ChainID, p)
  111. require.NoError(t, err)
  112. precommit.Signature = p.Signature
  113. cs.privValidator = nil // disable priv val so we don't do normal votes
  114. cs.mtx.Unlock()
  115. r.mtx.Lock()
  116. ids := make([]types.NodeID, 0, len(r.peers))
  117. for _, ps := range r.peers {
  118. ids = append(ids, ps.peerID)
  119. }
  120. r.mtx.Unlock()
  121. count := 0
  122. for _, peerID := range ids {
  123. count++
  124. err := r.voteCh.Send(ctx, p2p.Envelope{
  125. To: peerID,
  126. Message: &tmcons.Vote{
  127. Vote: precommit.ToProto(),
  128. },
  129. })
  130. // we want to have sent some of these votes,
  131. // but if the test completes without erroring
  132. // or not sending any messages, then we should
  133. // error.
  134. if errors.Is(err, context.Canceled) && count > 0 {
  135. break
  136. }
  137. require.NoError(t, err)
  138. }
  139. }()
  140. }