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

     1  package state_test
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     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"
    15  	"github.com/aakash4dev/cometbft/crypto/ed25519"
    16  	"github.com/aakash4dev/cometbft/internal/test"
    17  	cmtrand "github.com/aakash4dev/cometbft/libs/rand"
    18  	cmtstate "github.com/aakash4dev/cometbft/proto/tendermint/state"
    19  	sm "github.com/aakash4dev/cometbft/state"
    20  	"github.com/aakash4dev/cometbft/types"
    21  )
    22  
    23  func TestStoreLoadValidators(t *testing.T) {
    24  	stateDB := dbm.NewMemDB()
    25  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    26  		DiscardABCIResponses: false,
    27  	})
    28  	val, _ := types.RandValidator(true, 10)
    29  	vals := types.NewValidatorSet([]*types.Validator{val})
    30  
    31  	// 1) LoadValidators loads validators using a height where they were last changed
    32  	err := sm.SaveValidatorsInfo(stateDB, 1, 1, vals)
    33  	require.NoError(t, err)
    34  	err = sm.SaveValidatorsInfo(stateDB, 2, 1, vals)
    35  	require.NoError(t, err)
    36  	loadedVals, err := stateStore.LoadValidators(2)
    37  	require.NoError(t, err)
    38  	assert.NotZero(t, loadedVals.Size())
    39  
    40  	// 2) LoadValidators loads validators using a checkpoint height
    41  
    42  	err = sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval, 1, vals)
    43  	require.NoError(t, err)
    44  
    45  	loadedVals, err = stateStore.LoadValidators(sm.ValSetCheckpointInterval)
    46  	require.NoError(t, err)
    47  	assert.NotZero(t, loadedVals.Size())
    48  }
    49  
    50  func BenchmarkLoadValidators(b *testing.B) {
    51  	const valSetSize = 100
    52  
    53  	config := test.ResetTestRoot("state_")
    54  	defer os.RemoveAll(config.RootDir)
    55  	dbType := dbm.BackendType(config.DBBackend)
    56  	stateDB, err := dbm.NewDB("state", dbType, config.DBDir())
    57  	require.NoError(b, err)
    58  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    59  		DiscardABCIResponses: false,
    60  	})
    61  	state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile())
    62  	if err != nil {
    63  		b.Fatal(err)
    64  	}
    65  
    66  	state.Validators = genValSet(valSetSize)
    67  	state.NextValidators = state.Validators.CopyIncrementProposerPriority(1)
    68  	err = stateStore.Save(state)
    69  	require.NoError(b, err)
    70  
    71  	for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ...
    72  		i := i
    73  		if err := sm.SaveValidatorsInfo(stateDB,
    74  			int64(i), state.LastHeightValidatorsChanged, state.NextValidators); err != nil {
    75  			b.Fatal(err)
    76  		}
    77  
    78  		b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) {
    79  			for n := 0; n < b.N; n++ {
    80  				_, err := stateStore.LoadValidators(int64(i))
    81  				if err != nil {
    82  					b.Fatal(err)
    83  				}
    84  			}
    85  		})
    86  	}
    87  }
    88  
    89  func TestPruneStates(t *testing.T) {
    90  	testcases := map[string]struct {
    91  		makeHeights             int64
    92  		pruneFrom               int64
    93  		pruneTo                 int64
    94  		evidenceThresholdHeight int64
    95  		expectErr               bool
    96  		expectVals              []int64
    97  		expectParams            []int64
    98  		expectABCI              []int64
    99  	}{
   100  		"error on pruning from 0":      {100, 0, 5, 100, true, nil, nil, nil},
   101  		"error when from > to":         {100, 3, 2, 2, true, nil, nil, nil},
   102  		"error when from == to":        {100, 3, 3, 3, true, nil, nil, nil},
   103  		"error when to does not exist": {100, 1, 101, 101, true, nil, nil, nil},
   104  		"prune all":                    {100, 1, 100, 100, false, []int64{93, 100}, []int64{95, 100}, []int64{100}},
   105  		"prune some": {
   106  			10, 2, 8, 8, false,
   107  			[]int64{1, 3, 8, 9, 10},
   108  			[]int64{1, 5, 8, 9, 10},
   109  			[]int64{1, 8, 9, 10},
   110  		},
   111  		"prune across checkpoint": {
   112  			100001, 1, 100001, 100001, false,
   113  			[]int64{99993, 100000, 100001},
   114  			[]int64{99995, 100001},
   115  			[]int64{100001},
   116  		},
   117  		"prune when evidence height < height": {20, 1, 18, 17, false, []int64{13, 17, 18, 19, 20}, []int64{15, 18, 19, 20}, []int64{18, 19, 20}},
   118  	}
   119  	for name, tc := range testcases {
   120  		tc := tc
   121  		t.Run(name, func(t *testing.T) {
   122  			db := dbm.NewMemDB()
   123  			stateStore := sm.NewStore(db, sm.StoreOptions{
   124  				DiscardABCIResponses: false,
   125  			})
   126  			pk := ed25519.GenPrivKey().PubKey()
   127  
   128  			// Generate a bunch of state data. Validators change for heights ending with 3, and
   129  			// parameters when ending with 5.
   130  			validator := &types.Validator{Address: cmtrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk}
   131  			validatorSet := &types.ValidatorSet{
   132  				Validators: []*types.Validator{validator},
   133  				Proposer:   validator,
   134  			}
   135  			valsChanged := int64(0)
   136  			paramsChanged := int64(0)
   137  
   138  			for h := int64(1); h <= tc.makeHeights; h++ {
   139  				if valsChanged == 0 || h%10 == 2 {
   140  					valsChanged = h + 1 // Have to add 1, since NextValidators is what's stored
   141  				}
   142  				if paramsChanged == 0 || h%10 == 5 {
   143  					paramsChanged = h
   144  				}
   145  
   146  				state := sm.State{
   147  					InitialHeight:   1,
   148  					LastBlockHeight: h - 1,
   149  					Validators:      validatorSet,
   150  					NextValidators:  validatorSet,
   151  					ConsensusParams: types.ConsensusParams{
   152  						Block: types.BlockParams{MaxBytes: 10e6},
   153  					},
   154  					LastHeightValidatorsChanged:      valsChanged,
   155  					LastHeightConsensusParamsChanged: paramsChanged,
   156  				}
   157  
   158  				if state.LastBlockHeight >= 1 {
   159  					state.LastValidators = state.Validators
   160  				}
   161  
   162  				err := stateStore.Save(state)
   163  				require.NoError(t, err)
   164  
   165  				err = stateStore.SaveFinalizeBlockResponse(h, &abci.ResponseFinalizeBlock{
   166  					TxResults: []*abci.ExecTxResult{
   167  						{Data: []byte{1}},
   168  						{Data: []byte{2}},
   169  						{Data: []byte{3}},
   170  					},
   171  				})
   172  				require.NoError(t, err)
   173  			}
   174  
   175  			// Test assertions
   176  			err := stateStore.PruneStates(tc.pruneFrom, tc.pruneTo, tc.evidenceThresholdHeight)
   177  			if tc.expectErr {
   178  				require.Error(t, err)
   179  				return
   180  			}
   181  			require.NoError(t, err)
   182  
   183  			expectVals := sliceToMap(tc.expectVals)
   184  			expectParams := sliceToMap(tc.expectParams)
   185  			expectABCI := sliceToMap(tc.expectABCI)
   186  
   187  			for h := int64(1); h <= tc.makeHeights; h++ {
   188  				vals, err := stateStore.LoadValidators(h)
   189  				if expectVals[h] {
   190  					require.NoError(t, err, "validators height %v", h)
   191  					require.NotNil(t, vals)
   192  				} else {
   193  					require.Error(t, err, "validators height %v", h)
   194  					require.Equal(t, sm.ErrNoValSetForHeight{Height: h}, err)
   195  				}
   196  
   197  				params, err := stateStore.LoadConsensusParams(h)
   198  				if expectParams[h] {
   199  					require.NoError(t, err, "params height %v", h)
   200  					require.NotEmpty(t, params)
   201  				} else {
   202  					require.Error(t, err, "params height %v", h)
   203  					require.Empty(t, params)
   204  				}
   205  
   206  				abci, err := stateStore.LoadFinalizeBlockResponse(h)
   207  				if expectABCI[h] {
   208  					require.NoError(t, err, "abci height %v", h)
   209  					require.NotNil(t, abci)
   210  				} else {
   211  					require.Error(t, err, "abci height %v", h)
   212  					require.Equal(t, sm.ErrNoABCIResponsesForHeight{Height: h}, err)
   213  				}
   214  			}
   215  		})
   216  	}
   217  }
   218  
   219  func TestTxResultsHash(t *testing.T) {
   220  	txResults := []*abci.ExecTxResult{
   221  		{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
   222  	}
   223  
   224  	root := sm.TxResultsHash(txResults)
   225  
   226  	// root should be Merkle tree root of ExecTxResult responses
   227  	results := types.NewResults(txResults)
   228  	assert.Equal(t, root, results.Hash())
   229  
   230  	// test we can prove first ExecTxResult
   231  	proof := results.ProveResult(0)
   232  	bz, err := results[0].Marshal()
   233  	require.NoError(t, err)
   234  	assert.NoError(t, proof.Verify(root, bz))
   235  }
   236  
   237  func sliceToMap(s []int64) map[int64]bool {
   238  	m := make(map[int64]bool, len(s))
   239  	for _, i := range s {
   240  		m[i] = true
   241  	}
   242  	return m
   243  }
   244  
   245  func TestLastFinalizeBlockResponses(t *testing.T) {
   246  	// create an empty state store.
   247  	t.Run("Not persisting responses", func(t *testing.T) {
   248  		stateDB := dbm.NewMemDB()
   249  		stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   250  			DiscardABCIResponses: false,
   251  		})
   252  		responses, err := stateStore.LoadFinalizeBlockResponse(1)
   253  		require.Error(t, err)
   254  		require.Nil(t, responses)
   255  		// stub the abciresponses.
   256  		response1 := &abci.ResponseFinalizeBlock{
   257  			TxResults: []*abci.ExecTxResult{
   258  				{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
   259  			},
   260  		}
   261  		// create new db and state store and set discard abciresponses to false.
   262  		stateDB = dbm.NewMemDB()
   263  		stateStore = sm.NewStore(stateDB, sm.StoreOptions{DiscardABCIResponses: false})
   264  		height := int64(10)
   265  		// save the last abci response.
   266  		err = stateStore.SaveFinalizeBlockResponse(height, response1)
   267  		require.NoError(t, err)
   268  		// search for the last finalize block response and check if it has saved.
   269  		lastResponse, err := stateStore.LoadLastFinalizeBlockResponse(height)
   270  		require.NoError(t, err)
   271  		// check to see if the saved response height is the same as the loaded height.
   272  		assert.Equal(t, lastResponse, response1)
   273  		// use an incorret height to make sure the state store errors.
   274  		_, err = stateStore.LoadLastFinalizeBlockResponse(height + 1)
   275  		assert.Error(t, err)
   276  		// check if the abci response didnt save in the abciresponses.
   277  		responses, err = stateStore.LoadFinalizeBlockResponse(height)
   278  		require.NoError(t, err, responses)
   279  		require.Equal(t, response1, responses)
   280  	})
   281  
   282  	t.Run("persisting responses", func(t *testing.T) {
   283  		stateDB := dbm.NewMemDB()
   284  		height := int64(10)
   285  		// stub the second abciresponse.
   286  		response2 := &abci.ResponseFinalizeBlock{
   287  			TxResults: []*abci.ExecTxResult{
   288  				{Code: 44, Data: []byte("Hello again"), Log: "????"},
   289  			},
   290  		}
   291  		// create a new statestore with the responses on.
   292  		stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   293  			DiscardABCIResponses: true,
   294  		})
   295  		// save an additional response.
   296  		err := stateStore.SaveFinalizeBlockResponse(height+1, response2)
   297  		require.NoError(t, err)
   298  		// check to see if the response saved by calling the last response.
   299  		lastResponse2, err := stateStore.LoadLastFinalizeBlockResponse(height + 1)
   300  		require.NoError(t, err)
   301  		// check to see if the saved response height is the same as the loaded height.
   302  		assert.Equal(t, response2, lastResponse2)
   303  		// should error as we are no longer saving the response.
   304  		_, err = stateStore.LoadFinalizeBlockResponse(height + 1)
   305  		assert.Equal(t, sm.ErrFinalizeBlockResponsesNotPersisted, err)
   306  	})
   307  }
   308  
   309  func TestFinalizeBlockRecoveryUsingLegacyABCIResponses(t *testing.T) {
   310  	var (
   311  		height              int64 = 10
   312  		lastABCIResponseKey       = []byte("lastABCIResponseKey")
   313  		memDB                     = dbm.NewMemDB()
   314  		cp                        = types.DefaultConsensusParams().ToProto()
   315  		legacyResp                = cmtstate.ABCIResponsesInfo{
   316  			LegacyAbciResponses: &cmtstate.LegacyABCIResponses{
   317  				BeginBlock: &cmtstate.ResponseBeginBlock{
   318  					Events: []abci.Event{{
   319  						Type: "begin_block",
   320  						Attributes: []abci.EventAttribute{{
   321  							Key:   "key",
   322  							Value: "value",
   323  						}},
   324  					}},
   325  				},
   326  				DeliverTxs: []*abci.ExecTxResult{{
   327  					Events: []abci.Event{{
   328  						Type: "tx",
   329  						Attributes: []abci.EventAttribute{{
   330  							Key:   "key",
   331  							Value: "value",
   332  						}},
   333  					}},
   334  				}},
   335  				EndBlock: &cmtstate.ResponseEndBlock{
   336  					ConsensusParamUpdates: &cp,
   337  				},
   338  			},
   339  			Height: height,
   340  		}
   341  	)
   342  	bz, err := legacyResp.Marshal()
   343  	require.NoError(t, err)
   344  	// should keep this in parity with state/store.go
   345  	require.NoError(t, memDB.Set(lastABCIResponseKey, bz))
   346  	stateStore := sm.NewStore(memDB, sm.StoreOptions{DiscardABCIResponses: false})
   347  	resp, err := stateStore.LoadLastFinalizeBlockResponse(height)
   348  	require.NoError(t, err)
   349  	require.Equal(t, resp.ConsensusParamUpdates, &cp)
   350  	require.Equal(t, resp.Events, legacyResp.LegacyAbciResponses.BeginBlock.Events)
   351  	require.Equal(t, resp.TxResults[0], legacyResp.LegacyAbciResponses.DeliverTxs[0])
   352  }