github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/store/store_test.go (about)

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