github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/store/store_test.go (about)

     1  package store
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	stdlog "log"
     7  	"os"
     8  	"runtime/debug"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	dbm "github.com/badrootd/nibiru-db"
    14  	"github.com/cosmos/gogoproto/proto"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	cfg "github.com/badrootd/nibiru-cometbft/config"
    19  	"github.com/badrootd/nibiru-cometbft/crypto"
    20  	"github.com/badrootd/nibiru-cometbft/internal/test"
    21  	"github.com/badrootd/nibiru-cometbft/libs/log"
    22  	cmtrand "github.com/badrootd/nibiru-cometbft/libs/rand"
    23  	cmtstore "github.com/badrootd/nibiru-cometbft/proto/tendermint/store"
    24  	cmtversion "github.com/badrootd/nibiru-cometbft/proto/tendermint/version"
    25  	sm "github.com/badrootd/nibiru-cometbft/state"
    26  	"github.com/badrootd/nibiru-cometbft/types"
    27  	cmttime "github.com/badrootd/nibiru-cometbft/types/time"
    28  	"github.com/badrootd/nibiru-cometbft/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: cmtrand.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 makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFunc) {
    48  	config := cfg.ResetTestRoot("blockchain_reactor_test")
    49  	// blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB())
    50  	// stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB())
    51  	blockDB := dbm.NewMemDB()
    52  	stateDB := dbm.NewMemDB()
    53  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
    54  		DiscardABCIResponses: false,
    55  	})
    56  	state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile())
    57  	if err != nil {
    58  		panic(fmt.Errorf("error constructing state from genesis file: %w", err))
    59  	}
    60  	return state, NewBlockStore(blockDB), func() { os.RemoveAll(config.RootDir) }
    61  }
    62  
    63  func TestLoadBlockStoreState(t *testing.T) {
    64  	type blockStoreTest struct {
    65  		testName string
    66  		bss      *cmtstore.BlockStoreState
    67  		want     cmtstore.BlockStoreState
    68  	}
    69  
    70  	testCases := []blockStoreTest{
    71  		{
    72  			"success", &cmtstore.BlockStoreState{Base: 100, Height: 1000},
    73  			cmtstore.BlockStoreState{Base: 100, Height: 1000},
    74  		},
    75  		{"empty", &cmtstore.BlockStoreState{}, cmtstore.BlockStoreState{}},
    76  		{"no base", &cmtstore.BlockStoreState{Height: 1000}, cmtstore.BlockStoreState{Base: 1, Height: 1000}},
    77  	}
    78  
    79  	for _, tc := range testCases {
    80  		db := dbm.NewMemDB()
    81  		SaveBlockStoreState(tc.bss, db)
    82  		retrBSJ := LoadBlockStoreState(db)
    83  		assert.Equal(t, tc.want, retrBSJ, "expected the retrieved DBs to match: %s", tc.testName)
    84  	}
    85  }
    86  
    87  func TestNewBlockStore(t *testing.T) {
    88  	db := dbm.NewMemDB()
    89  	bss := cmtstore.BlockStoreState{Base: 100, Height: 10000}
    90  	bz, _ := proto.Marshal(&bss)
    91  	err := db.Set(blockStoreKey, bz)
    92  	require.NoError(t, err)
    93  	bs := NewBlockStore(db)
    94  	require.Equal(t, int64(100), bs.Base(), "failed to properly parse blockstore")
    95  	require.Equal(t, int64(10000), bs.Height(), "failed to properly parse blockstore")
    96  
    97  	panicCausers := []struct {
    98  		data    []byte
    99  		wantErr string
   100  	}{
   101  		{[]byte("artful-doger"), "not unmarshal bytes"},
   102  		{[]byte(" "), "unmarshal bytes"},
   103  	}
   104  
   105  	for i, tt := range panicCausers {
   106  		tt := tt
   107  		// Expecting a panic here on trying to parse an invalid blockStore
   108  		_, _, panicErr := doFn(func() (interface{}, error) {
   109  			err := db.Set(blockStoreKey, tt.data)
   110  			require.NoError(t, err)
   111  			_ = NewBlockStore(db)
   112  			return nil, nil
   113  		})
   114  		require.NotNil(t, panicErr, "#%d panicCauser: %q expected a panic", i, tt.data)
   115  		assert.Contains(t, fmt.Sprintf("%#v", panicErr), tt.wantErr, "#%d data: %q", i, tt.data)
   116  	}
   117  
   118  	err = db.Set(blockStoreKey, []byte{})
   119  	require.NoError(t, err)
   120  	bs = NewBlockStore(db)
   121  	assert.Equal(t, bs.Height(), int64(0), "expecting empty bytes to be unmarshaled alright")
   122  }
   123  
   124  func freshBlockStore() (*BlockStore, dbm.DB) {
   125  	db := dbm.NewMemDB()
   126  	return NewBlockStore(db), db
   127  }
   128  
   129  var (
   130  	state       sm.State
   131  	block       *types.Block
   132  	partSet     *types.PartSet
   133  	part1       *types.Part
   134  	part2       *types.Part
   135  	seenCommit1 *types.Commit
   136  )
   137  
   138  func TestMain(m *testing.M) {
   139  	var cleanup cleanupFunc
   140  	var err error
   141  	state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
   142  	block = state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
   143  
   144  	partSet, err = block.MakePartSet(2)
   145  	if err != nil {
   146  		stdlog.Fatal(err)
   147  	}
   148  	part1 = partSet.GetPart(0)
   149  	part2 = partSet.GetPart(1)
   150  	seenCommit1 = makeTestCommit(10, cmttime.Now())
   151  	code := m.Run()
   152  	cleanup()
   153  	os.Exit(code)
   154  }
   155  
   156  // TODO: This test should be simplified ...
   157  
   158  func TestBlockStoreSaveLoadBlock(t *testing.T) {
   159  	state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
   160  	defer cleanup()
   161  	require.Equal(t, bs.Base(), int64(0), "initially the base should be zero")
   162  	require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
   163  
   164  	// check there are no blocks at various heights
   165  	noBlockHeights := []int64{0, -1, 100, 1000, 2}
   166  	for i, height := range noBlockHeights {
   167  		if g := bs.LoadBlock(height); g != nil {
   168  			t.Errorf("#%d: height(%d) got a block; want nil", i, height)
   169  		}
   170  	}
   171  
   172  	// save a block
   173  	block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
   174  	validPartSet, err := block.MakePartSet(2)
   175  	require.NoError(t, err)
   176  	seenCommit := makeTestCommit(10, cmttime.Now())
   177  	bs.SaveBlock(block, partSet, seenCommit)
   178  	require.EqualValues(t, 1, bs.Base(), "expecting the new height to be changed")
   179  	require.EqualValues(t, block.Header.Height, bs.Height(), "expecting the new height to be changed")
   180  
   181  	incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2})
   182  	uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0})
   183  	_, err = uncontiguousPartSet.AddPart(part2)
   184  	require.Error(t, err)
   185  
   186  	header1 := types.Header{
   187  		Version:         cmtversion.Consensus{Block: version.BlockProtocol},
   188  		Height:          1,
   189  		ChainID:         "block_test",
   190  		Time:            cmttime.Now(),
   191  		ProposerAddress: cmtrand.Bytes(crypto.AddressSize),
   192  	}
   193  
   194  	// End of setup, test data
   195  
   196  	commitAtH10 := makeTestCommit(10, cmttime.Now())
   197  	tuples := []struct {
   198  		block      *types.Block
   199  		parts      *types.PartSet
   200  		seenCommit *types.Commit
   201  		wantPanic  string
   202  		wantErr    bool
   203  
   204  		corruptBlockInDB      bool
   205  		corruptCommitInDB     bool
   206  		corruptSeenCommitInDB bool
   207  		eraseCommitInDB       bool
   208  		eraseSeenCommitInDB   bool
   209  	}{
   210  		{
   211  			block:      newBlock(header1, commitAtH10),
   212  			parts:      validPartSet,
   213  			seenCommit: seenCommit1,
   214  		},
   215  
   216  		{
   217  			block:     nil,
   218  			wantPanic: "only save a non-nil block",
   219  		},
   220  
   221  		{
   222  			block: newBlock( // New block at height 5 in empty block store is fine
   223  				types.Header{
   224  					Version:         cmtversion.Consensus{Block: version.BlockProtocol},
   225  					Height:          5,
   226  					ChainID:         "block_test",
   227  					Time:            cmttime.Now(),
   228  					ProposerAddress: cmtrand.Bytes(crypto.AddressSize),
   229  				},
   230  				makeTestCommit(5, cmttime.Now()),
   231  			),
   232  			parts:      validPartSet,
   233  			seenCommit: makeTestCommit(5, cmttime.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 cmtproto.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{
   332  				block: bBlock, seenCommit: bSeenCommit, commit: bCommit,
   333  				meta: bBlockMeta,
   334  			}, 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 := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
   383  		partSet, err := block.MakePartSet(2)
   384  		require.NoError(t, err)
   385  		seenCommit := makeTestCommit(h, cmttime.Now())
   386  		bs.SaveBlock(block, partSet, seenCommit)
   387  	}
   388  
   389  	_, err = bs.PruneBlocks(4)
   390  	require.NoError(t, err)
   391  
   392  	baseBlock := bs.LoadBaseMeta()
   393  	assert.EqualValues(t, 4, baseBlock.Header.Height)
   394  	assert.EqualValues(t, 4, bs.Base())
   395  
   396  	require.NoError(t, bs.DeleteLatestBlock())
   397  	require.EqualValues(t, 9, bs.Height())
   398  }
   399  
   400  func TestLoadBlockPart(t *testing.T) {
   401  	bs, db := freshBlockStore()
   402  	height, index := int64(10), 1
   403  	loadPart := func() (interface{}, error) {
   404  		part := bs.LoadBlockPart(height, index)
   405  		return part, nil
   406  	}
   407  
   408  	// Initially no contents.
   409  	// 1. Requesting for a non-existent block shouldn't fail
   410  	res, _, panicErr := doFn(loadPart)
   411  	require.Nil(t, panicErr, "a non-existent block part shouldn't cause a panic")
   412  	require.Nil(t, res, "a non-existent block part should return nil")
   413  
   414  	// 2. Next save a corrupted block then try to load it
   415  	err := db.Set(calcBlockPartKey(height, index), []byte("CometBFT"))
   416  	require.NoError(t, err)
   417  	res, _, panicErr = doFn(loadPart)
   418  	require.NotNil(t, panicErr, "expecting a non-nil panic")
   419  	require.Contains(t, panicErr.Error(), "unmarshal to cmtproto.Part failed")
   420  
   421  	// 3. A good block serialized and saved to the DB should be retrievable
   422  	pb1, err := part1.ToProto()
   423  	require.NoError(t, err)
   424  	err = db.Set(calcBlockPartKey(height, index), mustEncode(pb1))
   425  	require.NoError(t, err)
   426  	gotPart, _, panicErr := doFn(loadPart)
   427  	require.Nil(t, panicErr, "an existent and proper block should not panic")
   428  	require.Nil(t, res, "a properly saved block should return a proper block")
   429  	require.Equal(t, gotPart.(*types.Part), part1,
   430  		"expecting successful retrieval of previously saved block")
   431  }
   432  
   433  func TestPruneBlocks(t *testing.T) {
   434  	config := cfg.ResetTestRoot("blockchain_reactor_test")
   435  	defer os.RemoveAll(config.RootDir)
   436  	stateStore := sm.NewStore(dbm.NewMemDB(), sm.StoreOptions{
   437  		DiscardABCIResponses: false,
   438  	})
   439  	state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile())
   440  	require.NoError(t, err)
   441  	db := dbm.NewMemDB()
   442  	bs := NewBlockStore(db)
   443  	assert.EqualValues(t, 0, bs.Base())
   444  	assert.EqualValues(t, 0, bs.Height())
   445  	assert.EqualValues(t, 0, bs.Size())
   446  
   447  	// pruning an empty store should error, even when pruning to 0
   448  	_, err = bs.PruneBlocks(1)
   449  	require.Error(t, err)
   450  
   451  	_, err = bs.PruneBlocks(0)
   452  	require.Error(t, err)
   453  
   454  	// make more than 1000 blocks, to test batch deletions
   455  	for h := int64(1); h <= 1500; h++ {
   456  		block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
   457  		partSet, err := block.MakePartSet(2)
   458  		require.NoError(t, err)
   459  		seenCommit := makeTestCommit(h, cmttime.Now())
   460  		bs.SaveBlock(block, partSet, seenCommit)
   461  	}
   462  
   463  	assert.EqualValues(t, 1, bs.Base())
   464  	assert.EqualValues(t, 1500, bs.Height())
   465  	assert.EqualValues(t, 1500, bs.Size())
   466  
   467  	prunedBlock := bs.LoadBlock(1199)
   468  
   469  	// Check that basic pruning works
   470  	pruned, err := bs.PruneBlocks(1200)
   471  	require.NoError(t, err)
   472  	assert.EqualValues(t, 1199, pruned)
   473  	assert.EqualValues(t, 1200, bs.Base())
   474  	assert.EqualValues(t, 1500, bs.Height())
   475  	assert.EqualValues(t, 301, bs.Size())
   476  	assert.EqualValues(t, cmtstore.BlockStoreState{
   477  		Base:   1200,
   478  		Height: 1500,
   479  	}, LoadBlockStoreState(db))
   480  
   481  	require.NotNil(t, bs.LoadBlock(1200))
   482  	require.Nil(t, bs.LoadBlock(1199))
   483  	require.Nil(t, bs.LoadBlockByHash(prunedBlock.Hash()))
   484  	require.Nil(t, bs.LoadBlockCommit(1199))
   485  	require.Nil(t, bs.LoadBlockMeta(1199))
   486  	require.Nil(t, bs.LoadBlockMetaByHash(prunedBlock.Hash()))
   487  	require.Nil(t, bs.LoadBlockPart(1199, 1))
   488  
   489  	for i := int64(1); i < 1200; i++ {
   490  		require.Nil(t, bs.LoadBlock(i))
   491  	}
   492  	for i := int64(1200); i <= 1500; i++ {
   493  		require.NotNil(t, bs.LoadBlock(i))
   494  	}
   495  
   496  	// Pruning below the current base should error
   497  	_, err = bs.PruneBlocks(1199)
   498  	require.Error(t, err)
   499  
   500  	// Pruning to the current base should work
   501  	pruned, err = bs.PruneBlocks(1200)
   502  	require.NoError(t, err)
   503  	assert.EqualValues(t, 0, pruned)
   504  
   505  	// Pruning again should work
   506  	pruned, err = bs.PruneBlocks(1300)
   507  	require.NoError(t, err)
   508  	assert.EqualValues(t, 100, pruned)
   509  	assert.EqualValues(t, 1300, bs.Base())
   510  
   511  	// Pruning beyond the current height should error
   512  	_, err = bs.PruneBlocks(1501)
   513  	require.Error(t, err)
   514  
   515  	// Pruning to the current height should work
   516  	pruned, err = bs.PruneBlocks(1500)
   517  	require.NoError(t, err)
   518  	assert.EqualValues(t, 200, pruned)
   519  	assert.Nil(t, bs.LoadBlock(1499))
   520  	assert.NotNil(t, bs.LoadBlock(1500))
   521  	assert.Nil(t, bs.LoadBlock(1501))
   522  }
   523  
   524  func TestLoadBlockMeta(t *testing.T) {
   525  	bs, db := freshBlockStore()
   526  	height := int64(10)
   527  	loadMeta := func() (interface{}, error) {
   528  		meta := bs.LoadBlockMeta(height)
   529  		return meta, nil
   530  	}
   531  
   532  	// Initially no contents.
   533  	// 1. Requesting for a non-existent blockMeta shouldn't fail
   534  	res, _, panicErr := doFn(loadMeta)
   535  	require.Nil(t, panicErr, "a non-existent blockMeta shouldn't cause a panic")
   536  	require.Nil(t, res, "a non-existent blockMeta should return nil")
   537  
   538  	// 2. Next save a corrupted blockMeta then try to load it
   539  	err := db.Set(calcBlockMetaKey(height), []byte("CometBFT-Meta"))
   540  	require.NoError(t, err)
   541  	res, _, panicErr = doFn(loadMeta)
   542  	require.NotNil(t, panicErr, "expecting a non-nil panic")
   543  	require.Contains(t, panicErr.Error(), "unmarshal to cmtproto.BlockMeta")
   544  
   545  	// 3. A good blockMeta serialized and saved to the DB should be retrievable
   546  	meta := &types.BlockMeta{Header: types.Header{
   547  		Version: cmtversion.Consensus{
   548  			Block: version.BlockProtocol, App: 0,
   549  		}, Height: 1, ProposerAddress: cmtrand.Bytes(crypto.AddressSize),
   550  	}}
   551  	pbm := meta.ToProto()
   552  	err = db.Set(calcBlockMetaKey(height), mustEncode(pbm))
   553  	require.NoError(t, err)
   554  	gotMeta, _, panicErr := doFn(loadMeta)
   555  	require.Nil(t, panicErr, "an existent and proper block should not panic")
   556  	require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
   557  	pbmeta := meta.ToProto()
   558  	if gmeta, ok := gotMeta.(*types.BlockMeta); ok {
   559  		pbgotMeta := gmeta.ToProto()
   560  		require.Equal(t, mustEncode(pbmeta), mustEncode(pbgotMeta),
   561  			"expecting successful retrieval of previously saved blockMeta")
   562  	}
   563  }
   564  
   565  func TestLoadBlockMetaByHash(t *testing.T) {
   566  	config := cfg.ResetTestRoot("blockchain_reactor_test")
   567  	defer os.RemoveAll(config.RootDir)
   568  	stateStore := sm.NewStore(dbm.NewMemDB(), sm.StoreOptions{
   569  		DiscardABCIResponses: false,
   570  	})
   571  	state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile())
   572  	require.NoError(t, err)
   573  	bs := NewBlockStore(dbm.NewMemDB())
   574  
   575  	b1 := state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
   576  	partSet, err := b1.MakePartSet(2)
   577  	require.NoError(t, err)
   578  	seenCommit := makeTestCommit(1, cmttime.Now())
   579  	bs.SaveBlock(b1, partSet, seenCommit)
   580  
   581  	baseBlock := bs.LoadBlockMetaByHash(b1.Hash())
   582  	assert.EqualValues(t, b1.Header.Height, baseBlock.Header.Height)
   583  	assert.EqualValues(t, b1.Header.LastBlockID, baseBlock.Header.LastBlockID)
   584  	assert.EqualValues(t, b1.Header.ChainID, baseBlock.Header.ChainID)
   585  }
   586  
   587  func TestBlockFetchAtHeight(t *testing.T) {
   588  	state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
   589  	defer cleanup()
   590  	require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
   591  	block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
   592  
   593  	partSet, err := block.MakePartSet(2)
   594  	require.NoError(t, err)
   595  	seenCommit := makeTestCommit(10, cmttime.Now())
   596  	bs.SaveBlock(block, partSet, seenCommit)
   597  	require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
   598  
   599  	blockAtHeight := bs.LoadBlock(bs.Height())
   600  	b1, err := block.ToProto()
   601  	require.NoError(t, err)
   602  	b2, err := blockAtHeight.ToProto()
   603  	require.NoError(t, err)
   604  	bz1 := mustEncode(b1)
   605  	bz2 := mustEncode(b2)
   606  	require.Equal(t, bz1, bz2)
   607  	require.Equal(t, block.Hash(), blockAtHeight.Hash(),
   608  		"expecting a successful load of the last saved block")
   609  
   610  	blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1)
   611  	require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1")
   612  	blockAtHeightPlus2 := bs.LoadBlock(bs.Height() + 2)
   613  	require.Nil(t, blockAtHeightPlus2, "expecting an unsuccessful load of Height()+2")
   614  }
   615  
   616  func doFn(fn func() (interface{}, error)) (res interface{}, err error, panicErr error) {
   617  	defer func() {
   618  		if r := recover(); r != nil {
   619  			switch e := r.(type) {
   620  			case error:
   621  				panicErr = e
   622  			case string:
   623  				panicErr = fmt.Errorf("%s", e)
   624  			default:
   625  				if st, ok := r.(fmt.Stringer); ok {
   626  					panicErr = fmt.Errorf("%s", st)
   627  				} else {
   628  					panicErr = fmt.Errorf("%s", debug.Stack())
   629  				}
   630  			}
   631  		}
   632  	}()
   633  
   634  	res, err = fn()
   635  	return res, err, panicErr
   636  }
   637  
   638  func newBlock(hdr types.Header, lastCommit *types.Commit) *types.Block {
   639  	return &types.Block{
   640  		Header:     hdr,
   641  		LastCommit: lastCommit,
   642  	}
   643  }