package storage import ( "flag" "fmt" "os" "path" "path/filepath" "strings" "syscall" "testing" "github.com/OffchainLabs/prysm/v7/beacon-chain/db/filesystem" "github.com/OffchainLabs/prysm/v7/cmd" das "github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/das/flags" "github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/testing/assert" "github.com/OffchainLabs/prysm/v7/testing/require" "github.com/urfave/cli/v2" ) func TestBlobStoragePath_NoFlagSpecified(t *testing.T) { app := cli.App{} set := flag.NewFlagSet("test", 0) set.String(cmd.DataDirFlag.Name, cmd.DataDirFlag.Value, cmd.DataDirFlag.Usage) cliCtx := cli.NewContext(&app, set, nil) storagePath := blobStoragePath(cliCtx) assert.Equal(t, cmd.DefaultDataDir()+"/blobs", storagePath) } func TestBlobStoragePath_FlagSpecified(t *testing.T) { app := cli.App{} set := flag.NewFlagSet("test", 0) set.String(BlobStoragePathFlag.Name, "/blah/blah", BlobStoragePathFlag.Usage) cliCtx := cli.NewContext(&app, set, nil) storagePath := blobStoragePath(cliCtx) assert.Equal(t, "/blah/blah", storagePath) } func TestConfigureBlobRetentionEpoch(t *testing.T) { params.SetupTestConfigCleanup(t) specMinEpochs := params.BeaconConfig().MinEpochsForBlobsSidecarsRequest app := cli.App{} set := flag.NewFlagSet("test", 0) cliCtx := cli.NewContext(&app, set, nil) // Test case: Spec default. epochs, err := blobRetentionEpoch(cliCtx) require.NoError(t, err) require.Equal(t, specMinEpochs, epochs) // manually define the flag in the set, so the following code can use set.Set set.Uint64(das.BlobRetentionEpochFlag.Name, 0, "") // Test case: Input epoch is greater than or equal to spec value. expectedChange := specMinEpochs + 1 require.NoError(t, set.Set(das.BlobRetentionEpochFlag.Name, fmt.Sprintf("%d", expectedChange))) epochs, err = blobRetentionEpoch(cliCtx) require.NoError(t, err) require.Equal(t, primitives.Epoch(expectedChange), epochs) // Test case: Input epoch is less than spec value. expectedChange = specMinEpochs - 1 require.NoError(t, set.Set(das.BlobRetentionEpochFlag.Name, fmt.Sprintf("%d", expectedChange))) _, err = blobRetentionEpoch(cliCtx) require.ErrorIs(t, err, errInvalidBlobRetentionEpochs) } func TestConfigureDataColumnRetentionEpoch(t *testing.T) { specValue := params.BeaconConfig().MinEpochsForDataColumnSidecarsRequest app := cli.App{} set := flag.NewFlagSet("test", 0) cliCtx := cli.NewContext(&app, set, nil) // Test case: Specification value expected := specValue actual, err := dataColumnRetentionEpoch(cliCtx) require.NoError(t, err) require.Equal(t, expected, actual) // Manually define the flag in the set, so the following code can use set.Set set.Uint64(das.BlobRetentionEpochFlag.Name, 0, "") // Test case: Input epoch is greater than or equal to specification value. expected = specValue + 1 err = set.Set(das.BlobRetentionEpochFlag.Name, fmt.Sprintf("%d", expected)) require.NoError(t, err) actual, err = dataColumnRetentionEpoch(cliCtx) require.NoError(t, err) require.Equal(t, primitives.Epoch(expected), actual) // Test case: Input epoch is less than specification value. expected = specValue - 1 err = set.Set(das.BlobRetentionEpochFlag.Name, fmt.Sprintf("%d", expected)) require.NoError(t, err) actual, err = dataColumnRetentionEpoch(cliCtx) require.ErrorIs(t, err, errInvalidBlobRetentionEpochs) require.Equal(t, specValue, actual) } func TestDataColumnStoragePath_FlagSpecified(t *testing.T) { app := cli.App{} set := flag.NewFlagSet("test", 0) set.String(DataColumnStoragePathFlag.Name, "/blah/blah", DataColumnStoragePathFlag.Usage) cliCtx := cli.NewContext(&app, set, nil) storagePath := dataColumnStoragePath(cliCtx) assert.Equal(t, "/blah/blah", storagePath) } type mockStringFlagGetter struct { v string } func (m mockStringFlagGetter) String(name string) string { return m.v } func TestDetectLayout(t *testing.T) { fakeRoot := "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" require.Equal(t, true, filesystem.IsBlockRootDir(fakeRoot)) withFlatRoot := func(t *testing.T, dir string) { require.NoError(t, os.MkdirAll(path.Join(dir, fakeRoot), 0o755)) } withByEpoch := func(t *testing.T, dir string) { require.NoError(t, os.MkdirAll(path.Join(dir, filesystem.PeriodicEpochBaseDir), 0o755)) } cases := []struct { name string expected string expectedErr error setup func(t *testing.T, dir string) getter mockStringFlagGetter }{ { name: "no blobs dir", expected: filesystem.LayoutNameByEpoch, }, { name: "blobs dir without root dirs", expected: filesystem.LayoutNameByEpoch, // empty subdirectory under blobs which doesn't match the block root pattern setup: func(t *testing.T, dir string) { require.NoError(t, os.MkdirAll(path.Join(dir, "some-dir"), 0o755)) }, }, { name: "blobs dir with root dir", setup: withFlatRoot, expected: filesystem.LayoutNameFlat, }, { name: "blobs dir with root dir overridden by flag", setup: withFlatRoot, expected: filesystem.LayoutNameByEpoch, getter: mockStringFlagGetter{v: filesystem.LayoutNameByEpoch}, }, { name: "only has by-epoch dir", setup: withByEpoch, expected: filesystem.LayoutNameByEpoch, }, { name: "contains by-epoch dir and root dirs", setup: func(t *testing.T, dir string) { withFlatRoot(t, dir) withByEpoch(t, dir) }, expected: filesystem.LayoutNameFlat, }, { name: "unreadable dir", // It isn't detectLayout's job to detect any errors reading the directory, // so it ignores errors from the os.Open call. But we can also get errors // from readdirnames, but this is hard to simulate in a test. So in the test // write a file in place of the dir, which will succeed in the Open call, but // fail when read as a directory. This is why the expected error is syscall.ENOTDIR // (syscall error code from using readdirnames syscall on an ordinary file). setup: func(t *testing.T, dir string) { parent := filepath.Dir(dir) require.NoError(t, os.MkdirAll(parent, 0o755)) require.NoError(t, os.WriteFile(dir, []byte{}, 0o755)) }, expectedErr: syscall.ENOTDIR, }, { name: "empty blobs dir", setup: func(t *testing.T, dir string) { require.NoError(t, os.MkdirAll(dir, 0o755)) }, expected: filesystem.LayoutNameByEpoch, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir := strings.Replace(t.Name(), " ", "_", -1) dir = path.Join(os.TempDir(), dir) if tc.setup != nil { tc.setup(t, dir) } if tc.expectedErr != nil { t.Log("hi") } layout, err := detectLayout(dir, tc.getter) if tc.expectedErr != nil { require.ErrorIs(t, err, tc.expectedErr) return } require.NoError(t, err) require.Equal(t, tc.expected, layout) assert.Equal(t, tc.expectedErr, err) assert.Equal(t, tc.expected, layout) }) } }