github.com/onflow/flow-go@v0.33.17/storage/merkle/tree_test.go (about) 1 // (c) 2019 Dapper Labs - ALL RIGHTS RESERVED 2 3 package merkle 4 5 import ( 6 crand "crypto/rand" 7 "encoding/hex" 8 "fmt" 9 mrand "math/rand" 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 const TreeTestLength = 100000 17 18 var expectedEmptyHash = []byte{ 19 14, 87, 81, 192, 38, 229, 67, 20 178, 232, 171, 46, 176, 96, 153, 21 218, 161, 209, 229, 223, 71, 119, 22 143, 119, 135, 250, 171, 69, 205, 23 241, 47, 227, 168} 24 25 // TestTreeInitialization verifies that tree initialization only accepts 26 // compatible key lengths. 27 func TestTreeInitialization(t *testing.T) { 28 // constructor should reject `keyLength` _outside_ of interval [1, maxKeyLength] 29 t.Run("key length outside of compatible bounds", func(t *testing.T) { 30 tree, err := NewTree(-1) 31 require.Nil(t, tree) 32 require.ErrorIs(t, err, ErrorIncompatibleKeyLength) 33 34 tree, err = NewTree(0) 35 require.Nil(t, tree) 36 require.ErrorIs(t, err, ErrorIncompatibleKeyLength) 37 38 tree, err = NewTree(maxKeyLength + 1) 39 require.Nil(t, tree) 40 require.ErrorIs(t, err, ErrorIncompatibleKeyLength) 41 }) 42 43 // constructor should accept `keyLength` values in the interval [1, maxKeyLength] 44 t.Run("compatible key length", func(t *testing.T) { 45 tree, err := NewTree(1) 46 require.NotNil(t, tree) 47 require.NoError(t, err) 48 49 tree, err = NewTree(maxKeyLength) 50 require.NotNil(t, tree) 51 require.NoError(t, err) 52 }) 53 } 54 55 // TestEmptyTreeHash verifies that an empty tree returns the expected empty hash. 56 // We test with: 57 // - different key sizes 58 // - a newly initialized trie (empty) 59 // - a trie, whose last element was removed 60 func TestEmptyTreeHash(t *testing.T) { 61 for _, keyLength := range []int{1, 32, maxKeyLength} { 62 tree, _ := NewTree(keyLength) 63 assert.Equal(t, tree.Hash(), expectedEmptyHash) 64 65 // generate random key-value pair 66 key := make([]byte, keyLength) 67 _, err := crand.Read(key) 68 require.NoError(t, err) 69 70 val := []byte{1} 71 72 // add key-value pair: hash should be non-empty 73 replaced, err := tree.Put(key, val) 74 assert.NoError(t, err) 75 assert.False(t, replaced) 76 assert.NotEmpty(t, tree.Hash()) 77 78 // remove key: hash should now be empty again 79 removed, err := tree.Del(key) 80 assert.NoError(t, err) 81 assert.True(t, removed) 82 assert.Equal(t, tree.Hash(), expectedEmptyHash) 83 } 84 } 85 86 // Test_ReferenceSingleEntry we construct a tree with a single key-value pair 87 // and compare its hash to a pre-computed value from a python reference implementation. 88 func Test_ReferenceSingleEntry(t *testing.T) { 89 val, _ := hex.DecodeString("bab02e6213dfad3546aa473922bba0") 90 91 t.Run("2-byte path", func(t *testing.T) { 92 key := []byte{22, 83} // key: 00010110 01010011 93 expectedRootHash := "3c4fd8e7bc5572d708d7ccab0a9ee06f74aac780e68c68d0b629ecb58a1fdf9d" // from python reference impl 94 95 tree, err := NewTree(len(key)) 96 assert.NoError(t, err) 97 replaced, err := tree.Put(key, val) 98 assert.NoError(t, err) 99 assert.False(t, replaced) 100 require.Equal(t, expectedRootHash, hex.EncodeToString(tree.Hash())) 101 }) 102 103 t.Run("32-byte path", func(t *testing.T) { 104 key, _ := hex.DecodeString("1b30482d4dc8c1a8d846d05765c03a33f0267b56b9a7be8defe38958f89c95fc") 105 expectedRootHash := "10eb7e9ffa397651acc2faf8a3c56207914418ca02ff9f39694effaf83d261e0" // from python reference impl 106 107 tree, err := NewTree(len(key)) 108 assert.NoError(t, err) 109 replaced, err := tree.Put(key, val) 110 assert.NoError(t, err) 111 assert.False(t, replaced) 112 require.Equal(t, expectedRootHash, hex.EncodeToString(tree.Hash())) 113 }) 114 115 t.Run("maxKeyLength-byte path", func(t *testing.T) { 116 // as key, we just repeat the following 32 bytes 256 times 117 k, _ := hex.DecodeString("1b30482d4dc8c1a8d846d05765c03a33f0267b56b9a7be8defe38958f89c95fc") 118 key := make([]byte, 0, maxKeyLength) 119 for i := 1; i <= maxKeyLength/len(k); i++ { 120 key = append(key, k...) 121 } 122 key = append(key, k[:maxKeyLength%len(k)]...) 123 124 expectedRootHash := "bf6eab5ce259b8a936f4fe205ca49f5d6614a7bee4162cafa5a6ab4691eba40d" // from python reference impl 125 tree, err := NewTree(len(key)) 126 assert.NoError(t, err) 127 replaced, err := tree.Put(key, val) 128 assert.NoError(t, err) 129 assert.False(t, replaced) 130 assert.Equal(t, expectedRootHash, hex.EncodeToString(tree.Hash())) 131 }) 132 } 133 134 // Test_2EntryTree we construct a tree with a 2 key-value pairs and compare 135 // its hash to a pre-computed value from a python reference implementation. 136 func Test_2EntryTree(t *testing.T) { 137 keyLength := 2 138 key0 := []byte{20, 3} // 00010100 00000011 139 key1 := []byte{23, 252} // 00010111 11111100 140 val0, _ := hex.DecodeString("62b0326507ebce9d4a242908d20559") 141 val1, _ := hex.DecodeString("bab02e6213dfad3546aa473922bba0") 142 expectedRootHash := "7f372aca94b91a527539967ba966c3a91c91e97b265fc4830801b4bcca01b06e" // from python reference impl 143 144 tree, err := NewTree(keyLength) 145 assert.NoError(t, err) 146 replaced, err := tree.Put(key0, val0) 147 require.False(t, replaced) 148 require.NoError(t, err) 149 replaced, err = tree.Put(key1, val1) 150 require.False(t, replaced) 151 require.NoError(t, err) 152 require.Equal(t, expectedRootHash, hex.EncodeToString(tree.Hash())) 153 } 154 155 // Test_KeyValuesAreSafeFromExternalModification verifies that the 156 // tree implementation is _not_ vulnerable to the slices of the key-value 157 // pair being modified in-place _after_ addition to the tree. 158 func Test_KeyValuesAreSafeFromExternalModification(t *testing.T) { 159 // we re-use the same key-value pairs as in Test_2EntryTree: 160 keyLength := 2 161 key0 := []byte{20, 3} // 00010100 00000011 162 key1 := []byte{23, 252} // 00010111 11111100 163 val0, _ := hex.DecodeString("62b0326507ebce9d4a242908d20559") 164 val1, _ := hex.DecodeString("bab02e6213dfad3546aa473922bba0") 165 expectedRootHash := "7f372aca94b91a527539967ba966c3a91c91e97b265fc4830801b4bcca01b06e" // from python reference impl 166 167 // we now put the key-value pairs into a tree, 168 // but modify the key and value slices right after *in-place* 169 postKey := []byte{255, 255} 170 postVal, _ := hex.DecodeString("1b30482d4dc8c1a8d846d05765c03a") 171 tree, err := NewTree(keyLength) 172 assert.NoError(t, err) 173 replaced, err := tree.Put(key0, val0) 174 require.False(t, replaced) 175 require.NoError(t, err) 176 copy(key0, postKey) 177 copy(val0, postVal) 178 replaced, err = tree.Put(key1, val1) 179 require.False(t, replaced) 180 require.NoError(t, err) 181 copy(key1, postKey) 182 copy(val1, postVal) 183 184 // (key1, val1) and (key2, val2) should now contain the same data as (postKey, postVal) 185 require.Equal(t, postKey, key0) 186 require.Equal(t, postVal, val0) 187 require.Equal(t, postKey, key1) 188 require.Equal(t, postVal, val1) 189 // but the tree's root hash should still be the expected value: 190 require.Equal(t, expectedRootHash, hex.EncodeToString(tree.Hash())) 191 } 192 193 // Test_KeyLengthChecked verifies that the Tree implementation checks that 194 // * the key has the length as configured at construction time 195 // * rejects addition of key-value pair, if key does not conform to pre-configured length 196 func Test_KeyLengthChecked(t *testing.T) { 197 val, _ := hex.DecodeString("bab02e6213dfad3546aa473922bba0") 198 199 t.Run("nil key", func(t *testing.T) { 200 tree, err := NewTree(1) 201 assert.NoError(t, err) 202 _, err = tree.Put(nil, val) // nil key is not of length 17 and should be rejected 203 assert.ErrorIs(t, err, ErrorIncompatibleKeyLength) 204 }) 205 206 t.Run("empty key", func(t *testing.T) { 207 tree, err := NewTree(1) 208 assert.NoError(t, err) 209 _, err = tree.Put([]byte{}, val) // empty key is not of length 17 and should be rejected 210 assert.ErrorIs(t, err, ErrorIncompatibleKeyLength) 211 }) 212 213 t.Run("1-byte key", func(t *testing.T) { 214 key := make([]byte, 1) 215 tree, err := NewTree(1) 216 assert.NoError(t, err) 217 replaced, err := tree.Put(key, val) // key has the pre-configured length and should be accepted 218 assert.NoError(t, err) 219 assert.False(t, replaced) 220 }) 221 222 t.Run("maxKeyLength-byte key", func(t *testing.T) { 223 key := make([]byte, maxKeyLength) 224 tree, err := NewTree(maxKeyLength) 225 assert.NoError(t, err) 226 replaced, err := tree.Put(key, val) // key has the pre-configured length and should be accepted 227 assert.NoError(t, err) 228 assert.False(t, replaced) 229 }) 230 231 t.Run("key too long", func(t *testing.T) { 232 key := make([]byte, maxKeyLength+1) 233 tree, err := NewTree(maxKeyLength) 234 assert.NoError(t, err) 235 _, err = tree.Put(key, val) 236 assert.ErrorIs(t, err, ErrorIncompatibleKeyLength) 237 }) 238 } 239 240 // TestTreeSingle verifies addition, retrieval and deletion operations 241 // of a _single_ key-value pair to an otherwise empty tree. 242 func TestTreeSingle(t *testing.T) { 243 // initialize the random generator, tree and zero hash 244 245 keyLength := 32 246 tree, err := NewTree(keyLength) 247 assert.NoError(t, err) 248 249 // for the pre-defined number of times... 250 for i := 0; i < TreeTestLength; i++ { 251 // insert a random key with a random value and make sure it didn't 252 // exist yet; collisions are unlikely enough to never happen 253 key, val := randomKeyValuePair(keyLength, 128) 254 replaced, err := tree.Put(key, val) 255 assert.NoError(t, err) 256 assert.False(t, replaced) 257 258 // retrieve the value again, check it as successful and the same 259 out, retrieved := tree.Get(key) 260 if assert.True(t, retrieved) { 261 assert.Equal(t, val, out) 262 } 263 264 // delete the value again, check it was successful 265 deleted, err := tree.Del(key) 266 assert.NoError(t, err) 267 assert.True(t, deleted) 268 _, retrieved = tree.Get(key) 269 assert.False(t, retrieved) 270 271 // get the root hash and make sure it's empty again as the tree is empty 272 assert.Equal(t, tree.Hash(), expectedEmptyHash) 273 } 274 } 275 276 // TestTreeBatch tests addition and deletion of multiple key-value pairs. 277 // Key-value pairs are added and deleted in the same order. 278 func TestTreeBatch(t *testing.T) { 279 // initialize random generator, tree, zero hash 280 281 keyLength := 32 282 tree, err := NewTree(keyLength) 283 assert.NoError(t, err) 284 285 // insert a batch of random key-value pairs 286 keys := make([][]byte, 0, TreeTestLength) 287 vals := make([][]byte, 0, TreeTestLength) 288 for i := 0; i < TreeTestLength; i++ { 289 key, val := randomKeyValuePair(keyLength, 128) 290 keys = append(keys, key) 291 vals = append(vals, val) 292 } 293 294 // insert key-value pairs and ensure there are no collisions 295 for i, key := range keys { 296 val := vals[i] 297 replaced, err := tree.Put(key, val) 298 assert.NoError(t, err) 299 assert.False(t, replaced) 300 } 301 302 // retrieve all key-value pairs, ensure they are found and are correct 303 for i, key := range keys { 304 val := vals[i] 305 out, retrieved := tree.Get(key) 306 if assert.True(t, retrieved) { 307 assert.Equal(t, val, out) 308 } 309 } 310 311 // remove all key-value pairs, ensure it worked 312 for _, key := range keys { 313 deleted, err := tree.Del(key) 314 assert.NoError(t, err) 315 assert.True(t, deleted) 316 } 317 318 // get the root hash and make sure it's empty again as the tree is empty 319 assert.Equal(t, tree.Hash(), EmptyTreeRootHash) 320 } 321 322 // TestRandomOrder tests that root hash of tree is independent of the order 323 // in which the elements were added. 324 func TestRandomOrder(t *testing.T) { 325 // initialize random generator, two trees and zero hash 326 327 keyLength := 32 328 tree1, err := NewTree(keyLength) 329 assert.NoError(t, err) 330 tree2, err := NewTree(keyLength) 331 assert.NoError(t, err) 332 333 // generate the desired number of keys and map a value to each key 334 keys := make([][]byte, 0, TreeTestLength) 335 vals := make(map[string][]byte) 336 for i := 0; i < TreeTestLength; i++ { 337 key, val := randomKeyValuePair(32, 128) 338 keys = append(keys, key) 339 vals[string(key)] = val 340 } 341 342 // insert all key-value paris into the first tree 343 for _, key := range keys { 344 val := vals[string(key)] 345 replaced, err := tree1.Put(key, val) 346 assert.NoError(t, err) 347 require.False(t, replaced) 348 } 349 350 // shuffle the keys and insert them with random order into the second tree 351 mrand.Shuffle(len(keys), func(i int, j int) { 352 keys[i], keys[j] = keys[j], keys[i] 353 }) 354 for _, key := range keys { 355 val := vals[string(key)] 356 replaced, err := tree2.Put(key, val) 357 assert.NoError(t, err) 358 require.False(t, replaced) 359 } 360 361 // make sure the tree hashes were the same, in spite of random order 362 assert.Equal(t, tree1.Hash(), tree2.Hash()) 363 364 // remove the key-value pairs from the first tree in random order 365 for _, key := range keys { 366 deleted, err := tree1.Del(key) 367 assert.NoError(t, err) 368 require.True(t, deleted) 369 } 370 371 // get the root hash and make sure it's empty again as the tree is empty 372 assert.Equal(t, tree1.Hash(), expectedEmptyHash) 373 } 374 375 func BenchmarkTree(b *testing.B) { 376 for n := 1000; n < 1000000; n *= 10 { 377 b.Run(fmt.Sprintf("put-%d", n), treePut(n)) 378 b.Run(fmt.Sprintf("get-%d", n), treeGet(n)) 379 b.Run(fmt.Sprintf("del-%d", n), treeDel(n)) 380 b.Run(fmt.Sprintf("hash-%d", n), treeHash(n)) 381 } 382 } 383 384 func randomKeyValuePair(keySize, valueSize int) ([]byte, []byte) { 385 key := make([]byte, keySize) 386 val := make([]byte, valueSize) 387 _, _ = crand.Read(key) 388 _, _ = crand.Read(val) 389 return key, val 390 } 391 392 func createTree(n int) *Tree { 393 t, err := NewTree(32) 394 if err != nil { 395 panic(err.Error()) 396 } 397 for i := 0; i < n; i++ { 398 key, val := randomKeyValuePair(32, 128) 399 _, _ = t.Put(key, val) 400 } 401 return t 402 } 403 404 func treePut(n int) func(*testing.B) { 405 return func(b *testing.B) { 406 t := createTree(n) 407 b.StopTimer() 408 b.ResetTimer() 409 for i := 0; i < b.N; i++ { 410 key, val := randomKeyValuePair(32, 128) 411 b.StartTimer() 412 _, _ = t.Put(key, val) 413 b.StopTimer() 414 _, _ = t.Del(key) 415 } 416 } 417 } 418 419 func treeGet(n int) func(*testing.B) { 420 return func(b *testing.B) { 421 t := createTree(n) 422 b.StopTimer() 423 b.ResetTimer() 424 for i := 0; i < b.N; i++ { 425 key, val := randomKeyValuePair(32, 128) 426 _, _ = t.Put(key, val) 427 b.StartTimer() 428 _, _ = t.Get(key) 429 b.StopTimer() 430 _, _ = t.Del(key) 431 } 432 } 433 } 434 435 func treeDel(n int) func(*testing.B) { 436 return func(b *testing.B) { 437 t := createTree(n) 438 b.StopTimer() 439 b.ResetTimer() 440 for i := 0; i < b.N; i++ { 441 key, val := randomKeyValuePair(32, 128) 442 _, _ = t.Put(key, val) 443 b.StartTimer() 444 _, _ = t.Del(key) 445 b.StopTimer() 446 } 447 } 448 } 449 450 func treeHash(n int) func(*testing.B) { 451 return func(b *testing.B) { 452 t := createTree(n) 453 b.ResetTimer() 454 for i := 0; i < b.N; i++ { 455 _ = t.Hash() 456 } 457 } 458 }