package filesystem import ( "encoding/binary" "os" "path/filepath" "testing" fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/testing/require" "github.com/OffchainLabs/prysm/v7/testing/util" "github.com/spf13/afero" ) func TestNewDataColumnStorage(t *testing.T) { ctx := t.Context() t.Run("No base path", func(t *testing.T) { _, err := NewDataColumnStorage(ctx) require.ErrorIs(t, err, errNoDataColumnBasePath) }) t.Run("Nominal", func(t *testing.T) { dir := t.TempDir() storage, err := NewDataColumnStorage(ctx, WithDataColumnBasePath(dir)) require.NoError(t, err) require.Equal(t, dir, storage.base) }) } func TestWarmCache(t *testing.T) { storage, err := NewDataColumnStorage( t.Context(), WithDataColumnBasePath(t.TempDir()), WithDataColumnRetentionEpochs(10_000), ) require.NoError(t, err) _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 33, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 1 {Slot: 33, Index: 4, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 1 {Slot: 128_002, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 4000 {Slot: 128_002, Index: 4, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 4000 {Slot: 128_003, Index: 1, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 4000 {Slot: 128_003, Index: 3, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 4000 {Slot: 128_034, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 4001 {Slot: 128_034, Index: 4, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 4001 {Slot: 131_138, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4098 {Slot: 131_138, Index: 1, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4098 {Slot: 131_168, Index: 0, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4099 }, ) err = storage.Save(verifiedRoDataColumnSidecars) require.NoError(t, err) storage.retentionEpochs = 4_096 storage.WarmCache() require.Equal(t, primitives.Epoch(4_000), storage.cache.lowestCachedEpoch) require.Equal(t, 5, len(storage.cache.cache)) summary, ok := storage.cache.get(verifiedRoDataColumnSidecars[2].BlockRoot()) require.Equal(t, true, ok) require.DeepEqual(t, DataColumnStorageSummary{epoch: 4_000, mask: [fieldparams.NumberOfColumns]bool{false, false, true, false, true}}, summary) summary, ok = storage.cache.get(verifiedRoDataColumnSidecars[4].BlockRoot()) require.Equal(t, true, ok) require.DeepEqual(t, DataColumnStorageSummary{epoch: 4_000, mask: [fieldparams.NumberOfColumns]bool{false, true, false, true}}, summary) summary, ok = storage.cache.get(verifiedRoDataColumnSidecars[6].BlockRoot()) require.Equal(t, true, ok) require.DeepEqual(t, DataColumnStorageSummary{epoch: 4_001, mask: [fieldparams.NumberOfColumns]bool{false, false, true, false, true}}, summary) summary, ok = storage.cache.get(verifiedRoDataColumnSidecars[8].BlockRoot()) require.Equal(t, true, ok) require.DeepEqual(t, DataColumnStorageSummary{epoch: 4_098, mask: [fieldparams.NumberOfColumns]bool{false, true, true}}, summary) summary, ok = storage.cache.get(verifiedRoDataColumnSidecars[10].BlockRoot()) require.Equal(t, true, ok) require.DeepEqual(t, DataColumnStorageSummary{epoch: 4_099, mask: [fieldparams.NumberOfColumns]bool{true}}, summary) } func TestSaveDataColumnsSidecars(t *testing.T) { t.Run("one of the column index is too large", func(t *testing.T) { _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{{Index: 12}, {Index: 1_000_000}, {Index: 48}}, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.ErrorIs(t, err, errDataColumnIndexTooLarge) }) t.Run("different slots", func(t *testing.T) { _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 2, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, }, ) // Create a sidecar with a different slot but the same root. alteredVerifiedRoDataColumnSidecars := make([]blocks.VerifiedRODataColumn, 0, 2) alteredVerifiedRoDataColumnSidecars = append(alteredVerifiedRoDataColumnSidecars, verifiedRoDataColumnSidecars[0]) altered, err := blocks.NewRODataColumnWithRoot( verifiedRoDataColumnSidecars[1].RODataColumn.DataColumnSidecar, verifiedRoDataColumnSidecars[0].BlockRoot(), ) require.NoError(t, err) verifiedAltered := blocks.NewVerifiedRODataColumn(altered) alteredVerifiedRoDataColumnSidecars = append(alteredVerifiedRoDataColumnSidecars, verifiedAltered) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err = dataColumnStorage.Save(alteredVerifiedRoDataColumnSidecars) require.ErrorIs(t, err, errDataColumnSidecarsFromDifferentSlots) }) t.Run("new file - no data columns to save", func(t *testing.T) { _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{}, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.NoError(t, err) }) t.Run("new file - different data column size", func(t *testing.T) { _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 1, Index: 13, Column: [][]byte{{1}, {2}, {3}, {4}}}, }, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.ErrorIs(t, err, errWrongSszEncodedDataColumnSidecarSize) }) t.Run("existing file - wrong incoming SSZ encoded size", func(t *testing.T) { _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, }, ) // Save data columns into a file. _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.NoError(t, err) // Build a data column sidecar for the same block but with a different // column index and an different SSZ encoded size. _, verifiedRoDataColumnSidecars = util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 13, Column: [][]byte{{1}, {2}, {3}, {4}}}, }, ) // Try to rewrite the file. err = dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.ErrorIs(t, err, errWrongSszEncodedDataColumnSidecarSize) }) t.Run("nominal", func(t *testing.T) { _, inputVerifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 1, Index: 11, Column: [][]byte{{3}, {4}, {5}}}, {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, // OK if duplicate {Slot: 1, Index: 13, Column: [][]byte{{6}, {7}, {8}}}, {Slot: 2, Index: 12, Column: [][]byte{{3}, {4}, {5}}}, {Slot: 2, Index: 13, Column: [][]byte{{6}, {7}, {8}}}, }, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(inputVerifiedRoDataColumnSidecars) require.NoError(t, err) _, inputVerifiedRoDataColumnSidecars = util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, // OK if duplicate {Slot: 1, Index: 15, Column: [][]byte{{2}, {3}, {4}}}, {Slot: 1, Index: 1, Column: [][]byte{{2}, {3}, {4}}}, {Slot: 3, Index: 6, Column: [][]byte{{3}, {4}, {5}}}, {Slot: 3, Index: 2, Column: [][]byte{{6}, {7}, {8}}}, }, ) err = dataColumnStorage.Save(inputVerifiedRoDataColumnSidecars) require.NoError(t, err) type fixture struct { fileName string expectedIndices [mandatoryNumberOfColumns]byte dataColumnParams []util.DataColumnParam } fixtures := []fixture{ { fileName: "0/0/0x8bb2f09de48c102635622dc27e6de03ae2b22639df7c33edbc8222b2ec423746.sszs", expectedIndices: [mandatoryNumberOfColumns]byte{ 0, nonZeroOffset + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, nonZeroOffset + 1, nonZeroOffset, nonZeroOffset + 2, 0, nonZeroOffset + 3, // The rest is filled with zeroes. }, dataColumnParams: []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 1, Index: 11, Column: [][]byte{{3}, {4}, {5}}}, {Slot: 1, Index: 13, Column: [][]byte{{6}, {7}, {8}}}, {Slot: 1, Index: 15, Column: [][]byte{{2}, {3}, {4}}}, {Slot: 1, Index: 1, Column: [][]byte{{2}, {3}, {4}}}, }, }, { fileName: "0/0/0x221f88cae2219050d4e9d8c2d0d83cb4c8ce4c84ab1bb3e0b89f3dec36077c4f.sszs", expectedIndices: [mandatoryNumberOfColumns]byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nonZeroOffset, nonZeroOffset + 1, 0, 0, // The rest is filled with zeroes. }, dataColumnParams: []util.DataColumnParam{ {Slot: 2, Index: 12, Column: [][]byte{{3}, {4}, {5}}}, {Slot: 2, Index: 13, Column: [][]byte{{6}, {7}, {8}}}, }, }, { fileName: "0/0/0x7b163bd57e1c4c8b5048c5389698098f4c957d62d7ce86f4ffa9bdc75c16a18b.sszs", expectedIndices: [mandatoryNumberOfColumns]byte{ 0, 0, nonZeroOffset + 1, 0, 0, 0, nonZeroOffset, 0, // The rest is filled with zeroes. }, dataColumnParams: []util.DataColumnParam{ {Slot: 3, Index: 6, Column: [][]byte{{3}, {4}, {5}}}, {Slot: 3, Index: 2, Column: [][]byte{{6}, {7}, {8}}}, }, }, } for _, fixture := range fixtures { // Build expected data column sidecars. _, expectedDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, fixture.dataColumnParams, ) // Build expected bytes. firstSszEncodedDataColumnSidecar, err := expectedDataColumnSidecars[0].MarshalSSZ() require.NoError(t, err) dataColumnSidecarsCount := len(expectedDataColumnSidecars) sszEncodedDataColumnSidecarSize := len(firstSszEncodedDataColumnSidecar) sszEncodedDataColumnSidecars := make([]byte, 0, dataColumnSidecarsCount*sszEncodedDataColumnSidecarSize) sszEncodedDataColumnSidecars = append(sszEncodedDataColumnSidecars, firstSszEncodedDataColumnSidecar...) for _, dataColumnSidecar := range expectedDataColumnSidecars[1:] { sszEncodedDataColumnSidecar, err := dataColumnSidecar.MarshalSSZ() require.NoError(t, err) sszEncodedDataColumnSidecars = append(sszEncodedDataColumnSidecars, sszEncodedDataColumnSidecar...) } var encodedSszEncodedDataColumnSidecarSize [sidecarByteLenSize]byte binary.BigEndian.PutUint32(encodedSszEncodedDataColumnSidecarSize[:], uint32(sszEncodedDataColumnSidecarSize)) expectedBytes := make([]byte, 0, headerSize+dataColumnSidecarsCount*sszEncodedDataColumnSidecarSize) expectedBytes = append(expectedBytes, []byte{0x01}...) expectedBytes = append(expectedBytes, encodedSszEncodedDataColumnSidecarSize[:]...) expectedBytes = append(expectedBytes, fixture.expectedIndices[:]...) expectedBytes = append(expectedBytes, sszEncodedDataColumnSidecars...) blockRoot := expectedDataColumnSidecars[0].BlockRoot() // Check the actual content of the file. actualBytes, err := afero.ReadFile(dataColumnStorage.fs, fixture.fileName) require.NoError(t, err) require.DeepSSZEqual(t, expectedBytes, actualBytes) // Check the summary. indices := map[uint64]bool{} for _, dataColumnParam := range fixture.dataColumnParams { indices[dataColumnParam.Index] = true } summary := dataColumnStorage.Summary(blockRoot) for index := range uint64(mandatoryNumberOfColumns) { require.Equal(t, indices[index], summary.HasIndex(index)) } err = dataColumnStorage.Remove(blockRoot) require.NoError(t, err) summary = dataColumnStorage.Summary(blockRoot) for index := range uint64(mandatoryNumberOfColumns) { require.Equal(t, false, summary.HasIndex(index)) } _, err = afero.ReadFile(dataColumnStorage.fs, fixture.fileName) require.ErrorIs(t, err, os.ErrNotExist) } }) } func TestGetDataColumnSidecars(t *testing.T) { t.Run("root not found", func(t *testing.T) { _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) verifiedRODataColumnSidecars, err := dataColumnStorage.Get([fieldparams.RootLength]byte{1}, []uint64{12, 13, 14}) require.NoError(t, err) require.Equal(t, 0, len(verifiedRODataColumnSidecars)) }) t.Run("indices not found", func(t *testing.T) { _, savedVerifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Index: 14, Column: [][]byte{{2}, {3}, {4}}}, }, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(savedVerifiedRoDataColumnSidecars) require.NoError(t, err) verifiedRODataColumnSidecars, err := dataColumnStorage.Get(savedVerifiedRoDataColumnSidecars[0].BlockRoot(), []uint64{3, 1, 2}) require.NoError(t, err) require.Equal(t, 0, len(verifiedRODataColumnSidecars)) }) t.Run("nominal", func(t *testing.T) { _, expectedVerifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Index: 14, Column: [][]byte{{2}, {3}, {4}}}, }, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(expectedVerifiedRoDataColumnSidecars) require.NoError(t, err) root := expectedVerifiedRoDataColumnSidecars[0].BlockRoot() verifiedRODataColumnSidecars, err := dataColumnStorage.Get(root, nil) require.NoError(t, err) require.DeepSSZEqual(t, expectedVerifiedRoDataColumnSidecars, verifiedRODataColumnSidecars) verifiedRODataColumnSidecars, err = dataColumnStorage.Get(root, []uint64{12, 13, 14}) require.NoError(t, err) require.DeepSSZEqual(t, expectedVerifiedRoDataColumnSidecars, verifiedRODataColumnSidecars) }) } func TestRemove(t *testing.T) { t.Run("not found", func(t *testing.T) { _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Remove([fieldparams.RootLength]byte{1}) require.NoError(t, err) }) t.Run("nominal", func(t *testing.T) { _, inputVerifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 32, Index: 10, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 32, Index: 11, Column: [][]byte{{2}, {3}, {4}}}, {Slot: 33, Index: 10, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 33, Index: 11, Column: [][]byte{{2}, {3}, {4}}}, }, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(inputVerifiedRoDataColumnSidecars) require.NoError(t, err) err = dataColumnStorage.Remove(inputVerifiedRoDataColumnSidecars[0].BlockRoot()) require.NoError(t, err) summary := dataColumnStorage.Summary(inputVerifiedRoDataColumnSidecars[0].BlockRoot()) require.Equal(t, primitives.Epoch(0), summary.epoch) require.Equal(t, uint64(0), summary.Count()) summary = dataColumnStorage.Summary(inputVerifiedRoDataColumnSidecars[3].BlockRoot()) require.Equal(t, primitives.Epoch(1), summary.epoch) require.Equal(t, uint64(2), summary.Count()) actual, err := dataColumnStorage.Get(inputVerifiedRoDataColumnSidecars[0].BlockRoot(), nil) require.NoError(t, err) require.Equal(t, 0, len(actual)) actual, err = dataColumnStorage.Get(inputVerifiedRoDataColumnSidecars[3].BlockRoot(), nil) require.NoError(t, err) require.Equal(t, 2, len(actual)) }) } func TestClear(t *testing.T) { _, inputVerifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, {Slot: 2, Index: 13, Column: [][]byte{{6}, {7}, {8}}}, }, ) _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(inputVerifiedRoDataColumnSidecars) require.NoError(t, err) filePaths := []string{ "0/0/0x8bb2f09de48c102635622dc27e6de03ae2b22639df7c33edbc8222b2ec423746.sszs", "0/0/0x221f88cae2219050d4e9d8c2d0d83cb4c8ce4c84ab1bb3e0b89f3dec36077c4f.sszs", } for _, filePath := range filePaths { _, err = afero.ReadFile(dataColumnStorage.fs, filePath) require.NoError(t, err) } err = dataColumnStorage.Clear() require.NoError(t, err) summary := dataColumnStorage.Summary([fieldparams.RootLength]byte{1}) for index := range uint64(mandatoryNumberOfColumns) { require.Equal(t, false, summary.HasIndex(index)) } for _, filePath := range filePaths { _, err = afero.ReadFile(dataColumnStorage.fs, filePath) require.ErrorIs(t, err, os.ErrNotExist) } } func TestMetadata(t *testing.T) { t.Run("wrong version", func(t *testing.T) { _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 1, Index: 12, Column: [][]byte{{1}, {2}, {3}}}, }, ) // Save data columns into a file. _, dataColumnStorage := NewEphemeralDataColumnStorageAndFs(t) err := dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.NoError(t, err) // Alter the version. const filePath = "0/0/0x8bb2f09de48c102635622dc27e6de03ae2b22639df7c33edbc8222b2ec423746.sszs" file, err := dataColumnStorage.fs.OpenFile(filePath, os.O_WRONLY, os.FileMode(0600)) require.NoError(t, err) count, err := file.Write([]byte{42}) require.NoError(t, err) require.Equal(t, 1, count) // Try to read the metadata. _, err = dataColumnStorage.metadata(file) require.ErrorIs(t, err, errWrongVersion) err = file.Close() require.NoError(t, err) }) } func TestNewStorageIndices(t *testing.T) { t.Run("wrong number of columns", func(t *testing.T) { _, err := newStorageIndices(nil) require.ErrorIs(t, err, errWrongNumberOfColumns) }) t.Run("nominal", func(t *testing.T) { var indices [mandatoryNumberOfColumns]byte indices[0] = 1 storageIndices, err := newStorageIndices(indices[:]) require.NoError(t, err) require.Equal(t, indices, storageIndices.indices) }) } func TestStorageIndicesGet(t *testing.T) { t.Run("index too large", func(t *testing.T) { var indices storageIndices _, _, err := indices.get(1_000_000) require.ErrorIs(t, errDataColumnIndexTooLarge, err) }) t.Run("index not set", func(t *testing.T) { const expected = false var indices storageIndices actual, _, err := indices.get(0) require.NoError(t, err) require.Equal(t, expected, actual) }) t.Run("index set", func(t *testing.T) { const ( expectedOk = true expectedPosition = int64(3) ) indices := storageIndices{indices: [mandatoryNumberOfColumns]byte{0, 131}} actualOk, actualPosition, err := indices.get(1) require.NoError(t, err) require.Equal(t, expectedOk, actualOk) require.Equal(t, expectedPosition, actualPosition) }) } func TestStorageIndicesLen(t *testing.T) { const expected = int64(2) indices := storageIndices{count: 2} actual := indices.len() require.Equal(t, expected, actual) } func TestStorageIndicesAll(t *testing.T) { expectedIndices := []uint64{1, 3} indices := storageIndices{indices: [mandatoryNumberOfColumns]byte{0, 131, 0, 128}} actualIndices := indices.all() require.DeepEqual(t, expectedIndices, actualIndices) } func TestStorageIndicesSet(t *testing.T) { t.Run("data column index too large", func(t *testing.T) { var indices storageIndices err := indices.set(1_000_000, 0) require.ErrorIs(t, errDataColumnIndexTooLarge, err) }) t.Run("position too large", func(t *testing.T) { var indices storageIndices err := indices.set(0, 255) require.ErrorIs(t, errDataColumnIndexTooLarge, err) }) t.Run("nominal", func(t *testing.T) { expected := [mandatoryNumberOfColumns]byte{0, 0, 128, 0, 131} var storageIndices storageIndices require.Equal(t, int64(0), storageIndices.len()) err := storageIndices.set(2, 1) require.NoError(t, err) require.Equal(t, int64(1), storageIndices.len()) err = storageIndices.set(4, 3) require.NoError(t, err) require.Equal(t, int64(2), storageIndices.len()) err = storageIndices.set(2, 0) require.NoError(t, err) require.Equal(t, int64(2), storageIndices.len()) actual := storageIndices.indices require.Equal(t, expected, actual) }) } func TestPrune(t *testing.T) { t.Run(("nothing to prune"), func(t *testing.T) { dir := t.TempDir() dataColumnStorage, err := NewDataColumnStorage(t.Context(), WithDataColumnBasePath(dir)) require.NoError(t, err) dataColumnStorage.prune() }) t.Run("nominal", func(t *testing.T) { var compareSlices = func(left, right []string) bool { if len(left) != len(right) { return false } leftMap := make(map[string]bool, len(left)) for _, leftItem := range left { leftMap[leftItem] = true } for _, rightItem := range right { if _, ok := leftMap[rightItem]; !ok { return false } } return true } _, verifiedRoDataColumnSidecars := util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 33, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 1 {Slot: 33, Index: 4, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 1 {Slot: 128_002, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 4000 {Slot: 128_002, Index: 4, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 4000 {Slot: 128_003, Index: 1, Column: [][]byte{{1}, {2}, {3}}}, // Period 0 - Epoch 4000 {Slot: 128_003, Index: 3, Column: [][]byte{{2}, {3}, {4}}}, // Period 0 - Epoch 4000 {Slot: 131_138, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4098 {Slot: 131_138, Index: 3, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4098 {Slot: 131_169, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4099 {Slot: 131_169, Index: 3, Column: [][]byte{{1}, {2}, {3}}}, // Period 1 - Epoch 4099 {Slot: 262_144, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 2 - Epoch 8192 {Slot: 262_144, Index: 3, Column: [][]byte{{1}, {2}, {3}}}, // Period 2 - Epoch 8292 }, ) dir := t.TempDir() dataColumnStorage, err := NewDataColumnStorage(t.Context(), WithDataColumnBasePath(dir), WithDataColumnRetentionEpochs(10_000)) require.NoError(t, err) err = dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.NoError(t, err) dirs, err := listDir(dataColumnStorage.fs, ".") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0", "1", "2"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "0") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"1", "4000"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "1") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"4099", "4098"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "2") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"8192"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "0/1") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0x775283f428813c949b7e8af07f01fef9790137f021b3597ad2d0d81e8be8f0f0.sszs"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "0/4000") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{ "0x9977031132157ebb9c81bce952003ce07a4f54e921ca63b7693d1562483fdf9f.sszs", "0xb2b14d9d858fa99b70f0405e4e39f38e51e36dd9a70343c109e24eeb5f77e369.sszs", }, dirs)) dirs, err = listDir(dataColumnStorage.fs, "1/4098") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0x5106745cdd6b1aa3602ef4d000ef373af672019264c167fa4bd641a1094aa5c5.sszs"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "1/4099") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0x4e5f2bd5bb84bf0422af8edd1cc5a52cc6cea85baf3d66d172fe41831ac1239c.sszs"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "2/8192") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0xa8adba7446eb56a01a9dd6d55e9c3990b10c91d43afb77847b4a21ac4ee62527.sszs"}, dirs)) _, verifiedRoDataColumnSidecars = util.CreateTestVerifiedRoDataColumnSidecars( t, []util.DataColumnParam{ {Slot: 451_141, Index: 2, Column: [][]byte{{1}, {2}, {3}}}, // Period 3 - Epoch 14_098 }, ) err = dataColumnStorage.Save(verifiedRoDataColumnSidecars) require.NoError(t, err) // dataColumnStorage.prune(14_098) dataColumnStorage.prune() dirs, err = listDir(dataColumnStorage.fs, ".") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"1", "2", "3"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "1") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"4099"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "2") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"8192"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "3") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"14098"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "1/4099") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0x4e5f2bd5bb84bf0422af8edd1cc5a52cc6cea85baf3d66d172fe41831ac1239c.sszs"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "2/8192") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0xa8adba7446eb56a01a9dd6d55e9c3990b10c91d43afb77847b4a21ac4ee62527.sszs"}, dirs)) dirs, err = listDir(dataColumnStorage.fs, "3/14098") require.NoError(t, err) require.Equal(t, true, compareSlices([]string{"0x0de28a18cae63cbc6f0b20dc1afb0b1df38da40824a5f09f92d485ade04de97f.sszs"}, dirs)) }) } func TestExtractFileMetadata(t *testing.T) { t.Run("Unix", func(t *testing.T) { // Test with Unix-style path separators (/) path := "12/1234/0x8bb2f09de48c102635622dc27e6de03ae2b22639df7c33edbc8222b2ec423746.sszs" metadata, err := extractFileMetadata(path) if filepath.Separator == '/' { // On Unix systems, this should succeed require.NoError(t, err) require.Equal(t, uint64(12), metadata.period) require.Equal(t, primitives.Epoch(1234), metadata.epoch) return } // On Windows systems, this should fail because it uses the wrong separator require.NotNil(t, err) }) t.Run("Windows", func(t *testing.T) { // Test with Windows-style path separators (\) path := "12\\1234\\0x8bb2f09de48c102635622dc27e6de03ae2b22639df7c33edbc8222b2ec423746.sszs" metadata, err := extractFileMetadata(path) if filepath.Separator == '\\' { // On Windows systems, this should succeed require.NoError(t, err) require.Equal(t, uint64(12), metadata.period) require.Equal(t, primitives.Epoch(1234), metadata.epoch) return } // On Unix systems, this should fail because it uses the wrong separator require.NotNil(t, err) }) }