github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/pebble/bootstrap_test.go (about)

     1  package pebble
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  	"testing"
    11  
    12  	"github.com/cockroachdb/pebble"
    13  	"github.com/rs/zerolog"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/onflow/flow-go/ledger"
    17  	"github.com/onflow/flow-go/ledger/common/convert"
    18  	"github.com/onflow/flow-go/ledger/common/testutils"
    19  	"github.com/onflow/flow-go/ledger/complete/mtrie/trie"
    20  	"github.com/onflow/flow-go/ledger/complete/wal"
    21  	"github.com/onflow/flow-go/model/flow"
    22  	"github.com/onflow/flow-go/utils/unittest"
    23  )
    24  
    25  const defaultRegisterValue = byte('v')
    26  
    27  func TestRegisterBootstrap_NewBootstrap(t *testing.T) {
    28  	t.Parallel()
    29  	unittest.RunWithTempDir(t, func(dir string) {
    30  		rootHeight := uint64(1)
    31  		rootHash := ledger.RootHash(unittest.StateCommitmentFixture())
    32  		log := zerolog.New(io.Discard)
    33  		p, err := OpenRegisterPebbleDB(dir)
    34  		require.NoError(t, err)
    35  		// set heights
    36  		require.NoError(t, initHeights(p, rootHeight))
    37  		// errors if FirstHeight or LastHeight are populated
    38  		_, err = NewRegisterBootstrap(p, dir, rootHeight, rootHash, log)
    39  		require.ErrorIs(t, err, ErrAlreadyBootstrapped)
    40  	})
    41  }
    42  
    43  func TestRegisterBootstrap_IndexCheckpointFile_Happy(t *testing.T) {
    44  	t.Parallel()
    45  	log := zerolog.New(io.Discard)
    46  	rootHeight := uint64(10000)
    47  	unittest.RunWithTempDir(t, func(dir string) {
    48  		tries, registerIDs := simpleTrieWithValidRegisterIDs(t)
    49  		rootHash := tries[0].RootHash()
    50  		fileName := "simple-checkpoint"
    51  		require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, fileName, log), "fail to store checkpoint")
    52  		checkpointFile := path.Join(dir, fileName)
    53  		pb, dbDir := createPebbleForTest(t)
    54  
    55  		bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log)
    56  		require.NoError(t, err)
    57  		err = bootstrap.IndexCheckpointFile(context.Background(), workerCount)
    58  		require.NoError(t, err)
    59  
    60  		// create registers instance and check values
    61  		reg, err := NewRegisters(pb)
    62  		require.NoError(t, err)
    63  
    64  		require.Equal(t, reg.LatestHeight(), rootHeight)
    65  		require.Equal(t, reg.FirstHeight(), rootHeight)
    66  
    67  		for _, register := range registerIDs {
    68  			val, err := reg.Get(*register, rootHeight)
    69  			require.NoError(t, err)
    70  			require.Equal(t, val, []byte{defaultRegisterValue})
    71  		}
    72  
    73  		require.NoError(t, pb.Close())
    74  		require.NoError(t, os.RemoveAll(dbDir))
    75  	})
    76  }
    77  
    78  func TestRegisterBootstrap_IndexCheckpointFile_Empty(t *testing.T) {
    79  	t.Parallel()
    80  	log := zerolog.New(io.Discard)
    81  	rootHeight := uint64(10000)
    82  	unittest.RunWithTempDir(t, func(dir string) {
    83  		tries := []*trie.MTrie{trie.NewEmptyMTrie()}
    84  		rootHash := tries[0].RootHash()
    85  		fileName := "empty-checkpoint"
    86  		require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, fileName, log), "fail to store checkpoint")
    87  		checkpointFile := path.Join(dir, fileName)
    88  		pb, dbDir := createPebbleForTest(t)
    89  
    90  		bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log)
    91  		require.NoError(t, err)
    92  		err = bootstrap.IndexCheckpointFile(context.Background(), workerCount)
    93  		require.NoError(t, err)
    94  
    95  		// create registers instance and check values
    96  		reg, err := NewRegisters(pb)
    97  		require.NoError(t, err)
    98  
    99  		require.Equal(t, reg.LatestHeight(), rootHeight)
   100  		require.Equal(t, reg.FirstHeight(), rootHeight)
   101  
   102  		require.NoError(t, pb.Close())
   103  		require.NoError(t, os.RemoveAll(dbDir))
   104  	})
   105  }
   106  
   107  func TestRegisterBootstrap_IndexCheckpointFile_FormatIssue(t *testing.T) {
   108  	t.Parallel()
   109  	pa1 := testutils.PathByUint8(0)
   110  	pa2 := testutils.PathByUint8(1)
   111  	rootHeight := uint64(666)
   112  	pl1 := testutils.LightPayload8('A', 'A')
   113  	pl2 := testutils.LightPayload('B', 'B')
   114  	paths := []ledger.Path{pa1, pa2}
   115  	payloads := []ledger.Payload{*pl1, *pl2}
   116  	emptyTrie := trie.NewEmptyMTrie()
   117  	trieWithInvalidEntry, _, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   118  	require.NoError(t, err)
   119  	rootHash := trieWithInvalidEntry.RootHash()
   120  	log := zerolog.New(io.Discard)
   121  
   122  	unittest.RunWithTempDir(t, func(dir string) {
   123  		fileName := "invalid-checkpoint"
   124  		require.NoErrorf(t, wal.StoreCheckpointV6Concurrently([]*trie.MTrie{trieWithInvalidEntry}, dir, fileName, log),
   125  			"fail to store checkpoint")
   126  		checkpointFile := path.Join(dir, fileName)
   127  		pb, dbDir := createPebbleForTest(t)
   128  
   129  		bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log)
   130  		require.NoError(t, err)
   131  		err = bootstrap.IndexCheckpointFile(context.Background(), workerCount)
   132  		require.ErrorContains(t, err, "unexpected ledger key format")
   133  		require.NoError(t, pb.Close())
   134  		require.NoError(t, os.RemoveAll(dbDir))
   135  	})
   136  
   137  }
   138  
   139  func TestRegisterBootstrap_IndexCheckpointFile_CorruptedCheckpointFile(t *testing.T) {
   140  	t.Parallel()
   141  	rootHeight := uint64(666)
   142  	log := zerolog.New(io.Discard)
   143  	unittest.RunWithTempDir(t, func(dir string) {
   144  		tries, _ := largeTrieWithValidRegisterIDs(t)
   145  		rootHash := tries[0].RootHash()
   146  		checkpointFileName := "large-checkpoint-incomplete"
   147  		require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, checkpointFileName, log), "fail to store checkpoint")
   148  		// delete 2nd part of the file (2nd subtrie)
   149  		fileToDelete := path.Join(dir, fmt.Sprintf("%v.%03d", checkpointFileName, 2))
   150  		err := os.RemoveAll(fileToDelete)
   151  		require.NoError(t, err)
   152  		pb, dbDir := createPebbleForTest(t)
   153  		bootstrap, err := NewRegisterBootstrap(pb, checkpointFileName, rootHeight, rootHash, log)
   154  		require.NoError(t, err)
   155  		err = bootstrap.IndexCheckpointFile(context.Background(), workerCount)
   156  		require.ErrorIs(t, err, os.ErrNotExist)
   157  		require.NoError(t, os.RemoveAll(dbDir))
   158  	})
   159  }
   160  
   161  func TestRegisterBootstrap_IndexCheckpointFile_MultipleBatch(t *testing.T) {
   162  	t.Parallel()
   163  	log := zerolog.New(io.Discard)
   164  	rootHeight := uint64(10000)
   165  	unittest.RunWithTempDir(t, func(dir string) {
   166  		tries, registerIDs := largeTrieWithValidRegisterIDs(t)
   167  		rootHash := tries[0].RootHash()
   168  		fileName := "large-checkpoint"
   169  		require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, fileName, log), "fail to store checkpoint")
   170  		checkpointFile := path.Join(dir, fileName)
   171  		pb, dbDir := createPebbleForTest(t)
   172  		bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log)
   173  		require.NoError(t, err)
   174  		err = bootstrap.IndexCheckpointFile(context.Background(), workerCount)
   175  		require.NoError(t, err)
   176  
   177  		// create registers instance and check values
   178  		reg, err := NewRegisters(pb)
   179  		require.NoError(t, err)
   180  
   181  		require.Equal(t, reg.LatestHeight(), rootHeight)
   182  		require.Equal(t, reg.FirstHeight(), rootHeight)
   183  
   184  		for _, register := range registerIDs {
   185  			val, err := reg.Get(*register, rootHeight)
   186  			require.NoError(t, err)
   187  			require.Equal(t, val, []byte{defaultRegisterValue})
   188  		}
   189  
   190  		require.NoError(t, pb.Close())
   191  		require.NoError(t, os.RemoveAll(dbDir))
   192  	})
   193  
   194  }
   195  
   196  func simpleTrieWithValidRegisterIDs(t *testing.T) ([]*trie.MTrie, []*flow.RegisterID) {
   197  	return trieWithValidRegisterIDs(t, 2)
   198  }
   199  
   200  const workerCount = 10
   201  
   202  func largeTrieWithValidRegisterIDs(t *testing.T) ([]*trie.MTrie, []*flow.RegisterID) {
   203  	// large enough trie so every worker should have something to index
   204  	largeTrieSize := 2 * pebbleBootstrapRegisterBatchLen * workerCount
   205  	return trieWithValidRegisterIDs(t, uint16(largeTrieSize))
   206  }
   207  
   208  func trieWithValidRegisterIDs(t *testing.T, n uint16) ([]*trie.MTrie, []*flow.RegisterID) {
   209  	emptyTrie := trie.NewEmptyMTrie()
   210  	resultRegisterIDs := make([]*flow.RegisterID, 0, n)
   211  	paths := randomRegisterPaths(n)
   212  	payloads := randomRegisterPayloads(n)
   213  	for _, payload := range payloads {
   214  		key, err := payload.Key()
   215  		require.NoError(t, err)
   216  		regID, err := convert.LedgerKeyToRegisterID(key)
   217  		require.NoError(t, err)
   218  		resultRegisterIDs = append(resultRegisterIDs, &regID)
   219  	}
   220  	populatedTrie, depth, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   221  	// make sure it has at least 1 leaf node
   222  	require.GreaterOrEqual(t, depth, uint16(1))
   223  	require.NoError(t, err)
   224  	resultTries := []*trie.MTrie{emptyTrie, populatedTrie}
   225  	return resultTries, resultRegisterIDs
   226  }
   227  
   228  func randomRegisterPayloads(n uint16) []ledger.Payload {
   229  	p := make([]ledger.Payload, 0, n)
   230  	for i := uint16(0); i < n; i++ {
   231  		o := make([]byte, 0, 8)
   232  		o = binary.BigEndian.AppendUint16(o, n)
   233  		k := ledger.Key{KeyParts: []ledger.KeyPart{
   234  			{Type: ledger.KeyPartOwner, Value: o},
   235  			{Type: ledger.KeyPartKey, Value: o},
   236  		}}
   237  		// values are always 'v' for ease of testing/checking
   238  		v := ledger.Value{defaultRegisterValue}
   239  		pl := ledger.NewPayload(k, v)
   240  		p = append(p, *pl)
   241  	}
   242  	return p
   243  }
   244  
   245  func randomRegisterPaths(n uint16) []ledger.Path {
   246  	p := make([]ledger.Path, 0, n)
   247  	for i := uint16(0); i < n; i++ {
   248  		p = append(p, testutils.PathByUint16(i))
   249  	}
   250  	return p
   251  }
   252  
   253  func createPebbleForTest(t *testing.T) (*pebble.DB, string) {
   254  	dbDir := unittest.TempPebblePath(t)
   255  	pb, err := OpenRegisterPebbleDB(dbDir)
   256  	require.NoError(t, err)
   257  	return pb, dbDir
   258  }