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