github.com/vipernet-xyz/tm@v0.34.24/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/vipernet-xyz/tm/config"
    18  	"github.com/vipernet-xyz/tm/crypto"
    19  	"github.com/vipernet-xyz/tm/libs/log"
    20  	tmrand "github.com/vipernet-xyz/tm/libs/rand"
    21  	tmstore "github.com/vipernet-xyz/tm/proto/tendermint/store"
    22  	tmversion "github.com/vipernet-xyz/tm/proto/tendermint/version"
    23  	sm "github.com/vipernet-xyz/tm/state"
    24  	"github.com/vipernet-xyz/tm/types"
    25  	tmtime "github.com/vipernet-xyz/tm/types/time"
    26  	"github.com/vipernet-xyz/tm/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  }