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