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(), ¶ms) 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 }