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