github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/state/store_test.go (about)

     1  package state_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	dbm "github.com/tendermint/tm-db"
    12  
    13  	abci "github.com/ari-anchor/sei-tendermint/abci/types"
    14  	"github.com/ari-anchor/sei-tendermint/config"
    15  	"github.com/ari-anchor/sei-tendermint/crypto"
    16  	"github.com/ari-anchor/sei-tendermint/crypto/ed25519"
    17  	sm "github.com/ari-anchor/sei-tendermint/internal/state"
    18  	"github.com/ari-anchor/sei-tendermint/internal/test/factory"
    19  	tmrand "github.com/ari-anchor/sei-tendermint/libs/rand"
    20  	"github.com/ari-anchor/sei-tendermint/types"
    21  )
    22  
    23  const (
    24  	// make sure this is the same as in state/store.go
    25  	valSetCheckpointInterval = 100000
    26  )
    27  
    28  func TestStoreBootstrap(t *testing.T) {
    29  	stateDB := dbm.NewMemDB()
    30  	stateStore := sm.NewStore(stateDB)
    31  	ctx, cancel := context.WithCancel(context.Background())
    32  	defer cancel()
    33  	val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
    34  	require.NoError(t, err)
    35  	val2, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
    36  	require.NoError(t, err)
    37  	val3, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
    38  	require.NoError(t, err)
    39  	vals := types.NewValidatorSet([]*types.Validator{val, val2, val3})
    40  	bootstrapState := makeRandomStateFromValidatorSet(vals, 100, 100)
    41  	require.NoError(t, stateStore.Bootstrap(bootstrapState))
    42  
    43  	// bootstrap should also save the previous validator
    44  	_, err = stateStore.LoadValidators(99)
    45  	require.NoError(t, err)
    46  
    47  	_, err = stateStore.LoadValidators(100)
    48  	require.NoError(t, err)
    49  
    50  	_, err = stateStore.LoadValidators(101)
    51  	require.NoError(t, err)
    52  
    53  	state, err := stateStore.Load()
    54  	require.NoError(t, err)
    55  	require.Equal(t, bootstrapState, state)
    56  }
    57  
    58  func TestStoreLoadValidators(t *testing.T) {
    59  	stateDB := dbm.NewMemDB()
    60  	stateStore := sm.NewStore(stateDB)
    61  	ctx, cancel := context.WithCancel(context.Background())
    62  	defer cancel()
    63  	val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
    64  	require.NoError(t, err)
    65  	val2, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
    66  	require.NoError(t, err)
    67  	val3, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
    68  	require.NoError(t, err)
    69  	vals := types.NewValidatorSet([]*types.Validator{val, val2, val3})
    70  
    71  	// 1) LoadValidators loads validators using a height where they were last changed
    72  	// Note that only the next validators at height h + 1 are saved
    73  	require.NoError(t, stateStore.Save(makeRandomStateFromValidatorSet(vals, 1, 1)))
    74  	require.NoError(t, stateStore.Save(makeRandomStateFromValidatorSet(vals.CopyIncrementProposerPriority(1), 2, 1)))
    75  	loadedVals, err := stateStore.LoadValidators(3)
    76  	require.NoError(t, err)
    77  	require.Equal(t, vals.CopyIncrementProposerPriority(3), loadedVals)
    78  
    79  	// 2) LoadValidators loads validators using a checkpoint height
    80  
    81  	// add a validator set at the checkpoint
    82  	err = stateStore.Save(makeRandomStateFromValidatorSet(vals, valSetCheckpointInterval, 1))
    83  	require.NoError(t, err)
    84  
    85  	// check that a request will go back to the last checkpoint
    86  	_, err = stateStore.LoadValidators(valSetCheckpointInterval + 1)
    87  	require.Error(t, err)
    88  	require.Equal(t, fmt.Sprintf("couldn't find validators at height %d (height %d was originally requested): "+
    89  		"value retrieved from db is empty",
    90  		valSetCheckpointInterval, valSetCheckpointInterval+1), err.Error())
    91  
    92  	// now save a validator set at that checkpoint
    93  	err = stateStore.Save(makeRandomStateFromValidatorSet(vals, valSetCheckpointInterval-1, 1))
    94  	require.NoError(t, err)
    95  
    96  	loadedVals, err = stateStore.LoadValidators(valSetCheckpointInterval)
    97  	require.NoError(t, err)
    98  	// validator set gets updated with the one given hence we expect it to equal next validators (with an increment of one)
    99  	// as opposed to being equal to an increment of 100000 - 1 (if we didn't save at the checkpoint)
   100  	require.Equal(t, vals.CopyIncrementProposerPriority(2), loadedVals)
   101  	require.NotEqual(t, vals.CopyIncrementProposerPriority(valSetCheckpointInterval), loadedVals)
   102  }
   103  
   104  // This benchmarks the speed of loading validators from different heights if there is no validator set change.
   105  // NOTE: This isn't too indicative of validator retrieval speed as the db is always (regardless of height) only
   106  // performing two operations: 1) retrieve validator info at height x, which has a last validator set change of 1
   107  // and 2) retrieve the validator set at the aforementioned height 1.
   108  func BenchmarkLoadValidators(b *testing.B) {
   109  	const valSetSize = 100
   110  
   111  	cfg, err := config.ResetTestRoot(b.TempDir(), "state_")
   112  	require.NoError(b, err)
   113  
   114  	defer os.RemoveAll(cfg.RootDir)
   115  	dbType := dbm.BackendType(cfg.DBBackend)
   116  	stateDB, err := dbm.NewDB("state", dbType, cfg.DBDir())
   117  	require.NoError(b, err)
   118  	stateStore := sm.NewStore(stateDB)
   119  	state, err := sm.MakeGenesisStateFromFile(cfg.GenesisFile())
   120  	if err != nil {
   121  		b.Fatal(err)
   122  	}
   123  
   124  	state.Validators = genValSet(valSetSize)
   125  	state.NextValidators = state.Validators.CopyIncrementProposerPriority(1)
   126  	err = stateStore.Save(state)
   127  	require.NoError(b, err)
   128  
   129  	b.ResetTimer()
   130  
   131  	for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ...
   132  		i := i
   133  		err = stateStore.Save(makeRandomStateFromValidatorSet(state.NextValidators,
   134  			int64(i)-1, state.LastHeightValidatorsChanged))
   135  		if err != nil {
   136  			b.Fatalf("error saving store: %v", err)
   137  		}
   138  
   139  		b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) {
   140  			for n := 0; n < b.N; n++ {
   141  				_, err := stateStore.LoadValidators(int64(i))
   142  				if err != nil {
   143  					b.Fatal(err)
   144  				}
   145  			}
   146  		})
   147  	}
   148  }
   149  
   150  func TestStoreLoadConsensusParams(t *testing.T) {
   151  	ctx, cancel := context.WithCancel(context.Background())
   152  	defer cancel()
   153  
   154  	stateDB := dbm.NewMemDB()
   155  	stateStore := sm.NewStore(stateDB)
   156  	err := stateStore.Save(makeRandomStateFromConsensusParams(ctx, t, types.DefaultConsensusParams(), 1, 1))
   157  	require.NoError(t, err)
   158  	params, err := stateStore.LoadConsensusParams(1)
   159  	require.NoError(t, err)
   160  	require.Equal(t, types.DefaultConsensusParams(), &params)
   161  
   162  	// we give the state store different params but say that the height hasn't changed, hence
   163  	// it should save a pointer to the params at height 1
   164  	differentParams := types.DefaultConsensusParams()
   165  	differentParams.Block.MaxBytes = 20000
   166  	err = stateStore.Save(makeRandomStateFromConsensusParams(ctx, t, differentParams, 10, 1))
   167  	require.NoError(t, err)
   168  	res, err := stateStore.LoadConsensusParams(10)
   169  	require.NoError(t, err)
   170  	require.Equal(t, res, params)
   171  	require.NotEqual(t, res, differentParams)
   172  }
   173  
   174  func TestPruneStates(t *testing.T) {
   175  	testcases := map[string]struct {
   176  		startHeight           int64
   177  		endHeight             int64
   178  		pruneHeight           int64
   179  		expectErr             bool
   180  		remainingValSetHeight int64
   181  		remainingParamsHeight int64
   182  	}{
   183  		"error when prune height is 0":           {1, 100, 0, true, 0, 0},
   184  		"error when prune height is negative":    {1, 100, -10, true, 0, 0},
   185  		"error when prune height does not exist": {1, 100, 101, true, 0, 0},
   186  		"prune all":                              {1, 100, 100, false, 93, 95},
   187  		"prune from non 1 height":                {10, 50, 40, false, 33, 35},
   188  		"prune some":                             {1, 10, 8, false, 3, 5},
   189  		// we test this because we flush to disk every 1000 "states"
   190  		"prune more than 1000 state": {1, 1010, 1010, false, 1003, 1005},
   191  		"prune across checkpoint":    {99900, 100002, 100002, false, 100000, 99995},
   192  	}
   193  	for name, tc := range testcases {
   194  		tc := tc
   195  		t.Run(name, func(t *testing.T) {
   196  			db := dbm.NewMemDB()
   197  
   198  			stateStore := sm.NewStore(db)
   199  			pk := ed25519.GenPrivKey().PubKey()
   200  
   201  			// Generate a bunch of state data. Validators change for heights ending with 3, and
   202  			// parameters when ending with 5.
   203  			validator := &types.Validator{Address: tmrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk}
   204  			validatorSet := &types.ValidatorSet{
   205  				Validators: []*types.Validator{validator},
   206  				Proposer:   validator,
   207  			}
   208  			valsChanged := int64(0)
   209  			paramsChanged := int64(0)
   210  
   211  			for h := tc.startHeight; h <= tc.endHeight; h++ {
   212  				if valsChanged == 0 || h%10 == 2 {
   213  					valsChanged = h + 1 // Have to add 1, since NextValidators is what's stored
   214  				}
   215  				if paramsChanged == 0 || h%10 == 5 {
   216  					paramsChanged = h
   217  				}
   218  
   219  				state := sm.State{
   220  					InitialHeight:   1,
   221  					LastBlockHeight: h - 1,
   222  					Validators:      validatorSet,
   223  					NextValidators:  validatorSet,
   224  					ConsensusParams: types.ConsensusParams{
   225  						Block: types.BlockParams{MaxBytes: 10e6},
   226  					},
   227  					LastHeightValidatorsChanged:      valsChanged,
   228  					LastHeightConsensusParamsChanged: paramsChanged,
   229  				}
   230  
   231  				if state.LastBlockHeight >= 1 {
   232  					state.LastValidators = state.Validators
   233  				}
   234  
   235  				err := stateStore.Save(state)
   236  				require.NoError(t, err)
   237  
   238  				err = stateStore.SaveFinalizeBlockResponses(h, &abci.ResponseFinalizeBlock{
   239  					TxResults: []*abci.ExecTxResult{
   240  						{Data: []byte{1}},
   241  						{Data: []byte{2}},
   242  						{Data: []byte{3}},
   243  					},
   244  				},
   245  				)
   246  				require.NoError(t, err)
   247  			}
   248  
   249  			// Test assertions
   250  			err := stateStore.PruneStates(tc.pruneHeight)
   251  			if tc.expectErr {
   252  				require.Error(t, err)
   253  				return
   254  			}
   255  			require.NoError(t, err)
   256  
   257  			for h := tc.pruneHeight; h <= tc.endHeight; h++ {
   258  				vals, err := stateStore.LoadValidators(h)
   259  				require.NoError(t, err, h)
   260  				require.NotNil(t, vals, h)
   261  
   262  				params, err := stateStore.LoadConsensusParams(h)
   263  				require.NoError(t, err, h)
   264  				require.NotNil(t, params, h)
   265  
   266  				finRes, err := stateStore.LoadFinalizeBlockResponses(h)
   267  				require.NoError(t, err, h)
   268  				require.NotNil(t, finRes, h)
   269  			}
   270  
   271  			emptyParams := types.ConsensusParams{}
   272  
   273  			for h := tc.startHeight; h < tc.pruneHeight; h++ {
   274  				vals, err := stateStore.LoadValidators(h)
   275  				if h == tc.remainingValSetHeight {
   276  					require.NoError(t, err, h)
   277  					require.NotNil(t, vals, h)
   278  				} else {
   279  					require.Error(t, err, h)
   280  					require.Nil(t, vals, h)
   281  				}
   282  
   283  				params, err := stateStore.LoadConsensusParams(h)
   284  				if h == tc.remainingParamsHeight {
   285  					require.NoError(t, err, h)
   286  					require.NotEqual(t, emptyParams, params, h)
   287  				} else {
   288  					require.Error(t, err, h)
   289  					require.Equal(t, emptyParams, params, h)
   290  				}
   291  
   292  				finRes, err := stateStore.LoadFinalizeBlockResponses(h)
   293  				require.Error(t, err, h)
   294  				require.Nil(t, finRes, h)
   295  			}
   296  		})
   297  	}
   298  }