- package consensus
-
- import (
- "context"
- "errors"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/internal/eventbus"
- "github.com/tendermint/tendermint/internal/p2p"
- "github.com/tendermint/tendermint/libs/bytes"
- tmrand "github.com/tendermint/tendermint/libs/rand"
- tmtime "github.com/tendermint/tendermint/libs/time"
- tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus"
- tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
- "github.com/tendermint/tendermint/types"
- )
-
- func TestReactorInvalidPrecommit(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- config := configSetup(t)
-
- n := 2
- states, cleanup := makeConsensusState(ctx, t,
- config, n, "consensus_reactor_test",
- newMockTickerFunc(true))
- t.Cleanup(cleanup)
-
- rts := setup(ctx, t, n, states, 100) // buffer must be large enough to not deadlock
-
- for _, reactor := range rts.reactors {
- reactor.SwitchToConsensus(ctx, reactor.state.state, false)
- }
-
- // this val sends a random precommit at each height
- node := rts.network.RandomNode()
-
- byzState := rts.states[node.NodeID]
- byzReactor := rts.reactors[node.NodeID]
-
- signal := make(chan struct{})
- // Update the doPrevote function to just send a valid precommit for a random
- // block and otherwise disable the priv validator.
- privVal := byzState.privValidator
- byzState.mtx.Lock()
- byzState.doPrevote = func(ctx context.Context, height int64, round int32) {
- defer close(signal)
- invalidDoPrevoteFunc(ctx, t, height, round, byzState, byzReactor, privVal)
- }
- byzState.mtx.Unlock()
- // wait for a bunch of blocks
- //
- // TODO: Make this tighter by ensuring the halt happens by block 2.
- var wg sync.WaitGroup
-
- for i := 0; i < 10; i++ {
- for _, sub := range rts.subs {
- wg.Add(1)
-
- go func(s eventbus.Subscription) {
- defer wg.Done()
- _, err := s.Next(ctx)
- if ctx.Err() != nil {
- return
- }
- if !assert.NoError(t, err) {
- cancel() // cancel other subscribers on failure
- }
- }(sub)
- }
- }
- wait := make(chan struct{})
- go func() { defer close(wait); wg.Wait() }()
-
- select {
- case <-wait:
- t.Error("test condition did not fire")
- case <-ctx.Done():
- t.Error("test condition did not fire after timeout")
- case <-signal:
- // test passed
- }
- }
-
- func invalidDoPrevoteFunc(
- ctx context.Context,
- t *testing.T,
- height int64,
- round int32,
- cs *State,
- r *Reactor,
- pv types.PrivValidator,
- ) {
- // routine to:
- // - precommit for a random block
- // - send precommit to all peers
- // - disable privValidator (so we don't do normal precommits)
- go func() {
- cs.mtx.Lock()
- cs.privValidator = pv
-
- pubKey, err := cs.privValidator.GetPubKey(ctx)
- require.NoError(t, err)
-
- addr := pubKey.Address()
- valIndex, _ := cs.Validators.GetByAddress(addr)
-
- // precommit a random block
- blockHash := bytes.HexBytes(tmrand.Bytes(32))
- precommit := &types.Vote{
- ValidatorAddress: addr,
- ValidatorIndex: valIndex,
- Height: cs.Height,
- Round: cs.Round,
- Timestamp: tmtime.Now(),
- Type: tmproto.PrecommitType,
- BlockID: types.BlockID{
- Hash: blockHash,
- PartSetHeader: types.PartSetHeader{Total: 1, Hash: tmrand.Bytes(32)}},
- }
-
- p := precommit.ToProto()
- err = cs.privValidator.SignVote(ctx, cs.state.ChainID, p)
- require.NoError(t, err)
-
- precommit.Signature = p.Signature
- cs.privValidator = nil // disable priv val so we don't do normal votes
- cs.mtx.Unlock()
-
- r.mtx.Lock()
- ids := make([]types.NodeID, 0, len(r.peers))
- for _, ps := range r.peers {
- ids = append(ids, ps.peerID)
- }
- r.mtx.Unlock()
-
- count := 0
- for _, peerID := range ids {
- count++
- err := r.voteCh.Send(ctx, p2p.Envelope{
- To: peerID,
- Message: &tmcons.Vote{
- Vote: precommit.ToProto(),
- },
- })
- // we want to have sent some of these votes,
- // but if the test completes without erroring
- // or not sending any messages, then we should
- // error.
- if errors.Is(err, context.Canceled) && count > 0 {
- break
- }
- require.NoError(t, err)
- }
- }()
- }
|