github.com/aakash4dev/cometbft@v0.38.2/state/validation_test.go (about)

     1  package state_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/mock"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	dbm "github.com/aakash4dev/cometbft-db"
    12  
    13  	abci "github.com/aakash4dev/cometbft/abci/types"
    14  	"github.com/aakash4dev/cometbft/crypto/ed25519"
    15  	"github.com/aakash4dev/cometbft/crypto/tmhash"
    16  	"github.com/aakash4dev/cometbft/internal/test"
    17  	"github.com/aakash4dev/cometbft/libs/log"
    18  	mpmocks "github.com/aakash4dev/cometbft/mempool/mocks"
    19  	cmtproto "github.com/aakash4dev/cometbft/proto/tendermint/types"
    20  	sm "github.com/aakash4dev/cometbft/state"
    21  	"github.com/aakash4dev/cometbft/state/mocks"
    22  	"github.com/aakash4dev/cometbft/store"
    23  	"github.com/aakash4dev/cometbft/types"
    24  	cmttime "github.com/aakash4dev/cometbft/types/time"
    25  )
    26  
    27  const validationTestsStopHeight int64 = 10
    28  
    29  func TestValidateBlockHeader(t *testing.T) {
    30  	proxyApp := newTestApp()
    31  	require.NoError(t, proxyApp.Start())
    32  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
    33  
    34  	state, stateDB, privVals := makeState(3, 1)
    35  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    36  		DiscardABCIResponses: false,
    37  	})
    38  	mp := &mpmocks.Mempool{}
    39  	mp.On("Lock").Return()
    40  	mp.On("Unlock").Return()
    41  	mp.On("FlushAppConn", mock.Anything).Return(nil)
    42  	mp.On("Update",
    43  		mock.Anything,
    44  		mock.Anything,
    45  		mock.Anything,
    46  		mock.Anything,
    47  		mock.Anything,
    48  		mock.Anything).Return(nil)
    49  
    50  	blockStore := store.NewBlockStore(dbm.NewMemDB())
    51  
    52  	blockExec := sm.NewBlockExecutor(
    53  		stateStore,
    54  		log.TestingLogger(),
    55  		proxyApp.Consensus(),
    56  		mp,
    57  		sm.EmptyEvidencePool{},
    58  		blockStore,
    59  	)
    60  	lastCommit := &types.Commit{}
    61  	var lastExtCommit *types.ExtendedCommit
    62  
    63  	// some bad values
    64  	wrongHash := tmhash.Sum([]byte("this hash is wrong"))
    65  	wrongVersion1 := state.Version.Consensus
    66  	wrongVersion1.Block += 2
    67  	wrongVersion2 := state.Version.Consensus
    68  	wrongVersion2.App += 2
    69  
    70  	// Manipulation of any header field causes failure.
    71  	testCases := []struct {
    72  		name          string
    73  		malleateBlock func(block *types.Block)
    74  	}{
    75  		{"Version wrong1", func(block *types.Block) { block.Version = wrongVersion1 }},
    76  		{"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }},
    77  		{"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }},
    78  		{"Height wrong", func(block *types.Block) { block.Height += 10 }},
    79  		{"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 1) }},
    80  
    81  		{"LastBlockID wrong", func(block *types.Block) { block.LastBlockID.PartSetHeader.Total += 10 }},
    82  		{"LastCommitHash wrong", func(block *types.Block) { block.LastCommitHash = wrongHash }},
    83  		{"DataHash wrong", func(block *types.Block) { block.DataHash = wrongHash }},
    84  
    85  		{"ValidatorsHash wrong", func(block *types.Block) { block.ValidatorsHash = wrongHash }},
    86  		{"NextValidatorsHash wrong", func(block *types.Block) { block.NextValidatorsHash = wrongHash }},
    87  		{"ConsensusHash wrong", func(block *types.Block) { block.ConsensusHash = wrongHash }},
    88  		{"AppHash wrong", func(block *types.Block) { block.AppHash = wrongHash }},
    89  		{"LastResultsHash wrong", func(block *types.Block) { block.LastResultsHash = wrongHash }},
    90  
    91  		{"EvidenceHash wrong", func(block *types.Block) { block.EvidenceHash = wrongHash }},
    92  		{"Proposer wrong", func(block *types.Block) { block.ProposerAddress = ed25519.GenPrivKey().PubKey().Address() }},
    93  		{"Proposer invalid", func(block *types.Block) { block.ProposerAddress = []byte("wrong size") }},
    94  	}
    95  
    96  	// Build up state for multiple heights
    97  	for height := int64(1); height < validationTestsStopHeight; height++ {
    98  		/*
    99  			Invalid blocks don't pass
   100  		*/
   101  		for _, tc := range testCases {
   102  			block := makeBlock(state, height, lastCommit)
   103  			tc.malleateBlock(block)
   104  			err := blockExec.ValidateBlock(state, block)
   105  			require.Error(t, err, tc.name)
   106  		}
   107  
   108  		/*
   109  			A good block passes
   110  		*/
   111  		var err error
   112  		state, _, lastExtCommit, err = makeAndCommitGoodBlock(
   113  			state, height, lastCommit, state.Validators.GetProposer().Address, blockExec, privVals, nil)
   114  		require.NoError(t, err, "height %d", height)
   115  		lastCommit = lastExtCommit.ToCommit()
   116  	}
   117  
   118  	nextHeight := validationTestsStopHeight
   119  	block := makeBlock(state, nextHeight, lastCommit)
   120  	state.InitialHeight = nextHeight + 1
   121  	err := blockExec.ValidateBlock(state, block)
   122  	require.Error(t, err, "expected an error when state is ahead of block")
   123  	assert.Contains(t, err.Error(), "lower than initial height")
   124  }
   125  
   126  func TestValidateBlockCommit(t *testing.T) {
   127  	proxyApp := newTestApp()
   128  	require.NoError(t, proxyApp.Start())
   129  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
   130  
   131  	state, stateDB, privVals := makeState(1, 1)
   132  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   133  		DiscardABCIResponses: false,
   134  	})
   135  	mp := &mpmocks.Mempool{}
   136  	mp.On("Lock").Return()
   137  	mp.On("Unlock").Return()
   138  	mp.On("FlushAppConn", mock.Anything).Return(nil)
   139  	mp.On("Update",
   140  		mock.Anything,
   141  		mock.Anything,
   142  		mock.Anything,
   143  		mock.Anything,
   144  		mock.Anything,
   145  		mock.Anything).Return(nil)
   146  
   147  	blockStore := store.NewBlockStore(dbm.NewMemDB())
   148  
   149  	blockExec := sm.NewBlockExecutor(
   150  		stateStore,
   151  		log.TestingLogger(),
   152  		proxyApp.Consensus(),
   153  		mp,
   154  		sm.EmptyEvidencePool{},
   155  		blockStore,
   156  	)
   157  	lastCommit := &types.Commit{}
   158  	var lastExtCommit *types.ExtendedCommit
   159  	wrongSigsCommit := &types.Commit{Height: 1}
   160  	badPrivVal := types.NewMockPV()
   161  
   162  	for height := int64(1); height < validationTestsStopHeight; height++ {
   163  		proposerAddr := state.Validators.GetProposer().Address
   164  		if height > 1 {
   165  			/*
   166  				#2589: ensure state.LastValidators.VerifyCommit fails here
   167  			*/
   168  			// should be height-1 instead of height
   169  			idx, _ := state.Validators.GetByAddress(proposerAddr)
   170  			wrongHeightVote := types.MakeVoteNoError(
   171  				t,
   172  				privVals[proposerAddr.String()],
   173  				chainID,
   174  				idx,
   175  				height,
   176  				0,
   177  				2,
   178  				state.LastBlockID,
   179  				time.Now(),
   180  			)
   181  			wrongHeightCommit := &types.Commit{
   182  				Height:     wrongHeightVote.Height,
   183  				Round:      wrongHeightVote.Round,
   184  				BlockID:    state.LastBlockID,
   185  				Signatures: []types.CommitSig{wrongHeightVote.CommitSig()},
   186  			}
   187  			block := makeBlock(state, height, wrongHeightCommit)
   188  			err := blockExec.ValidateBlock(state, block)
   189  			_, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight)
   190  			require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err)
   191  
   192  			/*
   193  				#2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size()
   194  			*/
   195  			block = makeBlock(state, height, wrongSigsCommit)
   196  			err = blockExec.ValidateBlock(state, block)
   197  			_, isErrInvalidCommitSignatures := err.(types.ErrInvalidCommitSignatures)
   198  			require.True(t, isErrInvalidCommitSignatures,
   199  				"expected ErrInvalidCommitSignatures at height %d, but got: %v",
   200  				height,
   201  				err,
   202  			)
   203  		}
   204  
   205  		/*
   206  			A good block passes
   207  		*/
   208  		var err error
   209  		var blockID types.BlockID
   210  		state, blockID, lastExtCommit, err = makeAndCommitGoodBlock(
   211  			state,
   212  			height,
   213  			lastCommit,
   214  			proposerAddr,
   215  			blockExec,
   216  			privVals,
   217  			nil,
   218  		)
   219  		require.NoError(t, err, "height %d", height)
   220  		lastCommit = lastExtCommit.ToCommit()
   221  
   222  		/*
   223  			wrongSigsCommit is fine except for the extra bad precommit
   224  		*/
   225  		idx, _ := state.Validators.GetByAddress(proposerAddr)
   226  		goodVote := types.MakeVoteNoError(
   227  			t,
   228  			privVals[proposerAddr.String()],
   229  			chainID,
   230  			idx,
   231  			height,
   232  			0,
   233  			cmtproto.PrecommitType,
   234  			blockID,
   235  			time.Now(),
   236  		)
   237  
   238  		bpvPubKey, err := badPrivVal.GetPubKey()
   239  		require.NoError(t, err)
   240  
   241  		badVote := &types.Vote{
   242  			ValidatorAddress: bpvPubKey.Address(),
   243  			ValidatorIndex:   0,
   244  			Height:           height,
   245  			Round:            0,
   246  			Timestamp:        cmttime.Now(),
   247  			Type:             cmtproto.PrecommitType,
   248  			BlockID:          blockID,
   249  		}
   250  
   251  		g := goodVote.ToProto()
   252  		b := badVote.ToProto()
   253  
   254  		err = badPrivVal.SignVote(chainID, g)
   255  		require.NoError(t, err, "height %d", height)
   256  		err = badPrivVal.SignVote(chainID, b)
   257  		require.NoError(t, err, "height %d", height)
   258  
   259  		goodVote.Signature, badVote.Signature = g.Signature, b.Signature
   260  
   261  		wrongSigsCommit = &types.Commit{
   262  			Height:     goodVote.Height,
   263  			Round:      goodVote.Round,
   264  			BlockID:    blockID,
   265  			Signatures: []types.CommitSig{goodVote.CommitSig(), badVote.CommitSig()},
   266  		}
   267  	}
   268  }
   269  
   270  func TestValidateBlockEvidence(t *testing.T) {
   271  	proxyApp := newTestApp()
   272  	require.NoError(t, proxyApp.Start())
   273  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
   274  
   275  	state, stateDB, privVals := makeState(4, 1)
   276  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   277  		DiscardABCIResponses: false,
   278  	})
   279  	defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
   280  
   281  	evpool := &mocks.EvidencePool{}
   282  	evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil)
   283  	evpool.On("Update", mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return()
   284  	evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return(
   285  		[]abci.Misbehavior{})
   286  
   287  	mp := &mpmocks.Mempool{}
   288  	mp.On("Lock").Return()
   289  	mp.On("Unlock").Return()
   290  	mp.On("FlushAppConn", mock.Anything).Return(nil)
   291  	mp.On("Update",
   292  		mock.Anything,
   293  		mock.Anything,
   294  		mock.Anything,
   295  		mock.Anything,
   296  		mock.Anything,
   297  		mock.Anything).Return(nil)
   298  	state.ConsensusParams.Evidence.MaxBytes = 1000
   299  	blockStore := store.NewBlockStore(dbm.NewMemDB())
   300  
   301  	blockExec := sm.NewBlockExecutor(
   302  		stateStore,
   303  		log.TestingLogger(),
   304  		proxyApp.Consensus(),
   305  		mp,
   306  		evpool,
   307  		blockStore,
   308  	)
   309  	lastCommit := &types.Commit{}
   310  	var lastExtCommit *types.ExtendedCommit
   311  
   312  	for height := int64(1); height < validationTestsStopHeight; height++ {
   313  		proposerAddr := state.Validators.GetProposer().Address
   314  		maxBytesEvidence := state.ConsensusParams.Evidence.MaxBytes
   315  		if height > 1 {
   316  			/*
   317  				A block with too much evidence fails
   318  			*/
   319  			evidence := make([]types.Evidence, 0)
   320  			var currentBytes int64
   321  			// more bytes than the maximum allowed for evidence
   322  			for currentBytes <= maxBytesEvidence {
   323  				newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(),
   324  					privVals[proposerAddr.String()], chainID)
   325  				require.NoError(t, err)
   326  				evidence = append(evidence, newEv)
   327  				currentBytes += int64(len(newEv.Bytes()))
   328  			}
   329  			block := state.MakeBlock(height, test.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr)
   330  
   331  			err := blockExec.ValidateBlock(state, block)
   332  			if assert.Error(t, err) {
   333  				_, ok := err.(*types.ErrEvidenceOverflow)
   334  				require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d but got %v", height, err)
   335  			}
   336  		}
   337  
   338  		/*
   339  			A good block with several pieces of good evidence passes
   340  		*/
   341  		evidence := make([]types.Evidence, 0)
   342  		var currentBytes int64
   343  		// precisely the amount of allowed evidence
   344  		for {
   345  			newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime,
   346  				privVals[proposerAddr.String()], chainID)
   347  			require.NoError(t, err)
   348  			currentBytes += int64(len(newEv.Bytes()))
   349  			if currentBytes >= maxBytesEvidence {
   350  				break
   351  			}
   352  			evidence = append(evidence, newEv)
   353  		}
   354  
   355  		var err error
   356  		state, _, lastExtCommit, err = makeAndCommitGoodBlock(
   357  			state,
   358  			height,
   359  			lastCommit,
   360  			proposerAddr,
   361  			blockExec,
   362  			privVals,
   363  			evidence,
   364  		)
   365  		require.NoError(t, err, "height %d", height)
   366  		lastCommit = lastExtCommit.ToCommit()
   367  
   368  	}
   369  }