github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/wal/checkpoint_v6_test.go (about)

     1  package wal
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/rand"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    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/onflow/flow-go/ledger"
    20  	"github.com/onflow/flow-go/ledger/common/hash"
    21  	"github.com/onflow/flow-go/ledger/common/testutils"
    22  	"github.com/onflow/flow-go/ledger/complete/mtrie/node"
    23  	"github.com/onflow/flow-go/ledger/complete/mtrie/trie"
    24  	"github.com/onflow/flow-go/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  	_, err := rand.Read(path[:])
    91  	if err != nil {
    92  		panic("randomness failed")
    93  	}
    94  	payload := testutils.RandomPayload(1, 100)
    95  	return path, *payload
    96  }
    97  
    98  func randNPathPayloads(n int) ([]ledger.Path, []ledger.Payload) {
    99  	paths := make([]ledger.Path, n)
   100  	payloads := make([]ledger.Payload, n)
   101  	for i := 0; i < n; i++ {
   102  		path, payload := randPathPayload()
   103  		paths[i] = path
   104  		payloads[i] = payload
   105  	}
   106  	return paths, payloads
   107  }
   108  
   109  func createMultipleRandomTries(t *testing.T) []*trie.MTrie {
   110  	tries := make([]*trie.MTrie, 0)
   111  	activeTrie := trie.NewEmptyMTrie()
   112  
   113  	var err error
   114  	// add tries with no shared paths
   115  	for i := 0; i < 100; i++ {
   116  		paths, payloads := randNPathPayloads(100)
   117  		activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, paths, payloads, false)
   118  		require.NoError(t, err, "update registers")
   119  		tries = append(tries, activeTrie)
   120  	}
   121  
   122  	// add trie with some shared path
   123  	sharedPaths, payloads1 := randNPathPayloads(100)
   124  	activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, sharedPaths, payloads1, false)
   125  	require.NoError(t, err, "update registers")
   126  	tries = append(tries, activeTrie)
   127  
   128  	_, payloads2 := randNPathPayloads(100)
   129  	activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, sharedPaths, payloads2, false)
   130  	require.NoError(t, err, "update registers")
   131  	tries = append(tries, activeTrie)
   132  
   133  	return tries
   134  }
   135  
   136  func createMultipleRandomTriesMini(t *testing.T) []*trie.MTrie {
   137  	tries := make([]*trie.MTrie, 0)
   138  	activeTrie := trie.NewEmptyMTrie()
   139  
   140  	var err error
   141  	// add tries with no shared paths
   142  	for i := 0; i < 5; i++ {
   143  		paths, payloads := randNPathPayloads(20)
   144  		activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, paths, payloads, false)
   145  		require.NoError(t, err, "update registers")
   146  		tries = append(tries, activeTrie)
   147  	}
   148  
   149  	// add trie with some shared path
   150  	sharedPaths, payloads1 := randNPathPayloads(10)
   151  	activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, sharedPaths, payloads1, false)
   152  	require.NoError(t, err, "update registers")
   153  	tries = append(tries, activeTrie)
   154  
   155  	_, payloads2 := randNPathPayloads(10)
   156  	activeTrie, _, err = trie.NewTrieWithUpdatedRegisters(activeTrie, sharedPaths, payloads2, false)
   157  	require.NoError(t, err, "update registers")
   158  	tries = append(tries, activeTrie)
   159  
   160  	return tries
   161  }
   162  
   163  func TestEncodeSubTrie(t *testing.T) {
   164  	file := "checkpoint"
   165  	logger := unittest.Logger()
   166  	tries := createMultipleRandomTries(t)
   167  	estimatedSubtrieNodeCount := estimateSubtrieNodeCount(tries[0])
   168  	subtrieRoots := createSubTrieRoots(tries)
   169  
   170  	for index, roots := range subtrieRoots {
   171  		unittest.RunWithTempDir(t, func(dir string) {
   172  			uniqueIndices, nodeCount, checksum, err := storeCheckpointSubTrie(
   173  				index, roots, estimatedSubtrieNodeCount, dir, file, logger)
   174  			require.NoError(t, err)
   175  
   176  			// subtrie roots might have duplciates, that why we group the them,
   177  			// and store each group in different part file in order to deduplicate.
   178  			// the returned uniqueIndices contains the index for each unique roots.
   179  			// in order to verify that, we build a uniqueRoots first, and verify
   180  			// if any unique root is missing from the uniqueIndices
   181  			uniqueRoots := make(map[*node.Node]struct{})
   182  			for i, root := range roots {
   183  				if root == nil {
   184  					fmt.Println(i, "-th subtrie root is nil")
   185  				}
   186  				_, ok := uniqueRoots[root]
   187  				if ok {
   188  					fmt.Println(i, "-th subtrie root is a duplicate")
   189  				}
   190  				uniqueRoots[root] = struct{}{}
   191  			}
   192  
   193  			// each root should be included in the uniqueIndices
   194  			for _, root := range roots {
   195  				_, ok := uniqueIndices[root]
   196  				require.True(t, ok, "each root should be included in the uniqueIndices")
   197  			}
   198  
   199  			if len(uniqueIndices) > 1 {
   200  				require.Len(t, uniqueIndices, len(uniqueRoots),
   201  					fmt.Sprintf("uniqueIndices should include all roots, uniqueIndices[nil] %v, roots[0] %v", uniqueIndices[nil], roots[0]))
   202  			}
   203  
   204  			logger.Info().Msgf("sub trie checkpoint stored, uniqueIndices: %v, node count: %v, checksum: %v",
   205  				uniqueIndices, nodeCount, checksum)
   206  
   207  			// all the nodes
   208  			nodes, err := readCheckpointSubTrie(dir, file, index, checksum, logger)
   209  			require.NoError(t, err)
   210  
   211  			for _, root := range roots {
   212  				if root == nil {
   213  					continue
   214  				}
   215  				index := uniqueIndices[root]
   216  				require.Equal(t, root.Hash(), nodes[index-1].Hash(), // -1 because readCheckpointSubTrie returns nodes[1:]
   217  					"readCheckpointSubTrie should return nodes where the root should be found "+
   218  						"by the index specified by the uniqueIndices returned by storeCheckpointSubTrie")
   219  			}
   220  		})
   221  	}
   222  }
   223  
   224  func randomNode() *node.Node {
   225  	var randomPath ledger.Path
   226  	_, err := rand.Read(randomPath[:])
   227  	if err != nil {
   228  		panic("randomness failed")
   229  	}
   230  
   231  	var randomHashValue hash.Hash
   232  	_, err = rand.Read(randomHashValue[:])
   233  	if err != nil {
   234  		panic("randomness failed")
   235  	}
   236  
   237  	return node.NewNode(256, nil, nil, randomPath, nil, randomHashValue)
   238  }
   239  func TestGetNodesByIndex(t *testing.T) {
   240  	n := 10
   241  	ns := make([]*node.Node, n)
   242  	for i := 0; i < n; i++ {
   243  		ns[i] = randomNode()
   244  	}
   245  	subtrieNodes := [][]*node.Node{
   246  		{ns[0], ns[1]},
   247  		{ns[2]},
   248  		{},
   249  		{},
   250  	}
   251  	topLevelNodes := []*node.Node{nil, ns[3]}
   252  	totalSubTrieNodeCount := computeTotalSubTrieNodeCount(subtrieNodes)
   253  
   254  	for i := uint64(1); i <= 4; i++ {
   255  		node, err := getNodeByIndex(subtrieNodes, totalSubTrieNodeCount, topLevelNodes, i)
   256  		require.NoError(t, err, "cannot get node by index", i)
   257  		require.Equal(t, ns[i-1], node, "got wrong node by index %v", i)
   258  	}
   259  }
   260  func TestWriteAndReadCheckpointV6EmptyTrie(t *testing.T) {
   261  	unittest.RunWithTempDir(t, func(dir string) {
   262  		tries := []*trie.MTrie{trie.NewEmptyMTrie()}
   263  		fileName := "checkpoint-empty-trie"
   264  		logger := unittest.Logger()
   265  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   266  		decoded, err := OpenAndReadCheckpointV6(dir, fileName, logger)
   267  		require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   268  		requireTriesEqual(t, tries, decoded)
   269  	})
   270  }
   271  
   272  func TestWriteAndReadCheckpointV6SimpleTrie(t *testing.T) {
   273  	unittest.RunWithTempDir(t, func(dir string) {
   274  		tries := createSimpleTrie(t)
   275  		fileName := "checkpoint"
   276  		logger := unittest.Logger()
   277  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   278  		decoded, err := OpenAndReadCheckpointV6(dir, fileName, logger)
   279  		require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   280  		requireTriesEqual(t, tries, decoded)
   281  	})
   282  }
   283  
   284  func TestWriteAndReadCheckpointV6MultipleTries(t *testing.T) {
   285  	unittest.RunWithTempDir(t, func(dir string) {
   286  		tries := createMultipleRandomTries(t)
   287  		fileName := "checkpoint-multi-file"
   288  		logger := unittest.Logger()
   289  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   290  		decoded, err := OpenAndReadCheckpointV6(dir, fileName, logger)
   291  		require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   292  		requireTriesEqual(t, tries, decoded)
   293  	})
   294  }
   295  
   296  // test running checkpointing twice will produce the same checkpoint file
   297  func TestCheckpointV6IsDeterminstic(t *testing.T) {
   298  	unittest.RunWithTempDir(t, func(dir string) {
   299  		tries := createMultipleRandomTries(t)
   300  		logger := unittest.Logger()
   301  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint1", logger), "fail to store checkpoint")
   302  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint2", logger), "fail to store checkpoint")
   303  		partFiles1 := filePaths(dir, "checkpoint1", subtrieLevel)
   304  		partFiles2 := filePaths(dir, "checkpoint2", subtrieLevel)
   305  		for i, partFile1 := range partFiles1 {
   306  			partFile2 := partFiles2[i]
   307  			require.NoError(t, compareFiles(
   308  				partFile1, partFile2),
   309  				"found difference in checkpoint files")
   310  
   311  		}
   312  	})
   313  }
   314  
   315  func TestWriteAndReadCheckpointV6LeafEmptyTrie(t *testing.T) {
   316  	unittest.RunWithTempDir(t, func(dir string) {
   317  		tries := []*trie.MTrie{trie.NewEmptyMTrie()}
   318  		fileName := "checkpoint-empty-trie"
   319  		logger := unittest.Logger()
   320  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   321  
   322  		bufSize := 10
   323  		leafNodesCh := make(chan *LeafNode, bufSize)
   324  		go func() {
   325  			err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger)
   326  			require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   327  		}()
   328  		for range leafNodesCh {
   329  			require.Fail(t, "should not return any nodes")
   330  		}
   331  	})
   332  }
   333  
   334  func TestWriteAndReadCheckpointV6LeafSimpleTrie(t *testing.T) {
   335  	unittest.RunWithTempDir(t, func(dir string) {
   336  		tries := createSimpleTrie(t)
   337  		fileName := "checkpoint"
   338  		logger := unittest.Logger()
   339  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   340  		bufSize := 1
   341  		leafNodesCh := make(chan *LeafNode, bufSize)
   342  		go func() {
   343  			err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger)
   344  			require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   345  		}()
   346  		resultPayloads := make([]*ledger.Payload, 0)
   347  		for leafNode := range leafNodesCh {
   348  			// avoid dummy payload from empty trie
   349  			if leafNode.Payload != nil {
   350  				resultPayloads = append(resultPayloads, leafNode.Payload)
   351  			}
   352  		}
   353  		require.EqualValues(t, tries[1].AllPayloads(), resultPayloads)
   354  	})
   355  }
   356  
   357  func TestWriteAndReadCheckpointV6LeafMultipleTries(t *testing.T) {
   358  	unittest.RunWithTempDir(t, func(dir string) {
   359  		fileName := "checkpoint-multi-leaf-file"
   360  		tries := createMultipleRandomTriesMini(t)
   361  		logger := unittest.Logger()
   362  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   363  		bufSize := 5
   364  		leafNodesCh := make(chan *LeafNode, bufSize)
   365  		go func() {
   366  			err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger)
   367  			require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   368  		}()
   369  		resultPayloads := make([]ledger.Payload, 0)
   370  		for leafNode := range leafNodesCh {
   371  			resultPayloads = append(resultPayloads, *leafNode.Payload)
   372  		}
   373  		require.NotEmpty(t, resultPayloads)
   374  	})
   375  }
   376  
   377  // compareFiles takes two files' full path, and read them bytes by bytes and compare if
   378  // the two files are identical
   379  // it returns nil if identical
   380  // it returns error if there is difference
   381  func compareFiles(file1, file2 string) error {
   382  	closable1, err := os.Open(file1)
   383  	if err != nil {
   384  		return fmt.Errorf("could not open file 1 %v: %w", closable1, err)
   385  	}
   386  	defer func(f *os.File) {
   387  		f.Close()
   388  	}(closable1)
   389  
   390  	closable2, err := os.Open(file1)
   391  	if err != nil {
   392  		return fmt.Errorf("could not open file 2 %v: %w", closable2, err)
   393  	}
   394  	defer func(f *os.File) {
   395  		f.Close()
   396  	}(closable2)
   397  
   398  	reader1 := bufio.NewReaderSize(closable1, defaultBufioReadSize)
   399  	reader2 := bufio.NewReaderSize(closable2, defaultBufioReadSize)
   400  
   401  	buf1 := make([]byte, defaultBufioReadSize)
   402  	buf2 := make([]byte, defaultBufioReadSize)
   403  	for {
   404  		_, err1 := reader1.Read(buf1)
   405  		_, err2 := reader2.Read(buf2)
   406  		if errors.Is(err1, io.EOF) && errors.Is(err2, io.EOF) {
   407  			break
   408  		}
   409  
   410  		if err1 != nil {
   411  			return err1
   412  		}
   413  		if err2 != nil {
   414  			return err2
   415  		}
   416  
   417  		if !bytes.Equal(buf1, buf2) {
   418  			return fmt.Errorf("bytes are different: %x, %x", buf1, buf2)
   419  		}
   420  	}
   421  
   422  	return nil
   423  }
   424  
   425  func storeCheckpointV5(tries []*trie.MTrie, dir string, fileName string, logger zerolog.Logger) error {
   426  	return StoreCheckpointV5(dir, fileName, logger, tries...)
   427  }
   428  
   429  func TestWriteAndReadCheckpointV5(t *testing.T) {
   430  	unittest.RunWithTempDir(t, func(dir string) {
   431  		tries := createMultipleRandomTries(t)
   432  		fileName := "checkpoint1"
   433  		logger := unittest.Logger()
   434  
   435  		require.NoErrorf(t, storeCheckpointV5(tries, dir, fileName, logger), "fail to store checkpoint")
   436  		decoded, err := LoadCheckpoint(filepath.Join(dir, fileName), logger)
   437  		require.NoErrorf(t, err, "fail to load checkpoint")
   438  		requireTriesEqual(t, tries, decoded)
   439  	})
   440  }
   441  
   442  // test that converting a v6 back to v5 would produce the same v5 checkpoint as
   443  // producing directly to v5
   444  func TestWriteAndReadCheckpointV6ThenBackToV5(t *testing.T) {
   445  	unittest.RunWithTempDir(t, func(dir string) {
   446  		tries := createMultipleRandomTries(t)
   447  		logger := unittest.Logger()
   448  
   449  		// store tries into v6 then read back, then store into v5
   450  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint-v6", logger), "fail to store checkpoint")
   451  		decoded, err := OpenAndReadCheckpointV6(dir, "checkpoint-v6", logger)
   452  		require.NoErrorf(t, err, "fail to read checkpoint %v/checkpoint-v6", dir)
   453  		require.NoErrorf(t, storeCheckpointV5(decoded, dir, "checkpoint-v6-v5", logger), "fail to store checkpoint")
   454  
   455  		// store tries directly into v5 checkpoint
   456  		require.NoErrorf(t, storeCheckpointV5(tries, dir, "checkpoint-v5", logger), "fail to store checkpoint")
   457  
   458  		// compare the two v5 checkpoint files should be identical
   459  		require.NoError(t, compareFiles(
   460  			path.Join(dir, "checkpoint-v5"),
   461  			path.Join(dir, "checkpoint-v6-v5")),
   462  			"found difference in checkpoint files")
   463  	})
   464  }
   465  
   466  func TestCleanupOnErrorIfNotExist(t *testing.T) {
   467  	t.Run("works if temp files not exist", func(t *testing.T) {
   468  		require.NoError(t, deleteCheckpointFiles("not-exist", "checkpoint-v6"))
   469  	})
   470  
   471  	// if it can clean up all files after successful storing, then it can
   472  	// clean up if failed in middle.
   473  	t.Run("clean up after finish storing files", func(t *testing.T) {
   474  		unittest.RunWithTempDir(t, func(dir string) {
   475  			tries := createMultipleRandomTries(t)
   476  			logger := unittest.Logger()
   477  
   478  			// store tries into v6 then read back, then store into v5
   479  			require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint-v6", logger), "fail to store checkpoint")
   480  			require.NoError(t, deleteCheckpointFiles(dir, "checkpoint-v6"))
   481  
   482  			// verify all files are removed
   483  			files := filePaths(dir, "checkpoint-v6", subtrieLevel)
   484  			for _, file := range files {
   485  				_, err := os.Stat(file)
   486  				require.True(t, os.IsNotExist(err), err)
   487  			}
   488  		})
   489  	})
   490  }
   491  
   492  // verify that if a part file is missing then os.ErrNotExist should return
   493  func TestAllPartFileExist(t *testing.T) {
   494  	unittest.RunWithTempDir(t, func(dir string) {
   495  		for i := 0; i < 17; i++ {
   496  			tries := createSimpleTrie(t)
   497  			fileName := fmt.Sprintf("checkpoint_missing_part_file_%v", i)
   498  			var fileToDelete string
   499  			var err error
   500  			if i == 16 {
   501  				fileToDelete, _ = filePathTopTries(dir, fileName)
   502  			} else {
   503  				fileToDelete, _, err = filePathSubTries(dir, fileName, i)
   504  			}
   505  			require.NoErrorf(t, err, "fail to find sub trie file path")
   506  
   507  			logger := unittest.Logger()
   508  			require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   509  
   510  			// delete i-th part file, then the error should mention i-th file missing
   511  			err = os.Remove(fileToDelete)
   512  			require.NoError(t, err, "fail to remove part file")
   513  
   514  			_, err = OpenAndReadCheckpointV6(dir, fileName, logger)
   515  			require.ErrorIs(t, err, os.ErrNotExist, "wrong error type returned")
   516  		}
   517  	})
   518  }
   519  
   520  // verify that if a part file is missing then os.ErrNotExist should return
   521  func TestAllPartFileExistLeafReader(t *testing.T) {
   522  	unittest.RunWithTempDir(t, func(dir string) {
   523  		for i := 0; i < 17; i++ {
   524  			tries := createSimpleTrie(t)
   525  			fileName := fmt.Sprintf("checkpoint_missing_part_file_%v", i)
   526  			var fileToDelete string
   527  			var err error
   528  			if i == 16 {
   529  				fileToDelete, _ = filePathTopTries(dir, fileName)
   530  			} else {
   531  				fileToDelete, _, err = filePathSubTries(dir, fileName, i)
   532  			}
   533  			require.NoErrorf(t, err, "fail to find sub trie file path")
   534  
   535  			logger := unittest.Logger()
   536  			require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   537  
   538  			// delete i-th part file, then the error should mention i-th file missing
   539  			err = os.Remove(fileToDelete)
   540  			require.NoError(t, err, "fail to remove part file")
   541  
   542  			bufSize := 10
   543  			leafNodesCh := make(chan *LeafNode, bufSize)
   544  			err = OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger)
   545  			require.ErrorIs(t, err, os.ErrNotExist, "wrong error type returned")
   546  		}
   547  	})
   548  }
   549  
   550  // verify that can't store the same checkpoint file twice, because a checkpoint already exists
   551  func TestCannotStoreTwice(t *testing.T) {
   552  	unittest.RunWithTempDir(t, func(dir string) {
   553  		tries := createSimpleTrie(t)
   554  		fileName := "checkpoint"
   555  		logger := unittest.Logger()
   556  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   557  		// checkpoint already exist, can't store again
   558  		require.Error(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger))
   559  	})
   560  }
   561  
   562  func filePaths(dir string, fileName string, subtrieLevel uint16) []string {
   563  	paths := make([]string, 0)
   564  
   565  	paths = append(paths, filePathCheckpointHeader(dir, fileName))
   566  
   567  	subtrieCount := subtrieCountByLevel(subtrieLevel)
   568  	for i := 0; i < subtrieCount; i++ {
   569  		partFile := partFileName(fileName, i)
   570  		paths = append(paths, path.Join(dir, partFile))
   571  	}
   572  
   573  	p, _ := filePathTopTries(dir, fileName)
   574  	paths = append(paths, p)
   575  	return paths
   576  }
   577  
   578  func TestCopyCheckpointFileV6(t *testing.T) {
   579  	unittest.RunWithTempDir(t, func(dir string) {
   580  		tries := createSimpleTrie(t)
   581  		fileName := "checkpoint"
   582  		logger := unittest.Logger()
   583  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   584  		to := filepath.Join(dir, "newfolder")
   585  		newPaths, err := CopyCheckpointFile(fileName, dir, to)
   586  		require.NoError(t, err)
   587  		log.Info().Msgf("copied to :%v", newPaths)
   588  		decoded, err := OpenAndReadCheckpointV6(to, fileName, logger)
   589  		require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName)
   590  		requireTriesEqual(t, tries, decoded)
   591  	})
   592  }
   593  
   594  func TestReadCheckpointRootHash(t *testing.T) {
   595  	unittest.RunWithTempDir(t, func(dir string) {
   596  		tries := createSimpleTrie(t)
   597  		fileName := "checkpoint"
   598  		logger := unittest.Logger()
   599  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   600  
   601  		trieRoots, err := ReadTriesRootHash(logger, dir, fileName)
   602  		require.NoError(t, err)
   603  		for i, root := range trieRoots {
   604  			expectedHash := tries[i].RootHash()
   605  			require.Equal(t, expectedHash, root)
   606  		}
   607  		require.Equal(t, len(tries), len(trieRoots))
   608  	})
   609  }
   610  
   611  func TestReadCheckpointRootHashValidateChecksum(t *testing.T) {
   612  	unittest.RunWithTempDir(t, func(dir string) {
   613  		tries := createSimpleTrie(t)
   614  		fileName := "checkpoint"
   615  		logger := unittest.Logger()
   616  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   617  
   618  		// add a wrong checksum to top trie file
   619  		topTrieFilePath, _ := filePathTopTries(dir, fileName)
   620  		file, err := os.OpenFile(topTrieFilePath, os.O_RDWR, 0644)
   621  		require.NoError(t, err)
   622  
   623  		fileInfo, err := file.Stat()
   624  		require.NoError(t, err)
   625  		fileSize := fileInfo.Size()
   626  
   627  		invalidSum := encodeCRC32Sum(10)
   628  		_, err = file.WriteAt(invalidSum, fileSize-crc32SumSize)
   629  		require.NoError(t, err)
   630  		require.NoError(t, file.Close())
   631  
   632  		// ReadTriesRootHash will first validate the checksum and detect the error
   633  		_, err = ReadTriesRootHash(logger, dir, fileName)
   634  		require.Error(t, err)
   635  	})
   636  }
   637  
   638  func TestReadCheckpointRootHashMulti(t *testing.T) {
   639  	unittest.RunWithTempDir(t, func(dir string) {
   640  		tries := createMultipleRandomTries(t)
   641  		fileName := "checkpoint"
   642  		logger := unittest.Logger()
   643  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   644  
   645  		trieRoots, err := ReadTriesRootHash(logger, dir, fileName)
   646  		require.NoError(t, err)
   647  		for i, root := range trieRoots {
   648  			expectedHash := tries[i].RootHash()
   649  			require.Equal(t, expectedHash, root)
   650  		}
   651  		require.Equal(t, len(tries), len(trieRoots))
   652  	})
   653  }
   654  
   655  func TestCheckpointHasRootHash(t *testing.T) {
   656  	unittest.RunWithTempDir(t, func(dir string) {
   657  		tries := createMultipleRandomTries(t)
   658  		fileName := "checkpoint"
   659  		logger := unittest.Logger()
   660  		require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, logger), "fail to store checkpoint")
   661  
   662  		trieRoots, err := ReadTriesRootHash(logger, dir, fileName)
   663  		require.NoError(t, err)
   664  		for _, root := range trieRoots {
   665  			require.NoError(t, CheckpointHasRootHash(logger, dir, fileName, root))
   666  		}
   667  
   668  		nonExist := ledger.RootHash(unittest.StateCommitmentFixture())
   669  		require.Error(t, CheckpointHasRootHash(logger, dir, fileName, nonExist))
   670  	})
   671  }