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