github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/mtrie/trie/trie_test.go (about) 1 package trie_test 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "math" 8 "sort" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 "gotest.tools/assert" 13 14 "github.com/onflow/flow-go/ledger" 15 "github.com/onflow/flow-go/ledger/common/bitutils" 16 "github.com/onflow/flow-go/ledger/common/hash" 17 "github.com/onflow/flow-go/ledger/common/testutils" 18 "github.com/onflow/flow-go/ledger/complete/mtrie/trie" 19 "github.com/onflow/flow-go/utils/unittest" 20 ) 21 22 // TestEmptyTrie tests whether the root hash of an empty trie matches the formal specification. 23 func Test_EmptyTrie(t *testing.T) { 24 // Make new Trie (independently of MForest): 25 emptyTrie := trie.NewEmptyMTrie() 26 rootHash := emptyTrie.RootHash() 27 require.Equal(t, ledger.GetDefaultHashForHeight(ledger.NodeMaxHeight), hash.Hash(rootHash)) 28 29 // verify root hash 30 expectedRootHashHex := "568f4ec740fe3b5de88034cb7b1fbddb41548b068f31aebc8ae9189e429c5749" 31 require.Equal(t, expectedRootHashHex, hashToString(rootHash)) 32 33 // check String() method does not panic: 34 _ = emptyTrie.String() 35 } 36 37 // Test_TrieWithLeftRegister tests whether the root hash of trie with only the left-most 38 // register populated matches the formal specification. 39 // The expected value is coming from a reference implementation in python and is hard-coded here. 40 func Test_TrieWithLeftRegister(t *testing.T) { 41 // Make new Trie (independently of MForest): 42 emptyTrie := trie.NewEmptyMTrie() 43 path := testutils.PathByUint16LeftPadded(0) 44 payload := testutils.LightPayload(11, 12345) 45 leftPopulatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true) 46 require.NoError(t, err) 47 require.Equal(t, uint16(0), maxDepthTouched) 48 require.Equal(t, uint64(1), leftPopulatedTrie.AllocatedRegCount()) 49 require.Equal(t, uint64(payload.Size()), leftPopulatedTrie.AllocatedRegSize()) 50 expectedRootHashHex := "b30c99cc3e027a6ff463876c638041b1c55316ed935f1b3699e52a2c3e3eaaab" 51 require.Equal(t, expectedRootHashHex, hashToString(leftPopulatedTrie.RootHash())) 52 } 53 54 // Test_TrieWithRightRegister tests whether the root hash of trie with only the right-most 55 // register populated matches the formal specification. 56 // The expected value is coming from a reference implementation in python and is hard-coded here. 57 func Test_TrieWithRightRegister(t *testing.T) { 58 // Make new Trie (independently of MForest): 59 emptyTrie := trie.NewEmptyMTrie() 60 // build a path with all 1s 61 var path ledger.Path 62 for i := 0; i < len(path); i++ { 63 path[i] = uint8(255) 64 } 65 payload := testutils.LightPayload(12346, 54321) 66 rightPopulatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true) 67 require.NoError(t, err) 68 require.Equal(t, uint16(0), maxDepthTouched) 69 require.Equal(t, uint64(1), rightPopulatedTrie.AllocatedRegCount()) 70 require.Equal(t, uint64(payload.Size()), rightPopulatedTrie.AllocatedRegSize()) 71 expectedRootHashHex := "4313d22bcabbf21b1cfb833d38f1921f06a91e7198a6672bc68fa24eaaa1a961" 72 require.Equal(t, expectedRootHashHex, hashToString(rightPopulatedTrie.RootHash())) 73 } 74 75 // Test_TrieWithMiddleRegister tests the root hash of trie holding only a single 76 // allocated register somewhere in the middle. 77 // The expected value is coming from a reference implementation in python and is hard-coded here. 78 func Test_TrieWithMiddleRegister(t *testing.T) { 79 // Make new Trie (independently of MForest): 80 emptyTrie := trie.NewEmptyMTrie() 81 82 path := testutils.PathByUint16LeftPadded(56809) 83 payload := testutils.LightPayload(12346, 59656) 84 leftPopulatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true) 85 require.Equal(t, uint16(0), maxDepthTouched) 86 require.Equal(t, uint64(1), leftPopulatedTrie.AllocatedRegCount()) 87 require.Equal(t, uint64(payload.Size()), leftPopulatedTrie.AllocatedRegSize()) 88 require.NoError(t, err) 89 expectedRootHashHex := "4a29dad0b7ae091a1f035955e0c9aab0692b412f60ae83290b6290d4bf3eb296" 90 require.Equal(t, expectedRootHashHex, hashToString(leftPopulatedTrie.RootHash())) 91 } 92 93 // Test_TrieWithManyRegisters tests whether the root hash of a trie storing 12001 randomly selected registers 94 // matches the formal specification. 95 // The expected value is coming from a reference implementation in python and is hard-coded here. 96 func Test_TrieWithManyRegisters(t *testing.T) { 97 // Make new Trie (independently of MForest): 98 emptyTrie := trie.NewEmptyMTrie() 99 // allocate single random register 100 rng := &LinearCongruentialGenerator{seed: 0} 101 paths, payloads := deduplicateWrites(sampleRandomRegisterWrites(rng, 12001)) 102 var totalPayloadSize uint64 103 for _, p := range payloads { 104 totalPayloadSize += uint64(p.Size()) 105 } 106 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 107 require.NoError(t, err) 108 require.Equal(t, uint16(255), maxDepthTouched) 109 require.Equal(t, uint64(12001), updatedTrie.AllocatedRegCount()) 110 require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize()) 111 expectedRootHashHex := "74f748dbe563bb5819d6c09a34362a048531fd9647b4b2ea0b6ff43f200198aa" 112 require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash())) 113 } 114 115 // Test_FullTrie tests whether the root hash of a trie, 116 // whose left-most 65536 registers are populated, matches the formal specification. 117 // The expected value is coming from a reference implementation in python and is hard-coded here. 118 func Test_FullTrie(t *testing.T) { 119 // Make new Trie (independently of MForest): 120 emptyTrie := trie.NewEmptyMTrie() 121 122 // allocate 65536 left-most registers 123 numberRegisters := 65536 124 rng := &LinearCongruentialGenerator{seed: 0} 125 paths := make([]ledger.Path, 0, numberRegisters) 126 payloads := make([]ledger.Payload, 0, numberRegisters) 127 var totalPayloadSize uint64 128 for i := 0; i < numberRegisters; i++ { 129 paths = append(paths, testutils.PathByUint16LeftPadded(uint16(i))) 130 temp := rng.next() 131 payload := testutils.LightPayload(temp, temp) 132 payloads = append(payloads, *payload) 133 totalPayloadSize += uint64(payload.Size()) 134 } 135 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 136 require.NoError(t, err) 137 require.Equal(t, uint16(256), maxDepthTouched) 138 require.Equal(t, uint64(numberRegisters), updatedTrie.AllocatedRegCount()) 139 require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize()) 140 expectedRootHashHex := "6b3a48d672744f5586c571c47eae32d7a4a3549c1d4fa51a0acfd7b720471de9" 141 require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash())) 142 } 143 144 // TestUpdateTrie tests whether iteratively updating a Trie matches the formal specification. 145 // The expected root hashes are coming from a reference implementation in python and is hard-coded here. 146 func Test_UpdateTrie(t *testing.T) { 147 expectedRootHashes := []string{ 148 "08db9aeed2b9fcc66b63204a26a4c28652e44e3035bd87ba0ed632a227b3f6dd", 149 "2f4b0f490fa05e5b3bbd43176e367c3e9b64cdb710e45d4508fff11759d7a08e", 150 "668811792995cd960e7e343540a360682ac375f7ec5533f774c464cd6b34adc9", 151 "169c145eaeda2038a0e409068a12cb26bde5e890115ad1ef624f422007fb2d2a", 152 "8f87b503a706d9eaf50873030e0e627850c841cc0cf382187b81ba26cec57588", 153 "faacc057336e10e13ff6f5667aefc3ac9d9d390b34ee50391a6f7f305dfdf761", 154 "049e035735a13fee09a3c36a7f567daf05baee419ac90ade538108492d80b279", 155 "bb8340a9772ab6d6aa4862b23c8bb830da226cdf6f6c26f1e1e850077be600af", 156 "8b9b7eb5c489bf4aeffd86d3a215dc045856094d0abe5cf7b4cc3f835d499168", 157 "6514743e986f20fcf22a02e50ba352a5bfde50fe949b57b990aeb863cfcd81d1", 158 "33c3d386e1c7c707f727fdeb65c52117537d175da9ab3f60a0a576301d20756e", 159 "09df0bc6eee9d0f76df05d19b2ac550cde8c4294cd6eafaa1332718bd62e912f", 160 "8b1fccbf7d1eca093441305ebff72d3f12b8b7cce5b4f89d6f464fc5df83b0d3", 161 "0830e2d015742e284c56075050e94d3ff9618a46f28aa9066379f012e45c05fc", 162 "9d95255bb75dddc317deda4e45223aa4a5ac02eaa537dc9e602d6f03fa26d626", 163 "74f748dbe563bb5819d6c09a34362a048531fd9647b4b2ea0b6ff43f200198aa", 164 "c06903580432a27dee461e9022a6546cb4ddec2f8598c48429e9ba7a96a892da", 165 "a117f94e9cc6114e19b7639eaa630304788979cf92037736bbeb23ed1504638a", 166 "d382c97020371d8788d4c27971a89f1617f9bbf21c49c922f1b683cc36a4646c", 167 "ce633e9ca6329d6984c37a46e0a479bb1841674c2db00970dacfe035882d4aba", 168 } 169 170 // Make new Trie (independently of MForest): 171 emptyTrie := trie.NewEmptyMTrie() 172 173 // allocate single random register 174 rng := &LinearCongruentialGenerator{seed: 0} 175 path := testutils.PathByUint16LeftPadded(rng.next()) 176 temp := rng.next() 177 payload := testutils.LightPayload(temp, temp) 178 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true) 179 require.NoError(t, err) 180 require.Equal(t, uint16(0), maxDepthTouched) 181 require.Equal(t, uint64(1), updatedTrie.AllocatedRegCount()) 182 require.Equal(t, uint64(payload.Size()), updatedTrie.AllocatedRegSize()) 183 expectedRootHashHex := "08db9aeed2b9fcc66b63204a26a4c28652e44e3035bd87ba0ed632a227b3f6dd" 184 require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash())) 185 186 var paths []ledger.Path 187 var payloads []ledger.Payload 188 parentTrieRegCount := updatedTrie.AllocatedRegCount() 189 parentTrieRegSize := updatedTrie.AllocatedRegSize() 190 for r := 0; r < 20; r++ { 191 paths, payloads = deduplicateWrites(sampleRandomRegisterWrites(rng, r*100)) 192 var totalPayloadSize uint64 193 for _, p := range payloads { 194 totalPayloadSize += uint64(p.Size()) 195 } 196 updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths, payloads, true) 197 require.NoError(t, err) 198 switch r { 199 case 0: 200 require.Equal(t, uint16(0), maxDepthTouched) 201 case 1: 202 require.Equal(t, uint16(254), maxDepthTouched) 203 default: 204 require.Equal(t, uint16(255), maxDepthTouched) 205 } 206 require.Equal(t, parentTrieRegCount+uint64(len(payloads)), updatedTrie.AllocatedRegCount()) 207 require.Equal(t, parentTrieRegSize+totalPayloadSize, updatedTrie.AllocatedRegSize()) 208 require.Equal(t, expectedRootHashes[r], hashToString(updatedTrie.RootHash())) 209 210 parentTrieRegCount = updatedTrie.AllocatedRegCount() 211 parentTrieRegSize = updatedTrie.AllocatedRegSize() 212 } 213 // update with the same registers with the same values 214 newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(updatedTrie, paths, payloads, true) 215 require.NoError(t, err) 216 require.Equal(t, uint16(255), maxDepthTouched) 217 require.Equal(t, updatedTrie.AllocatedRegCount(), newTrie.AllocatedRegCount()) 218 require.Equal(t, updatedTrie.AllocatedRegSize(), newTrie.AllocatedRegSize()) 219 require.Equal(t, expectedRootHashes[19], hashToString(updatedTrie.RootHash())) 220 // check the root node pointers are equal 221 require.True(t, updatedTrie.RootNode() == newTrie.RootNode()) 222 } 223 224 // Test_UnallocateRegisters tests whether unallocating registers matches the formal specification. 225 // Unallocating here means, to set the stored register value to an empty byte slice. 226 // The expected value is coming from a reference implementation in python and is hard-coded here. 227 func Test_UnallocateRegisters(t *testing.T) { 228 rng := &LinearCongruentialGenerator{seed: 0} 229 emptyTrie := trie.NewEmptyMTrie() 230 231 // we first draw 99 random key-value pairs that will be first allocated and later unallocated: 232 paths1, payloads1 := deduplicateWrites(sampleRandomRegisterWrites(rng, 99)) 233 var totalPayloadSize1 uint64 234 for _, p := range payloads1 { 235 totalPayloadSize1 += uint64(p.Size()) 236 } 237 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths1, payloads1, true) 238 require.NoError(t, err) 239 require.Equal(t, uint16(254), maxDepthTouched) 240 require.Equal(t, uint64(len(payloads1)), updatedTrie.AllocatedRegCount()) 241 require.Equal(t, totalPayloadSize1, updatedTrie.AllocatedRegSize()) 242 243 // we then write an additional 117 registers 244 paths2, payloads2 := deduplicateWrites(sampleRandomRegisterWrites(rng, 117)) 245 var totalPayloadSize2 uint64 246 for _, p := range payloads2 { 247 totalPayloadSize2 += uint64(p.Size()) 248 } 249 updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths2, payloads2, true) 250 require.Equal(t, uint16(254), maxDepthTouched) 251 require.Equal(t, uint64(len(payloads1)+len(payloads2)), updatedTrie.AllocatedRegCount()) 252 require.Equal(t, totalPayloadSize1+totalPayloadSize2, updatedTrie.AllocatedRegSize()) 253 require.NoError(t, err) 254 255 // and now we override the first 99 registers with default values, i.e. unallocate them 256 payloads0 := make([]ledger.Payload, len(payloads1)) 257 updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths1, payloads0, true) 258 require.Equal(t, uint16(254), maxDepthTouched) 259 require.Equal(t, uint64(len(payloads2)), updatedTrie.AllocatedRegCount()) 260 require.Equal(t, totalPayloadSize2, updatedTrie.AllocatedRegSize()) 261 require.NoError(t, err) 262 263 // this should be identical to the first 99 registers never been written 264 expectedRootHashHex := "d81e27a93f2bef058395f70e00fb5d3c8e426e22b3391d048b34017e1ecb483e" 265 comparisonTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths2, payloads2, true) 266 require.NoError(t, err) 267 require.Equal(t, uint16(254), maxDepthTouched) 268 require.Equal(t, uint64(len(payloads2)), comparisonTrie.AllocatedRegCount()) 269 require.Equal(t, totalPayloadSize2, comparisonTrie.AllocatedRegSize()) 270 require.Equal(t, expectedRootHashHex, hashToString(comparisonTrie.RootHash())) 271 require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash())) 272 } 273 274 // simple Linear congruential RNG 275 // https://en.wikipedia.org/wiki/Linear_congruential_generator 276 // with configuration for 16bit output used by Microsoft Visual Basic 6 and earlier 277 type LinearCongruentialGenerator struct { 278 seed uint64 279 } 280 281 func (rng *LinearCongruentialGenerator) next() uint16 { 282 rng.seed = (rng.seed*1140671485 + 12820163) % 65536 283 return uint16(rng.seed) 284 } 285 286 // sampleRandomRegisterWrites generates path-payload tuples for `number` randomly selected registers; 287 // caution: registers might repeat 288 func sampleRandomRegisterWrites(rng *LinearCongruentialGenerator, number int) ([]ledger.Path, []ledger.Payload) { 289 paths := make([]ledger.Path, 0, number) 290 payloads := make([]ledger.Payload, 0, number) 291 for i := 0; i < number; i++ { 292 path := testutils.PathByUint16LeftPadded(rng.next()) 293 paths = append(paths, path) 294 t := rng.next() 295 payload := testutils.LightPayload(t, t) 296 payloads = append(payloads, *payload) 297 } 298 return paths, payloads 299 } 300 301 // sampleRandomRegisterWritesWithPrefix generates path-payload tuples for `number` randomly selected registers; 302 // each path is starting with the specified `prefix` and is filled to the full length with random bytes 303 // caution: register paths might repeat 304 func sampleRandomRegisterWritesWithPrefix(rng *LinearCongruentialGenerator, number int, prefix []byte) ([]ledger.Path, []ledger.Payload) { 305 prefixLen := len(prefix) 306 if prefixLen >= hash.HashLen { 307 panic("prefix must be shorter than full path length, so there is some space left for random path segment") 308 } 309 310 paths := make([]ledger.Path, 0, number) 311 payloads := make([]ledger.Payload, 0, number) 312 nextRandomBytes := make([]byte, 2) 313 nextRandomByteIndex := 2 // index of next unused byte in nextRandomBytes; if value is >= 2, we need to generate new random bytes 314 for i := 0; i < number; i++ { 315 var p ledger.Path 316 copy(p[:prefixLen], prefix) 317 for b := prefixLen; b < hash.HashLen; b++ { 318 if nextRandomByteIndex >= 2 { 319 // pre-generate next 2 bytes 320 binary.BigEndian.PutUint16(nextRandomBytes, rng.next()) 321 nextRandomByteIndex = 0 322 } 323 p[b] = nextRandomBytes[nextRandomByteIndex] 324 nextRandomByteIndex++ 325 } 326 paths = append(paths, p) 327 328 t := rng.next() 329 payload := testutils.LightPayload(t, t) 330 payloads = append(payloads, *payload) 331 } 332 return paths, payloads 333 } 334 335 // deduplicateWrites retains only the last register write 336 func deduplicateWrites(paths []ledger.Path, payloads []ledger.Payload) ([]ledger.Path, []ledger.Payload) { 337 payloadMapping := make(map[ledger.Path]int) 338 if len(paths) != len(payloads) { 339 panic("size mismatch (paths and payloads)") 340 } 341 for i, path := range paths { 342 // we override the latest in the slice 343 payloadMapping[path] = i 344 } 345 dedupedPaths := make([]ledger.Path, 0, len(payloadMapping)) 346 dedupedPayloads := make([]ledger.Payload, 0, len(payloadMapping)) 347 for path := range payloadMapping { 348 dedupedPaths = append(dedupedPaths, path) 349 dedupedPayloads = append(dedupedPayloads, payloads[payloadMapping[path]]) 350 } 351 return dedupedPaths, dedupedPayloads 352 } 353 354 func TestSplitByPath(t *testing.T) { 355 rand := unittest.GetPRG(t) 356 357 const pathsNumber = 100 358 const redundantPaths = 10 359 const pathsSize = 32 360 randomIndex := rand.Intn(pathsSize) 361 362 // create path slice with redundant paths 363 paths := make([]ledger.Path, 0, pathsNumber) 364 for i := 0; i < pathsNumber-redundantPaths; i++ { 365 var p ledger.Path 366 _, err := rand.Read(p[:]) 367 require.NoError(t, err) 368 paths = append(paths, p) 369 } 370 for i := 0; i < redundantPaths; i++ { 371 paths = append(paths, paths[i]) 372 } 373 374 // save a sorted paths copy for later check 375 sortedPaths := make([]ledger.Path, len(paths)) 376 copy(sortedPaths, paths) 377 sort.Slice(sortedPaths, func(i, j int) bool { 378 return bytes.Compare(sortedPaths[i][:], sortedPaths[j][:]) < 0 379 }) 380 381 // split paths 382 index := trie.SplitPaths(paths, randomIndex) 383 384 // check correctness 385 for i := 0; i < index; i++ { 386 assert.Equal(t, bitutils.ReadBit(paths[i][:], randomIndex), 0) 387 } 388 for i := index; i < len(paths); i++ { 389 assert.Equal(t, bitutils.ReadBit(paths[i][:], randomIndex), 1) 390 } 391 392 // check the multi-set didn't change 393 sort.Slice(paths, func(i, j int) bool { 394 return bytes.Compare(paths[i][:], paths[j][:]) < 0 395 }) 396 for i := index; i < len(paths); i++ { 397 assert.Equal(t, paths[i], sortedPaths[i]) 398 } 399 } 400 401 // Test_DifferentiateEmptyVsLeaf tests correct behaviour for a very specific edge case for pruning: 402 // - By convention, a node in the trie is a leaf if both children are nil. 403 // - Therefore, we consider a completely unallocated subtrie also as a potential leaf. 404 // 405 // An edge case can now arise when unallocating a previously allocated leaf (see vertex '■' in the illustration below): 406 // 407 // - Before the update, both children of the leaf are nil (because it is a leaf) 408 // - After the update-algorithm updated the sub-Trie with root ■, both children of the updated vertex are 409 // also nil. But the sub-trie has now changed: the register previously represented by ■ is now gone. 410 // 411 // This case must be explicitly handled by the update algorithm: 412 // 413 // - (i) If the vertex is an interim node, i.e. it had at least one child, it is legal to re-use the vertex if neither 414 // of its child-subtries were affected by the update. 415 // - (ii) If the vertex is a leaf, only checking that neither child-subtries were affected by the update is insufficient. 416 // This is because the register the leaf represents might itself be affected by the update. 417 // 418 // Condition (ii) is particularly subtle, if there are register updates in the subtrie of the leaf: 419 // 420 // - From an API perspective, it is a legal operation to set an unallocated register to nil (essentially a no-op). 421 // 422 // - Though, the Trie-update algorithm only realizes that the register is already unallocated, once it traverses 423 // into the respective sub-trie. When bubbling up from the recursion, nothing has changed in the children of ■ 424 // but the vertex ■ itself has changed from an allocated leaf register to an unallocated register. 425 func Test_DifferentiateEmptyVsLeaf(t *testing.T) { 426 // ⋮ commonPrefix29bytes 101 .... 427 // o 428 // / \ 429 // / \ 430 // / \ 431 // ■ o 432 // Left / \ 433 // SubTrie ⋮ ⋮ 434 // Right 435 // SubTrie 436 // Left Sub-Trie (■) is a single compactified leaf 437 // Right Sub-Trie contains multiple (18) allocated registers 438 439 commonPrefix29bytes := "a0115ce6d49ffe0c9c3d8382826bbec896a9555e4c7720c45b558e7a9e" 440 leftSubTriePrefix, _ := hex.DecodeString(commonPrefix29bytes + "0") // in total 30 bytes 441 rightSubTriePrefix, _ := hex.DecodeString(commonPrefix29bytes + "1") // in total 30 bytes 442 443 rng := &LinearCongruentialGenerator{seed: 0} 444 leftSubTriePath, leftSubTriePayload := sampleRandomRegisterWritesWithPrefix(rng, 1, leftSubTriePrefix) 445 rightSubTriePath, rightSubTriePayload := deduplicateWrites(sampleRandomRegisterWritesWithPrefix(rng, 18, rightSubTriePrefix)) 446 447 // initialize Trie to the depicted state 448 paths := append(leftSubTriePath, rightSubTriePath...) 449 payloads := append(leftSubTriePayload, rightSubTriePayload...) 450 var leftSubTriePayloadSize, rightSubTriePayloadSize uint64 451 for _, p := range leftSubTriePayload { 452 leftSubTriePayloadSize += uint64(p.Size()) 453 } 454 for _, p := range rightSubTriePayload { 455 rightSubTriePayloadSize += uint64(p.Size()) 456 } 457 startTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), paths, payloads, true) 458 require.NoError(t, err) 459 require.Equal(t, uint16(241), maxDepthTouched) 460 require.Equal(t, uint64(len(payloads)), startTrie.AllocatedRegCount()) 461 require.Equal(t, leftSubTriePayloadSize+rightSubTriePayloadSize, startTrie.AllocatedRegSize()) 462 expectedRootHashHex := "8cf6659db0af7626ab0991e2a49019353d549aa4a8c4be1b33e8953d1a9b7fdd" 463 require.Equal(t, expectedRootHashHex, hashToString(startTrie.RootHash())) 464 465 // Register update: 466 // * de-allocate the compactified leaf (■), i.e. set its payload to nil. 467 // * also set a previously already unallocated register to nil as well 468 unallocatedRegister := leftSubTriePath[0] // copy path to leaf and modify it (next line) 469 unallocatedRegister[len(unallocatedRegister)-1] ^= 1 // path differs only in the last byte, i.e. register is also in the left Sub-Trie 470 updatedPaths := append(leftSubTriePath, unallocatedRegister) 471 updatedPayloads := []ledger.Payload{*ledger.EmptyPayload(), *ledger.EmptyPayload()} 472 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(startTrie, updatedPaths, updatedPayloads, true) 473 require.Equal(t, uint16(256), maxDepthTouched) 474 require.Equal(t, uint64(len(rightSubTriePayload)), updatedTrie.AllocatedRegCount()) 475 require.Equal(t, rightSubTriePayloadSize, updatedTrie.AllocatedRegSize()) 476 require.NoError(t, err) 477 478 // The updated trie should equal to a trie containing only the right sub-Trie 479 expectedUpdatedRootHashHex := "576e12a7ef5c760d5cc808ce50e9297919b21b87656b0cc0d9fe8a1a589cf42c" 480 require.Equal(t, expectedUpdatedRootHashHex, hashToString(updatedTrie.RootHash())) 481 referenceTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), rightSubTriePath, rightSubTriePayload, true) 482 require.NoError(t, err) 483 require.Equal(t, uint16(241), maxDepthTouched) 484 require.Equal(t, uint64(len(rightSubTriePayload)), referenceTrie.AllocatedRegCount()) 485 require.Equal(t, rightSubTriePayloadSize, referenceTrie.AllocatedRegSize()) 486 require.Equal(t, expectedUpdatedRootHashHex, hashToString(referenceTrie.RootHash())) 487 } 488 489 func Test_Pruning(t *testing.T) { 490 rand := unittest.GetPRG(t) 491 emptyTrie := trie.NewEmptyMTrie() 492 493 path1 := testutils.PathByUint16(1 << 12) // 000100... 494 path2 := testutils.PathByUint16(1 << 13) // 001000... 495 path4 := testutils.PathByUint16(1<<14 + 1<<13) // 01100... 496 path6 := testutils.PathByUint16(1 << 15) // 1000... 497 498 payload1 := testutils.LightPayload(2, 1) 499 payload2 := testutils.LightPayload(2, 2) 500 payload4 := testutils.LightPayload(2, 4) 501 payload6 := testutils.LightPayload(2, 6) 502 emptyPayload := ledger.EmptyPayload() 503 504 paths := []ledger.Path{path1, path2, path4, path6} 505 payloads := []ledger.Payload{*payload1, *payload2, *payload4, *payload6} 506 507 var totalPayloadSize uint64 508 for _, p := range payloads { 509 totalPayloadSize += uint64(p.Size()) 510 } 511 512 // n7 513 // / \ 514 // / \ 515 // n5 n6 (path6/payload6) // 1000 516 // / \ 517 // / \ 518 // / \ 519 // n3 n4 (path4/payload4) // 01100... 520 // / \ 521 // / \ 522 // / \ 523 // n1 (path1, n2 (path2) 524 // payload1) /payload2) 525 526 baseTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 527 require.NoError(t, err) 528 require.Equal(t, uint16(3), maxDepthTouched) 529 require.Equal(t, uint64(len(payloads)), baseTrie.AllocatedRegCount()) 530 require.Equal(t, totalPayloadSize, baseTrie.AllocatedRegSize()) 531 532 t.Run("leaf update with pruning test", func(t *testing.T) { 533 expectedRegCount := baseTrie.AllocatedRegCount() - 1 534 expectedRegSize := baseTrie.AllocatedRegSize() - uint64(payload1.Size()) 535 536 trie1, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path1}, []ledger.Payload{*emptyPayload}, false) 537 require.NoError(t, err) 538 require.Equal(t, uint16(3), maxDepthTouched) 539 require.Equal(t, expectedRegCount, trie1.AllocatedRegCount()) 540 require.Equal(t, expectedRegSize, trie1.AllocatedRegSize()) 541 542 trie1withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path1}, []ledger.Payload{*emptyPayload}, true) 543 require.NoError(t, err) 544 require.Equal(t, uint16(3), maxDepthTouched) 545 require.Equal(t, expectedRegCount, trie1withpruning.AllocatedRegCount()) 546 require.Equal(t, expectedRegSize, trie1withpruning.AllocatedRegSize()) 547 require.True(t, trie1withpruning.RootNode().VerifyCachedHash()) 548 549 // after pruning 550 // n7 551 // / \ 552 // / \ 553 // n5 n6 (path6/payload6) // 1000 554 // / \ 555 // / \ 556 // / \ 557 // n3 (path2 n4 (path4 558 // /payload2) /payload4) // 01100... 559 require.Equal(t, trie1.RootHash(), trie1withpruning.RootHash()) 560 }) 561 562 t.Run("leaf update with two level pruning test", func(t *testing.T) { 563 expectedRegCount := baseTrie.AllocatedRegCount() - 1 564 expectedRegSize := baseTrie.AllocatedRegSize() - uint64(payload4.Size()) 565 566 // setting path4 to zero from baseTrie 567 trie2, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path4}, []ledger.Payload{*emptyPayload}, false) 568 require.NoError(t, err) 569 require.Equal(t, uint16(2), maxDepthTouched) 570 require.Equal(t, expectedRegCount, trie2.AllocatedRegCount()) 571 require.Equal(t, expectedRegSize, trie2.AllocatedRegSize()) 572 573 // pruning is not activated here because n3 is not a leaf node 574 trie2withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path4}, []ledger.Payload{*emptyPayload}, true) 575 require.NoError(t, err) 576 require.Equal(t, uint16(2), maxDepthTouched) 577 require.Equal(t, expectedRegCount, trie2withpruning.AllocatedRegCount()) 578 require.Equal(t, expectedRegSize, trie2withpruning.AllocatedRegSize()) 579 require.True(t, trie2withpruning.RootNode().VerifyCachedHash()) 580 581 require.Equal(t, trie2.RootHash(), trie2withpruning.RootHash()) 582 583 // now setting path2 to zero should do the pruning for two levels 584 expectedRegCount -= 1 585 expectedRegSize -= uint64(payload2.Size()) 586 587 trie22, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie2, []ledger.Path{path2}, []ledger.Payload{*emptyPayload}, false) 588 require.NoError(t, err) 589 require.Equal(t, uint16(3), maxDepthTouched) 590 require.Equal(t, expectedRegCount, trie22.AllocatedRegCount()) 591 require.Equal(t, expectedRegSize, trie22.AllocatedRegSize()) 592 593 trie22withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie2withpruning, []ledger.Path{path2}, []ledger.Payload{*emptyPayload}, true) 594 require.NoError(t, err) 595 require.Equal(t, uint16(3), maxDepthTouched) 596 require.Equal(t, expectedRegCount, trie22withpruning.AllocatedRegCount()) 597 require.Equal(t, expectedRegSize, trie22withpruning.AllocatedRegSize()) 598 599 // after pruning 600 // n7 601 // / \ 602 // / \ 603 // n5 (path1, n6 (path6/payload6) // 1000 604 // /payload1) 605 606 require.Equal(t, trie22.RootHash(), trie22withpruning.RootHash()) 607 require.True(t, trie22withpruning.RootNode().VerifyCachedHash()) 608 609 }) 610 611 t.Run("several updates at the same time", func(t *testing.T) { 612 // setting path4 to zero from baseTrie 613 trie3, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path2, path4, path6}, []ledger.Payload{*emptyPayload, *emptyPayload, *emptyPayload}, false) 614 require.NoError(t, err) 615 require.Equal(t, uint16(3), maxDepthTouched) 616 require.Equal(t, uint64(1), trie3.AllocatedRegCount()) 617 require.Equal(t, uint64(payload1.Size()), trie3.AllocatedRegSize()) 618 619 // this should prune two levels 620 trie3withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path2, path4, path6}, []ledger.Payload{*emptyPayload, *emptyPayload, *emptyPayload}, true) 621 require.NoError(t, err) 622 require.Equal(t, uint16(3), maxDepthTouched) 623 require.Equal(t, uint64(1), trie3withpruning.AllocatedRegCount()) 624 require.Equal(t, uint64(payload1.Size()), trie3withpruning.AllocatedRegSize()) 625 626 // after pruning 627 // n7 (path1/payload1) 628 require.Equal(t, trie3.RootHash(), trie3withpruning.RootHash()) 629 require.True(t, trie3withpruning.RootNode().VerifyCachedHash()) 630 }) 631 632 t.Run("smoke testing trie pruning", func(t *testing.T) { 633 unittest.SkipUnless(t, unittest.TEST_LONG_RUNNING, "skipping trie pruning smoke testing as its not needed to always run") 634 635 numberOfSteps := 1000 636 numberOfUpdates := 750 637 numberOfRemovals := 750 638 639 var err error 640 activeTrie := trie.NewEmptyMTrie() 641 activeTrieWithPruning := trie.NewEmptyMTrie() 642 allPaths := make(map[ledger.Path]ledger.Payload) 643 var maxDepthTouched, maxDepthTouchedWithPruning uint16 644 var parentTrieRegCount, parentTrieRegSize uint64 645 646 for step := 0; step < numberOfSteps; step++ { 647 648 updatePaths := make([]ledger.Path, 0) 649 updatePayloads := make([]ledger.Payload, 0) 650 651 var expectedRegCountDelta int64 652 var expectedRegSizeDelta int64 653 654 for i := 0; i < numberOfUpdates; { 655 var path ledger.Path 656 _, err := rand.Read(path[:]) 657 require.NoError(t, err) 658 // deduplicate 659 if _, found := allPaths[path]; !found { 660 payload := testutils.RandomPayload(1, 100) 661 updatePaths = append(updatePaths, path) 662 updatePayloads = append(updatePayloads, *payload) 663 expectedRegCountDelta++ 664 expectedRegSizeDelta += int64(payload.Size()) 665 i++ 666 } 667 } 668 669 i := 0 670 samplesNeeded := int(math.Min(float64(numberOfRemovals), float64(len(allPaths)))) 671 for p, pl := range allPaths { 672 updatePaths = append(updatePaths, p) 673 updatePayloads = append(updatePayloads, *emptyPayload) 674 expectedRegCountDelta-- 675 expectedRegSizeDelta -= int64(pl.Size()) 676 delete(allPaths, p) 677 i++ 678 if i > samplesNeeded { 679 break 680 } 681 } 682 683 // only set it for the updates 684 for i := 0; i < numberOfUpdates; i++ { 685 allPaths[updatePaths[i]] = updatePayloads[i] 686 } 687 688 activeTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(activeTrie, updatePaths, updatePayloads, false) 689 require.NoError(t, err) 690 require.Equal(t, uint64(int64(parentTrieRegCount)+expectedRegCountDelta), activeTrie.AllocatedRegCount()) 691 require.Equal(t, uint64(int64(parentTrieRegSize)+expectedRegSizeDelta), activeTrie.AllocatedRegSize()) 692 693 activeTrieWithPruning, maxDepthTouchedWithPruning, err = trie.NewTrieWithUpdatedRegisters(activeTrieWithPruning, updatePaths, updatePayloads, true) 694 require.NoError(t, err) 695 require.True(t, maxDepthTouched >= maxDepthTouchedWithPruning) 696 require.Equal(t, uint64(int64(parentTrieRegCount)+expectedRegCountDelta), activeTrieWithPruning.AllocatedRegCount()) 697 require.Equal(t, uint64(int64(parentTrieRegSize)+expectedRegSizeDelta), activeTrieWithPruning.AllocatedRegSize()) 698 699 require.Equal(t, activeTrie.RootHash(), activeTrieWithPruning.RootHash()) 700 701 parentTrieRegCount = activeTrie.AllocatedRegCount() 702 parentTrieRegSize = activeTrie.AllocatedRegSize() 703 704 // fetch all values and compare 705 queryPaths := make([]ledger.Path, 0) 706 for path := range allPaths { 707 queryPaths = append(queryPaths, path) 708 } 709 710 payloads := activeTrie.UnsafeRead(queryPaths) 711 for i, pp := range payloads { 712 expectedPayload := allPaths[queryPaths[i]] 713 require.True(t, pp.Equals(&expectedPayload)) 714 } 715 716 payloads = activeTrieWithPruning.UnsafeRead(queryPaths) 717 for i, pp := range payloads { 718 expectedPayload := allPaths[queryPaths[i]] 719 require.True(t, pp.Equals(&expectedPayload)) 720 } 721 722 } 723 }) 724 } 725 726 func hashToString(hash ledger.RootHash) string { 727 return hex.EncodeToString(hash[:]) 728 } 729 730 // TestValueSizes tests value sizes of existent and non-existent paths for trie of different layouts. 731 func TestValueSizes(t *testing.T) { 732 733 emptyTrie := trie.NewEmptyMTrie() 734 735 // Test value sizes for non-existent path in empty trie 736 t.Run("empty trie", func(t *testing.T) { 737 path := testutils.PathByUint16LeftPadded(0) 738 pathsToGetValueSize := []ledger.Path{path} 739 sizes := emptyTrie.UnsafeValueSizes(pathsToGetValueSize) 740 require.Equal(t, len(pathsToGetValueSize), len(sizes)) 741 require.Equal(t, 0, sizes[0]) 742 }) 743 744 // Test value sizes for a mix of existent and non-existent paths 745 // in trie with compact leaf as root node. 746 t.Run("compact leaf as root", func(t *testing.T) { 747 path1 := testutils.PathByUint16LeftPadded(0) 748 payload1 := testutils.RandomPayload(1, 100) 749 750 path2 := testutils.PathByUint16LeftPadded(1) // This path will not be inserted into trie. 751 752 paths := []ledger.Path{path1} 753 payloads := []ledger.Payload{*payload1} 754 755 newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 756 require.NoError(t, err) 757 require.Equal(t, uint16(0), maxDepthTouched) 758 759 pathsToGetValueSize := []ledger.Path{path1, path2} 760 761 sizes := newTrie.UnsafeValueSizes(pathsToGetValueSize) 762 require.Equal(t, len(pathsToGetValueSize), len(sizes)) 763 require.Equal(t, payload1.Value().Size(), sizes[0]) 764 require.Equal(t, 0, sizes[1]) 765 }) 766 767 // Test value sizes for a mix of existent and non-existent paths in partial trie. 768 t.Run("partial trie", func(t *testing.T) { 769 path1 := testutils.PathByUint16(1 << 12) // 000100... 770 path2 := testutils.PathByUint16(1 << 13) // 001000... 771 772 payload1 := testutils.RandomPayload(1, 100) 773 payload2 := testutils.RandomPayload(1, 100) 774 775 paths := []ledger.Path{path1, path2} 776 payloads := []ledger.Payload{*payload1, *payload2} 777 778 // Create a new trie with 2 leaf nodes (n1 and n2) at height 253. 779 newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 780 require.NoError(t, err) 781 require.Equal(t, uint16(3), maxDepthTouched) 782 783 // n5 784 // / 785 // / 786 // n4 787 // / 788 // / 789 // n3 790 // / \ 791 // / \ 792 // n1 (path1/ n2 (path2/ 793 // payload1) payload2) 794 // 795 796 // Populate pathsToGetValueSize with all possible paths for the first 4 bits. 797 pathsToGetValueSize := make([]ledger.Path, 16) 798 for i := 0; i < 16; i++ { 799 pathsToGetValueSize[i] = testutils.PathByUint16(uint16(i << 12)) 800 } 801 802 // Test value sizes for a mix of existent and non-existent paths. 803 sizes := newTrie.UnsafeValueSizes(pathsToGetValueSize) 804 require.Equal(t, len(pathsToGetValueSize), len(sizes)) 805 for i, p := range pathsToGetValueSize { 806 switch p { 807 case path1: 808 require.Equal(t, payload1.Value().Size(), sizes[i]) 809 case path2: 810 require.Equal(t, payload2.Value().Size(), sizes[i]) 811 default: 812 // Test value size for non-existent path 813 require.Equal(t, 0, sizes[i]) 814 } 815 } 816 817 // Test value size for a single existent path 818 pathsToGetValueSize = []ledger.Path{path1} 819 sizes = newTrie.UnsafeValueSizes(pathsToGetValueSize) 820 require.Equal(t, len(pathsToGetValueSize), len(sizes)) 821 require.Equal(t, payload1.Value().Size(), sizes[0]) 822 823 // Test value size for a single non-existent path 824 pathsToGetValueSize = []ledger.Path{testutils.PathByUint16(3 << 12)} 825 sizes = newTrie.UnsafeValueSizes(pathsToGetValueSize) 826 require.Equal(t, len(pathsToGetValueSize), len(sizes)) 827 require.Equal(t, 0, sizes[0]) 828 }) 829 } 830 831 // TestValueSizesWithDuplicatePaths tests value sizes of duplicate existent and non-existent paths. 832 func TestValueSizesWithDuplicatePaths(t *testing.T) { 833 path1 := testutils.PathByUint16(0) 834 path2 := testutils.PathByUint16(1) 835 path3 := testutils.PathByUint16(2) // This path will not be inserted into trie. 836 837 payload1 := testutils.RandomPayload(1, 100) 838 payload2 := testutils.RandomPayload(1, 100) 839 840 paths := []ledger.Path{path1, path2} 841 payloads := []ledger.Payload{*payload1, *payload2} 842 843 emptyTrie := trie.NewEmptyMTrie() 844 newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 845 require.NoError(t, err) 846 require.Equal(t, uint16(16), maxDepthTouched) 847 848 // pathsToGetValueSize is a mix of duplicate existent and nonexistent paths. 849 pathsToGetValueSize := []ledger.Path{ 850 path1, path2, path3, 851 path1, path2, path3, 852 } 853 854 sizes := newTrie.UnsafeValueSizes(pathsToGetValueSize) 855 require.Equal(t, len(pathsToGetValueSize), len(sizes)) 856 for i, p := range pathsToGetValueSize { 857 switch p { 858 case path1: 859 require.Equal(t, payload1.Value().Size(), sizes[i]) 860 case path2: 861 require.Equal(t, payload2.Value().Size(), sizes[i]) 862 default: 863 // Test payload size for non-existent path 864 require.Equal(t, 0, sizes[i]) 865 } 866 } 867 } 868 869 // TestTrieAllocatedRegCountRegSize tests allocated register count and register size for updated trie. 870 // It tests the following updates with prune flag set to true: 871 // - update empty trie with new paths and payloads 872 // - update trie with existing paths and updated payload 873 // - update trie with new paths and empty payloads 874 // - update trie with existing path and empty payload one by one until trie is empty 875 // 876 // It also tests the following updates with prune flag set to false: 877 // - update trie with existing path and empty payload one by one until trie is empty 878 // - update trie with removed paths and empty payloads 879 // - update trie with removed paths and non-empty payloads 880 func TestTrieAllocatedRegCountRegSize(t *testing.T) { 881 882 rng := &LinearCongruentialGenerator{seed: 0} 883 884 // Allocate 255 registers 885 numberRegisters := 255 886 paths := make([]ledger.Path, numberRegisters) 887 payloads := make([]ledger.Payload, numberRegisters) 888 var totalPayloadSize uint64 889 for i := 0; i < numberRegisters; i++ { 890 var p ledger.Path 891 p[0] = byte(i) 892 893 payload := testutils.LightPayload(rng.next(), rng.next()) 894 paths[i] = p 895 payloads[i] = *payload 896 897 totalPayloadSize += uint64(payload.Size()) 898 } 899 900 // Update trie with registers to test reg count and size with new registers. 901 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), paths, payloads, true) 902 require.NoError(t, err) 903 require.True(t, maxDepthTouched <= 256) 904 require.Equal(t, uint64(len(payloads)), updatedTrie.AllocatedRegCount()) 905 require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize()) 906 907 // Update trie with existing paths and updated payloads 908 // to test reg count and size with updated registers 909 // (old payload size > 0 and new payload size > 0). 910 for i := 0; i < len(payloads); i += 2 { 911 newPayload := testutils.LightPayload(rng.next(), rng.next()) 912 oldPayload := payloads[i] 913 payloads[i] = *newPayload 914 totalPayloadSize += uint64(newPayload.Size()) - uint64(oldPayload.Size()) 915 } 916 917 updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths, payloads, true) 918 require.NoError(t, err) 919 require.True(t, maxDepthTouched <= 256) 920 require.Equal(t, uint64(len(payloads)), updatedTrie.AllocatedRegCount()) 921 require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize()) 922 923 rootHash := updatedTrie.RootHash() 924 925 // Update trie with new paths and empty payloads 926 // to test reg count and size with new empty registers. 927 newPaths := []ledger.Path{} 928 newPayloads := []ledger.Payload{} 929 for i := 0; i < len(paths); i++ { 930 oldPath := paths[i] 931 932 path1, _ := ledger.ToPath(oldPath[:]) 933 path1[1] = 1 934 payload1 := *ledger.NewPayload( 935 ledger.Key{KeyParts: []ledger.KeyPart{{Type: 0, Value: []byte{0x00, byte(i)}}}}, 936 nil, 937 ) 938 939 path2, _ := ledger.ToPath(oldPath[:]) 940 path2[1] = 2 941 payload2 := ledger.EmptyPayload() 942 943 newPaths = append(newPaths, oldPath, path1, path2) 944 newPayloads = append(newPayloads, payloads[i], payload1, *payload2) 945 } 946 947 updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, newPaths, newPayloads, true) 948 require.NoError(t, err) 949 require.Equal(t, rootHash, updatedTrie.RootHash()) 950 require.True(t, maxDepthTouched <= 256) 951 require.Equal(t, uint64(len(payloads)), updatedTrie.AllocatedRegCount()) 952 require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize()) 953 954 t.Run("pruning", func(t *testing.T) { 955 expectedRegCount := uint64(len(payloads)) 956 expectedRegSize := totalPayloadSize 957 958 updatedTrieWithPruning := updatedTrie 959 960 // Remove register one by one to test reg count and size with empty registers 961 // (old payload size > 0 and new payload size == 0) 962 for i := 0; i < len(paths); i++ { 963 newPaths := []ledger.Path{paths[i]} 964 newPayloads := []ledger.Payload{*ledger.EmptyPayload()} 965 966 expectedRegCount-- 967 expectedRegSize -= uint64(payloads[i].Size()) 968 969 updatedTrieWithPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieWithPruning, newPaths, newPayloads, true) 970 require.NoError(t, err) 971 require.True(t, maxDepthTouched <= 256) 972 require.Equal(t, expectedRegCount, updatedTrieWithPruning.AllocatedRegCount()) 973 require.Equal(t, expectedRegSize, updatedTrieWithPruning.AllocatedRegSize()) 974 } 975 976 // After all registered are removed, reg count and size should be 0. 977 require.Equal(t, trie.EmptyTrieRootHash(), updatedTrieWithPruning.RootHash()) 978 require.Equal(t, uint64(0), updatedTrieWithPruning.AllocatedRegSize()) 979 require.Equal(t, uint64(0), updatedTrieWithPruning.AllocatedRegSize()) 980 }) 981 982 t.Run("no pruning", func(t *testing.T) { 983 expectedRegCount := uint64(len(payloads)) 984 expectedRegSize := totalPayloadSize 985 986 updatedTrieNoPruning := updatedTrie 987 988 // Remove register one by one to test reg count and size with empty registers 989 // (old payload size > 0 and new payload size == 0) 990 for i := 0; i < len(paths); i++ { 991 newPaths := []ledger.Path{paths[i]} 992 newPayloads := []ledger.Payload{*ledger.EmptyPayload()} 993 994 expectedRegCount-- 995 expectedRegSize -= uint64(payloads[i].Size()) 996 997 updatedTrieNoPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieNoPruning, newPaths, newPayloads, false) 998 require.NoError(t, err) 999 require.True(t, maxDepthTouched <= 256) 1000 require.Equal(t, expectedRegCount, updatedTrieNoPruning.AllocatedRegCount()) 1001 require.Equal(t, expectedRegSize, updatedTrieNoPruning.AllocatedRegSize()) 1002 } 1003 1004 // After all registered are removed, reg count and size should be 0. 1005 require.Equal(t, trie.EmptyTrieRootHash(), updatedTrieNoPruning.RootHash()) 1006 require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegCount()) 1007 require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegSize()) 1008 1009 // Update with removed paths and empty payloads 1010 // (old payload size == 0 and new payload size == 0) 1011 newPayloads := make([]ledger.Payload, len(paths)) 1012 for i := 0; i < len(paths); i++ { 1013 newPayloads[i] = *ledger.EmptyPayload() 1014 } 1015 1016 updatedTrieNoPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieNoPruning, paths, newPayloads, false) 1017 require.NoError(t, err) 1018 require.True(t, maxDepthTouched <= 256) 1019 require.Equal(t, trie.EmptyTrieRootHash(), updatedTrieNoPruning.RootHash()) 1020 require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegCount()) 1021 require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegSize()) 1022 1023 // Update with removed paths and non-empty payloads 1024 // (old payload size == 0 and new payload size > 0) 1025 updatedTrieNoPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieNoPruning, paths, payloads, false) 1026 require.NoError(t, err) 1027 require.Equal(t, rootHash, updatedTrie.RootHash()) 1028 require.True(t, maxDepthTouched <= 256) 1029 require.Equal(t, uint64(len(payloads)), updatedTrieNoPruning.AllocatedRegCount()) 1030 require.Equal(t, totalPayloadSize, updatedTrieNoPruning.AllocatedRegSize()) 1031 }) 1032 } 1033 1034 // TestTrieAllocatedRegCountRegSizeWithMixedPruneFlag tests allocated register count and size 1035 // for updated trie with mixed pruning flag. 1036 // It tests the following updates: 1037 // - step 1 : update empty trie with new paths and payloads (255 allocated registers) 1038 // - step 2 : remove a payload without pruning (254 allocated registers) 1039 // - step 3a: remove previously removed payload with pruning (254 allocated registers) 1040 // - step 3b: update trie from step 2 with a new payload (sibling of removed payload) 1041 // with pruning (255 allocated registers) 1042 func TestTrieAllocatedRegCountRegSizeWithMixedPruneFlag(t *testing.T) { 1043 rng := &LinearCongruentialGenerator{seed: 0} 1044 1045 // Allocate 255 registers 1046 numberRegisters := 255 1047 paths := make([]ledger.Path, numberRegisters) 1048 payloads := make([]ledger.Payload, numberRegisters) 1049 var totalPayloadSize uint64 1050 for i := 0; i < numberRegisters; i++ { 1051 var p ledger.Path 1052 p[0] = byte(i) 1053 1054 payload := testutils.LightPayload(rng.next(), rng.next()) 1055 paths[i] = p 1056 payloads[i] = *payload 1057 1058 totalPayloadSize += uint64(payload.Size()) 1059 } 1060 1061 expectedAllocatedRegCount := uint64(len(payloads)) 1062 expectedAllocatedRegSize := totalPayloadSize 1063 1064 // Update trie with registers to test reg count and size with new registers. 1065 baseTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), paths, payloads, true) 1066 require.NoError(t, err) 1067 require.True(t, maxDepthTouched <= 256) 1068 require.Equal(t, expectedAllocatedRegCount, baseTrie.AllocatedRegCount()) 1069 require.Equal(t, expectedAllocatedRegSize, baseTrie.AllocatedRegSize()) 1070 1071 // Remove one payload without pruning 1072 expectedAllocatedRegCount-- 1073 expectedAllocatedRegSize -= uint64(payloads[0].Size()) 1074 1075 removePaths := []ledger.Path{paths[0]} 1076 removePayloads := []ledger.Payload{*ledger.EmptyPayload()} 1077 unprunedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, removePaths, removePayloads, false) 1078 require.NoError(t, err) 1079 require.True(t, maxDepthTouched <= 256) 1080 require.Equal(t, expectedAllocatedRegCount, unprunedTrie.AllocatedRegCount()) 1081 require.Equal(t, expectedAllocatedRegSize, unprunedTrie.AllocatedRegSize()) 1082 1083 // Remove the same payload (no affect) from unprunedTrie with pruning 1084 // expected reg count and reg size remain unchanged. 1085 updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(unprunedTrie, removePaths, removePayloads, true) 1086 require.NoError(t, err) 1087 require.True(t, maxDepthTouched <= 256) 1088 require.Equal(t, expectedAllocatedRegCount, updatedTrie.AllocatedRegCount()) 1089 require.Equal(t, expectedAllocatedRegSize, updatedTrie.AllocatedRegSize()) 1090 1091 // Add sibling of removed path from unprunedTrie with pruning 1092 newPath := paths[0] 1093 bitutils.SetBit(newPath[:], ledger.PathLen*8-1) 1094 newPaths := []ledger.Path{newPath} 1095 newPayloads := []ledger.Payload{*testutils.LightPayload(rng.next(), rng.next())} 1096 1097 // expected reg count is incremented and expected reg size is increase by new payload size. 1098 expectedAllocatedRegCount++ 1099 expectedAllocatedRegSize += uint64(newPayloads[0].Size()) 1100 1101 updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(unprunedTrie, newPaths, newPayloads, true) 1102 require.NoError(t, err) 1103 require.True(t, maxDepthTouched <= 256) 1104 require.Equal(t, expectedAllocatedRegCount, updatedTrie.AllocatedRegCount()) 1105 require.Equal(t, expectedAllocatedRegSize, updatedTrie.AllocatedRegSize()) 1106 } 1107 1108 // TestReadSinglePayload tests reading a single payload of existent/non-existent path for trie of different layouts. 1109 func TestReadSinglePayload(t *testing.T) { 1110 1111 emptyTrie := trie.NewEmptyMTrie() 1112 1113 // Test reading payload in empty trie 1114 t.Run("empty trie", func(t *testing.T) { 1115 savedRootHash := emptyTrie.RootHash() 1116 1117 path := testutils.PathByUint16LeftPadded(0) 1118 payload := emptyTrie.ReadSinglePayload(path) 1119 require.True(t, payload.IsEmpty()) 1120 require.Equal(t, savedRootHash, emptyTrie.RootHash()) 1121 }) 1122 1123 // Test reading payload for existent/non-existent path 1124 // in trie with compact leaf as root node. 1125 t.Run("compact leaf as root", func(t *testing.T) { 1126 path1 := testutils.PathByUint16LeftPadded(0) 1127 payload1 := testutils.RandomPayload(1, 100) 1128 1129 paths := []ledger.Path{path1} 1130 payloads := []ledger.Payload{*payload1} 1131 1132 newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 1133 require.NoError(t, err) 1134 require.Equal(t, uint16(0), maxDepthTouched) 1135 1136 savedRootHash := newTrie.RootHash() 1137 1138 // Get payload for existent path path 1139 retPayload := newTrie.ReadSinglePayload(path1) 1140 require.Equal(t, payload1, retPayload) 1141 require.Equal(t, savedRootHash, newTrie.RootHash()) 1142 1143 // Get payload for non-existent path 1144 path2 := testutils.PathByUint16LeftPadded(1) 1145 retPayload = newTrie.ReadSinglePayload(path2) 1146 require.True(t, retPayload.IsEmpty()) 1147 require.Equal(t, savedRootHash, newTrie.RootHash()) 1148 }) 1149 1150 // Test reading payload for existent/non-existent path in an unpruned trie. 1151 t.Run("trie", func(t *testing.T) { 1152 path1 := testutils.PathByUint16(1 << 12) // 000100... 1153 path2 := testutils.PathByUint16(1 << 13) // 001000... 1154 path3 := testutils.PathByUint16(1 << 14) // 010000... 1155 1156 payload1 := testutils.RandomPayload(1, 100) 1157 payload2 := testutils.RandomPayload(1, 100) 1158 payload3 := ledger.EmptyPayload() 1159 1160 paths := []ledger.Path{path1, path2, path3} 1161 payloads := []ledger.Payload{*payload1, *payload2, *payload3} 1162 1163 // Create an unpruned trie with 3 leaf nodes (n1, n2, n3). 1164 newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, false) 1165 require.NoError(t, err) 1166 require.Equal(t, uint16(3), maxDepthTouched) 1167 1168 savedRootHash := newTrie.RootHash() 1169 1170 // n5 1171 // / 1172 // / 1173 // n4 1174 // / \ 1175 // / \ 1176 // n3 n3 (path3/ 1177 // / \ payload3) 1178 // / \ 1179 // n1 (path1/ n2 (path2/ 1180 // payload1) payload2) 1181 // 1182 1183 // Test reading payload for all possible paths for the first 4 bits. 1184 for i := 0; i < 16; i++ { 1185 path := testutils.PathByUint16(uint16(i << 12)) 1186 1187 retPayload := newTrie.ReadSinglePayload(path) 1188 require.Equal(t, savedRootHash, newTrie.RootHash()) 1189 switch path { 1190 case path1: 1191 require.Equal(t, payload1, retPayload) 1192 case path2: 1193 require.Equal(t, payload2, retPayload) 1194 default: 1195 require.True(t, retPayload.IsEmpty()) 1196 } 1197 } 1198 }) 1199 }