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 }