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