github.com/onflow/flow-go@v0.33.17/ledger/complete/wal/checkpoint_v6_writer.go (about) 1 package wal 2 3 import ( 4 "bufio" 5 "encoding/binary" 6 "encoding/hex" 7 "fmt" 8 "io" 9 "os" 10 "path" 11 "path/filepath" 12 13 "github.com/docker/go-units" 14 "github.com/hashicorp/go-multierror" 15 "github.com/rs/zerolog" 16 17 "github.com/onflow/flow-go/ledger" 18 "github.com/onflow/flow-go/ledger/complete/mtrie/flattener" 19 "github.com/onflow/flow-go/ledger/complete/mtrie/node" 20 "github.com/onflow/flow-go/ledger/complete/mtrie/trie" 21 utilsio "github.com/onflow/flow-go/utils/io" 22 ) 23 24 const subtrieLevel = 4 25 const subtrieCount = 1 << subtrieLevel // 16 26 27 func subtrieCountByLevel(level uint16) int { 28 return 1 << level 29 } 30 31 // StoreCheckpointV6SingleThread stores checkpoint file in v6 in a single threaded manner, 32 // useful when EN is executing block. 33 func StoreCheckpointV6SingleThread(tries []*trie.MTrie, outputDir string, outputFile string, logger zerolog.Logger) error { 34 return StoreCheckpointV6(tries, outputDir, outputFile, logger, 1) 35 } 36 37 // StoreCheckpointV6Concurrently stores checkpoint file in v6 in max workers, 38 // useful during state extraction 39 func StoreCheckpointV6Concurrently(tries []*trie.MTrie, outputDir string, outputFile string, logger zerolog.Logger) error { 40 return StoreCheckpointV6(tries, outputDir, outputFile, logger, 16) 41 } 42 43 // StoreCheckpointV6 stores checkpoint file into a main file and 17 file parts. 44 // the main file stores: 45 // - version 46 // - checksum of each part file (17 in total) 47 // - checksum of the main file itself 48 // the first 16 files parts contain the trie nodes below the subtrieLevel 49 // the last part file contains the top level trie nodes above the subtrieLevel and all the trie root nodes. 50 // 51 // nWorker specifies how many workers to encode subtrie concurrently, valid range [1,16] 52 func StoreCheckpointV6( 53 tries []*trie.MTrie, outputDir string, outputFile string, logger zerolog.Logger, nWorker uint) error { 54 err := storeCheckpointV6(tries, outputDir, outputFile, logger, nWorker) 55 if err != nil { 56 cleanupErr := deleteCheckpointFiles(outputDir, outputFile) 57 if cleanupErr != nil { 58 return fmt.Errorf("fail to cleanup temp file %s, after running into error: %w", cleanupErr, err) 59 } 60 return err 61 } 62 63 return nil 64 } 65 66 func storeCheckpointV6( 67 tries []*trie.MTrie, outputDir string, outputFile string, logger zerolog.Logger, nWorker uint) error { 68 if len(tries) == 0 { 69 logger.Info().Msg("no tries to be checkpointed") 70 return nil 71 } 72 73 first, last := tries[0], tries[len(tries)-1] 74 lg := logger.With(). 75 Int("version", 6). 76 Int("trie_count", len(tries)). 77 Str("checkpoint_file", path.Join(outputDir, outputFile)). 78 Logger() 79 80 lg.Info(). 81 Str("first_hash", first.RootHash().String()). 82 Uint64("first_reg_count", first.AllocatedRegCount()). 83 Str("first_reg_size", units.BytesSize(float64(first.AllocatedRegSize()))). 84 Str("last_hash", last.RootHash().String()). 85 Uint64("last_reg_count", last.AllocatedRegCount()). 86 Str("last_reg_size", units.BytesSize(float64(last.AllocatedRegSize()))). 87 Msg("storing checkpoint") 88 89 // make sure a checkpoint file with same name doesn't exist 90 // part file with same name doesn't exist either 91 matched, err := findCheckpointPartFiles(outputDir, outputFile) 92 if err != nil { 93 return fmt.Errorf("fail to check if checkpoint file already exist: %w", err) 94 } 95 96 // found checkpoint file with the same checkpoint number 97 if len(matched) != 0 { 98 return fmt.Errorf("checkpoint part file already exists: %v", matched) 99 } 100 101 subtrieRoots := createSubTrieRoots(tries) 102 103 subTrieRootIndices, subTriesNodeCount, subTrieChecksums, err := storeSubTrieConcurrently( 104 subtrieRoots, 105 estimateSubtrieNodeCount(last), // considering the last trie most likely have more registers than others 106 subTrieRootAndTopLevelTrieCount(tries), 107 outputDir, 108 outputFile, 109 lg, 110 nWorker, 111 ) 112 if err != nil { 113 return fmt.Errorf("could not store sub trie: %w", err) 114 } 115 116 lg.Info().Msgf("subtrie have been stored. sub trie node count: %v", subTriesNodeCount) 117 118 topTrieChecksum, err := storeTopLevelNodesAndTrieRoots( 119 tries, subTrieRootIndices, subTriesNodeCount, outputDir, outputFile, lg) 120 if err != nil { 121 return fmt.Errorf("could not store top level tries: %w", err) 122 } 123 124 err = storeCheckpointHeader(subTrieChecksums, topTrieChecksum, outputDir, outputFile, lg) 125 if err != nil { 126 return fmt.Errorf("could not store checkpoint header: %w", err) 127 } 128 129 lg.Info().Uint32("topsum", topTrieChecksum).Msg("checkpoint file has been successfully stored") 130 131 return nil 132 } 133 134 // 1. version 135 // 2. checksum of each part file (17 in total) 136 // 3. checksum of the main file itself 137 func storeCheckpointHeader( 138 subTrieChecksums []uint32, 139 topTrieChecksum uint32, 140 outputDir string, 141 outputFile string, 142 logger zerolog.Logger, 143 ) ( 144 errToReturn error, 145 ) { 146 // sanity check 147 if len(subTrieChecksums) != subtrieCountByLevel(subtrieLevel) { 148 return fmt.Errorf("expect subtrie level %v to have %v checksums, but got %v", 149 subtrieLevel, subtrieCountByLevel(subtrieLevel), len(subTrieChecksums)) 150 } 151 152 closable, err := createWriterForCheckpointHeader(outputDir, outputFile, logger) 153 if err != nil { 154 return fmt.Errorf("could not store checkpoint header: %w", err) 155 } 156 defer func() { 157 errToReturn = closeAndMergeError(closable, errToReturn) 158 }() 159 160 writer := NewCRC32Writer(closable) 161 162 // write version 163 _, err = writer.Write(encodeVersion(MagicBytesCheckpointHeader, VersionV6)) 164 if err != nil { 165 return fmt.Errorf("cannot write version into checkpoint header: %w", err) 166 } 167 168 // encode subtrieCount 169 _, err = writer.Write(encodeSubtrieCount(subtrieCount)) 170 if err != nil { 171 return fmt.Errorf("cannot write subtrie level into checkpoint header: %w", err) 172 } 173 174 // write subtrie checksums 175 for i, subtrieSum := range subTrieChecksums { 176 _, err = writer.Write(encodeCRC32Sum(subtrieSum)) 177 if err != nil { 178 return fmt.Errorf("cannot write %v-th subtriechecksum into checkpoint header: %w", i, err) 179 } 180 } 181 182 // write top level trie checksum 183 _, err = writer.Write(encodeCRC32Sum(topTrieChecksum)) 184 if err != nil { 185 return fmt.Errorf("cannot write top level trie checksum into checkpoint header: %w", err) 186 } 187 188 // write checksum to the end of the file 189 checksum := writer.Crc32() 190 _, err = writer.Write(encodeCRC32Sum(checksum)) 191 if err != nil { 192 return fmt.Errorf("cannot write CRC32 checksum to checkpoint header: %w", err) 193 } 194 return nil 195 } 196 197 var createWriterForCheckpointHeader = createClosableWriter 198 199 // 17th part file contains: 200 // 1. checkpoint version 201 // 2. subtrieNodeCount 202 // 3. top level nodes 203 // 4. trie roots 204 // 5. node count 205 // 6. trie count 206 // 7. checksum 207 func storeTopLevelNodesAndTrieRoots( 208 tries []*trie.MTrie, 209 subTrieRootIndices map[*node.Node]uint64, 210 subTriesNodeCount uint64, 211 outputDir string, 212 outputFile string, 213 logger zerolog.Logger, 214 ) ( 215 checksumOfTopTriePartFile uint32, 216 errToReturn error, 217 ) { 218 // the remaining nodes and data will be stored into the same file 219 closable, err := createWriterForTopTries(outputDir, outputFile, logger) 220 if err != nil { 221 return 0, fmt.Errorf("could not create writer for top tries: %w", err) 222 } 223 defer func() { 224 errToReturn = closeAndMergeError(closable, errToReturn) 225 }() 226 227 writer := NewCRC32Writer(closable) 228 229 // write version 230 _, err = writer.Write(encodeVersion(MagicBytesCheckpointToptrie, VersionV6)) 231 if err != nil { 232 return 0, fmt.Errorf("cannot write version into checkpoint header: %w", err) 233 } 234 235 // write subTriesNodeCount 236 _, err = writer.Write(encodeNodeCount(subTriesNodeCount)) 237 if err != nil { 238 return 0, fmt.Errorf("could not write subtrie node count: %w", err) 239 } 240 241 scratch := make([]byte, 1024*4) 242 243 // write top level nodes 244 topLevelNodeIndices, topLevelNodesCount, err := storeTopLevelNodes( 245 scratch, 246 tries, 247 subTrieRootIndices, 248 subTriesNodeCount+1, // the counter is 1 more than the node count, because the first item is nil 249 writer) 250 251 if err != nil { 252 return 0, fmt.Errorf("could not store top level nodes: %w", err) 253 } 254 255 logger.Info().Msgf("top level nodes have been stored. top level node count: %v", topLevelNodesCount) 256 257 // write tries 258 err = storeTries(scratch, tries, topLevelNodeIndices, writer) 259 if err != nil { 260 return 0, fmt.Errorf("could not store trie root nodes: %w", err) 261 } 262 263 // write checksum 264 checksum, err := storeTopLevelTrieFooter(topLevelNodesCount, uint16(len(tries)), writer) 265 if err != nil { 266 return 0, fmt.Errorf("could not store footer: %w", err) 267 } 268 269 return checksum, nil 270 } 271 272 func createSubTrieRoots(tries []*trie.MTrie) [subtrieCount][]*node.Node { 273 var subtrieRoots [subtrieCount][]*node.Node 274 for i := 0; i < len(subtrieRoots); i++ { 275 subtrieRoots[i] = make([]*node.Node, len(tries)) 276 } 277 278 for trieIndex, t := range tries { 279 // subtries is an array with subtrieCount trie nodes 280 // in breadth-first order at subtrieLevel of the trie `t` 281 subtries := getNodesAtLevel(t.RootNode(), subtrieLevel) 282 for subtrieIndex, subtrieRoot := range subtries { 283 subtrieRoots[subtrieIndex][trieIndex] = subtrieRoot 284 } 285 } 286 return subtrieRoots 287 } 288 289 // estimateSubtrieNodeCount estimate the average number of registers in each subtrie. 290 func estimateSubtrieNodeCount(trie *trie.MTrie) int { 291 estimatedTrieNodeCount := 2*int(trie.AllocatedRegCount()) - 1 292 return estimatedTrieNodeCount / subtrieCount 293 } 294 295 // subTrieRootAndTopLevelTrieCount return the total number of subtrie root nodes 296 // and top level trie nodes for given number of tries 297 // it is used for preallocating memory for the map that holds all unique nodes in 298 // all sub trie roots and top level trie nodoes. 299 // the top level trie nodes has nearly same number of nodes as subtrie node count at subtrieLevel 300 // that's it needs to * 2. 301 func subTrieRootAndTopLevelTrieCount(tries []*trie.MTrie) int { 302 return len(tries) * subtrieCount * 2 303 } 304 305 type resultStoringSubTrie struct { 306 Index int 307 Roots map[*node.Node]uint64 // node index for root nodes 308 NodeCount uint64 309 Checksum uint32 310 Err error 311 } 312 313 type jobStoreSubTrie struct { 314 Index int 315 Roots []*node.Node 316 Result chan<- *resultStoringSubTrie 317 } 318 319 func storeSubTrieConcurrently( 320 subtrieRoots [subtrieCount][]*node.Node, 321 estimatedSubtrieNodeCount int, // useful for preallocating memory for building unique node map when processing sub tries 322 subAndTopNodeCount int, // useful for preallocating memory for the node indices map to be returned 323 outputDir string, 324 outputFile string, 325 logger zerolog.Logger, 326 nWorker uint, 327 ) ( 328 map[*node.Node]uint64, // node indices 329 uint64, // node count 330 []uint32, //checksums 331 error, // any exception 332 ) { 333 logger.Info().Msgf("storing %v subtrie groups with average node count %v for each subtrie", subtrieCount, estimatedSubtrieNodeCount) 334 335 if nWorker == 0 || nWorker > subtrieCount { 336 return nil, 0, nil, fmt.Errorf("invalid nWorker %v, the valid range is [1,%v]", nWorker, subtrieCount) 337 } 338 339 jobs := make(chan jobStoreSubTrie, len(subtrieRoots)) 340 resultChs := make([]<-chan *resultStoringSubTrie, len(subtrieRoots)) 341 342 // push all jobs into the channel 343 for i, roots := range subtrieRoots { 344 resultCh := make(chan *resultStoringSubTrie) 345 resultChs[i] = resultCh 346 jobs <- jobStoreSubTrie{ 347 Index: i, 348 Roots: roots, 349 Result: resultCh, 350 } 351 } 352 close(jobs) 353 354 // start nWorker number of goroutine to take the job from the jobs channel concurrently 355 // and work on them, after finish, continue until the jobs channel is drained 356 for i := 0; i < int(nWorker); i++ { 357 go func() { 358 for job := range jobs { 359 roots, nodeCount, checksum, err := storeCheckpointSubTrie( 360 job.Index, job.Roots, estimatedSubtrieNodeCount, outputDir, outputFile, logger) 361 362 job.Result <- &resultStoringSubTrie{ 363 Index: job.Index, 364 Roots: roots, 365 NodeCount: nodeCount, 366 Checksum: checksum, 367 Err: err, 368 } 369 close(job.Result) 370 } 371 }() 372 } 373 374 results := make(map[*node.Node]uint64, subAndTopNodeCount) 375 results[nil] = 0 376 nodeCounter := uint64(0) 377 checksums := make([]uint32, 0, len(subtrieRoots)) 378 379 // reading job results in the same order as their indices 380 for _, resultCh := range resultChs { 381 result := <-resultCh 382 383 if result.Err != nil { 384 return nil, 0, nil, fmt.Errorf("fail to store %v-th subtrie, trie: %w", result.Index, result.Err) 385 } 386 387 for root, index := range result.Roots { 388 // nil is always 0 389 if root == nil { 390 results[root] = 0 391 } else { 392 // the original index is relative to the subtrie file itself. 393 // but we need a global index to be referenced by top level trie, 394 // therefore we need to add the nodeCounter 395 results[root] = index + nodeCounter 396 } 397 } 398 nodeCounter += result.NodeCount 399 checksums = append(checksums, result.Checksum) 400 } 401 402 return results, nodeCounter, checksums, nil 403 } 404 405 func createWriterForTopTries(dir string, file string, logger zerolog.Logger) (io.WriteCloser, error) { 406 _, topTriesFileName := filePathTopTries(dir, file) 407 408 return createClosableWriter(dir, topTriesFileName, logger) 409 } 410 411 func createWriterForSubtrie(dir string, file string, logger zerolog.Logger, index int) (io.WriteCloser, error) { 412 _, subTriesFileName, err := filePathSubTries(dir, file, index) 413 if err != nil { 414 return nil, err 415 } 416 417 return createClosableWriter(dir, subTriesFileName, logger) 418 } 419 420 func createClosableWriter(dir string, fileName string, logger zerolog.Logger) (io.WriteCloser, error) { 421 fullPath := path.Join(dir, fileName) 422 if utilsio.FileExists(fullPath) { 423 return nil, fmt.Errorf("checkpoint part file %v already exists", fullPath) 424 } 425 426 tmpFile, err := os.CreateTemp(dir, fmt.Sprintf("writing-%v-*", fileName)) 427 if err != nil { 428 return nil, fmt.Errorf("could not create temporary file for checkpoint toptries: %w", err) 429 } 430 431 writer := bufio.NewWriterSize(tmpFile, defaultBufioWriteSize) 432 return &SyncOnCloseRenameFile{ 433 logger: logger, 434 file: tmpFile, 435 targetName: fullPath, 436 Writer: writer, 437 }, nil 438 } 439 440 // storeCheckpointSubTrie traverse each root node, and store the subtrie nodes into 441 // the subtrie part file at index i 442 // subtrie file contains: 443 // 1. checkpoint version 444 // 2. nodes 445 // 3. node count 446 // 4. checksum 447 func storeCheckpointSubTrie( 448 i int, 449 roots []*node.Node, 450 estimatedSubtrieNodeCount int, // for estimate the amount of memory to be preallocated 451 outputDir string, 452 outputFile string, 453 logger zerolog.Logger, 454 ) ( 455 rootNodesOfAllSubtries map[*node.Node]uint64, // the stored position of each unique root node 456 totalSubtrieNodeCount uint64, 457 checksumOfSubtriePartfile uint32, 458 errToReturn error, 459 ) { 460 461 closable, err := createWriterForSubtrie(outputDir, outputFile, logger, i) 462 if err != nil { 463 return nil, 0, 0, fmt.Errorf("could not create writer for sub trie: %w", err) 464 } 465 466 defer func() { 467 errToReturn = closeAndMergeError(closable, errToReturn) 468 }() 469 470 // create a CRC32 writer, so that any bytes passed to the writer will 471 // be used to calculate CRC32 checksum 472 writer := NewCRC32Writer(closable) 473 474 // write version 475 _, err = writer.Write(encodeVersion(MagicBytesCheckpointSubtrie, VersionV6)) 476 if err != nil { 477 return nil, 0, 0, fmt.Errorf("cannot write version into checkpoint subtrie file: %w", err) 478 } 479 480 // subtrieRootNodes unique subtrie root nodes, the uint64 value is the index of each root node 481 // stored in the part file. 482 subtrieRootNodes := make(map[*node.Node]uint64, len(roots)) 483 484 // nodeCounter is counter for all unique nodes. 485 // It starts from 1, as 0 marks nil node. 486 nodeCounter := uint64(1) 487 488 logging := logProgress(fmt.Sprintf("storing %v-th sub trie roots", i), estimatedSubtrieNodeCount, logger) 489 490 // traversedSubtrieNodes contains all unique nodes of subtries of the same path and their index. 491 traversedSubtrieNodes := make(map[*node.Node]uint64, estimatedSubtrieNodeCount) 492 // index 0 is nil, it can be used in a node's left child or right child to indicate 493 // a node's left child or right child is nil 494 traversedSubtrieNodes[nil] = 0 495 496 scratch := make([]byte, 1024*4) 497 for _, root := range roots { 498 // Note: nodeCounter is to assign an global index to each node in the order of it being seralized 499 // into the checkpoint file. Therefore, it has to be reused when iterating each subtrie. 500 // storeUniqueNodes will add the unique visited node into traversedSubtrieNodes with key as the node 501 // itself, and value as n-th node being seralized in the checkpoint file. 502 nodeCounter, err = storeUniqueNodes(root, traversedSubtrieNodes, nodeCounter, scratch, writer, logging) 503 if err != nil { 504 return nil, 0, 0, fmt.Errorf("fail to store nodes in step 1 for subtrie root %v: %w", root.Hash(), err) 505 } 506 // Save subtrie root node index in topLevelNodes, 507 // so when traversing top level tries 508 // (from level 0 to subtrieLevel) using topLevelNodes, 509 // node iterator skips subtrie as visited nodes. 510 subtrieRootNodes[root] = traversedSubtrieNodes[root] 511 } 512 513 // -1 to account for 0 node meaning nil 514 totalNodeCount := nodeCounter - 1 515 516 // write total number of node as footer 517 checksum, err := storeSubtrieFooter(totalNodeCount, writer) 518 if err != nil { 519 return nil, 0, 0, fmt.Errorf("could not store subtrie footer %w", err) 520 } 521 522 return subtrieRootNodes, totalNodeCount, checksum, nil 523 } 524 525 func storeTopLevelNodes( 526 scratch []byte, 527 tries []*trie.MTrie, 528 subTrieRootIndices map[*node.Node]uint64, 529 initNodeCounter uint64, 530 writer io.Writer) ( 531 map[*node.Node]uint64, 532 uint64, 533 error) { 534 nodeCounter := initNodeCounter 535 var err error 536 for _, t := range tries { 537 root := t.RootNode() 538 if root == nil { 539 continue 540 } 541 // if we iterate through the root trie with an empty visited nodes map, then it will iterate through 542 // all nodes at all levels. In order to skip the nodes above subtrieLevel, since they have been seralized in step 1, 543 // we will need to pass in a visited nodes map that contains all the subtrie root nodes, which is the topLevelNodes. 544 // The topLevelNodes was built in step 1, when seralizing each subtrie root nodes. 545 nodeCounter, err = storeUniqueNodes(root, subTrieRootIndices, nodeCounter, scratch, writer, func(uint64) {}) 546 if err != nil { 547 return nil, 0, fmt.Errorf("fail to store nodes in step 2 for root trie %v: %w", root.Hash(), err) 548 } 549 } 550 551 topLevelNodesCount := nodeCounter - initNodeCounter 552 return subTrieRootIndices, topLevelNodesCount, nil 553 } 554 555 func storeTries( 556 scratch []byte, 557 tries []*trie.MTrie, 558 topLevelNodes map[*node.Node]uint64, 559 writer io.Writer) error { 560 for _, t := range tries { 561 rootNode := t.RootNode() 562 if !t.IsEmpty() && rootNode.Height() != ledger.NodeMaxHeight { 563 return fmt.Errorf("height of root node must be %d, but is %d", 564 ledger.NodeMaxHeight, rootNode.Height()) 565 } 566 567 // Get root node index 568 rootIndex, found := topLevelNodes[rootNode] 569 if !found { 570 rootHash := t.RootHash() 571 return fmt.Errorf("internal error: missing node with hash %s", hex.EncodeToString(rootHash[:])) 572 } 573 574 encTrie := flattener.EncodeTrie(t, rootIndex, scratch) 575 _, err := writer.Write(encTrie) 576 if err != nil { 577 return fmt.Errorf("cannot serialize trie: %w", err) 578 } 579 } 580 581 return nil 582 } 583 584 // deleteCheckpointFiles removes any checkpoint files with given checkpoint prefix in the outputDir. 585 func deleteCheckpointFiles(outputDir string, outputFile string) error { 586 pattern := filePathPattern(outputDir, outputFile) 587 filesToRemove, err := filepath.Glob(pattern) 588 if err != nil { 589 return fmt.Errorf("could not glob checkpoint files to delete with pattern %v: %w", 590 pattern, err, 591 ) 592 } 593 594 var merror *multierror.Error 595 for _, file := range filesToRemove { 596 err := os.Remove(file) 597 if err != nil { 598 merror = multierror.Append(merror, err) 599 } 600 } 601 602 return merror.ErrorOrNil() 603 } 604 605 func storeTopLevelTrieFooter(topLevelNodesCount uint64, rootTrieCount uint16, writer *Crc32Writer) (uint32, error) { 606 footer := encodeTopLevelNodesAndTriesFooter(topLevelNodesCount, rootTrieCount) 607 _, err := writer.Write(footer) 608 if err != nil { 609 return 0, fmt.Errorf("cannot write checkpoint footer: %w", err) 610 } 611 612 // write checksum to the end of the file 613 checksum := writer.Crc32() 614 _, err = writer.Write(encodeCRC32Sum(checksum)) 615 if err != nil { 616 return 0, fmt.Errorf("cannot write CRC32 checksum to top level part file: %w", err) 617 } 618 619 return checksum, nil 620 } 621 622 func storeSubtrieFooter(nodeCount uint64, writer *Crc32Writer) (uint32, error) { 623 footer := encodeNodeCount(nodeCount) 624 _, err := writer.Write(footer) 625 if err != nil { 626 return 0, fmt.Errorf("cannot write checkpoint subtrie footer: %w", err) 627 } 628 629 // write checksum to the end of the file 630 crc32Sum := writer.Crc32() 631 _, err = writer.Write(encodeCRC32Sum(crc32Sum)) 632 if err != nil { 633 return 0, fmt.Errorf("cannot write CRC32 checksum %v", err) 634 } 635 return crc32Sum, nil 636 } 637 638 func encodeTopLevelNodesAndTriesFooter(topLevelNodesCount uint64, rootTrieCount uint16) []byte { 639 footer := make([]byte, encNodeCountSize+encTrieCountSize) 640 binary.BigEndian.PutUint64(footer, topLevelNodesCount) 641 binary.BigEndian.PutUint16(footer[encNodeCountSize:], rootTrieCount) 642 return footer 643 } 644 645 func decodeTopLevelNodesAndTriesFooter(footer []byte) (uint64, uint16, error) { 646 const footerSize = encNodeCountSize + encTrieCountSize // footer doesn't include crc32 sum 647 if len(footer) != footerSize { 648 return 0, 0, fmt.Errorf("wrong footer size, expect %v, got %v", footerSize, len(footer)) 649 } 650 nodesCount := binary.BigEndian.Uint64(footer) 651 triesCount := binary.BigEndian.Uint16(footer[encNodeCountSize:]) 652 return nodesCount, triesCount, nil 653 } 654 655 func encodeNodeCount(nodeCount uint64) []byte { 656 buf := make([]byte, encNodeCountSize) 657 binary.BigEndian.PutUint64(buf, nodeCount) 658 return buf 659 } 660 661 func decodeNodeCount(encoded []byte) (uint64, error) { 662 if len(encoded) != encNodeCountSize { 663 return 0, fmt.Errorf("wrong subtrie node count size, expect %v, got %v", encNodeCountSize, len(encoded)) 664 } 665 return binary.BigEndian.Uint64(encoded), nil 666 } 667 668 func encodeCRC32Sum(checksum uint32) []byte { 669 buf := make([]byte, crc32SumSize) 670 binary.BigEndian.PutUint32(buf, checksum) 671 return buf 672 } 673 674 func decodeCRC32Sum(encoded []byte) (uint32, error) { 675 if len(encoded) != crc32SumSize { 676 return 0, fmt.Errorf("wrong crc32sum size, expect %v, got %v", crc32SumSize, len(encoded)) 677 } 678 return binary.BigEndian.Uint32(encoded), nil 679 } 680 681 func encodeVersion(magic uint16, version uint16) []byte { 682 // Write header: magic (2 bytes) + version (2 bytes) 683 header := make([]byte, encMagicSize+encVersionSize) 684 binary.BigEndian.PutUint16(header, magic) 685 binary.BigEndian.PutUint16(header[encMagicSize:], version) 686 return header 687 } 688 689 func decodeVersion(encoded []byte) (uint16, uint16, error) { 690 if len(encoded) != encMagicSize+encVersionSize { 691 return 0, 0, fmt.Errorf("wrong version size, expect %v, got %v", encMagicSize+encVersionSize, len(encoded)) 692 } 693 magicBytes := binary.BigEndian.Uint16(encoded) 694 version := binary.BigEndian.Uint16(encoded[encMagicSize:]) 695 return magicBytes, version, nil 696 } 697 698 func encodeSubtrieCount(level uint16) []byte { 699 bytes := make([]byte, encSubtrieCountSize) 700 binary.BigEndian.PutUint16(bytes, level) 701 return bytes 702 } 703 704 func decodeSubtrieCount(encoded []byte) (uint16, error) { 705 if len(encoded) != encSubtrieCountSize { 706 return 0, fmt.Errorf("wrong subtrie level size, expect %v, got %v", encSubtrieCountSize, len(encoded)) 707 } 708 return binary.BigEndian.Uint16(encoded), nil 709 } 710 711 // closeAndMergeError close the closable and merge the closeErr with the given err into a multierror 712 // Note: when using this function in a defer function, don't use as below: 713 // func XXX() ( 714 // 715 // err error, 716 // ) { 717 // def func() { 718 // // bad, because the definition of err might get overwritten 719 // err = closeAndMergeError(closable, err) 720 // }() 721 // 722 // Better to use as below: 723 // func XXX() ( 724 // 725 // errToReturn error, 726 // ) { 727 // def func() { 728 // // good, because the error to returned is only updated here, and guaranteed to be returned 729 // errToReturn = closeAndMergeError(closable, errToReturn) 730 // }() 731 func closeAndMergeError(closable io.Closer, err error) error { 732 var merr *multierror.Error 733 if err != nil { 734 merr = multierror.Append(merr, err) 735 } 736 737 closeError := closable.Close() 738 if closeError != nil { 739 merr = multierror.Append(merr, closeError) 740 } 741 742 return merr.ErrorOrNil() 743 } 744 745 // withFile opens the file at the given path, and calls the given function with the opened file. 746 // it handles closing the file and evicting the file from Linux page cache. 747 func withFile(logger zerolog.Logger, filepath string, f func(file *os.File) error) ( 748 errToReturn error, 749 ) { 750 751 file, err := os.Open(filepath) 752 if err != nil { 753 return fmt.Errorf("could not open file %v: %w", filepath, err) 754 } 755 defer func(file *os.File) { 756 evictErr := evictFileFromLinuxPageCache(file, false, logger) 757 if evictErr != nil { 758 logger.Warn().Msgf("failed to evict top trie file %s from Linux page cache: %s", filepath, evictErr) 759 // No need to return this error because it's possible to continue normal operations. 760 } 761 errToReturn = closeAndMergeError(file, errToReturn) 762 }(file) 763 764 return f(file) 765 }