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