Skip to content

Commit 66ec80c

Browse files
kaseynisdas
authored andcommitted
use [32]byte keys in the filesystem cache (#13885)
Co-authored-by: Kasey Kirkham <[email protected]>
1 parent d02b43d commit 66ec80c

File tree

7 files changed

+99
-34
lines changed

7 files changed

+99
-34
lines changed

beacon-chain/db/filesystem/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ go_library(
1818
"//config/params:go_default_library",
1919
"//consensus-types/blocks:go_default_library",
2020
"//consensus-types/primitives:go_default_library",
21+
"//encoding/bytesutil:go_default_library",
2122
"//io/file:go_default_library",
2223
"//proto/prysm/v1alpha1:go_default_library",
2324
"//runtime/logging:go_default_library",
2425
"//time/slots:go_default_library",
26+
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
2527
"@com_github_pkg_errors//:go_default_library",
2628
"@com_github_prometheus_client_golang//prometheus:go_default_library",
2729
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",

beacon-chain/db/filesystem/blob.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/ethereum/go-ethereum/common/hexutil"
1314
"github.com/pkg/errors"
1415
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
1516
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
1617
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
1718
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
19+
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
1820
"github.com/prysmaticlabs/prysm/v5/io/file"
1921
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
2022
"github.com/prysmaticlabs/prysm/v5/runtime/logging"
@@ -27,6 +29,7 @@ var (
2729
errEmptyBlobWritten = errors.New("zero bytes written to disk when saving blob sidecar")
2830
errSidecarEmptySSZData = errors.New("sidecar marshalled to an empty ssz byte slice")
2931
errNoBasePath = errors.New("BlobStorage base path not specified in init")
32+
errInvalidRootString = errors.New("Could not parse hex string as a [32]byte")
3033
)
3134

3235
const (
@@ -333,3 +336,11 @@ func (p blobNamer) path() string {
333336
func rootString(root [32]byte) string {
334337
return fmt.Sprintf("%#x", root)
335338
}
339+
340+
func stringToRoot(str string) ([32]byte, error) {
341+
slice, err := hexutil.Decode(str)
342+
if err != nil {
343+
return [32]byte{}, errors.Wrapf(errInvalidRootString, "input=%s", str)
344+
}
345+
return bytesutil.ToBytes32(slice), nil
346+
}

beacon-chain/db/filesystem/cache.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,26 @@ type BlobStorageSummarizer interface {
4848
type blobStorageCache struct {
4949
mu sync.RWMutex
5050
nBlobs float64
51-
cache map[string]BlobStorageSummary
51+
cache map[[32]byte]BlobStorageSummary
5252
}
5353

5454
var _ BlobStorageSummarizer = &blobStorageCache{}
5555

5656
func newBlobStorageCache() *blobStorageCache {
5757
return &blobStorageCache{
58-
cache: make(map[string]BlobStorageSummary, params.BeaconConfig().MinEpochsForBlobsSidecarsRequest*fieldparams.SlotsPerEpoch),
58+
cache: make(map[[32]byte]BlobStorageSummary, params.BeaconConfig().MinEpochsForBlobsSidecarsRequest*fieldparams.SlotsPerEpoch),
5959
}
6060
}
6161

6262
// Summary returns the BlobStorageSummary for `root`. The BlobStorageSummary can be used to check for the presence of
6363
// BlobSidecars based on Index.
6464
func (s *blobStorageCache) Summary(root [32]byte) BlobStorageSummary {
65-
k := rootString(root)
6665
s.mu.RLock()
6766
defer s.mu.RUnlock()
68-
return s.cache[k]
67+
return s.cache[root]
6968
}
7069

71-
func (s *blobStorageCache) ensure(key string, slot primitives.Slot, idx uint64) error {
70+
func (s *blobStorageCache) ensure(key [32]byte, slot primitives.Slot, idx uint64) error {
7271
if idx >= fieldparams.MaxBlobsPerBlock {
7372
return errIndexOutOfBounds
7473
}
@@ -84,7 +83,7 @@ func (s *blobStorageCache) ensure(key string, slot primitives.Slot, idx uint64)
8483
return nil
8584
}
8685

87-
func (s *blobStorageCache) slot(key string) (primitives.Slot, bool) {
86+
func (s *blobStorageCache) slot(key [32]byte) (primitives.Slot, bool) {
8887
s.mu.RLock()
8988
defer s.mu.RUnlock()
9089
v, ok := s.cache[key]
@@ -94,7 +93,7 @@ func (s *blobStorageCache) slot(key string) (primitives.Slot, bool) {
9493
return v.slot, ok
9594
}
9695

97-
func (s *blobStorageCache) evict(key string) {
96+
func (s *blobStorageCache) evict(key [32]byte) {
9897
var deleted float64
9998
s.mu.Lock()
10099
v, ok := s.cache[key]

beacon-chain/db/filesystem/cache_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestSlotByRoot_Summary(t *testing.T) {
4848
sc := newBlobStorageCache()
4949
for _, c := range cases {
5050
if c.expected != nil {
51-
key := rootString(bytesutil.ToBytes32([]byte(c.name)))
51+
key := bytesutil.ToBytes32([]byte(c.name))
5252
sc.cache[key] = BlobStorageSummary{slot: 0, mask: *c.expected}
5353
}
5454
}

beacon-chain/db/filesystem/mock.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func NewMockBlobStorageSummarizer(t *testing.T, set map[[32]byte][]int) BlobStor
6666
c := newBlobStorageCache()
6767
for k, v := range set {
6868
for i := range v {
69-
if err := c.ensure(rootString(k), 0, uint64(v[i])); err != nil {
69+
if err := c.ensure(k, 0, uint64(v[i])); err != nil {
7070
t.Fatal(err)
7171
}
7272
}

beacon-chain/db/filesystem/pruner.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func newBlobPruner(fs afero.Fs, retain primitives.Epoch, opts ...prunerOpt) (*bl
6464
// notify updates the pruner's view of root->blob mappings. This allows the pruner to build a cache
6565
// of root->slot mappings and decide when to evict old blobs based on the age of present blobs.
6666
func (p *blobPruner) notify(root [32]byte, latest primitives.Slot, idx uint64) error {
67-
if err := p.cache.ensure(rootString(root), latest, idx); err != nil {
67+
if err := p.cache.ensure(root, latest, idx); err != nil {
6868
return err
6969
}
7070
pruned := uint64(windowMin(latest, p.windowSize))
@@ -160,7 +160,10 @@ func shouldRetain(slot, pruneBefore primitives.Slot) bool {
160160
}
161161

162162
func (p *blobPruner) tryPruneDir(dir string, pruneBefore primitives.Slot) (int, error) {
163-
root := rootFromDir(dir)
163+
root, err := rootFromDir(dir)
164+
if err != nil {
165+
return 0, errors.Wrapf(err, "invalid directory, could not parse subdir as root %s", dir)
166+
}
164167
slot, slotCached := p.cache.slot(root)
165168
// Return early if the slot is cached and doesn't need pruning.
166169
if slotCached && shouldRetain(slot, pruneBefore) {
@@ -218,7 +221,7 @@ func (p *blobPruner) tryPruneDir(dir string, pruneBefore primitives.Slot) (int,
218221
return removed, errors.Wrapf(err, "unable to remove blob directory %s", dir)
219222
}
220223

221-
p.cache.evict(rootFromDir(dir))
224+
p.cache.evict(root)
222225
return len(scFiles), nil
223226
}
224227

@@ -235,8 +238,13 @@ func idxFromPath(fname string) (uint64, error) {
235238
return strconv.ParseUint(parts[0], 10, 64)
236239
}
237240

238-
func rootFromDir(dir string) string {
239-
return filepath.Base(dir) // end of the path should be the blob directory, named by hex encoding of root
241+
func rootFromDir(dir string) ([32]byte, error) {
242+
subdir := filepath.Base(dir) // end of the path should be the blob directory, named by hex encoding of root
243+
root, err := stringToRoot(subdir)
244+
if err != nil {
245+
return root, errors.Wrapf(err, "invalid directory, could not parse subdir as root %s", dir)
246+
}
247+
return root, nil
240248
}
241249

242250
// Read slot from marshaled BlobSidecar data in the given file. See slotFromBlob for details.

beacon-chain/db/filesystem/pruner_test.go

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ func TestTryPruneDir_CachedNotExpired(t *testing.T) {
2525
_, sidecars := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, slot, fieldparams.MaxBlobsPerBlock)
2626
sc, err := verification.BlobSidecarNoop(sidecars[0])
2727
require.NoError(t, err)
28-
root := fmt.Sprintf("%#x", sc.BlockRoot())
28+
rootStr := rootString(sc.BlockRoot())
2929
// This slot is right on the edge of what would need to be pruned, so by adding it to the cache and
3030
// skipping any other test setup, we can be certain the hot cache path never touches the filesystem.
31-
require.NoError(t, pr.cache.ensure(root, sc.Slot(), 0))
32-
pruned, err := pr.tryPruneDir(root, pr.windowSize)
31+
require.NoError(t, pr.cache.ensure(sc.BlockRoot(), sc.Slot(), 0))
32+
pruned, err := pr.tryPruneDir(rootStr, pr.windowSize)
3333
require.NoError(t, err)
3434
require.Equal(t, 0, pruned)
3535
}
@@ -43,10 +43,10 @@ func TestTryPruneDir_CachedExpired(t *testing.T) {
4343
_, sidecars := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, slot, 1)
4444
sc, err := verification.BlobSidecarNoop(sidecars[0])
4545
require.NoError(t, err)
46-
root := fmt.Sprintf("%#x", sc.BlockRoot())
47-
require.NoError(t, fs.Mkdir(root, directoryPermissions)) // make empty directory
48-
require.NoError(t, pr.cache.ensure(root, sc.Slot(), 0))
49-
pruned, err := pr.tryPruneDir(root, slot+1)
46+
rootStr := rootString(sc.BlockRoot())
47+
require.NoError(t, fs.Mkdir(rootStr, directoryPermissions)) // make empty directory
48+
require.NoError(t, pr.cache.ensure(sc.BlockRoot(), sc.Slot(), 0))
49+
pruned, err := pr.tryPruneDir(rootStr, slot+1)
5050
require.NoError(t, err)
5151
require.Equal(t, 0, pruned)
5252
})
@@ -61,20 +61,21 @@ func TestTryPruneDir_CachedExpired(t *testing.T) {
6161
require.NoError(t, bs.Save(scs[1]))
6262

6363
// check that the root->slot is cached
64-
root := fmt.Sprintf("%#x", scs[0].BlockRoot())
65-
cs, cok := bs.pruner.cache.slot(root)
64+
root := scs[0].BlockRoot()
65+
rootStr := rootString(root)
66+
cs, cok := bs.pruner.cache.slot(scs[0].BlockRoot())
6667
require.Equal(t, true, cok)
6768
require.Equal(t, slot, cs)
6869

6970
// ensure that we see the saved files in the filesystem
70-
files, err := listDir(fs, root)
71+
files, err := listDir(fs, rootStr)
7172
require.NoError(t, err)
7273
require.Equal(t, 2, len(files))
7374

74-
pruned, err := bs.pruner.tryPruneDir(root, slot+1)
75+
pruned, err := bs.pruner.tryPruneDir(rootStr, slot+1)
7576
require.NoError(t, err)
7677
require.Equal(t, 2, pruned)
77-
files, err = listDir(fs, root)
78+
files, err = listDir(fs, rootStr)
7879
require.ErrorIs(t, err, os.ErrNotExist)
7980
require.Equal(t, 0, len(files))
8081
})
@@ -92,7 +93,8 @@ func TestTryPruneDir_SlotFromFile(t *testing.T) {
9293
require.NoError(t, bs.Save(scs[1]))
9394

9495
// check that the root->slot is cached
95-
root := fmt.Sprintf("%#x", scs[0].BlockRoot())
96+
root := scs[0].BlockRoot()
97+
rootStr := rootString(root)
9698
cs, ok := bs.pruner.cache.slot(root)
9799
require.Equal(t, true, ok)
98100
require.Equal(t, slot, cs)
@@ -102,14 +104,14 @@ func TestTryPruneDir_SlotFromFile(t *testing.T) {
102104
require.Equal(t, false, ok)
103105

104106
// ensure that we see the saved files in the filesystem
105-
files, err := listDir(fs, root)
107+
files, err := listDir(fs, rootStr)
106108
require.NoError(t, err)
107109
require.Equal(t, 2, len(files))
108110

109-
pruned, err := bs.pruner.tryPruneDir(root, slot+1)
111+
pruned, err := bs.pruner.tryPruneDir(rootStr, slot+1)
110112
require.NoError(t, err)
111113
require.Equal(t, 2, pruned)
112-
files, err = listDir(fs, root)
114+
files, err = listDir(fs, rootStr)
113115
require.ErrorIs(t, err, os.ErrNotExist)
114116
require.Equal(t, 0, len(files))
115117
})
@@ -125,24 +127,25 @@ func TestTryPruneDir_SlotFromFile(t *testing.T) {
125127
require.NoError(t, bs.Save(scs[1]))
126128

127129
// Evict slot mapping from the cache so that we trigger the file read path.
128-
root := fmt.Sprintf("%#x", scs[0].BlockRoot())
130+
root := scs[0].BlockRoot()
131+
rootStr := rootString(root)
129132
bs.pruner.cache.evict(root)
130133
_, ok := bs.pruner.cache.slot(root)
131134
require.Equal(t, false, ok)
132135

133136
// Ensure that we see the saved files in the filesystem.
134-
files, err := listDir(fs, root)
137+
files, err := listDir(fs, rootStr)
135138
require.NoError(t, err)
136139
require.Equal(t, 2, len(files))
137140

138141
// This should use the slotFromFile code (simulating restart).
139142
// Setting pruneBefore == slot, so that the slot will be outside the window (at the boundary).
140-
pruned, err := bs.pruner.tryPruneDir(root, slot)
143+
pruned, err := bs.pruner.tryPruneDir(rootStr, slot)
141144
require.NoError(t, err)
142145
require.Equal(t, 0, pruned)
143146

144147
// Ensure files are still present.
145-
files, err = listDir(fs, root)
148+
files, err = listDir(fs, rootStr)
146149
require.NoError(t, err)
147150
require.Equal(t, 2, len(files))
148151
})
@@ -316,3 +319,45 @@ func TestListDir(t *testing.T) {
316319
})
317320
}
318321
}
322+
323+
func TestRootFromDir(t *testing.T) {
324+
cases := []struct {
325+
name string
326+
dir string
327+
err error
328+
root [32]byte
329+
}{
330+
{
331+
name: "happy path",
332+
dir: "0xffff875e1d985c5ccb214894983f2428edb271f0f87b68ba7010e4a99df3b5cb",
333+
root: [32]byte{255, 255, 135, 94, 29, 152, 92, 92, 203, 33, 72, 148, 152, 63, 36, 40,
334+
237, 178, 113, 240, 248, 123, 104, 186, 112, 16, 228, 169, 157, 243, 181, 203},
335+
},
336+
{
337+
name: "too short",
338+
dir: "0xffff875e1d985c5ccb214894983f2428edb271f0f87b68ba7010e4a99df3b5c",
339+
err: errInvalidRootString,
340+
},
341+
{
342+
name: "too log",
343+
dir: "0xffff875e1d985c5ccb214894983f2428edb271f0f87b68ba7010e4a99df3b5cbb",
344+
err: errInvalidRootString,
345+
},
346+
{
347+
name: "missing prefix",
348+
dir: "ffff875e1d985c5ccb214894983f2428edb271f0f87b68ba7010e4a99df3b5cb",
349+
err: errInvalidRootString,
350+
},
351+
}
352+
for _, c := range cases {
353+
t.Run(c.name, func(t *testing.T) {
354+
root, err := stringToRoot(c.dir)
355+
if c.err != nil {
356+
require.ErrorIs(t, err, c.err)
357+
return
358+
}
359+
require.NoError(t, err)
360+
require.Equal(t, c.root, root)
361+
})
362+
}
363+
}

0 commit comments

Comments
 (0)