github.com/Finschia/ostracon@v1.1.5/state/store_test.go (about)

     1  package state_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	abci "github.com/tendermint/tendermint/abci/types"
    13  	tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
    14  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    15  	dbm "github.com/tendermint/tm-db"
    16  
    17  	cfg "github.com/Finschia/ostracon/config"
    18  	"github.com/Finschia/ostracon/crypto"
    19  	"github.com/Finschia/ostracon/crypto/ed25519"
    20  	tmrand "github.com/Finschia/ostracon/libs/rand"
    21  	sm "github.com/Finschia/ostracon/state"
    22  	statemocks "github.com/Finschia/ostracon/state/mocks"
    23  	"github.com/Finschia/ostracon/types"
    24  )
    25  
    26  const (
    27  	// persist validators every valSetCheckpointInterval blocks to avoid
    28  	// LoadValidators taking too much time.
    29  	// https://github.com/tendermint/tendermint/pull/3438
    30  	// 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators)
    31  	valSetCheckpointInterval = 100000
    32  )
    33  
    34  func TestStoreLoadValidators(t *testing.T) {
    35  	stateDB := dbm.NewMemDB()
    36  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    37  		DiscardABCIResponses: false,
    38  	})
    39  	val, _ := types.RandValidator(true, 10)
    40  	vals := types.NewValidatorSet([]*types.Validator{val})
    41  
    42  	// 1) LoadValidators loads validators using a height where they were last changed
    43  	err := sm.SaveValidatorsInfo(stateDB, 1, 1, []byte{}, vals)
    44  	require.NoError(t, err)
    45  	err = sm.SaveValidatorsInfo(stateDB, 2, 1, []byte{}, vals)
    46  	require.NoError(t, err)
    47  	loadedVals, err := stateStore.LoadValidators(2)
    48  	require.NoError(t, err)
    49  	assert.NotZero(t, loadedVals.Size())
    50  
    51  	// 2) LoadValidators loads validators using a checkpoint height
    52  
    53  	err = sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval, 1, []byte{}, vals)
    54  	require.NoError(t, err)
    55  
    56  	loadedVals, err = stateStore.LoadValidators(sm.ValSetCheckpointInterval)
    57  	require.NoError(t, err)
    58  	assert.NotZero(t, loadedVals.Size())
    59  }
    60  
    61  func BenchmarkLoadValidators(b *testing.B) {
    62  	const valSetSize = 100
    63  
    64  	config := cfg.ResetTestRoot("state_")
    65  	defer os.RemoveAll(config.RootDir)
    66  	dbType := dbm.BackendType(config.DBBackend)
    67  	stateDB, err := dbm.NewDB("state", dbType, config.DBDir())
    68  	require.NoError(b, err)
    69  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    70  		DiscardABCIResponses: false,
    71  	})
    72  	state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile())
    73  	if err != nil {
    74  		b.Fatal(err)
    75  	}
    76  
    77  	state.Validators = genValSet(valSetSize)
    78  	state.Validators.SelectProposer([]byte{}, 1, 0)
    79  	state.NextValidators = state.Validators.Copy()
    80  	state.NextValidators.SelectProposer([]byte{}, 2, 0)
    81  	err = stateStore.Save(state)
    82  	require.NoError(b, err)
    83  
    84  	for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ...
    85  		i := i
    86  		if err := sm.SaveValidatorsInfo(stateDB,
    87  			int64(i), state.LastHeightValidatorsChanged, []byte{}, state.NextValidators); err != nil {
    88  			b.Fatal(err)
    89  		}
    90  
    91  		b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) {
    92  			for n := 0; n < b.N; n++ {
    93  				_, err := stateStore.LoadValidators(int64(i))
    94  				if err != nil {
    95  					b.Fatal(err)
    96  				}
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  func createState(height, valsChanged, paramsChanged int64, validatorSet *types.ValidatorSet) sm.State {
   103  	if height < 1 {
   104  		panic(height)
   105  	}
   106  	state := sm.State{
   107  		InitialHeight:   1,
   108  		LastBlockHeight: height - 1,
   109  		Validators:      validatorSet,
   110  		NextValidators:  validatorSet,
   111  		ConsensusParams: tmproto.ConsensusParams{
   112  			Block: tmproto.BlockParams{MaxBytes: 10e6},
   113  		},
   114  		LastHeightValidatorsChanged:      valsChanged,
   115  		LastHeightConsensusParamsChanged: paramsChanged,
   116  		LastProofHash:                    []byte{0},
   117  	}
   118  	if state.LastBlockHeight >= 1 {
   119  		state.LastValidators = state.Validators
   120  	}
   121  	return state
   122  }
   123  
   124  func createStates(makeHeights int64) []sm.State {
   125  	states := []sm.State{}
   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: tmrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk}
   131  	validatorSet := &types.ValidatorSet{
   132  		Validators: []*types.Validator{validator},
   133  	}
   134  	valsChanged := int64(0)
   135  	paramsChanged := int64(0)
   136  
   137  	for h := int64(1); h <= makeHeights; h++ {
   138  		if valsChanged == 0 || h%10 == 2 {
   139  			valsChanged = h + 1 // Have to add 1, since NextValidators is what's stored
   140  		}
   141  		if paramsChanged == 0 || h%10 == 5 {
   142  			paramsChanged = h
   143  		}
   144  
   145  		state := createState(h, valsChanged, paramsChanged, validatorSet)
   146  		states = append(states, state)
   147  	}
   148  	return states
   149  }
   150  
   151  func TestPruneStates(t *testing.T) {
   152  	testcases := map[string]struct {
   153  		makeHeights  int64
   154  		pruneFrom    int64
   155  		pruneTo      int64
   156  		expectErr    bool
   157  		expectVals   []int64
   158  		expectParams []int64
   159  		expectABCI   []int64
   160  	}{
   161  		"error on pruning from 0":      {100, 0, 5, true, nil, nil, nil},
   162  		"error when from > to":         {100, 3, 2, true, nil, nil, nil},
   163  		"error when from == to":        {100, 3, 3, true, nil, nil, nil},
   164  		"error when to does not exist": {100, 1, 101, true, nil, nil, nil},
   165  		"prune all":                    {100, 1, 100, false, []int64{93, 100}, []int64{95, 100}, []int64{100}},
   166  		"prune some": {10, 2, 8, false, []int64{1, 3, 8, 9, 10},
   167  			[]int64{1, 5, 8, 9, 10}, []int64{1, 8, 9, 10}},
   168  		"prune across checkpoint": {100001, 1, 100001, false, []int64{99993, 100000, 100001},
   169  			[]int64{99995, 100001}, []int64{100001}},
   170  	}
   171  	for name, tc := range testcases {
   172  		tc := tc
   173  		t.Run(name, func(t *testing.T) {
   174  			db := dbm.NewMemDB()
   175  			stateStore := sm.NewStore(db, sm.StoreOptions{
   176  				DiscardABCIResponses: false,
   177  			})
   178  
   179  			states := createStates(tc.makeHeights)
   180  
   181  			for _, state := range states {
   182  				err := stateStore.Save(state)
   183  				require.NoError(t, err)
   184  
   185  				currentHeight := state.LastBlockHeight + int64(1)
   186  				err = stateStore.SaveABCIResponses(currentHeight, &tmstate.ABCIResponses{
   187  					DeliverTxs: []*abci.ResponseDeliverTx{
   188  						{Data: []byte{1}},
   189  						{Data: []byte{2}},
   190  						{Data: []byte{3}},
   191  					},
   192  				})
   193  				require.NoError(t, err)
   194  			}
   195  
   196  			// Test assertions
   197  			err := stateStore.PruneStates(tc.pruneFrom, tc.pruneTo)
   198  			if tc.expectErr {
   199  				require.Error(t, err)
   200  				return
   201  			}
   202  			require.NoError(t, err)
   203  
   204  			expectVals := sliceToMap(tc.expectVals)
   205  			expectParams := sliceToMap(tc.expectParams)
   206  			expectABCI := sliceToMap(tc.expectABCI)
   207  
   208  			for h := int64(1); h <= tc.makeHeights; h++ {
   209  				vals, err := stateStore.LoadValidators(h)
   210  				if expectVals[h] {
   211  					require.NoError(t, err, "validators height %v", h)
   212  					require.NotNil(t, vals)
   213  				} else {
   214  					require.Error(t, err, "validators height %v", h)
   215  					require.Equal(t, sm.ErrNoValSetForHeight{Height: h}, err)
   216  				}
   217  
   218  				params, err := stateStore.LoadConsensusParams(h)
   219  				if expectParams[h] {
   220  					require.NoError(t, err, "params height %v", h)
   221  					require.False(t, params.Equal(&tmproto.ConsensusParams{}))
   222  				} else {
   223  					require.Error(t, err, "params height %v", h)
   224  				}
   225  
   226  				abci, err := stateStore.LoadABCIResponses(h)
   227  				if expectABCI[h] {
   228  					require.NoError(t, err, "abci height %v", h)
   229  					require.NotNil(t, abci)
   230  				} else {
   231  					require.Error(t, err, "abci height %v", h)
   232  					require.Equal(t, sm.ErrNoABCIResponsesForHeight{Height: h}, err)
   233  				}
   234  			}
   235  		})
   236  	}
   237  }
   238  
   239  func TestPruneStatesDeleteErrHandle(t *testing.T) {
   240  	testcases := map[string]struct {
   241  		deleteValidatorsRet      error
   242  		deleteConsensusParamsRet error
   243  		deleteProofHashRet       error
   244  	}{
   245  		"error on deleting validators":       {errors.New("error"), nil, nil},
   246  		"error on deleting consensus params": {nil, errors.New("error"), nil},
   247  		"error on deleting proof hash":       {nil, nil, errors.New("error")},
   248  		"error on deleting all":              {errors.New("error"), errors.New("error"), errors.New("error")},
   249  	}
   250  	for name, tc := range testcases {
   251  		tc := tc
   252  		t.Run(name, func(t *testing.T) {
   253  			batchMock := &statemocks.Batch{}
   254  			batchMock.On("Close").Return(nil)
   255  			dbMock := &statemocks.DB{}
   256  			dbMock.On("NewBatch").Return(batchMock)
   257  
   258  			states := createStates(10)
   259  
   260  			for _, state := range states {
   261  				// Prepare a mock for prune states
   262  				nextHeight := state.LastBlockHeight + 1
   263  				if nextHeight == 1 {
   264  					nextHeight = state.InitialHeight
   265  					bufValidators, err := validatorsInfoToByte(nextHeight, nextHeight, state.Validators)
   266  					require.NoError(t, err)
   267  					batchMock.On("Delete", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight))).Return(nil)
   268  					dbMock.On("Get", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight))).Return(bufValidators, nil)
   269  				}
   270  				// create validators mock method
   271  				bufValidators, err := validatorsInfoToByte(nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
   272  				require.NoError(t, err)
   273  				batchMock.On("Delete", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight+1))).Return(tc.deleteValidatorsRet)
   274  				dbMock.On("Get", []byte(fmt.Sprintf("validatorsKey:%v", nextHeight+1))).Return(bufValidators, nil)
   275  
   276  				// create consensus params mock method
   277  				bufConsensusParams, err := consensusParamsInfoToByte(nextHeight,
   278  					state.LastHeightConsensusParamsChanged, state.ConsensusParams)
   279  				require.NoError(t, err)
   280  				batchMock.On("Delete", []byte(fmt.Sprintf("consensusParamsKey:%v", nextHeight))).Return(tc.deleteConsensusParamsRet)
   281  				dbMock.On("Get", []byte(fmt.Sprintf("consensusParamsKey:%v", nextHeight))).Return(bufConsensusParams, nil)
   282  
   283  				// create proof hash mock method
   284  				batchMock.On("Delete", []byte(fmt.Sprintf("proofHashKey:%v", nextHeight))).Return(tc.deleteProofHashRet)
   285  				dbMock.On("Get", []byte(fmt.Sprintf("proofHashKey:%v", nextHeight))).Return(state.LastProofHash, nil)
   286  			}
   287  
   288  			stateStoreInMock := sm.NewStore(dbMock, sm.StoreOptions{
   289  				DiscardABCIResponses: false,
   290  			})
   291  			err := stateStoreInMock.PruneStates(1, 10)
   292  			require.Error(t, err)
   293  		})
   294  	}
   295  }
   296  
   297  func TestABCIResponsesResultsHash(t *testing.T) {
   298  	responses := &tmstate.ABCIResponses{
   299  		BeginBlock: &abci.ResponseBeginBlock{},
   300  		DeliverTxs: []*abci.ResponseDeliverTx{
   301  			{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
   302  		},
   303  		EndBlock: &abci.ResponseEndBlock{},
   304  	}
   305  
   306  	root := sm.ABCIResponsesResultsHash(responses)
   307  
   308  	// root should be Merkle tree root of DeliverTxs responses
   309  	results := types.NewResults(responses.DeliverTxs)
   310  	assert.Equal(t, root, results.Hash())
   311  
   312  	// test we can prove first DeliverTx
   313  	proof := results.ProveResult(0)
   314  	bz, err := results[0].Marshal()
   315  	require.NoError(t, err)
   316  	assert.NoError(t, proof.Verify(root, bz))
   317  }
   318  
   319  func sliceToMap(s []int64) map[int64]bool {
   320  	m := make(map[int64]bool, len(s))
   321  	for _, i := range s {
   322  		m[i] = true
   323  	}
   324  	return m
   325  }
   326  
   327  func validatorsInfoToByte(height, lastHeightChanged int64, valSet *types.ValidatorSet) ([]byte, error) {
   328  	valInfo := &tmstate.ValidatorsInfo{
   329  		LastHeightChanged: lastHeightChanged,
   330  	}
   331  	if height == lastHeightChanged || height%valSetCheckpointInterval == 0 {
   332  		pv, err := valSet.ToProto()
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  		valInfo.ValidatorSet = pv
   337  	}
   338  
   339  	bz, err := valInfo.Marshal()
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	return bz, nil
   345  }
   346  
   347  func consensusParamsInfoToByte(nextHeight, changeHeight int64, params tmproto.ConsensusParams) ([]byte, error) {
   348  	paramsInfo := &tmstate.ConsensusParamsInfo{
   349  		LastHeightChanged: changeHeight,
   350  	}
   351  
   352  	if changeHeight == nextHeight {
   353  		paramsInfo.ConsensusParams = params
   354  	}
   355  	bz, err := paramsInfo.Marshal()
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	return bz, nil
   361  }
   362  
   363  func TestLastABCIResponses(t *testing.T) {
   364  	// create an empty state store.
   365  	t.Run("Not persisting responses", func(t *testing.T) {
   366  		stateDB := dbm.NewMemDB()
   367  		stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   368  			DiscardABCIResponses: false,
   369  		})
   370  		responses, err := stateStore.LoadABCIResponses(1)
   371  		require.Error(t, err)
   372  		require.Nil(t, responses)
   373  		// stub the abciresponses.
   374  		response1 := &tmstate.ABCIResponses{
   375  			BeginBlock: &abci.ResponseBeginBlock{},
   376  			DeliverTxs: []*abci.ResponseDeliverTx{
   377  				{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
   378  			},
   379  			EndBlock: &abci.ResponseEndBlock{},
   380  		}
   381  		// create new db and state store and set discard abciresponses to false.
   382  		stateDB = dbm.NewMemDB()
   383  		stateStore = sm.NewStore(stateDB, sm.StoreOptions{DiscardABCIResponses: false})
   384  		height := int64(10)
   385  		// save the last abci response.
   386  		err = stateStore.SaveABCIResponses(height, response1)
   387  		require.NoError(t, err)
   388  		// search for the last abciresponse and check if it has saved.
   389  		lastResponse, err := stateStore.LoadLastABCIResponse(height)
   390  		require.NoError(t, err)
   391  		// check to see if the saved response height is the same as the loaded height.
   392  		assert.Equal(t, lastResponse, response1)
   393  		// use an incorret height to make sure the state store errors.
   394  		_, err = stateStore.LoadLastABCIResponse(height + 1)
   395  		assert.Error(t, err)
   396  		// check if the abci response didnt save in the abciresponses.
   397  		responses, err = stateStore.LoadABCIResponses(height)
   398  		require.NoError(t, err, responses)
   399  		require.Equal(t, response1, responses)
   400  	})
   401  
   402  	t.Run("persisting responses", func(t *testing.T) {
   403  		stateDB := dbm.NewMemDB()
   404  		height := int64(10)
   405  		// stub the second abciresponse.
   406  		response2 := &tmstate.ABCIResponses{
   407  			BeginBlock: &abci.ResponseBeginBlock{},
   408  			DeliverTxs: []*abci.ResponseDeliverTx{
   409  				{Code: 44, Data: []byte("Hello again"), Log: "????"},
   410  			},
   411  			EndBlock: &abci.ResponseEndBlock{},
   412  		}
   413  		// create a new statestore with the responses on.
   414  		stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   415  			DiscardABCIResponses: true,
   416  		})
   417  		// save an additional response.
   418  		err := stateStore.SaveABCIResponses(height+1, response2)
   419  		require.NoError(t, err)
   420  		// check to see if the response saved by calling the last response.
   421  		lastResponse2, err := stateStore.LoadLastABCIResponse(height + 1)
   422  		require.NoError(t, err)
   423  		// check to see if the saved response height is the same as the loaded height.
   424  		assert.Equal(t, response2, lastResponse2)
   425  		// should error as we are no longer saving the response.
   426  		_, err = stateStore.LoadABCIResponses(height + 1)
   427  		assert.Equal(t, sm.ErrABCIResponsesNotPersisted, err)
   428  	})
   429  
   430  }