// nolint: vetshadow package lite_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/lite" liteErr "github.com/tendermint/tendermint/lite/errors" ) // missingProvider doesn't store anything, always a miss // Designed as a mock for testing type missingProvider struct{} // NewMissingProvider returns a provider which does not store anything and always misses. func NewMissingProvider() lite.Provider { return missingProvider{} } func (missingProvider) StoreCommit(lite.FullCommit) error { return nil } func (missingProvider) GetByHeight(int64) (lite.FullCommit, error) { return lite.FullCommit{}, liteErr.ErrCommitNotFound() } func (missingProvider) GetByHash([]byte) (lite.FullCommit, error) { return lite.FullCommit{}, liteErr.ErrCommitNotFound() } func (missingProvider) LatestCommit() (lite.FullCommit, error) { return lite.FullCommit{}, liteErr.ErrCommitNotFound() } func TestMemProvider(t *testing.T) { p := lite.NewMemStoreProvider() checkProvider(t, p, "test-mem", "empty") } func TestCacheProvider(t *testing.T) { p := lite.NewCacheProvider( NewMissingProvider(), lite.NewMemStoreProvider(), NewMissingProvider(), ) checkProvider(t, p, "test-cache", "kjfhekfhkewhgit") } func checkProvider(t *testing.T, p lite.Provider, chainID, app string) { assert, require := assert.New(t), require.New(t) appHash := []byte(app) keys := lite.GenValKeys(5) count := 10 // make a bunch of commits... commits := make([]lite.FullCommit, count) for i := 0; i < count; i++ { // two commits for each validator, to check how we handle dups // (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ... vals := keys.ToValidators(10, int64(count/2)) h := int64(20 + 10*i) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5) } // check provider is empty fc, err := p.GetByHeight(20) require.NotNil(err) assert.True(liteErr.IsCommitNotFoundErr(err)) fc, err = p.GetByHash(commits[3].ValidatorsHash()) require.NotNil(err) assert.True(liteErr.IsCommitNotFoundErr(err)) // now add them all to the provider for _, s := range commits { err = p.StoreCommit(s) require.Nil(err) // and make sure we can get it back s2, err := p.GetByHash(s.ValidatorsHash()) assert.Nil(err) assert.Equal(s, s2) // by height as well s2, err = p.GetByHeight(s.Height()) assert.Nil(err) assert.Equal(s, s2) } // make sure we get the last hash if we overstep fc, err = p.GetByHeight(5000) if assert.Nil(err) { assert.Equal(commits[count-1].Height(), fc.Height()) assert.Equal(commits[count-1], fc) } // and middle ones as well fc, err = p.GetByHeight(47) if assert.Nil(err) { // we only step by 10, so 40 must be the one below this assert.EqualValues(40, fc.Height()) } } type binarySearchHeightGetter interface { GetByHeightBinarySearch(h int64) (lite.FullCommit, error) } // this will make a get height, and if it is good, set the data as well func checkGetHeight(t *testing.T, p lite.Provider, ask, expect int64) { // The goal here is to test checkGetHeight using both // provider.GetByHeight // *memStoreProvider.GetHeightBinarySearch fnMap := map[string]func(int64) (lite.FullCommit, error){ "getByHeight": p.GetByHeight, } if bshg, ok := p.(binarySearchHeightGetter); ok { fnMap["getByHeightBinary"] = bshg.GetByHeightBinarySearch } for algo, fn := range fnMap { fc, err := fn(ask) // t.Logf("%s got=%v want=%d", algo, expect, fc.Height()) require.Nil(t, err, "%s: %+v", algo, err) if assert.Equal(t, expect, fc.Height()) { err = p.StoreCommit(fc) require.Nil(t, err, "%s: %+v", algo, err) } } } func TestCacheGetsBestHeight(t *testing.T) { // assert, require := assert.New(t), require.New(t) require := require.New(t) // we will write data to the second level of the cache (p2), // and see what gets cached, stored in p := lite.NewMemStoreProvider() p2 := lite.NewMemStoreProvider() cp := lite.NewCacheProvider(p, p2) chainID := "cache-best-height" appHash := []byte("01234567") keys := lite.GenValKeys(5) count := 10 // set a bunch of commits for i := 0; i < count; i++ { vals := keys.ToValidators(10, int64(count/2)) h := int64(10 * (i + 1)) fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5) err := p2.StoreCommit(fc) require.NoError(err) } // let's get a few heights from the cache and set them proper checkGetHeight(t, cp, 57, 50) checkGetHeight(t, cp, 33, 30) // make sure they are set in p as well (but nothing else) checkGetHeight(t, p, 44, 30) checkGetHeight(t, p, 50, 50) checkGetHeight(t, p, 99, 50) // now, query the cache for a higher value checkGetHeight(t, p2, 99, 90) checkGetHeight(t, cp, 99, 90) } var blankFullCommit lite.FullCommit func ensureNonExistentCommitsAtHeight(t *testing.T, prefix string, fn func(int64) (lite.FullCommit, error), data []int64) { for i, qh := range data { fc, err := fn(qh) assert.NotNil(t, err, "#%d: %s: height=%d should return non-nil error", i, prefix, qh) assert.Equal(t, fc, blankFullCommit, "#%d: %s: height=%d\ngot =%+v\nwant=%+v", i, prefix, qh, fc, blankFullCommit) } } func TestMemStoreProviderGetByHeightBinaryAndLinearSameResult(t *testing.T) { p := lite.NewMemStoreProvider() // Store a bunch of commits at specific heights // and then ensure that: // * GetByHeight // * GetByHeightBinarySearch // both return the exact same result // 1. Non-existent height commits nonExistent := []int64{-1000, -1, 0, 1, 10, 11, 17, 31, 67, 1000, 1e9} ensureNonExistentCommitsAtHeight(t, "GetByHeight", p.GetByHeight, nonExistent) ensureNonExistentCommitsAtHeight(t, "GetByHeightBinarySearch", p.(binarySearchHeightGetter).GetByHeightBinarySearch, nonExistent) // 2. Save some known height commits knownHeights := []int64{0, 1, 7, 9, 12, 13, 18, 44, 23, 16, 1024, 100, 199, 1e9} createAndStoreCommits(t, p, knownHeights) // 3. Now check if those heights are retrieved ensureExistentCommitsAtHeight(t, "GetByHeight", p.GetByHeight, knownHeights) ensureExistentCommitsAtHeight(t, "GetByHeightBinarySearch", p.(binarySearchHeightGetter).GetByHeightBinarySearch, knownHeights) // 4. And now for the height probing to ensure that any height // requested returns a fullCommit of height <= requestedHeight. checkGetHeight(t, p, 0, 0) checkGetHeight(t, p, 1, 1) checkGetHeight(t, p, 2, 1) checkGetHeight(t, p, 5, 1) checkGetHeight(t, p, 7, 7) checkGetHeight(t, p, 10, 9) checkGetHeight(t, p, 12, 12) checkGetHeight(t, p, 14, 13) checkGetHeight(t, p, 19, 18) checkGetHeight(t, p, 43, 23) checkGetHeight(t, p, 45, 44) checkGetHeight(t, p, 1025, 1024) checkGetHeight(t, p, 101, 100) checkGetHeight(t, p, 1e3, 199) checkGetHeight(t, p, 1e4, 1024) checkGetHeight(t, p, 1e9, 1e9) checkGetHeight(t, p, 1e9+1, 1e9) } func ensureExistentCommitsAtHeight(t *testing.T, prefix string, fn func(int64) (lite.FullCommit, error), data []int64) { for i, qh := range data { fc, err := fn(qh) assert.Nil(t, err, "#%d: %s: height=%d should not return an error: %v", i, prefix, qh, err) assert.NotEqual(t, fc, blankFullCommit, "#%d: %s: height=%d got a blankCommit", i, prefix, qh) } } func createAndStoreCommits(t *testing.T, p lite.Provider, heights []int64) { chainID := "cache-best-height-binary-and-linear" appHash := []byte("0xdeadbeef") keys := lite.GenValKeys(len(heights) / 2) for _, h := range heights { vals := keys.ToValidators(10, int64(len(heights)/2)) fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5) err := p.StoreCommit(fc) require.NoError(t, err, "StoreCommit height=%d", h) } }