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.

118 lines
3.1 KiB

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