github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/partial/ptrie/partialTrie_test.go (about) 1 package ptrie 2 3 import ( 4 "math/rand" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 "github.com/onflow/flow-go/ledger" 11 "github.com/onflow/flow-go/ledger/common/testutils" 12 "github.com/onflow/flow-go/ledger/complete/mtrie" 13 "github.com/onflow/flow-go/module/metrics" 14 ) 15 16 func withForest( 17 t *testing.T, 18 pathByteSize int, 19 numberOfActiveTries int, f func(t *testing.T, f *mtrie.Forest)) { 20 21 forest, err := mtrie.NewForest(numberOfActiveTries, &metrics.NoopCollector{}, nil) 22 require.NoError(t, err) 23 24 f(t, forest) 25 } 26 27 func TestPartialTrieEmptyTrie(t *testing.T) { 28 29 pathByteSize := 32 30 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 31 32 // add path1 to the empty trie 33 // 00000000...0 (0) 34 path1 := testutils.PathByUint16(0) 35 payload1 := testutils.LightPayload('A', 'a') 36 37 paths := []ledger.Path{path1} 38 payloads := []*ledger.Payload{payload1} 39 40 rootHash := f.GetEmptyRootHash() 41 r := &ledger.TrieRead{RootHash: rootHash, Paths: paths} 42 bp, err := f.Proofs(r) 43 require.NoError(t, err, "error getting proofs values") 44 45 psmt, err := NewPSMT(rootHash, bp) 46 require.NoError(t, err, "error building partial trie") 47 ensureRootHash(t, rootHash, psmt) 48 49 u := &ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads} 50 rootHash, err = f.Update(u) 51 require.NoError(t, err, "error updating trie") 52 53 _, err = psmt.Update(paths, payloads) 54 require.NoError(t, err, "error updating psmt") 55 ensureRootHash(t, rootHash, psmt) 56 57 updatedPayload1 := testutils.LightPayload('B', 'b') 58 payloads = []*ledger.Payload{updatedPayload1} 59 60 u = &ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads} 61 rootHash, err = f.Update(u) 62 require.NoError(t, err, "error updating trie") 63 64 _, err = psmt.Update(paths, payloads) 65 require.NoError(t, err, "error updating psmt") 66 ensureRootHash(t, rootHash, psmt) 67 }) 68 } 69 70 // TestPartialTrieGet gets payloads from existent and non-existent paths. 71 func TestPartialTrieGet(t *testing.T) { 72 73 pathByteSize := 32 74 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 75 76 path1 := testutils.PathByUint16(0) 77 payload1 := testutils.LightPayload('A', 'a') 78 79 path2 := testutils.PathByUint16(1) 80 payload2 := testutils.LightPayload('B', 'b') 81 82 paths := []ledger.Path{path1, path2} 83 payloads := []*ledger.Payload{payload1, payload2} 84 85 u := &ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: paths, Payloads: payloads} 86 rootHash, err := f.Update(u) 87 require.NoError(t, err, "error updating trie") 88 89 r := &ledger.TrieRead{RootHash: rootHash, Paths: paths} 90 bp, err := f.Proofs(r) 91 require.NoError(t, err, "error getting batch proof") 92 93 psmt, err := NewPSMT(rootHash, bp) 94 require.NoError(t, err, "error building partial trie") 95 ensureRootHash(t, rootHash, psmt) 96 97 t.Run("non-existent key", func(t *testing.T) { 98 path3 := testutils.PathByUint16(2) 99 path4 := testutils.PathByUint16(4) 100 101 nonExistentPaths := []ledger.Path{path3, path4} 102 retPayloads, err := psmt.Get(nonExistentPaths) 103 require.Nil(t, retPayloads) 104 105 e, ok := err.(*ErrMissingPath) 106 require.True(t, ok) 107 assert.Equal(t, 2, len(e.Paths)) 108 require.Equal(t, path3, e.Paths[0]) 109 require.Equal(t, path4, e.Paths[1]) 110 }) 111 112 t.Run("existent key", func(t *testing.T) { 113 retPayloads, err := psmt.Get(paths) 114 require.NoError(t, err) 115 require.Equal(t, len(paths), len(retPayloads)) 116 require.Equal(t, payload1, retPayloads[0]) 117 require.Equal(t, payload2, retPayloads[1]) 118 }) 119 120 t.Run("mix of existent and non-existent keys", func(t *testing.T) { 121 path3 := testutils.PathByUint16(2) 122 path4 := testutils.PathByUint16(4) 123 124 retPayloads, err := psmt.Get([]ledger.Path{path1, path2, path3, path4}) 125 require.Nil(t, retPayloads) 126 127 e, ok := err.(*ErrMissingPath) 128 require.True(t, ok) 129 assert.Equal(t, 2, len(e.Paths)) 130 require.Equal(t, path3, e.Paths[0]) 131 require.Equal(t, path4, e.Paths[1]) 132 }) 133 }) 134 } 135 136 // TestPartialTrieGetSinglePayload gets single payload from existent/non-existent path. 137 func TestPartialTrieGetSinglePayload(t *testing.T) { 138 139 pathByteSize := 32 140 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 141 142 path1 := testutils.PathByUint16(0) 143 payload1 := testutils.LightPayload('A', 'a') 144 145 path2 := testutils.PathByUint16(1) 146 payload2 := testutils.LightPayload('B', 'b') 147 148 paths := []ledger.Path{path1, path2} 149 payloads := []*ledger.Payload{payload1, payload2} 150 151 u := &ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: paths, Payloads: payloads} 152 rootHash, err := f.Update(u) 153 require.NoError(t, err, "error updating trie") 154 155 r := &ledger.TrieRead{RootHash: rootHash, Paths: paths} 156 bp, err := f.Proofs(r) 157 require.NoError(t, err, "error getting batch proof") 158 159 psmt, err := NewPSMT(rootHash, bp) 160 require.NoError(t, err, "error building partial trie") 161 ensureRootHash(t, rootHash, psmt) 162 163 retPayload, err := psmt.GetSinglePayload(path1) 164 require.NoError(t, err) 165 require.Equal(t, payload1, retPayload) 166 167 retPayload, err = psmt.GetSinglePayload(path2) 168 require.NoError(t, err) 169 require.Equal(t, payload2, retPayload) 170 171 path3 := testutils.PathByUint16(2) 172 173 retPayload, err = psmt.GetSinglePayload(path3) 174 require.Nil(t, retPayload) 175 176 var errMissingPath *ErrMissingPath 177 require.ErrorAs(t, err, &errMissingPath) 178 missingPath := err.(*ErrMissingPath) 179 require.Equal(t, 1, len(missingPath.Paths)) 180 require.Equal(t, path3, missingPath.Paths[0]) 181 }) 182 } 183 184 func TestPartialTrieLeafUpdates(t *testing.T) { 185 186 pathByteSize := 32 187 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 188 189 path1 := testutils.PathByUint16(0) 190 payload1 := testutils.LightPayload('A', 'a') 191 updatedPayload1 := testutils.LightPayload('B', 'b') 192 193 path2 := testutils.PathByUint16(1) 194 payload2 := testutils.LightPayload('C', 'c') 195 updatedPayload2 := testutils.LightPayload('D', 'd') 196 197 path3 := testutils.PathByUint16(2) 198 payload3 := testutils.LightPayload('E', 'e') 199 200 paths := []ledger.Path{path1, path2} 201 payloads := []*ledger.Payload{payload1, payload2} 202 203 u := &ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: paths, Payloads: payloads} 204 rootHash, err := f.Update(u) 205 require.NoError(t, err, "error updating trie") 206 207 r := &ledger.TrieRead{RootHash: rootHash, Paths: paths} 208 bp, err := f.Proofs(r) 209 require.NoError(t, err, "error getting batch proof") 210 211 psmt, err := NewPSMT(rootHash, bp) 212 require.NoError(t, err, "error building partial trie") 213 ensureRootHash(t, rootHash, psmt) 214 215 payloads = []*ledger.Payload{updatedPayload1, updatedPayload2} 216 rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 217 require.NoError(t, err, "error updating trie") 218 219 _, err = psmt.Update(paths, payloads) 220 require.NoError(t, err, "error updating psmt") 221 ensureRootHash(t, rootHash, psmt) 222 223 // Update on non-existent leafs 224 _, err = psmt.Update([]ledger.Path{path3}, []*ledger.Payload{payload3}) 225 missingPathErr, ok := err.(*ErrMissingPath) 226 require.True(t, ok) 227 require.Equal(t, 1, len(missingPathErr.Paths)) 228 require.Equal(t, path3, missingPathErr.Paths[0]) 229 }) 230 231 } 232 233 func TestPartialTrieMiddleBranching(t *testing.T) { 234 235 pathByteSize := 32 236 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 237 238 path1 := testutils.PathByUint16(0) 239 payload1 := testutils.LightPayload('A', 'a') 240 updatedPayload1 := testutils.LightPayload('B', 'b') 241 242 path2 := testutils.PathByUint16(2) 243 payload2 := testutils.LightPayload('C', 'c') 244 updatedPayload2 := testutils.LightPayload('D', 'd') 245 246 path3 := testutils.PathByUint16(8) 247 payload3 := testutils.LightPayload('E', 'e') 248 updatedPayload3 := testutils.LightPayload('F', 'f') 249 250 paths := []ledger.Path{path1, path2, path3} 251 payloads := []*ledger.Payload{payload1, payload2, payload3} 252 253 rootHash := f.GetEmptyRootHash() 254 bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths}) 255 require.NoError(t, err, "error getting batch proof") 256 257 psmt, err := NewPSMT(rootHash, bp) 258 require.NoError(t, err, "error building partial trie") 259 ensureRootHash(t, f.GetEmptyRootHash(), psmt) 260 261 // first update 262 rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 263 require.NoError(t, err, "error updating trie") 264 265 _, err = psmt.Update(paths, payloads) 266 require.NoError(t, err, "error updating psmt") 267 ensureRootHash(t, rootHash, psmt) 268 269 // second update 270 payloads = []*ledger.Payload{updatedPayload1, updatedPayload2, updatedPayload3} 271 rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 272 require.NoError(t, err, "error updating trie") 273 274 _, err = psmt.Update(paths, payloads) 275 require.NoError(t, err, "error updating psmt") 276 ensureRootHash(t, rootHash, psmt) 277 }) 278 279 } 280 281 func TestPartialTrieRootUpdates(t *testing.T) { 282 283 pathByteSize := 32 284 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 285 286 path1 := testutils.PathByUint16(0) 287 payload1 := testutils.LightPayload('A', 'a') 288 updatedPayload1 := testutils.LightPayload('B', 'b') 289 // 10000....0 290 path2 := testutils.PathByUint16(32768) 291 payload2 := testutils.LightPayload('C', 'c') 292 updatedPayload2 := testutils.LightPayload('D', 'd') 293 294 paths := []ledger.Path{path1, path2} 295 payloads := []*ledger.Payload{payload1, payload2} 296 297 rootHash := f.GetEmptyRootHash() 298 bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths}) 299 require.NoError(t, err, "error getting batch proof") 300 301 psmt, err := NewPSMT(rootHash, bp) 302 require.NoError(t, err, "error building partial trie") 303 ensureRootHash(t, rootHash, psmt) 304 305 // first update 306 rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 307 require.NoError(t, err, "error updating trie") 308 309 pRootHash, err := psmt.Update(paths, payloads) 310 require.NoError(t, err, "error updating psmt") 311 assert.Equal(t, rootHash, pRootHash, "rootNode hash doesn't match [after update]") 312 313 // second update 314 payloads = []*ledger.Payload{updatedPayload1, updatedPayload2} 315 rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 316 require.NoError(t, err, "error updating trie") 317 318 pRootHash, err = psmt.Update(paths, payloads) 319 require.NoError(t, err, "error updating psmt") 320 assert.Equal(t, rootHash, pRootHash, "rootNode hash doesn't match [after second update]") 321 }) 322 323 } 324 325 func TestMixProof(t *testing.T) { 326 pathByteSize := 32 327 withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) { 328 329 path1 := testutils.PathByUint16(0) 330 payload1 := testutils.LightPayload('A', 'a') 331 332 path2 := testutils.PathByUint16(2) 333 updatedPayload2 := testutils.LightPayload('D', 'd') 334 335 path3 := testutils.PathByUint16(8) 336 payload3 := testutils.LightPayload('E', 'e') 337 338 paths := []ledger.Path{path1, path3} 339 payloads := []*ledger.Payload{payload1, payload3} 340 341 rootHash := f.GetEmptyRootHash() 342 rootHash, err := f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 343 require.NoError(t, err, "error updating trie") 344 345 paths = []ledger.Path{path1, path2, path3} 346 347 bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths}) 348 require.NoError(t, err, "error getting batch proof") 349 350 psmt, err := NewPSMT(rootHash, bp) 351 require.NoError(t, err, "error building partial trie") 352 ensureRootHash(t, rootHash, psmt) 353 354 paths = []ledger.Path{path2, path3} 355 payloads = []*ledger.Payload{updatedPayload2, updatedPayload2} 356 357 rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}) 358 require.NoError(t, err, "error updating trie") 359 360 pRootHash, err := psmt.Update(paths, payloads) 361 require.NoError(t, err, "error updating partial trie") 362 ensureRootHash(t, rootHash, psmt) 363 assert.Equal(t, rootHash, pRootHash, "root2 hash doesn't match [%x] != [%x]", rootHash, pRootHash) 364 }) 365 366 } 367 368 func TestRandomProofs(t *testing.T) { 369 pathByteSize := 32 // key size of 16 bits 370 minPayloadSize := 2 371 maxPayloadSize := 10 372 experimentRep := 20 373 for e := 0; e < experimentRep; e++ { 374 withForest(t, pathByteSize, experimentRep+1, func(t *testing.T, f *mtrie.Forest) { 375 376 // generate some random paths and payloads 377 numberOfPaths := rand.Intn(256) + 1 378 paths := testutils.RandomPaths(numberOfPaths) 379 payloads := testutils.RandomPayloads(numberOfPaths, minPayloadSize, maxPayloadSize) 380 // keep a subset as initial insert and keep the rest for reading default values 381 split := rand.Intn(numberOfPaths) 382 insertPaths := paths[:split] 383 insertPayloads := payloads[:split] 384 385 rootHash, err := f.Update(&ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: insertPaths, Payloads: insertPayloads}) 386 require.NoError(t, err, "error updating trie") 387 388 // shuffle paths for read 389 rand.Shuffle(len(paths), func(i, j int) { 390 paths[i], paths[j] = paths[j], paths[i] 391 payloads[i], payloads[j] = payloads[j], payloads[i] 392 }) 393 394 bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths}) 395 require.NoError(t, err, "error getting batch proof") 396 397 psmt, err := NewPSMT(rootHash, bp) 398 require.NoError(t, err, "error building partial trie") 399 ensureRootHash(t, rootHash, psmt) 400 401 // select a subset of shuffled paths for random updates 402 split = rand.Intn(numberOfPaths) 403 updatePaths := paths[:split] 404 updatePayloads := payloads[:split] 405 // random updates 406 rand.Shuffle(len(updatePayloads), func(i, j int) { 407 updatePayloads[i], updatePayloads[j] = updatePayloads[j], updatePayloads[i] 408 }) 409 410 rootHash2, err := f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: updatePaths, Payloads: updatePayloads}) 411 require.NoError(t, err, "error updating trie") 412 413 pRootHash2, err := psmt.Update(updatePaths, updatePayloads) 414 require.NoError(t, err, "error updating partial trie") 415 assert.Equal(t, pRootHash2, rootHash2, "root2 hash doesn't match [%x] != [%x]", rootHash2, pRootHash2) 416 }) 417 } 418 } 419 420 // TODO add test for incompatible proofs [Byzantine milestone] 421 // TODO add test key not exist [Byzantine milestone] 422 423 func ensureRootHash(t *testing.T, expectedRootHash ledger.RootHash, psmt *PSMT) { 424 if expectedRootHash != ledger.RootHash(psmt.root.Hash()) { 425 t.Fatal("rootNode hash doesn't match") 426 } 427 if expectedRootHash != psmt.RootHash() { 428 t.Fatal("rootNode hash doesn't match") 429 } 430 if expectedRootHash != ledger.RootHash(psmt.root.forceComputeHash()) { 431 t.Fatal("rootNode hash doesn't match") 432 } 433 }