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