github.com/fiagdao/tendermint@v0.32.11-0.20220824195748-2087fcc480c1/store/store_test.go (about) 1 package store 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "runtime/debug" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/pkg/errors" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 db "github.com/tendermint/tm-db" 16 dbm "github.com/tendermint/tm-db" 17 18 cfg "github.com/tendermint/tendermint/config" 19 "github.com/tendermint/tendermint/libs/log" 20 sm "github.com/tendermint/tendermint/state" 21 22 "github.com/tendermint/tendermint/types" 23 tmtime "github.com/tendermint/tendermint/types/time" 24 ) 25 26 // A cleanupFunc cleans up any config / test files created for a particular 27 // test. 28 type cleanupFunc func() 29 30 // make a Commit with a single vote containing just the height and a timestamp 31 func makeTestCommit(height int64, timestamp time.Time) *types.Commit { 32 commitSigs := []types.CommitSig{{ 33 BlockIDFlag: types.BlockIDFlagCommit, 34 ValidatorAddress: []byte("ValidatorAddress"), 35 Timestamp: timestamp, 36 Signature: []byte("Signature"), 37 }} 38 return types.NewCommit(height, 0, types.BlockID{}, commitSigs) 39 } 40 41 func makeTxs(height int64) (txs []types.Tx) { 42 for i := 0; i < 10; i++ { 43 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 44 } 45 return txs 46 } 47 48 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 49 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address) 50 return block 51 } 52 53 func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFunc) { 54 config := cfg.ResetTestRoot("blockchain_reactor_test") 55 // blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB()) 56 // stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB()) 57 blockDB := dbm.NewMemDB() 58 stateDB := dbm.NewMemDB() 59 state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) 60 if err != nil { 61 panic(errors.Wrap(err, "error constructing state from genesis file")) 62 } 63 return state, NewBlockStore(blockDB), func() { os.RemoveAll(config.RootDir) } 64 } 65 66 func TestLoadBlockStoreStateJSON(t *testing.T) { 67 db := db.NewMemDB() 68 bsj := &BlockStoreStateJSON{Base: 100, Height: 1000} 69 bsj.Save(db) 70 71 retrBSJ := LoadBlockStoreStateJSON(db) 72 assert.Equal(t, *bsj, retrBSJ, "expected the retrieved DBs to match") 73 } 74 75 func TestLoadBlockStoreStateJSON_Empty(t *testing.T) { 76 db := db.NewMemDB() 77 78 bsj := &BlockStoreStateJSON{} 79 bsj.Save(db) 80 81 retrBSJ := LoadBlockStoreStateJSON(db) 82 assert.Equal(t, BlockStoreStateJSON{}, retrBSJ, "expected the retrieved DBs to match") 83 } 84 85 func TestLoadBlockStoreStateJSON_NoBase(t *testing.T) { 86 db := db.NewMemDB() 87 88 bsj := &BlockStoreStateJSON{Height: 1000} 89 bsj.Save(db) 90 91 retrBSJ := LoadBlockStoreStateJSON(db) 92 assert.Equal(t, BlockStoreStateJSON{Base: 1, Height: 1000}, retrBSJ, "expected the retrieved DBs to match") 93 } 94 95 func TestNewBlockStore(t *testing.T) { 96 db := db.NewMemDB() 97 err := db.Set(blockStoreKey, []byte(`{"base": "100", "height": "10000"}`)) 98 require.NoError(t, err) 99 bs := NewBlockStore(db) 100 require.Equal(t, int64(100), bs.Base(), "failed to properly parse blockstore") 101 require.Equal(t, int64(10000), bs.Height(), "failed to properly parse blockstore") 102 103 panicCausers := []struct { 104 data []byte 105 wantErr string 106 }{ 107 {[]byte("artful-doger"), "not unmarshal bytes"}, 108 {[]byte(" "), "unmarshal bytes"}, 109 } 110 111 for i, tt := range panicCausers { 112 tt := tt 113 // Expecting a panic here on trying to parse an invalid blockStore 114 _, _, panicErr := doFn(func() (interface{}, error) { 115 err := db.Set(blockStoreKey, tt.data) 116 require.NoError(t, err) 117 _ = NewBlockStore(db) 118 return nil, nil 119 }) 120 require.NotNil(t, panicErr, "#%d panicCauser: %q expected a panic", i, tt.data) 121 assert.Contains(t, fmt.Sprintf("%#v", panicErr), tt.wantErr, "#%d data: %q", i, tt.data) 122 } 123 124 err = db.Set(blockStoreKey, nil) 125 require.NoError(t, err) 126 bs = NewBlockStore(db) 127 assert.Equal(t, bs.Height(), int64(0), "expecting nil bytes to be unmarshaled alright") 128 } 129 130 func freshBlockStore() (*BlockStore, db.DB) { 131 db := db.NewMemDB() 132 return NewBlockStore(db), db 133 } 134 135 var ( 136 state sm.State 137 block *types.Block 138 partSet *types.PartSet 139 part1 *types.Part 140 part2 *types.Part 141 seenCommit1 *types.Commit 142 ) 143 144 func TestMain(m *testing.M) { 145 var cleanup cleanupFunc 146 state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) 147 block = makeBlock(1, state, new(types.Commit)) 148 partSet = block.MakePartSet(2) 149 part1 = partSet.GetPart(0) 150 part2 = partSet.GetPart(1) 151 seenCommit1 = makeTestCommit(10, tmtime.Now()) 152 code := m.Run() 153 cleanup() 154 os.Exit(code) 155 } 156 157 // TODO: This test should be simplified ... 158 159 func TestBlockStoreSaveLoadBlock(t *testing.T) { 160 state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) 161 defer cleanup() 162 require.Equal(t, bs.Base(), int64(0), "initially the base should be zero") 163 require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") 164 165 // check there are no blocks at various heights 166 noBlockHeights := []int64{0, -1, 100, 1000, 2} 167 for i, height := range noBlockHeights { 168 if g := bs.LoadBlock(height); g != nil { 169 t.Errorf("#%d: height(%d) got a block; want nil", i, height) 170 } 171 } 172 173 // save a block 174 block := makeBlock(bs.Height()+1, state, new(types.Commit)) 175 validPartSet := block.MakePartSet(2) 176 seenCommit := makeTestCommit(10, tmtime.Now()) 177 bs.SaveBlock(block, partSet, seenCommit) 178 require.EqualValues(t, 1, bs.Base(), "expecting the new height to be changed") 179 require.EqualValues(t, block.Header.Height, bs.Height(), "expecting the new height to be changed") 180 181 incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2}) 182 uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0}) 183 uncontiguousPartSet.AddPart(part2) 184 185 header1 := types.Header{ 186 Height: 1, 187 ChainID: "block_test", 188 Time: tmtime.Now(), 189 } 190 191 // End of setup, test data 192 193 commitAtH10 := makeTestCommit(10, tmtime.Now()) 194 tuples := []struct { 195 block *types.Block 196 parts *types.PartSet 197 seenCommit *types.Commit 198 wantPanic string 199 wantErr bool 200 201 corruptBlockInDB bool 202 corruptCommitInDB bool 203 corruptSeenCommitInDB bool 204 eraseCommitInDB bool 205 eraseSeenCommitInDB bool 206 }{ 207 { 208 block: newBlock(header1, commitAtH10), 209 parts: validPartSet, 210 seenCommit: seenCommit1, 211 }, 212 213 { 214 block: nil, 215 wantPanic: "only save a non-nil block", 216 }, 217 218 { 219 block: newBlock( // New block at height 5 in empty block store is fine 220 types.Header{Height: 5, ChainID: "block_test", Time: tmtime.Now()}, 221 makeTestCommit(5, tmtime.Now()), 222 ), 223 parts: validPartSet, 224 seenCommit: makeTestCommit(5, tmtime.Now()), 225 }, 226 227 { 228 block: newBlock(header1, commitAtH10), 229 parts: incompletePartSet, 230 wantPanic: "only save complete block", // incomplete parts 231 }, 232 233 { 234 block: newBlock(header1, commitAtH10), 235 parts: validPartSet, 236 seenCommit: seenCommit1, 237 corruptCommitInDB: true, // Corrupt the DB's commit entry 238 wantPanic: "unmarshal to types.Commit failed", 239 }, 240 241 { 242 block: newBlock(header1, commitAtH10), 243 parts: validPartSet, 244 seenCommit: seenCommit1, 245 wantPanic: "unmarshal to types.BlockMeta failed", 246 corruptBlockInDB: true, // Corrupt the DB's block entry 247 }, 248 249 { 250 block: newBlock(header1, commitAtH10), 251 parts: validPartSet, 252 seenCommit: seenCommit1, 253 254 // Expecting no error and we want a nil back 255 eraseSeenCommitInDB: true, 256 }, 257 258 { 259 block: newBlock(header1, commitAtH10), 260 parts: validPartSet, 261 seenCommit: seenCommit1, 262 263 corruptSeenCommitInDB: true, 264 wantPanic: "unmarshal to types.Commit failed", 265 }, 266 267 { 268 block: newBlock(header1, commitAtH10), 269 parts: validPartSet, 270 seenCommit: seenCommit1, 271 272 // Expecting no error and we want a nil back 273 eraseCommitInDB: true, 274 }, 275 } 276 277 type quad struct { 278 block *types.Block 279 commit *types.Commit 280 meta *types.BlockMeta 281 282 seenCommit *types.Commit 283 } 284 285 for i, tuple := range tuples { 286 tuple := tuple 287 bs, db := freshBlockStore() 288 // SaveBlock 289 res, err, panicErr := doFn(func() (interface{}, error) { 290 bs.SaveBlock(tuple.block, tuple.parts, tuple.seenCommit) 291 if tuple.block == nil { 292 return nil, nil 293 } 294 295 if tuple.corruptBlockInDB { 296 err := db.Set(calcBlockMetaKey(tuple.block.Height), []byte("block-bogus")) 297 require.NoError(t, err) 298 } 299 bBlock := bs.LoadBlock(tuple.block.Height) 300 bBlockMeta := bs.LoadBlockMeta(tuple.block.Height) 301 302 if tuple.eraseSeenCommitInDB { 303 db.Delete(calcSeenCommitKey(tuple.block.Height)) 304 } 305 if tuple.corruptSeenCommitInDB { 306 err := db.Set(calcSeenCommitKey(tuple.block.Height), []byte("bogus-seen-commit")) 307 require.NoError(t, err) 308 } 309 bSeenCommit := bs.LoadSeenCommit(tuple.block.Height) 310 311 commitHeight := tuple.block.Height - 1 312 if tuple.eraseCommitInDB { 313 db.Delete(calcBlockCommitKey(commitHeight)) 314 } 315 if tuple.corruptCommitInDB { 316 err := db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus")) 317 require.NoError(t, err) 318 } 319 bCommit := bs.LoadBlockCommit(commitHeight) 320 return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit, 321 meta: bBlockMeta}, nil 322 }) 323 324 if subStr := tuple.wantPanic; subStr != "" { 325 if panicErr == nil { 326 t.Errorf("#%d: want a non-nil panic", i) 327 } else if got := fmt.Sprintf("%#v", panicErr); !strings.Contains(got, subStr) { 328 t.Errorf("#%d:\n\tgotErr: %q\nwant substring: %q", i, got, subStr) 329 } 330 continue 331 } 332 333 if tuple.wantErr { 334 if err == nil { 335 t.Errorf("#%d: got nil error", i) 336 } 337 continue 338 } 339 340 assert.Nil(t, panicErr, "#%d: unexpected panic", i) 341 assert.Nil(t, err, "#%d: expecting a non-nil error", i) 342 qua, ok := res.(*quad) 343 if !ok || qua == nil { 344 t.Errorf("#%d: got nil quad back; gotType=%T", i, res) 345 continue 346 } 347 if tuple.eraseSeenCommitInDB { 348 assert.Nil(t, qua.seenCommit, 349 "erased the seenCommit in the DB hence we should get back a nil seenCommit") 350 } 351 if tuple.eraseCommitInDB { 352 assert.Nil(t, qua.commit, 353 "erased the commit in the DB hence we should get back a nil commit") 354 } 355 } 356 } 357 358 func TestLoadBlockPart(t *testing.T) { 359 bs, db := freshBlockStore() 360 height, index := int64(10), 1 361 loadPart := func() (interface{}, error) { 362 part := bs.LoadBlockPart(height, index) 363 return part, nil 364 } 365 366 // Initially no contents. 367 // 1. Requesting for a non-existent block shouldn't fail 368 res, _, panicErr := doFn(loadPart) 369 require.Nil(t, panicErr, "a non-existent block part shouldn't cause a panic") 370 require.Nil(t, res, "a non-existent block part should return nil") 371 372 // 2. Next save a corrupted block then try to load it 373 err := db.Set(calcBlockPartKey(height, index), []byte("Tendermint")) 374 require.NoError(t, err) 375 res, _, panicErr = doFn(loadPart) 376 require.NotNil(t, panicErr, "expecting a non-nil panic") 377 require.Contains(t, panicErr.Error(), "unmarshal to types.Part failed") 378 379 // 3. A good block serialized and saved to the DB should be retrievable 380 err = db.Set(calcBlockPartKey(height, index), cdc.MustMarshalBinaryBare(part1)) 381 require.NoError(t, err) 382 gotPart, _, panicErr := doFn(loadPart) 383 require.Nil(t, panicErr, "an existent and proper block should not panic") 384 require.Nil(t, res, "a properly saved block should return a proper block") 385 require.Equal(t, gotPart.(*types.Part), part1, 386 "expecting successful retrieval of previously saved block") 387 } 388 389 func TestPruneBlocks(t *testing.T) { 390 config := cfg.ResetTestRoot("blockchain_reactor_test") 391 defer os.RemoveAll(config.RootDir) 392 state, err := sm.LoadStateFromDBOrGenesisFile(dbm.NewMemDB(), config.GenesisFile()) 393 require.NoError(t, err) 394 db := dbm.NewMemDB() 395 bs := NewBlockStore(db) 396 assert.EqualValues(t, 0, bs.Base()) 397 assert.EqualValues(t, 0, bs.Height()) 398 assert.EqualValues(t, 0, bs.Size()) 399 400 // pruning an empty store should error, even when pruning to 0 401 _, err = bs.PruneBlocks(1) 402 require.Error(t, err) 403 404 _, err = bs.PruneBlocks(0) 405 require.Error(t, err) 406 407 // make more than 1000 blocks, to test batch deletions 408 for h := int64(1); h <= 1500; h++ { 409 block := makeBlock(h, state, new(types.Commit)) 410 partSet := block.MakePartSet(2) 411 seenCommit := makeTestCommit(h, tmtime.Now()) 412 bs.SaveBlock(block, partSet, seenCommit) 413 } 414 415 assert.EqualValues(t, 1, bs.Base()) 416 assert.EqualValues(t, 1500, bs.Height()) 417 assert.EqualValues(t, 1500, bs.Size()) 418 419 prunedBlock := bs.LoadBlock(1199) 420 421 // Check that basic pruning works 422 pruned, err := bs.PruneBlocks(1200) 423 require.NoError(t, err) 424 assert.EqualValues(t, 1199, pruned) 425 assert.EqualValues(t, 1200, bs.Base()) 426 assert.EqualValues(t, 1500, bs.Height()) 427 assert.EqualValues(t, 301, bs.Size()) 428 assert.EqualValues(t, BlockStoreStateJSON{ 429 Base: 1200, 430 Height: 1500, 431 }, LoadBlockStoreStateJSON(db)) 432 433 require.NotNil(t, bs.LoadBlock(1200)) 434 require.Nil(t, bs.LoadBlock(1199)) 435 require.Nil(t, bs.LoadBlockByHash(prunedBlock.Hash())) 436 require.Nil(t, bs.LoadBlockCommit(1199)) 437 require.Nil(t, bs.LoadBlockMeta(1199)) 438 require.Nil(t, bs.LoadBlockPart(1199, 1)) 439 440 for i := int64(1); i < 1200; i++ { 441 require.Nil(t, bs.LoadBlock(i)) 442 } 443 for i := int64(1200); i <= 1500; i++ { 444 require.NotNil(t, bs.LoadBlock(i)) 445 } 446 447 // Pruning below the current base should error 448 _, err = bs.PruneBlocks(1199) 449 require.Error(t, err) 450 451 // Pruning to the current base should work 452 pruned, err = bs.PruneBlocks(1200) 453 require.NoError(t, err) 454 assert.EqualValues(t, 0, pruned) 455 456 // Pruning again should work 457 pruned, err = bs.PruneBlocks(1300) 458 require.NoError(t, err) 459 assert.EqualValues(t, 100, pruned) 460 assert.EqualValues(t, 1300, bs.Base()) 461 462 // Pruning beyond the current height should error 463 _, err = bs.PruneBlocks(1501) 464 require.Error(t, err) 465 466 // Pruning to the current height should work 467 pruned, err = bs.PruneBlocks(1500) 468 require.NoError(t, err) 469 assert.EqualValues(t, 200, pruned) 470 assert.Nil(t, bs.LoadBlock(1499)) 471 assert.NotNil(t, bs.LoadBlock(1500)) 472 assert.Nil(t, bs.LoadBlock(1501)) 473 } 474 475 func TestLoadBlockMeta(t *testing.T) { 476 bs, db := freshBlockStore() 477 height := int64(10) 478 loadMeta := func() (interface{}, error) { 479 meta := bs.LoadBlockMeta(height) 480 return meta, nil 481 } 482 483 // Initially no contents. 484 // 1. Requesting for a non-existent blockMeta shouldn't fail 485 res, _, panicErr := doFn(loadMeta) 486 require.Nil(t, panicErr, "a non-existent blockMeta shouldn't cause a panic") 487 require.Nil(t, res, "a non-existent blockMeta should return nil") 488 489 // 2. Next save a corrupted blockMeta then try to load it 490 err := db.Set(calcBlockMetaKey(height), []byte("Tendermint-Meta")) 491 require.NoError(t, err) 492 res, _, panicErr = doFn(loadMeta) 493 require.NotNil(t, panicErr, "expecting a non-nil panic") 494 require.Contains(t, panicErr.Error(), "unmarshal to types.BlockMeta") 495 496 // 3. A good blockMeta serialized and saved to the DB should be retrievable 497 meta := &types.BlockMeta{} 498 err = db.Set(calcBlockMetaKey(height), cdc.MustMarshalBinaryBare(meta)) 499 require.NoError(t, err) 500 gotMeta, _, panicErr := doFn(loadMeta) 501 require.Nil(t, panicErr, "an existent and proper block should not panic") 502 require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ") 503 require.Equal(t, cdc.MustMarshalBinaryBare(meta), cdc.MustMarshalBinaryBare(gotMeta), 504 "expecting successful retrieval of previously saved blockMeta") 505 } 506 507 func TestBlockFetchAtHeight(t *testing.T) { 508 state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) 509 defer cleanup() 510 require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") 511 block := makeBlock(bs.Height()+1, state, new(types.Commit)) 512 513 partSet := block.MakePartSet(2) 514 seenCommit := makeTestCommit(10, tmtime.Now()) 515 bs.SaveBlock(block, partSet, seenCommit) 516 require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") 517 518 blockAtHeight := bs.LoadBlock(bs.Height()) 519 bz1 := cdc.MustMarshalBinaryBare(block) 520 bz2 := cdc.MustMarshalBinaryBare(blockAtHeight) 521 require.Equal(t, bz1, bz2) 522 require.Equal(t, block.Hash(), blockAtHeight.Hash(), 523 "expecting a successful load of the last saved block") 524 525 blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1) 526 require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1") 527 blockAtHeightPlus2 := bs.LoadBlock(bs.Height() + 2) 528 require.Nil(t, blockAtHeightPlus2, "expecting an unsuccessful load of Height()+2") 529 } 530 531 func doFn(fn func() (interface{}, error)) (res interface{}, err error, panicErr error) { 532 defer func() { 533 if r := recover(); r != nil { 534 switch e := r.(type) { 535 case error: 536 panicErr = e 537 case string: 538 panicErr = fmt.Errorf("%s", e) 539 default: 540 if st, ok := r.(fmt.Stringer); ok { 541 panicErr = fmt.Errorf("%s", st) 542 } else { 543 panicErr = fmt.Errorf("%s", debug.Stack()) 544 } 545 } 546 } 547 }() 548 549 res, err = fn() 550 return res, err, panicErr 551 } 552 553 func newBlock(hdr types.Header, lastCommit *types.Commit) *types.Block { 554 return &types.Block{ 555 Header: hdr, 556 LastCommit: lastCommit, 557 } 558 }