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