github.com/evdatsion/aphelion-dpos-bft@v0.32.1/blockchain/store_test.go (about)

     1  package blockchain
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"runtime/debug"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	cfg "github.com/evdatsion/aphelion-dpos-bft/config"
    15  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    16  	"github.com/evdatsion/aphelion-dpos-bft/libs/db"
    17  	dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db"
    18  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
    19  	sm "github.com/evdatsion/aphelion-dpos-bft/state"
    20  
    21  	"github.com/evdatsion/aphelion-dpos-bft/types"
    22  	tmtime "github.com/evdatsion/aphelion-dpos-bft/types/time"
    23  )
    24  
    25  // A cleanupFunc cleans up any config / test files created for a particular
    26  // test.
    27  type cleanupFunc func()
    28  
    29  // make a Commit with a single vote containing just the height and a timestamp
    30  func makeTestCommit(height int64, timestamp time.Time) *types.Commit {
    31  	commitSigs := []*types.CommitSig{{Height: height, Timestamp: timestamp}}
    32  	return types.NewCommit(types.BlockID{}, commitSigs)
    33  }
    34  
    35  func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFunc) {
    36  	config := cfg.ResetTestRoot("blockchain_reactor_test")
    37  	// blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB())
    38  	// stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB())
    39  	blockDB := dbm.NewMemDB()
    40  	stateDB := dbm.NewMemDB()
    41  	state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile())
    42  	if err != nil {
    43  		panic(cmn.ErrorWrap(err, "error constructing state from genesis file"))
    44  	}
    45  	return state, NewBlockStore(blockDB), func() { os.RemoveAll(config.RootDir) }
    46  }
    47  
    48  func TestLoadBlockStoreStateJSON(t *testing.T) {
    49  	db := db.NewMemDB()
    50  
    51  	bsj := &BlockStoreStateJSON{Height: 1000}
    52  	bsj.Save(db)
    53  
    54  	retrBSJ := LoadBlockStoreStateJSON(db)
    55  
    56  	assert.Equal(t, *bsj, retrBSJ, "expected the retrieved DBs to match")
    57  }
    58  
    59  func TestNewBlockStore(t *testing.T) {
    60  	db := db.NewMemDB()
    61  	db.Set(blockStoreKey, []byte(`{"height": "10000"}`))
    62  	bs := NewBlockStore(db)
    63  	require.Equal(t, int64(10000), bs.Height(), "failed to properly parse blockstore")
    64  
    65  	panicCausers := []struct {
    66  		data    []byte
    67  		wantErr string
    68  	}{
    69  		{[]byte("artful-doger"), "not unmarshal bytes"},
    70  		{[]byte(" "), "unmarshal bytes"},
    71  	}
    72  
    73  	for i, tt := range panicCausers {
    74  		// Expecting a panic here on trying to parse an invalid blockStore
    75  		_, _, panicErr := doFn(func() (interface{}, error) {
    76  			db.Set(blockStoreKey, tt.data)
    77  			_ = NewBlockStore(db)
    78  			return nil, nil
    79  		})
    80  		require.NotNil(t, panicErr, "#%d panicCauser: %q expected a panic", i, tt.data)
    81  		assert.Contains(t, fmt.Sprintf("%#v", panicErr), tt.wantErr, "#%d data: %q", i, tt.data)
    82  	}
    83  
    84  	db.Set(blockStoreKey, nil)
    85  	bs = NewBlockStore(db)
    86  	assert.Equal(t, bs.Height(), int64(0), "expecting nil bytes to be unmarshaled alright")
    87  }
    88  
    89  func freshBlockStore() (*BlockStore, db.DB) {
    90  	db := db.NewMemDB()
    91  	return NewBlockStore(db), db
    92  }
    93  
    94  var (
    95  	state       sm.State
    96  	block       *types.Block
    97  	partSet     *types.PartSet
    98  	part1       *types.Part
    99  	part2       *types.Part
   100  	seenCommit1 *types.Commit
   101  )
   102  
   103  func TestMain(m *testing.M) {
   104  	var cleanup cleanupFunc
   105  	state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
   106  	block = makeBlock(1, state, new(types.Commit))
   107  	partSet = block.MakePartSet(2)
   108  	part1 = partSet.GetPart(0)
   109  	part2 = partSet.GetPart(1)
   110  	seenCommit1 = makeTestCommit(10, tmtime.Now())
   111  	code := m.Run()
   112  	cleanup()
   113  	os.Exit(code)
   114  }
   115  
   116  // TODO: This test should be simplified ...
   117  
   118  func TestBlockStoreSaveLoadBlock(t *testing.T) {
   119  	state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
   120  	defer cleanup()
   121  	require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
   122  
   123  	// check there are no blocks at various heights
   124  	noBlockHeights := []int64{0, -1, 100, 1000, 2}
   125  	for i, height := range noBlockHeights {
   126  		if g := bs.LoadBlock(height); g != nil {
   127  			t.Errorf("#%d: height(%d) got a block; want nil", i, height)
   128  		}
   129  	}
   130  
   131  	// save a block
   132  	block := makeBlock(bs.Height()+1, state, new(types.Commit))
   133  	validPartSet := block.MakePartSet(2)
   134  	seenCommit := makeTestCommit(10, tmtime.Now())
   135  	bs.SaveBlock(block, partSet, seenCommit)
   136  	require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
   137  
   138  	incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2})
   139  	uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0})
   140  	uncontiguousPartSet.AddPart(part2)
   141  
   142  	header1 := types.Header{
   143  		Height:  1,
   144  		NumTxs:  100,
   145  		ChainID: "block_test",
   146  		Time:    tmtime.Now(),
   147  	}
   148  	header2 := header1
   149  	header2.Height = 4
   150  
   151  	// End of setup, test data
   152  
   153  	commitAtH10 := makeTestCommit(10, tmtime.Now())
   154  	tuples := []struct {
   155  		block      *types.Block
   156  		parts      *types.PartSet
   157  		seenCommit *types.Commit
   158  		wantErr    bool
   159  		wantPanic  string
   160  
   161  		corruptBlockInDB      bool
   162  		corruptCommitInDB     bool
   163  		corruptSeenCommitInDB bool
   164  		eraseCommitInDB       bool
   165  		eraseSeenCommitInDB   bool
   166  	}{
   167  		{
   168  			block:      newBlock(header1, commitAtH10),
   169  			parts:      validPartSet,
   170  			seenCommit: seenCommit1,
   171  		},
   172  
   173  		{
   174  			block:     nil,
   175  			wantPanic: "only save a non-nil block",
   176  		},
   177  
   178  		{
   179  			block:     newBlock(header2, commitAtH10),
   180  			parts:     uncontiguousPartSet,
   181  			wantPanic: "only save contiguous blocks", // and incomplete and uncontiguous parts
   182  		},
   183  
   184  		{
   185  			block:     newBlock(header1, commitAtH10),
   186  			parts:     incompletePartSet,
   187  			wantPanic: "only save complete block", // incomplete parts
   188  		},
   189  
   190  		{
   191  			block:             newBlock(header1, commitAtH10),
   192  			parts:             validPartSet,
   193  			seenCommit:        seenCommit1,
   194  			corruptCommitInDB: true, // Corrupt the DB's commit entry
   195  			wantPanic:         "unmarshal to types.Commit failed",
   196  		},
   197  
   198  		{
   199  			block:            newBlock(header1, commitAtH10),
   200  			parts:            validPartSet,
   201  			seenCommit:       seenCommit1,
   202  			wantPanic:        "unmarshal to types.BlockMeta failed",
   203  			corruptBlockInDB: true, // Corrupt the DB's block entry
   204  		},
   205  
   206  		{
   207  			block:      newBlock(header1, commitAtH10),
   208  			parts:      validPartSet,
   209  			seenCommit: seenCommit1,
   210  
   211  			// Expecting no error and we want a nil back
   212  			eraseSeenCommitInDB: true,
   213  		},
   214  
   215  		{
   216  			block:      newBlock(header1, commitAtH10),
   217  			parts:      validPartSet,
   218  			seenCommit: seenCommit1,
   219  
   220  			corruptSeenCommitInDB: true,
   221  			wantPanic:             "unmarshal to types.Commit failed",
   222  		},
   223  
   224  		{
   225  			block:      newBlock(header1, commitAtH10),
   226  			parts:      validPartSet,
   227  			seenCommit: seenCommit1,
   228  
   229  			// Expecting no error and we want a nil back
   230  			eraseCommitInDB: true,
   231  		},
   232  	}
   233  
   234  	type quad struct {
   235  		block  *types.Block
   236  		commit *types.Commit
   237  		meta   *types.BlockMeta
   238  
   239  		seenCommit *types.Commit
   240  	}
   241  
   242  	for i, tuple := range tuples {
   243  		bs, db := freshBlockStore()
   244  		// SaveBlock
   245  		res, err, panicErr := doFn(func() (interface{}, error) {
   246  			bs.SaveBlock(tuple.block, tuple.parts, tuple.seenCommit)
   247  			if tuple.block == nil {
   248  				return nil, nil
   249  			}
   250  
   251  			if tuple.corruptBlockInDB {
   252  				db.Set(calcBlockMetaKey(tuple.block.Height), []byte("block-bogus"))
   253  			}
   254  			bBlock := bs.LoadBlock(tuple.block.Height)
   255  			bBlockMeta := bs.LoadBlockMeta(tuple.block.Height)
   256  
   257  			if tuple.eraseSeenCommitInDB {
   258  				db.Delete(calcSeenCommitKey(tuple.block.Height))
   259  			}
   260  			if tuple.corruptSeenCommitInDB {
   261  				db.Set(calcSeenCommitKey(tuple.block.Height), []byte("bogus-seen-commit"))
   262  			}
   263  			bSeenCommit := bs.LoadSeenCommit(tuple.block.Height)
   264  
   265  			commitHeight := tuple.block.Height - 1
   266  			if tuple.eraseCommitInDB {
   267  				db.Delete(calcBlockCommitKey(commitHeight))
   268  			}
   269  			if tuple.corruptCommitInDB {
   270  				db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus"))
   271  			}
   272  			bCommit := bs.LoadBlockCommit(commitHeight)
   273  			return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit,
   274  				meta: bBlockMeta}, nil
   275  		})
   276  
   277  		if subStr := tuple.wantPanic; subStr != "" {
   278  			if panicErr == nil {
   279  				t.Errorf("#%d: want a non-nil panic", i)
   280  			} else if got := fmt.Sprintf("%#v", panicErr); !strings.Contains(got, subStr) {
   281  				t.Errorf("#%d:\n\tgotErr: %q\nwant substring: %q", i, got, subStr)
   282  			}
   283  			continue
   284  		}
   285  
   286  		if tuple.wantErr {
   287  			if err == nil {
   288  				t.Errorf("#%d: got nil error", i)
   289  			}
   290  			continue
   291  		}
   292  
   293  		assert.Nil(t, panicErr, "#%d: unexpected panic", i)
   294  		assert.Nil(t, err, "#%d: expecting a non-nil error", i)
   295  		qua, ok := res.(*quad)
   296  		if !ok || qua == nil {
   297  			t.Errorf("#%d: got nil quad back; gotType=%T", i, res)
   298  			continue
   299  		}
   300  		if tuple.eraseSeenCommitInDB {
   301  			assert.Nil(t, qua.seenCommit,
   302  				"erased the seenCommit in the DB hence we should get back a nil seenCommit")
   303  		}
   304  		if tuple.eraseCommitInDB {
   305  			assert.Nil(t, qua.commit,
   306  				"erased the commit in the DB hence we should get back a nil commit")
   307  		}
   308  	}
   309  }
   310  
   311  func TestLoadBlockPart(t *testing.T) {
   312  	bs, db := freshBlockStore()
   313  	height, index := int64(10), 1
   314  	loadPart := func() (interface{}, error) {
   315  		part := bs.LoadBlockPart(height, index)
   316  		return part, nil
   317  	}
   318  
   319  	// Initially no contents.
   320  	// 1. Requesting for a non-existent block shouldn't fail
   321  	res, _, panicErr := doFn(loadPart)
   322  	require.Nil(t, panicErr, "a non-existent block part shouldn't cause a panic")
   323  	require.Nil(t, res, "a non-existent block part should return nil")
   324  
   325  	// 2. Next save a corrupted block then try to load it
   326  	db.Set(calcBlockPartKey(height, index), []byte("Tendermint"))
   327  	res, _, panicErr = doFn(loadPart)
   328  	require.NotNil(t, panicErr, "expecting a non-nil panic")
   329  	require.Contains(t, panicErr.Error(), "unmarshal to types.Part failed")
   330  
   331  	// 3. A good block serialized and saved to the DB should be retrievable
   332  	db.Set(calcBlockPartKey(height, index), cdc.MustMarshalBinaryBare(part1))
   333  	gotPart, _, panicErr := doFn(loadPart)
   334  	require.Nil(t, panicErr, "an existent and proper block should not panic")
   335  	require.Nil(t, res, "a properly saved block should return a proper block")
   336  	require.Equal(t, gotPart.(*types.Part), part1,
   337  		"expecting successful retrieval of previously saved block")
   338  }
   339  
   340  func TestLoadBlockMeta(t *testing.T) {
   341  	bs, db := freshBlockStore()
   342  	height := int64(10)
   343  	loadMeta := func() (interface{}, error) {
   344  		meta := bs.LoadBlockMeta(height)
   345  		return meta, nil
   346  	}
   347  
   348  	// Initially no contents.
   349  	// 1. Requesting for a non-existent blockMeta shouldn't fail
   350  	res, _, panicErr := doFn(loadMeta)
   351  	require.Nil(t, panicErr, "a non-existent blockMeta shouldn't cause a panic")
   352  	require.Nil(t, res, "a non-existent blockMeta should return nil")
   353  
   354  	// 2. Next save a corrupted blockMeta then try to load it
   355  	db.Set(calcBlockMetaKey(height), []byte("Tendermint-Meta"))
   356  	res, _, panicErr = doFn(loadMeta)
   357  	require.NotNil(t, panicErr, "expecting a non-nil panic")
   358  	require.Contains(t, panicErr.Error(), "unmarshal to types.BlockMeta")
   359  
   360  	// 3. A good blockMeta serialized and saved to the DB should be retrievable
   361  	meta := &types.BlockMeta{}
   362  	db.Set(calcBlockMetaKey(height), cdc.MustMarshalBinaryBare(meta))
   363  	gotMeta, _, panicErr := doFn(loadMeta)
   364  	require.Nil(t, panicErr, "an existent and proper block should not panic")
   365  	require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
   366  	require.Equal(t, cdc.MustMarshalBinaryBare(meta), cdc.MustMarshalBinaryBare(gotMeta),
   367  		"expecting successful retrieval of previously saved blockMeta")
   368  }
   369  
   370  func TestBlockFetchAtHeight(t *testing.T) {
   371  	state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
   372  	defer cleanup()
   373  	require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
   374  	block := makeBlock(bs.Height()+1, state, new(types.Commit))
   375  
   376  	partSet := block.MakePartSet(2)
   377  	seenCommit := makeTestCommit(10, tmtime.Now())
   378  	bs.SaveBlock(block, partSet, seenCommit)
   379  	require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
   380  
   381  	blockAtHeight := bs.LoadBlock(bs.Height())
   382  	bz1 := cdc.MustMarshalBinaryBare(block)
   383  	bz2 := cdc.MustMarshalBinaryBare(blockAtHeight)
   384  	require.Equal(t, bz1, bz2)
   385  	require.Equal(t, block.Hash(), blockAtHeight.Hash(),
   386  		"expecting a successful load of the last saved block")
   387  
   388  	blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1)
   389  	require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1")
   390  	blockAtHeightPlus2 := bs.LoadBlock(bs.Height() + 2)
   391  	require.Nil(t, blockAtHeightPlus2, "expecting an unsuccessful load of Height()+2")
   392  }
   393  
   394  func doFn(fn func() (interface{}, error)) (res interface{}, err error, panicErr error) {
   395  	defer func() {
   396  		if r := recover(); r != nil {
   397  			switch e := r.(type) {
   398  			case error:
   399  				panicErr = e
   400  			case string:
   401  				panicErr = fmt.Errorf("%s", e)
   402  			default:
   403  				if st, ok := r.(fmt.Stringer); ok {
   404  					panicErr = fmt.Errorf("%s", st)
   405  				} else {
   406  					panicErr = fmt.Errorf("%s", debug.Stack())
   407  				}
   408  			}
   409  		}
   410  	}()
   411  
   412  	res, err = fn()
   413  	return res, err, panicErr
   414  }
   415  
   416  func newBlock(hdr types.Header, lastCommit *types.Commit) *types.Block {
   417  	return &types.Block{
   418  		Header:     hdr,
   419  		LastCommit: lastCommit,
   420  	}
   421  }