github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/operation/cluster_test.go (about)

     1  package operation_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/rand"
     7  	"testing"
     8  
     9  	"github.com/dgraph-io/badger/v2"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/onflow/flow-go/model/flow"
    14  	"github.com/onflow/flow-go/storage"
    15  	"github.com/onflow/flow-go/storage/badger/operation"
    16  	"github.com/onflow/flow-go/utils/unittest"
    17  )
    18  
    19  func TestClusterHeights(t *testing.T) {
    20  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
    21  		var (
    22  			clusterID flow.ChainID = "cluster"
    23  			height    uint64       = 42
    24  			expected               = unittest.IdentifierFixture()
    25  			err       error
    26  		)
    27  
    28  		t.Run("retrieve non-existent", func(t *testing.T) {
    29  			var actual flow.Identifier
    30  			err = db.View(operation.LookupClusterBlockHeight(clusterID, height, &actual))
    31  			t.Log(err)
    32  			assert.True(t, errors.Is(err, storage.ErrNotFound))
    33  		})
    34  
    35  		t.Run("insert/retrieve", func(t *testing.T) {
    36  			err = db.Update(operation.IndexClusterBlockHeight(clusterID, height, expected))
    37  			assert.Nil(t, err)
    38  
    39  			var actual flow.Identifier
    40  			err = db.View(operation.LookupClusterBlockHeight(clusterID, height, &actual))
    41  			assert.Nil(t, err)
    42  			assert.Equal(t, expected, actual)
    43  		})
    44  
    45  		t.Run("multiple chain IDs", func(t *testing.T) {
    46  			for i := 0; i < 3; i++ {
    47  				// use different cluster ID but same block height
    48  				clusterID = flow.ChainID(fmt.Sprintf("cluster-%d", i))
    49  				expected = unittest.IdentifierFixture()
    50  
    51  				var actual flow.Identifier
    52  				err = db.View(operation.LookupClusterBlockHeight(clusterID, height, &actual))
    53  				assert.True(t, errors.Is(err, storage.ErrNotFound))
    54  
    55  				err = db.Update(operation.IndexClusterBlockHeight(clusterID, height, expected))
    56  				assert.Nil(t, err)
    57  
    58  				err = db.View(operation.LookupClusterBlockHeight(clusterID, height, &actual))
    59  				assert.Nil(t, err)
    60  				assert.Equal(t, expected, actual)
    61  			}
    62  		})
    63  	})
    64  }
    65  
    66  func TestClusterBoundaries(t *testing.T) {
    67  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
    68  		var (
    69  			clusterID flow.ChainID = "cluster"
    70  			expected  uint64       = 42
    71  			err       error
    72  		)
    73  
    74  		t.Run("retrieve non-existant", func(t *testing.T) {
    75  			var actual uint64
    76  			err = db.View(operation.RetrieveClusterFinalizedHeight(clusterID, &actual))
    77  			t.Log(err)
    78  			assert.True(t, errors.Is(err, storage.ErrNotFound))
    79  		})
    80  
    81  		t.Run("insert/retrieve", func(t *testing.T) {
    82  			err = db.Update(operation.InsertClusterFinalizedHeight(clusterID, 21))
    83  			assert.Nil(t, err)
    84  
    85  			err = db.Update(operation.UpdateClusterFinalizedHeight(clusterID, expected))
    86  			assert.Nil(t, err)
    87  
    88  			var actual uint64
    89  			err = db.View(operation.RetrieveClusterFinalizedHeight(clusterID, &actual))
    90  			assert.Nil(t, err)
    91  			assert.Equal(t, expected, actual)
    92  		})
    93  
    94  		t.Run("multiple chain IDs", func(t *testing.T) {
    95  			for i := 0; i < 3; i++ {
    96  				// use different cluster ID but same boundary
    97  				clusterID = flow.ChainID(fmt.Sprintf("cluster-%d", i))
    98  				expected = uint64(i)
    99  
   100  				var actual uint64
   101  				err = db.View(operation.RetrieveClusterFinalizedHeight(clusterID, &actual))
   102  				assert.True(t, errors.Is(err, storage.ErrNotFound))
   103  
   104  				err = db.Update(operation.InsertClusterFinalizedHeight(clusterID, expected))
   105  				assert.Nil(t, err)
   106  
   107  				err = db.View(operation.RetrieveClusterFinalizedHeight(clusterID, &actual))
   108  				assert.Nil(t, err)
   109  				assert.Equal(t, expected, actual)
   110  			}
   111  		})
   112  	})
   113  }
   114  
   115  func TestClusterBlockByReferenceHeight(t *testing.T) {
   116  
   117  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
   118  		t.Run("should be able to index cluster block by reference height", func(t *testing.T) {
   119  			id := unittest.IdentifierFixture()
   120  			height := rand.Uint64()
   121  			err := db.Update(operation.IndexClusterBlockByReferenceHeight(height, id))
   122  			assert.NoError(t, err)
   123  
   124  			var retrieved []flow.Identifier
   125  			err = db.View(operation.LookupClusterBlocksByReferenceHeightRange(height, height, &retrieved))
   126  			assert.NoError(t, err)
   127  			require.Len(t, retrieved, 1)
   128  			assert.Equal(t, id, retrieved[0])
   129  		})
   130  	})
   131  
   132  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
   133  		t.Run("should be able to index multiple cluster blocks at same reference height", func(t *testing.T) {
   134  			ids := unittest.IdentifierListFixture(10)
   135  			height := rand.Uint64()
   136  			for _, id := range ids {
   137  				err := db.Update(operation.IndexClusterBlockByReferenceHeight(height, id))
   138  				assert.NoError(t, err)
   139  			}
   140  
   141  			var retrieved []flow.Identifier
   142  			err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(height, height, &retrieved))
   143  			assert.NoError(t, err)
   144  			assert.Len(t, retrieved, len(ids))
   145  			assert.ElementsMatch(t, ids, retrieved)
   146  		})
   147  	})
   148  
   149  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
   150  		t.Run("should be able to lookup cluster blocks across height range", func(t *testing.T) {
   151  			ids := unittest.IdentifierListFixture(100)
   152  			nextHeight := rand.Uint64()
   153  			// keep track of height range
   154  			minHeight, maxHeight := nextHeight, nextHeight
   155  			// keep track of which ids are indexed at each nextHeight
   156  			lookup := make(map[uint64][]flow.Identifier)
   157  
   158  			for i := 0; i < len(ids); i++ {
   159  				// randomly adjust the nextHeight, increasing on average
   160  				r := rand.Intn(100)
   161  				if r < 20 {
   162  					nextHeight -= 1 // 20%
   163  				} else if r < 40 {
   164  					// nextHeight stays the same - 20%
   165  				} else if r < 80 {
   166  					nextHeight += 1 // 40%
   167  				} else {
   168  					nextHeight += 2 // 20%
   169  				}
   170  
   171  				lookup[nextHeight] = append(lookup[nextHeight], ids[i])
   172  				if nextHeight < minHeight {
   173  					minHeight = nextHeight
   174  				}
   175  				if nextHeight > maxHeight {
   176  					maxHeight = nextHeight
   177  				}
   178  
   179  				err := db.Update(operation.IndexClusterBlockByReferenceHeight(nextHeight, ids[i]))
   180  				assert.NoError(t, err)
   181  			}
   182  
   183  			// determine which ids we expect to be retrieved for a given height range
   184  			idsInHeightRange := func(min, max uint64) []flow.Identifier {
   185  				var idsForHeight []flow.Identifier
   186  				for height, id := range lookup {
   187  					if min <= height && height <= max {
   188  						idsForHeight = append(idsForHeight, id...)
   189  					}
   190  				}
   191  				return idsForHeight
   192  			}
   193  
   194  			// Test cases are described as follows:
   195  			// {---} represents the queried height range
   196  			// [---] represents the indexed height range
   197  			// [{ means the left endpoint of both ranges are the same
   198  			// {-[ means the left endpoint of the queried range is strictly less than the indexed range
   199  			t.Run("{-}--[-]", func(t *testing.T) {
   200  				var retrieved []flow.Identifier
   201  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(minHeight-100, minHeight-1, &retrieved))
   202  				assert.NoError(t, err)
   203  				assert.Len(t, retrieved, 0)
   204  			})
   205  			t.Run("{-[--}-]", func(t *testing.T) {
   206  				var retrieved []flow.Identifier
   207  				min := minHeight - 100
   208  				max := minHeight + (maxHeight-minHeight)/2
   209  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(min, max, &retrieved))
   210  				assert.NoError(t, err)
   211  
   212  				expected := idsInHeightRange(min, max)
   213  				assert.NotEmpty(t, expected, "test assumption broken")
   214  				assert.Len(t, retrieved, len(expected))
   215  				assert.ElementsMatch(t, expected, retrieved)
   216  			})
   217  			t.Run("{[--}--]", func(t *testing.T) {
   218  				var retrieved []flow.Identifier
   219  				min := minHeight
   220  				max := minHeight + (maxHeight-minHeight)/2
   221  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(min, max, &retrieved))
   222  				assert.NoError(t, err)
   223  
   224  				expected := idsInHeightRange(min, max)
   225  				assert.NotEmpty(t, expected, "test assumption broken")
   226  				assert.Len(t, retrieved, len(expected))
   227  				assert.ElementsMatch(t, expected, retrieved)
   228  			})
   229  			t.Run("[-{--}-]", func(t *testing.T) {
   230  				var retrieved []flow.Identifier
   231  				min := minHeight + 1
   232  				max := maxHeight - 1
   233  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(min, max, &retrieved))
   234  				assert.NoError(t, err)
   235  
   236  				expected := idsInHeightRange(min, max)
   237  				assert.NotEmpty(t, expected, "test assumption broken")
   238  				assert.Len(t, retrieved, len(expected))
   239  				assert.ElementsMatch(t, expected, retrieved)
   240  			})
   241  			t.Run("[{----}]", func(t *testing.T) {
   242  				var retrieved []flow.Identifier
   243  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(minHeight, maxHeight, &retrieved))
   244  				assert.NoError(t, err)
   245  
   246  				expected := idsInHeightRange(minHeight, maxHeight)
   247  				assert.NotEmpty(t, expected, "test assumption broken")
   248  				assert.Len(t, retrieved, len(expected))
   249  				assert.ElementsMatch(t, expected, retrieved)
   250  			})
   251  			t.Run("[--{--}]", func(t *testing.T) {
   252  				var retrieved []flow.Identifier
   253  				min := minHeight + (maxHeight-minHeight)/2
   254  				max := maxHeight
   255  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(min, max, &retrieved))
   256  				assert.NoError(t, err)
   257  
   258  				expected := idsInHeightRange(min, max)
   259  				assert.NotEmpty(t, expected, "test assumption broken")
   260  				assert.Len(t, retrieved, len(expected))
   261  				assert.ElementsMatch(t, expected, retrieved)
   262  			})
   263  			t.Run("[-{--]-}", func(t *testing.T) {
   264  				var retrieved []flow.Identifier
   265  				min := minHeight + (maxHeight-minHeight)/2
   266  				max := maxHeight + 100
   267  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(min, max, &retrieved))
   268  				assert.NoError(t, err)
   269  
   270  				expected := idsInHeightRange(min, max)
   271  				assert.NotEmpty(t, expected, "test assumption broken")
   272  				assert.Len(t, retrieved, len(expected))
   273  				assert.ElementsMatch(t, expected, retrieved)
   274  			})
   275  			t.Run("[-]--{-}", func(t *testing.T) {
   276  				var retrieved []flow.Identifier
   277  				err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(maxHeight+1, maxHeight+100, &retrieved))
   278  				assert.NoError(t, err)
   279  				assert.Len(t, retrieved, 0)
   280  			})
   281  		})
   282  	})
   283  }
   284  
   285  // expected average case # of blocks to lookup on Mainnet
   286  func BenchmarkLookupClusterBlocksByReferenceHeightRange_1200(b *testing.B) {
   287  	benchmarkLookupClusterBlocksByReferenceHeightRange(b, 1200)
   288  }
   289  
   290  // 5x average case on Mainnet
   291  func BenchmarkLookupClusterBlocksByReferenceHeightRange_6_000(b *testing.B) {
   292  	benchmarkLookupClusterBlocksByReferenceHeightRange(b, 6_000)
   293  }
   294  
   295  func BenchmarkLookupClusterBlocksByReferenceHeightRange_100_000(b *testing.B) {
   296  	benchmarkLookupClusterBlocksByReferenceHeightRange(b, 100_000)
   297  }
   298  
   299  func benchmarkLookupClusterBlocksByReferenceHeightRange(b *testing.B, n int) {
   300  	unittest.RunWithBadgerDB(b, func(db *badger.DB) {
   301  		for i := 0; i < n; i++ {
   302  			err := db.Update(operation.IndexClusterBlockByReferenceHeight(rand.Uint64()%1000, unittest.IdentifierFixture()))
   303  			require.NoError(b, err)
   304  		}
   305  
   306  		b.ResetTimer()
   307  		for i := 0; i < b.N; i++ {
   308  			var blockIDs []flow.Identifier
   309  			err := db.View(operation.LookupClusterBlocksByReferenceHeightRange(0, 1000, &blockIDs))
   310  			require.NoError(b, err)
   311  		}
   312  	})
   313  }