github.com/koko1123/flow-go-1@v0.29.6/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/v3" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "github.com/koko1123/flow-go-1/model/flow" 14 "github.com/koko1123/flow-go-1/storage" 15 "github.com/koko1123/flow-go-1/storage/badger/operation" 16 "github.com/koko1123/flow-go-1/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 }