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.

120 lines
3.2 KiB

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