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