github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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 }