github.com/koko1123/flow-go-1@v0.29.6/ledger/complete/wal/checkpoint_v6_test.go (about) 1 package wal 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "math/rand" 10 "os" 11 "path" 12 "path/filepath" 13 "testing" 14 15 "github.com/rs/zerolog" 16 "github.com/rs/zerolog/log" 17 "github.com/stretchr/testify/require" 18 19 "github.com/koko1123/flow-go-1/ledger" 20 "github.com/koko1123/flow-go-1/ledger/common/hash" 21 "github.com/koko1123/flow-go-1/ledger/common/testutils" 22 "github.com/koko1123/flow-go-1/ledger/complete/mtrie/node" 23 "github.com/koko1123/flow-go-1/ledger/complete/mtrie/trie" 24 "github.com/koko1123/flow-go-1/utils/unittest" 25 ) 26 27 func TestVersion(t *testing.T) { 28 m, v, err := decodeVersion(encodeVersion(MagicBytesCheckpointHeader, VersionV6)) 29 require.NoError(t, err) 30 require.Equal(t, MagicBytesCheckpointHeader, m) 31 require.Equal(t, VersionV6, v) 32 } 33 34 func TestSubtrieCount(t *testing.T) { 35 l, err := decodeSubtrieCount(encodeSubtrieCount(subtrieCount)) 36 require.NoError(t, err) 37 require.Equal(t, uint16(subtrieCount), l) 38 } 39 40 func TestCRC32SumEncoding(t *testing.T) { 41 v := uint32(3) 42 s, err := decodeCRC32Sum(encodeCRC32Sum(v)) 43 require.NoError(t, err) 44 require.Equal(t, v, s) 45 } 46 47 func TestSubtrieNodeCountEncoding(t *testing.T) { 48 v := uint64(10000) 49 s, err := decodeNodeCount(encodeNodeCount(v)) 50 require.NoError(t, err) 51 require.Equal(t, v, s) 52 } 53 54 func TestFooterEncoding(t *testing.T) { 55 n1, r1 := uint64(40), uint16(500) 56 n2, r2, err := decodeTopLevelNodesAndTriesFooter(encodeTopLevelNodesAndTriesFooter(n1, r1)) 57 require.NoError(t, err) 58 require.Equal(t, n1, n2) 59 require.Equal(t, r1, r2) 60 } 61 62 func requireTriesEqual(t *testing.T, tries1, tries2 []*trie.MTrie) { 63 require.Equal(t, len(tries1), len(tries2), "tries have different length") 64 for i, expect := range tries1 { 65 actual := tries2[i] 66 require.True(t, expect.Equals(actual), "%v-th trie is different", i) 67 } 68 } 69 70 func createSimpleTrie(t *testing.T) []*trie.MTrie { 71 emptyTrie := trie.NewEmptyMTrie() 72 73 p1 := testutils.PathByUint8(0) 74 v1 := testutils.LightPayload8('A', 'a') 75 76 p2 := testutils.PathByUint8(1) 77 v2 := testutils.LightPayload8('B', 'b') 78 79 paths := []ledger.Path{p1, p2} 80 payloads := []ledger.Payload{*v1, *v2} 81 82 updatedTrie, _, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) 83 require.NoError(t, err) 84 tries := []*trie.MTrie{emptyTrie, updatedTrie} 85 return tries 86 } 87 88 func randPathPayload() (ledger.Path, ledger.Payload) { 89 var path ledger.Path 90 rand.Read(path[:]) 91 payload := testutils.RandomPayload(1, 100) 92 return path, *payload 93 } 94 95 func randNPathPayloads(n int) ([]ledger.Path, []ledger.Payload) { 96 paths := make([]ledger.Path, n) 97 payloads := make([]ledger.Payload, n) 98 for i := 0; i < n; i++ { 99 path, payload := randPathPayload() 100 paths[i] = path 101 payloads[i] = payload 102 } 103 return paths, payloads 104 } 105 106 func createMultipleRandomTries(t *testing.T) []*trie.MTrie { 107 tries := make([]*trie.MTrie, 0) 108 activeTrie := trie.NewEmptyMTrie() 109 110 var err error 111 // add tries with no shared paths 112 for i := 0; i < 100; i++ { 113 paths, payloads := randNPathPayloads(100) 114 activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, paths, payloads, false) 115 require.NoError(t, err, "update registers") 116 tries = append(tries, activeTrie) 117 } 118 119 // add trie with some shared path 120 sharedPaths, payloads1 := randNPathPayloads(100) 121 activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, sharedPaths, payloads1, false) 122 require.NoError(t, err, "update registers") 123 tries = append(tries, activeTrie) 124 125 _, payloads2 := randNPathPayloads(100) 126 activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, sharedPaths, payloads2, false) 127 require.NoError(t, err, "update registers") 128 tries = append(tries, activeTrie) 129 130 return tries 131 } 132 133 func TestEncodeSubTrie(t *testing.T) { 134 file := "checkpoint" 135 logger := unittest.Logger() 136 tries := createMultipleRandomTries(t) 137 estimatedSubtrieNodeCount := estimateSubtrieNodeCount(tries[0]) 138 subtrieRoots := createSubTrieRoots(tries) 139 140 for index, roots := range subtrieRoots { 141 unittest.RunWithTempDir(t, func(dir string) { 142 uniqueIndices, nodeCount, checksum, err := storeCheckpointSubTrie( 143 index, roots, estimatedSubtrieNodeCount, dir, file, &logger) 144 require.NoError(t, err) 145 146 // subtrie roots might have duplciates, that why we group the them, 147 // and store each group in different part file in order to deduplicate. 148 // the returned uniqueIndices contains the index for each unique roots. 149 // in order to verify that, we build a uniqueRoots first, and verify 150 // if any unique root is missing from the uniqueIndices 151 uniqueRoots := make(map[*node.Node]struct{}) 152 for i, root := range roots { 153 if root == nil { 154 fmt.Println(i, "-th subtrie root is nil") 155 } 156 _, ok := uniqueRoots[root] 157 if ok { 158 fmt.Println(i, "-th subtrie root is a duplicate") 159 } 160 uniqueRoots[root] = struct{}{} 161 } 162 163 // each root should be included in the uniqueIndices 164 for _, root := range roots { 165 _, ok := uniqueIndices[root] 166 require.True(t, ok, "each root should be included in the uniqueIndices") 167 } 168 169 if len(uniqueIndices) > 1 { 170 require.Len(t, uniqueIndices, len(uniqueRoots), 171 fmt.Sprintf("uniqueIndices should include all roots, uniqueIndices[nil] %v, roots[0] %v", uniqueIndices[nil], roots[0])) 172 } 173 174 logger.Info().Msgf("sub trie checkpoint stored, uniqueIndices: %v, node count: %v, checksum: %v", 175 uniqueIndices, nodeCount, checksum) 176 177 // all the nodes 178 nodes, err := readCheckpointSubTrie(dir, file, index, checksum, &logger) 179 require.NoError(t, err) 180 181 for _, root := range roots { 182 if root == nil { 183 continue 184 } 185 index := uniqueIndices[root] 186 require.Equal(t, root.Hash(), nodes[index-1].Hash(), // -1 because readCheckpointSubTrie returns nodes[1:] 187 "readCheckpointSubTrie should return nodes where the root should be found "+ 188 "by the index specified by the uniqueIndices returned by storeCheckpointSubTrie") 189 } 190 }) 191 } 192 } 193 194 func randomNode() *node.Node { 195 var randomPath ledger.Path 196 rand.Read(randomPath[:]) 197 198 var randomHashValue hash.Hash 199 rand.Read(randomHashValue[:]) 200 201 return node.NewNode(256, nil, nil, randomPath, nil, randomHashValue) 202 } 203 func TestGetNodesByIndex(t *testing.T) { 204 n := 10 205 ns := make([]*node.Node, n) 206 for i := 0; i < n; i++ { 207 ns[i] = randomNode() 208 } 209 subtrieNodes := [][]*node.Node{ 210 []*node.Node{ns[0], ns[1]}, 211 []*node.Node{ns[2]}, 212 []*node.Node{}, 213 []*node.Node{}, 214 } 215 topLevelNodes := []*node.Node{nil, ns[3]} 216 totalSubTrieNodeCount := computeTotalSubTrieNodeCount(subtrieNodes) 217 218 for i := uint64(1); i <= 4; i++ { 219 node, err := getNodeByIndex(subtrieNodes, totalSubTrieNodeCount, topLevelNodes, i) 220 require.NoError(t, err, "cannot get node by index", i) 221 require.Equal(t, ns[i-1], node, "got wrong node by index %v", i) 222 } 223 } 224 func TestWriteAndReadCheckpointV6EmptyTrie(t *testing.T) { 225 unittest.RunWithTempDir(t, func(dir string) { 226 tries := []*trie.MTrie{trie.NewEmptyMTrie()} 227 fileName := "checkpoint-empty-trie" 228 logger := unittest.Logger() 229 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") 230 decoded, err := OpenAndReadCheckpointV6(dir, fileName, &logger) 231 require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) 232 requireTriesEqual(t, tries, decoded) 233 }) 234 } 235 236 func TestWriteAndReadCheckpointV6SimpleTrie(t *testing.T) { 237 unittest.RunWithTempDir(t, func(dir string) { 238 tries := createSimpleTrie(t) 239 fileName := "checkpoint" 240 logger := unittest.Logger() 241 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") 242 decoded, err := OpenAndReadCheckpointV6(dir, fileName, &logger) 243 require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) 244 requireTriesEqual(t, tries, decoded) 245 }) 246 } 247 248 func TestWriteAndReadCheckpointV6MultipleTries(t *testing.T) { 249 unittest.RunWithTempDir(t, func(dir string) { 250 tries := createMultipleRandomTries(t) 251 fileName := "checkpoint-multi-file" 252 logger := unittest.Logger() 253 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") 254 decoded, err := OpenAndReadCheckpointV6(dir, fileName, &logger) 255 require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) 256 requireTriesEqual(t, tries, decoded) 257 }) 258 } 259 260 // test running checkpointing twice will produce the same checkpoint file 261 func TestCheckpointV6IsDeterminstic(t *testing.T) { 262 unittest.RunWithTempDir(t, func(dir string) { 263 tries := createMultipleRandomTries(t) 264 logger := unittest.Logger() 265 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint1", &logger), "fail to store checkpoint") 266 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint2", &logger), "fail to store checkpoint") 267 partFiles1 := filePaths(dir, "checkpoint1", subtrieLevel) 268 partFiles2 := filePaths(dir, "checkpoint2", subtrieLevel) 269 for i, partFile1 := range partFiles1 { 270 partFile2 := partFiles2[i] 271 require.NoError(t, compareFiles( 272 partFile1, partFile2), 273 "found difference in checkpoint files") 274 275 } 276 }) 277 } 278 279 // compareFiles takes two files' full path, and read them bytes by bytes and compare if 280 // the two files are identical 281 // it returns nil if identical 282 // it returns error if there is difference 283 func compareFiles(file1, file2 string) error { 284 closable1, err := os.Open(file1) 285 if err != nil { 286 return fmt.Errorf("could not open file 1 %v: %w", closable1, err) 287 } 288 defer func(f *os.File) { 289 f.Close() 290 }(closable1) 291 292 closable2, err := os.Open(file1) 293 if err != nil { 294 return fmt.Errorf("could not open file 2 %v: %w", closable2, err) 295 } 296 defer func(f *os.File) { 297 f.Close() 298 }(closable2) 299 300 reader1 := bufio.NewReaderSize(closable1, defaultBufioReadSize) 301 reader2 := bufio.NewReaderSize(closable2, defaultBufioReadSize) 302 303 buf1 := make([]byte, defaultBufioReadSize) 304 buf2 := make([]byte, defaultBufioReadSize) 305 for { 306 _, err1 := reader1.Read(buf1) 307 _, err2 := reader2.Read(buf2) 308 if errors.Is(err1, io.EOF) && errors.Is(err2, io.EOF) { 309 break 310 } 311 312 if err1 != nil { 313 return err1 314 } 315 if err2 != nil { 316 return err2 317 } 318 319 if !bytes.Equal(buf1, buf2) { 320 return fmt.Errorf("bytes are different: %x, %x", buf1, buf2) 321 } 322 } 323 324 return nil 325 } 326 327 func storeCheckpointV5(tries []*trie.MTrie, dir string, fileName string, logger *zerolog.Logger) error { 328 return StoreCheckpointV5(dir, fileName, logger, tries...) 329 } 330 331 func TestWriteAndReadCheckpointV5(t *testing.T) { 332 unittest.RunWithTempDir(t, func(dir string) { 333 tries := createMultipleRandomTries(t) 334 fileName := "checkpoint1" 335 logger := unittest.Logger() 336 337 require.NoErrorf(t, storeCheckpointV5(tries, dir, fileName, &logger), "fail to store checkpoint") 338 decoded, err := LoadCheckpoint(filepath.Join(dir, fileName), &logger) 339 require.NoErrorf(t, err, "fail to load checkpoint") 340 requireTriesEqual(t, tries, decoded) 341 }) 342 } 343 344 // test that converting a v6 back to v5 would produce the same v5 checkpoint as 345 // producing directly to v5 346 func TestWriteAndReadCheckpointV6ThenBackToV5(t *testing.T) { 347 unittest.RunWithTempDir(t, func(dir string) { 348 tries := createMultipleRandomTries(t) 349 logger := unittest.Logger() 350 351 // store tries into v6 then read back, then store into v5 352 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint-v6", &logger), "fail to store checkpoint") 353 decoded, err := OpenAndReadCheckpointV6(dir, "checkpoint-v6", &logger) 354 require.NoErrorf(t, err, "fail to read checkpoint %v/checkpoint-v6", dir) 355 require.NoErrorf(t, storeCheckpointV5(decoded, dir, "checkpoint-v6-v5", &logger), "fail to store checkpoint") 356 357 // store tries directly into v5 checkpoint 358 require.NoErrorf(t, storeCheckpointV5(tries, dir, "checkpoint-v5", &logger), "fail to store checkpoint") 359 360 // compare the two v5 checkpoint files should be identical 361 require.NoError(t, compareFiles( 362 path.Join(dir, "checkpoint-v5"), 363 path.Join(dir, "checkpoint-v6-v5")), 364 "found difference in checkpoint files") 365 }) 366 } 367 368 func TestCleanupOnErrorIfNotExist(t *testing.T) { 369 t.Run("works if temp files not exist", func(t *testing.T) { 370 require.NoError(t, deleteCheckpointFiles("not-exist", "checkpoint-v6")) 371 }) 372 373 // if it can clean up all files after successful storing, then it can 374 // clean up if failed in middle. 375 t.Run("clean up after finish storing files", func(t *testing.T) { 376 unittest.RunWithTempDir(t, func(dir string) { 377 tries := createMultipleRandomTries(t) 378 logger := unittest.Logger() 379 380 // store tries into v6 then read back, then store into v5 381 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint-v6", &logger), "fail to store checkpoint") 382 require.NoError(t, deleteCheckpointFiles(dir, "checkpoint-v6")) 383 384 // verify all files are removed 385 files := filePaths(dir, "checkpoint-v6", subtrieLevel) 386 for _, file := range files { 387 _, err := os.Stat(file) 388 require.True(t, os.IsNotExist(err), err) 389 } 390 }) 391 }) 392 } 393 394 // verify that if a part file is missing then os.ErrNotExist should return 395 func TestAllPartFileExist(t *testing.T) { 396 unittest.RunWithTempDir(t, func(dir string) { 397 for i := 0; i < 17; i++ { 398 tries := createSimpleTrie(t) 399 fileName := fmt.Sprintf("checkpoint_missing_part_file_%v", i) 400 var fileToDelete string 401 var err error 402 if i == 16 { 403 fileToDelete, _ = filePathTopTries(dir, fileName) 404 } else { 405 fileToDelete, _, err = filePathSubTries(dir, fileName, i) 406 } 407 require.NoErrorf(t, err, "fail to find sub trie file path") 408 409 logger := unittest.Logger() 410 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") 411 412 // delete i-th part file, then the error should mention i-th file missing 413 err = os.Remove(fileToDelete) 414 require.NoError(t, err, "fail to remove part file") 415 416 _, err = OpenAndReadCheckpointV6(dir, fileName, &logger) 417 require.ErrorIs(t, err, os.ErrNotExist, "wrong error type returned") 418 } 419 }) 420 } 421 422 // verify that can't store the same checkpoint file twice, because a checkpoint already exists 423 func TestCannotStoreTwice(t *testing.T) { 424 unittest.RunWithTempDir(t, func(dir string) { 425 tries := createSimpleTrie(t) 426 fileName := "checkpoint" 427 logger := unittest.Logger() 428 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") 429 // checkpoint already exist, can't store again 430 require.Error(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger)) 431 }) 432 } 433 434 func filePaths(dir string, fileName string, subtrieLevel uint16) []string { 435 paths := make([]string, 0) 436 437 paths = append(paths, filePathCheckpointHeader(dir, fileName)) 438 439 subtrieCount := subtrieCountByLevel(subtrieLevel) 440 for i := 0; i < subtrieCount; i++ { 441 partFile := partFileName(fileName, i) 442 paths = append(paths, path.Join(dir, partFile)) 443 } 444 445 p, _ := filePathTopTries(dir, fileName) 446 paths = append(paths, p) 447 return paths 448 } 449 450 func TestCopyCheckpointFileV6(t *testing.T) { 451 unittest.RunWithTempDir(t, func(dir string) { 452 tries := createSimpleTrie(t) 453 fileName := "checkpoint" 454 logger := unittest.Logger() 455 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") 456 to := filepath.Join(dir, "newfolder") 457 newPaths, err := CopyCheckpointFile(fileName, dir, to) 458 require.NoError(t, err) 459 log.Info().Msgf("copied to :%v", newPaths) 460 decoded, err := OpenAndReadCheckpointV6(to, fileName, &logger) 461 require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) 462 requireTriesEqual(t, tries, decoded) 463 }) 464 }