github.com/onflow/flow-go@v0.33.17/storage/pebble/registers_test.go (about)

     1  package pebble
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math/rand"
     7  	"os"
     8  	"path"
     9  	"strconv"
    10  	"testing"
    11  
    12  	"github.com/cockroachdb/pebble"
    13  	"github.com/pkg/errors"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/onflow/flow-go/model/flow"
    18  	"github.com/onflow/flow-go/storage"
    19  	"github.com/onflow/flow-go/storage/pebble/registers"
    20  	"github.com/onflow/flow-go/utils/unittest"
    21  )
    22  
    23  // TestRegisters_Initialize
    24  func TestRegisters_Initialize(t *testing.T) {
    25  	t.Parallel()
    26  	p, dir := unittest.TempPebbleDBWithOpts(t, nil)
    27  	// fail on blank database without FirstHeight and LastHeight set
    28  	_, err := NewRegisters(p)
    29  	require.Error(t, err)
    30  	// verify the error type
    31  	require.True(t, errors.Is(err, storage.ErrNotBootstrapped))
    32  	err = os.RemoveAll(dir)
    33  	require.NoError(t, err)
    34  }
    35  
    36  // TestRegisters_Get tests the expected Get function behavior on a single height
    37  func TestRegisters_Get(t *testing.T) {
    38  	t.Parallel()
    39  	height1 := uint64(1)
    40  	RunWithRegistersStorageAtHeight1(t, func(r *Registers) {
    41  		// invalid keys return correct error type
    42  		invalidKey := flow.RegisterID{Owner: "invalid", Key: "invalid"}
    43  		_, err := r.Get(invalidKey, height1)
    44  		require.ErrorIs(t, err, storage.ErrNotFound)
    45  
    46  		// insert new data
    47  		height2 := uint64(2)
    48  		key1 := flow.RegisterID{Owner: "owner", Key: "key1"}
    49  		expectedValue1 := []byte("value1")
    50  		entries := flow.RegisterEntries{
    51  			{Key: key1, Value: expectedValue1},
    52  		}
    53  
    54  		err = r.Store(entries, height2)
    55  		require.NoError(t, err)
    56  
    57  		// happy path
    58  		value1, err := r.Get(key1, height2)
    59  		require.NoError(t, err)
    60  		require.Equal(t, expectedValue1, value1)
    61  
    62  		// out of range
    63  		beforeFirstHeight := uint64(0)
    64  		_, err = r.Get(key1, beforeFirstHeight)
    65  		require.ErrorIs(t, err, storage.ErrHeightNotIndexed)
    66  		afterLatestHeight := uint64(3)
    67  		_, err = r.Get(key1, afterLatestHeight)
    68  		require.ErrorIs(t, err, storage.ErrHeightNotIndexed)
    69  	})
    70  }
    71  
    72  // TestRegisters_Store tests the expected store behaviour on a single height
    73  func TestRegisters_Store(t *testing.T) {
    74  	t.Parallel()
    75  	RunWithRegistersStorageAtHeight1(t, func(r *Registers) {
    76  		// insert new data
    77  		key1 := flow.RegisterID{Owner: "owner", Key: "key1"}
    78  		expectedValue1 := []byte("value1")
    79  		entries := flow.RegisterEntries{
    80  			{Key: key1, Value: expectedValue1},
    81  		}
    82  		height2 := uint64(2)
    83  		err := r.Store(entries, height2)
    84  		require.NoError(t, err)
    85  
    86  		// idempotent at same height
    87  		err = r.Store(entries, height2)
    88  		require.NoError(t, err)
    89  
    90  		// out of range
    91  		height4 := uint64(4)
    92  		err = r.Store(entries, height4)
    93  		require.Error(t, err)
    94  
    95  		height1 := uint64(1)
    96  		err = r.Store(entries, height1)
    97  		require.Error(t, err)
    98  
    99  	})
   100  }
   101  
   102  // TestRegisters_Heights tests the expected store behaviour on a single height
   103  func TestRegisters_Heights(t *testing.T) {
   104  	t.Parallel()
   105  	RunWithRegistersStorageAtHeight1(t, func(r *Registers) {
   106  		// first and latest heights are the same
   107  		firstHeight := r.FirstHeight()
   108  		latestHeight := r.LatestHeight()
   109  		require.Equal(t, firstHeight, latestHeight)
   110  		// insert new data
   111  		key1 := flow.RegisterID{Owner: "owner", Key: "key1"}
   112  		expectedValue1 := []byte("value1")
   113  		entries := flow.RegisterEntries{
   114  			{Key: key1, Value: expectedValue1},
   115  		}
   116  		height2 := uint64(2)
   117  		err := r.Store(entries, height2)
   118  		require.NoError(t, err)
   119  
   120  		firstHeight2 := r.FirstHeight()
   121  		latestHeight2 := r.LatestHeight()
   122  
   123  		// new latest height
   124  		require.Equal(t, latestHeight2, height2)
   125  
   126  		// same first height
   127  		require.Equal(t, firstHeight, firstHeight2)
   128  	})
   129  }
   130  
   131  // TestRegisters_Store_RoundTrip tests the round trip of a payload storage.
   132  func TestRegisters_Store_RoundTrip(t *testing.T) {
   133  	t.Parallel()
   134  	minHeight := uint64(2)
   135  	RunWithRegistersStorageAtInitialHeights(t, minHeight, minHeight, func(r *Registers) {
   136  		key1 := flow.RegisterID{Owner: "owner", Key: "key1"}
   137  		expectedValue1 := []byte("value1")
   138  		entries := flow.RegisterEntries{
   139  			{Key: key1, Value: expectedValue1},
   140  		}
   141  		testHeight := minHeight + 1
   142  		// happy path
   143  		err := r.Store(entries, testHeight)
   144  		require.NoError(t, err)
   145  
   146  		// lookup with exact height returns the correct value
   147  		value1, err := r.Get(key1, testHeight)
   148  		require.NoError(t, err)
   149  		require.Equal(t, expectedValue1, value1)
   150  
   151  		value11, err := r.Get(key1, testHeight)
   152  		require.NoError(t, err)
   153  		require.Equal(t, expectedValue1, value11)
   154  	})
   155  }
   156  
   157  // TestRegisters_Store_Versioning tests the scan functionality for the most recent value
   158  func TestRegisters_Store_Versioning(t *testing.T) {
   159  	t.Parallel()
   160  	RunWithRegistersStorageAtHeight1(t, func(r *Registers) {
   161  		// Save key11 is a prefix of the key1, and we save it first.
   162  		// It should be invisible for our prefix scan.
   163  		key11 := flow.RegisterID{Owner: "owner", Key: "key11"}
   164  		expectedValue11 := []byte("value11")
   165  
   166  		key1 := flow.RegisterID{Owner: "owner", Key: "key1"}
   167  		expectedValue1 := []byte("value1")
   168  		entries1 := flow.RegisterEntries{
   169  			{Key: key1, Value: expectedValue1},
   170  			{Key: key11, Value: expectedValue11},
   171  		}
   172  
   173  		height2 := uint64(2)
   174  
   175  		// check increment in height after Store()
   176  		err := r.Store(entries1, height2)
   177  		require.NoError(t, err)
   178  
   179  		// Add new version of key1.
   180  		height3 := uint64(3)
   181  		expectedValue1ge3 := []byte("value1ge3")
   182  		entries3 := flow.RegisterEntries{
   183  			{Key: key1, Value: expectedValue1ge3},
   184  		}
   185  
   186  		// check increment in height after Store()
   187  		err = r.Store(entries3, height3)
   188  		require.NoError(t, err)
   189  		updatedHeight := r.LatestHeight()
   190  		require.Equal(t, updatedHeight, height3)
   191  
   192  		// test old version at previous height
   193  		value1, err := r.Get(key1, height2)
   194  		require.NoError(t, err)
   195  		require.Equal(t, expectedValue1, value1)
   196  
   197  		// test new version at new height
   198  		value1, err = r.Get(key1, height3)
   199  		require.NoError(t, err)
   200  		require.Equal(t, expectedValue1ge3, value1)
   201  
   202  		// test unchanged key at incremented height
   203  		value11, err := r.Get(key11, height3)
   204  		require.NoError(t, err)
   205  		require.Equal(t, expectedValue11, value11)
   206  
   207  		// make sure the key is unavailable at height 1
   208  		_, err = r.Get(key1, uint64(1))
   209  		require.ErrorIs(t, err, storage.ErrNotFound)
   210  	})
   211  }
   212  
   213  // TestRegisters_GetAndStoreEmptyOwner tests behavior of storing and retrieving registers with
   214  // an empty owner value, which is used for global state variables.
   215  func TestRegisters_GetAndStoreEmptyOwner(t *testing.T) {
   216  	t.Parallel()
   217  	height := uint64(2)
   218  	emptyOwnerKey := flow.RegisterID{Owner: "", Key: "uuid"}
   219  	zeroOwnerKey := flow.RegisterID{Owner: flow.EmptyAddress.Hex(), Key: "uuid"}
   220  	expectedValue := []byte("first value")
   221  	otherValue := []byte("other value")
   222  
   223  	t.Run("empty owner", func(t *testing.T) {
   224  		RunWithRegistersStorageAtInitialHeights(t, 1, 1, func(r *Registers) {
   225  			// First, only set the empty Owner key, and make sure the empty value is available,
   226  			// and the zero value returns an errors
   227  			entries := flow.RegisterEntries{
   228  				{Key: emptyOwnerKey, Value: expectedValue},
   229  			}
   230  
   231  			err := r.Store(entries, height)
   232  			require.NoError(t, err)
   233  
   234  			actual, err := r.Get(emptyOwnerKey, height)
   235  			assert.NoError(t, err)
   236  			assert.Equal(t, expectedValue, actual)
   237  
   238  			actual, err = r.Get(zeroOwnerKey, height)
   239  			assert.Error(t, err)
   240  			assert.Nil(t, actual)
   241  
   242  			// Next, add the zero value, and make sure it is returned
   243  			entries = flow.RegisterEntries{
   244  				{Key: zeroOwnerKey, Value: otherValue},
   245  			}
   246  
   247  			err = r.Store(entries, height+1)
   248  			require.NoError(t, err)
   249  
   250  			actual, err = r.Get(zeroOwnerKey, height+1)
   251  			assert.NoError(t, err)
   252  			assert.Equal(t, otherValue, actual)
   253  		})
   254  	})
   255  
   256  	t.Run("zero owner", func(t *testing.T) {
   257  		RunWithRegistersStorageAtInitialHeights(t, 1, 1, func(r *Registers) {
   258  			// First, only set the zero Owner key, and make sure the zero value is available,
   259  			// and the empty value returns an errors
   260  			entries := flow.RegisterEntries{
   261  				{Key: zeroOwnerKey, Value: expectedValue},
   262  			}
   263  
   264  			err := r.Store(entries, height)
   265  			require.NoError(t, err)
   266  
   267  			actual, err := r.Get(zeroOwnerKey, height)
   268  			assert.NoError(t, err)
   269  			assert.Equal(t, expectedValue, actual)
   270  
   271  			actual, err = r.Get(emptyOwnerKey, height)
   272  			assert.Error(t, err)
   273  			assert.Nil(t, actual)
   274  
   275  			// Next, add the empty value, and make sure it is returned
   276  			entries = flow.RegisterEntries{
   277  				{Key: emptyOwnerKey, Value: otherValue},
   278  			}
   279  
   280  			err = r.Store(entries, height+1)
   281  			require.NoError(t, err)
   282  
   283  			actual, err = r.Get(emptyOwnerKey, height+1)
   284  			assert.NoError(t, err)
   285  			assert.Equal(t, otherValue, actual)
   286  		})
   287  	})
   288  }
   289  
   290  // Benchmark_PayloadStorage benchmarks the SetBatch method.
   291  func Benchmark_PayloadStorage(b *testing.B) {
   292  	cache := pebble.NewCache(32 << 20)
   293  	defer cache.Unref()
   294  	opts := DefaultPebbleOptions(cache, registers.NewMVCCComparer())
   295  
   296  	dbpath := path.Join(b.TempDir(), "benchmark1.db")
   297  	db, err := pebble.Open(dbpath, opts)
   298  	require.NoError(b, err)
   299  	s, err := NewRegisters(db)
   300  	require.NoError(b, err)
   301  	require.NotNil(b, s)
   302  
   303  	owner := unittest.RandomAddressFixture()
   304  	batchSizeKey := flow.NewRegisterID(owner, "size")
   305  	const maxBatchSize = 1024
   306  	var totalBatchSize int
   307  
   308  	keyForBatchSize := func(i int) flow.RegisterID {
   309  		return flow.NewRegisterID(owner, strconv.Itoa(i))
   310  	}
   311  	valueForHeightAndKey := func(i, j int) []byte {
   312  		return []byte(fmt.Sprintf("%d-%d", i, j))
   313  	}
   314  	b.ResetTimer()
   315  
   316  	// Write a random number of entries in each batch.
   317  	for i := 0; i < b.N; i++ {
   318  		b.StopTimer()
   319  		batchSize := rand.Intn(maxBatchSize) + 1
   320  		totalBatchSize += batchSize
   321  		entries := make(flow.RegisterEntries, 1, batchSize)
   322  		entries[0] = flow.RegisterEntry{
   323  			Key:   batchSizeKey,
   324  			Value: []byte(fmt.Sprintf("%d", batchSize)),
   325  		}
   326  		for j := 1; j < batchSize; j++ {
   327  			entries = append(entries, flow.RegisterEntry{
   328  				Key:   keyForBatchSize(j),
   329  				Value: valueForHeightAndKey(i, j),
   330  			})
   331  		}
   332  		b.StartTimer()
   333  
   334  		err = s.Store(entries, uint64(i))
   335  		require.NoError(b, err)
   336  	}
   337  
   338  	b.StopTimer()
   339  
   340  	// verify written batches
   341  	for i := 0; i < b.N; i++ {
   342  		// get number of batches written for height
   343  		batchSizeBytes, err := s.Get(batchSizeKey, uint64(i))
   344  		require.NoError(b, err)
   345  		batchSize, err := strconv.Atoi(string(batchSizeBytes))
   346  		require.NoError(b, err)
   347  
   348  		// verify that all entries can be read with correct values
   349  		for j := 1; j < batchSize; j++ {
   350  			value, err := s.Get(keyForBatchSize(j), uint64(i))
   351  			require.NoError(b, err)
   352  			require.Equal(b, valueForHeightAndKey(i, j), value)
   353  		}
   354  
   355  		// verify that the rest of the batches either do not exist or have a previous height
   356  		for j := batchSize; j < maxBatchSize+1; j++ {
   357  			value, err := s.Get(keyForBatchSize(j), uint64(i))
   358  			require.Nil(b, err)
   359  
   360  			if len(value) > 0 {
   361  				ij := bytes.Split(value, []byte("-"))
   362  
   363  				// verify that we've got a value for a previous height
   364  				height, err := strconv.Atoi(string(ij[0]))
   365  				require.NoError(b, err)
   366  				require.Lessf(b, height, i, "height: %d, j: %d", height, j)
   367  
   368  				// verify that we've got a value corresponding to the index
   369  				index, err := strconv.Atoi(string(ij[1]))
   370  				require.NoError(b, err)
   371  				require.Equal(b, index, j)
   372  			}
   373  		}
   374  	}
   375  }
   376  
   377  func RunWithRegistersStorageAtHeight1(tb testing.TB, f func(r *Registers)) {
   378  	defaultHeight := uint64(1)
   379  	RunWithRegistersStorageAtInitialHeights(tb, defaultHeight, defaultHeight, f)
   380  }