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  }