Files
prysm/cmd/beacon-chain/storage/options_test.go
kasey 61de11e2c4 Backfill data columns (#15580)
**What type of PR is this?**

Feature

**What does this PR do? Why is it needed?**

Adds data column support to backfill.

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description to this PR with sufficient context for
reviewers to understand this PR.

---------

Co-authored-by: Kasey <kasey@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Preston Van Loon <preston@pvl.dev>
2025-12-02 15:19:32 +00:00

228 lines
6.9 KiB
Go

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)
})
}
}