Skip to content

Commit 0dba4e7

Browse files
authored
feat(cache): cache pruning (#2761)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview Supersed #2748 Except it is fully contained in the cache. We delete all the cache for the height that just been marked as included. <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. Ex: Closes #<issue number> -->
1 parent bd7891f commit 0dba4e7

File tree

10 files changed

+187
-82
lines changed

10 files changed

+187
-82
lines changed

block/internal/cache/generic_cache.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type Cache[T any] struct {
1616
hashes *sync.Map
1717
// daIncluded tracks the DA inclusion height for a given hash
1818
daIncluded *sync.Map
19+
// hashByHeight tracks the hash associated with each height for pruning
20+
hashByHeight *sync.Map
1921
}
2022

2123
// NewCache returns a new Cache struct
@@ -24,6 +26,7 @@ func NewCache[T any]() *Cache[T] {
2426
itemsByHeight: new(sync.Map),
2527
hashes: new(sync.Map),
2628
daIncluded: new(sync.Map),
29+
hashByHeight: new(sync.Map),
2730
}
2831
}
2932

@@ -46,22 +49,6 @@ func (c *Cache[T]) setItem(height uint64, item *T) {
4649
c.itemsByHeight.Store(height, item)
4750
}
4851

49-
// rangeByHeight iterates over items keyed by height in an unspecified order and calls fn for each.
50-
// If fn returns false, iteration stops early.
51-
func (c *Cache[T]) rangeByHeight(fn func(height uint64, item *T) bool) {
52-
c.itemsByHeight.Range(func(k, v any) bool {
53-
height, ok := k.(uint64)
54-
if !ok {
55-
return true
56-
}
57-
it, ok := v.(*T)
58-
if !ok {
59-
return true
60-
}
61-
return fn(height, it)
62-
})
63-
}
64-
6552
// getNextItem returns the item at the specified height and removes it from cache if found.
6653
// Returns nil if not found.
6754
func (c *Cache[T]) getNextItem(height uint64) *T {
@@ -85,9 +72,10 @@ func (c *Cache[T]) isSeen(hash string) bool {
8572
return seen.(bool)
8673
}
8774

88-
// setSeen sets the hash as seen
89-
func (c *Cache[T]) setSeen(hash string) {
75+
// setSeen sets the hash as seen and tracks its height for pruning
76+
func (c *Cache[T]) setSeen(hash string, height uint64) {
9077
c.hashes.Store(hash, true)
78+
c.hashByHeight.Store(height, hash)
9179
}
9280

9381
// getDAIncluded returns the DA height if the hash has been DA-included, otherwise it returns 0.
@@ -99,16 +87,28 @@ func (c *Cache[T]) getDAIncluded(hash string) (uint64, bool) {
9987
return daIncluded.(uint64), true
10088
}
10189

102-
// setDAIncluded sets the hash as DA-included with the given DA height
103-
func (c *Cache[T]) setDAIncluded(hash string, daHeight uint64) {
90+
// setDAIncluded sets the hash as DA-included with the given DA height and tracks block height for pruning
91+
func (c *Cache[T]) setDAIncluded(hash string, daHeight uint64, blockHeight uint64) {
10492
c.daIncluded.Store(hash, daHeight)
93+
c.hashByHeight.Store(blockHeight, hash)
10594
}
10695

10796
// removeDAIncluded removes the DA-included status of the hash
10897
func (c *Cache[T]) removeDAIncluded(hash string) {
10998
c.daIncluded.Delete(hash)
11099
}
111100

101+
// deleteAll removes all items and their associated data from the cache at the given height
102+
func (c *Cache[T]) deleteAllForHeight(height uint64) {
103+
c.itemsByHeight.Delete(height)
104+
hash, ok := c.hashByHeight.Load(height)
105+
if ok {
106+
c.hashes.Delete(hash)
107+
c.hashByHeight.Delete(height)
108+
// c.daIncluded.Delete(hash) // we actually do not want to delete the DA-included status here
109+
}
110+
}
111+
112112
const (
113113
itemsByHeightFilename = "items_by_height.gob"
114114
hashesFilename = "hashes.gob"

block/internal/cache/generic_cache_test.go

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,14 @@ func init() {
1414
gob.Register(&testItem{})
1515
}
1616

17-
// TestCache_TypeSafety ensures methods gracefully handle invalid underlying types.
18-
func TestCache_TypeSafety(t *testing.T) {
19-
c := NewCache[testItem]()
20-
21-
// Inject invalid value types directly into maps (bypassing typed methods)
22-
c.itemsByHeight.Store(uint64(1), "not-a-*testItem")
23-
24-
if got := c.getItem(1); got != nil {
25-
t.Fatalf("expected nil for invalid stored type, got %#v", got)
26-
}
27-
28-
// Range should skip invalid entries and not panic
29-
ran := false
30-
c.rangeByHeight(func(_ uint64, _ *testItem) bool { ran = true; return true })
31-
_ = ran // ensure no panic
32-
}
33-
3417
// TestCache_SaveLoad_ErrorPaths covers SaveToDisk and LoadFromDisk error scenarios.
3518
func TestCache_SaveLoad_ErrorPaths(t *testing.T) {
3619
c := NewCache[testItem]()
3720
for i := 0; i < 5; i++ {
3821
v := &testItem{V: i}
3922
c.setItem(uint64(i), v)
40-
c.setSeen(fmt.Sprintf("s%d", i))
41-
c.setDAIncluded(fmt.Sprintf("d%d", i), uint64(i))
23+
c.setSeen(fmt.Sprintf("s%d", i), uint64(i))
24+
c.setDAIncluded(fmt.Sprintf("d%d", i), uint64(i), uint64(i))
4225
}
4326

4427
// Normal save/load roundtrip

block/internal/cache/manager.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ func registerGobTypes() {
4040
type Manager interface {
4141
// Header operations
4242
IsHeaderSeen(hash string) bool
43-
SetHeaderSeen(hash string)
43+
SetHeaderSeen(hash string, blockHeight uint64)
4444
GetHeaderDAIncluded(hash string) (uint64, bool)
45-
SetHeaderDAIncluded(hash string, daHeight uint64)
45+
SetHeaderDAIncluded(hash string, daHeight uint64, blockHeight uint64)
4646
RemoveHeaderDAIncluded(hash string)
4747

4848
// Data operations
4949
IsDataSeen(hash string) bool
50-
SetDataSeen(hash string)
50+
SetDataSeen(hash string, blockHeight uint64)
5151
GetDataDAIncluded(hash string) (uint64, bool)
52-
SetDataDAIncluded(hash string, daHeight uint64)
52+
SetDataDAIncluded(hash string, daHeight uint64, blockHeight uint64)
5353

5454
// Pending operations
5555
GetPendingHeaders(ctx context.Context) ([]*types.SignedHeader, error)
@@ -60,13 +60,16 @@ type Manager interface {
6060
NumPendingData() uint64
6161

6262
// Pending events syncing coordination
63-
GetNextPendingEvent(height uint64) *common.DAHeightEvent
64-
SetPendingEvent(height uint64, event *common.DAHeightEvent)
63+
GetNextPendingEvent(blockHeight uint64) *common.DAHeightEvent
64+
SetPendingEvent(blockHeight uint64, event *common.DAHeightEvent)
6565

66-
// Cleanup operations
66+
// Disk operations
6767
SaveToDisk() error
6868
LoadFromDisk() error
6969
ClearFromDisk() error
70+
71+
// Cleanup operations
72+
DeleteHeight(blockHeight uint64)
7073
}
7174

7275
var _ Manager = (*implementation)(nil)
@@ -100,6 +103,7 @@ func NewManager(cfg config.Config, store store.Store, logger zerolog.Logger) (Ma
100103
return nil, fmt.Errorf("failed to create pending data: %w", err)
101104
}
102105

106+
registerGobTypes()
103107
impl := &implementation{
104108
headerCache: headerCache,
105109
dataCache: dataCache,
@@ -130,16 +134,16 @@ func (m *implementation) IsHeaderSeen(hash string) bool {
130134
return m.headerCache.isSeen(hash)
131135
}
132136

133-
func (m *implementation) SetHeaderSeen(hash string) {
134-
m.headerCache.setSeen(hash)
137+
func (m *implementation) SetHeaderSeen(hash string, blockHeight uint64) {
138+
m.headerCache.setSeen(hash, blockHeight)
135139
}
136140

137141
func (m *implementation) GetHeaderDAIncluded(hash string) (uint64, bool) {
138142
return m.headerCache.getDAIncluded(hash)
139143
}
140144

141-
func (m *implementation) SetHeaderDAIncluded(hash string, daHeight uint64) {
142-
m.headerCache.setDAIncluded(hash, daHeight)
145+
func (m *implementation) SetHeaderDAIncluded(hash string, daHeight uint64, blockHeight uint64) {
146+
m.headerCache.setDAIncluded(hash, daHeight, blockHeight)
143147
}
144148

145149
func (m *implementation) RemoveHeaderDAIncluded(hash string) {
@@ -151,16 +155,24 @@ func (m *implementation) IsDataSeen(hash string) bool {
151155
return m.dataCache.isSeen(hash)
152156
}
153157

154-
func (m *implementation) SetDataSeen(hash string) {
155-
m.dataCache.setSeen(hash)
158+
func (m *implementation) SetDataSeen(hash string, blockHeight uint64) {
159+
m.dataCache.setSeen(hash, blockHeight)
156160
}
157161

158162
func (m *implementation) GetDataDAIncluded(hash string) (uint64, bool) {
159163
return m.dataCache.getDAIncluded(hash)
160164
}
161165

162-
func (m *implementation) SetDataDAIncluded(hash string, daHeight uint64) {
163-
m.dataCache.setDAIncluded(hash, daHeight)
166+
func (m *implementation) SetDataDAIncluded(hash string, daHeight uint64, blockHeight uint64) {
167+
m.dataCache.setDAIncluded(hash, daHeight, blockHeight)
168+
}
169+
170+
// DeleteHeight removes from all caches the given height.
171+
// This can be done when a height has been da included.
172+
func (m *implementation) DeleteHeight(blockHeight uint64) {
173+
m.headerCache.deleteAllForHeight(blockHeight)
174+
m.dataCache.deleteAllForHeight(blockHeight)
175+
m.pendingEventsCache.deleteAllForHeight(blockHeight)
164176
}
165177

166178
// Pending operations

block/internal/cache/manager_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ func TestManager_HeaderDataOperations(t *testing.T) {
3939
require.NoError(t, err)
4040

4141
// seen & DA included flags
42-
m.SetHeaderSeen("h1")
43-
m.SetDataSeen("d1")
42+
m.SetHeaderSeen("h1", 1)
43+
m.SetDataSeen("d1", 1)
4444
assert.True(t, m.IsHeaderSeen("h1"))
4545
assert.True(t, m.IsDataSeen("d1"))
4646

47-
m.SetHeaderDAIncluded("h1", 10)
48-
m.SetDataDAIncluded("d1", 11)
47+
m.SetHeaderDAIncluded("h1", 10, 2)
48+
m.SetDataDAIncluded("d1", 11, 2)
4949
_, ok := m.GetHeaderDAIncluded("h1")
5050
assert.True(t, ok)
5151
_, ok = m.GetDataDAIncluded("d1")
@@ -102,10 +102,10 @@ func TestManager_SaveAndLoadFromDisk(t *testing.T) {
102102
// populate caches
103103
hdr := &types.SignedHeader{Header: types.Header{BaseHeader: types.BaseHeader{ChainID: "c", Height: 2}}}
104104
dat := &types.Data{Metadata: &types.Metadata{ChainID: "c", Height: 2}}
105-
m1.SetHeaderSeen("H2")
106-
m1.SetDataSeen("D2")
107-
m1.SetHeaderDAIncluded("H2", 100)
108-
m1.SetDataDAIncluded("D2", 101)
105+
m1.SetHeaderSeen("H2", 2)
106+
m1.SetDataSeen("D2", 2)
107+
m1.SetHeaderDAIncluded("H2", 100, 2)
108+
m1.SetDataDAIncluded("D2", 101, 2)
109109
m1.SetPendingEvent(2, &common.DAHeightEvent{Header: hdr, Data: dat, DaHeight: 99})
110110

111111
// persist

block/internal/submitting/da_submitter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func (s *DASubmitter) SubmitHeaders(ctx context.Context, cache cache.Manager) er
225225
},
226226
func(submitted []*types.SignedHeader, res *coreda.ResultSubmit, gasPrice float64) {
227227
for _, header := range submitted {
228-
cache.SetHeaderDAIncluded(header.Hash().String(), res.Height)
228+
cache.SetHeaderDAIncluded(header.Hash().String(), res.Height, header.Height())
229229
}
230230
// Update last submitted height
231231
if l := len(submitted); l > 0 {
@@ -270,7 +270,7 @@ func (s *DASubmitter) SubmitData(ctx context.Context, cache cache.Manager, signe
270270
},
271271
func(submitted []*types.SignedData, res *coreda.ResultSubmit, gasPrice float64) {
272272
for _, sd := range submitted {
273-
cache.SetDataDAIncluded(sd.Data.DACommitment().String(), res.Height)
273+
cache.SetDataDAIncluded(sd.Data.DACommitment().String(), res.Height, sd.Height())
274274
}
275275
if l := len(submitted); l > 0 {
276276
lastHeight := submitted[l-1].Height()

block/internal/submitting/submitter.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ func (s *Submitter) processDAInclusionLoop() {
234234
if err := s.store.SetMetadata(s.ctx, store.DAIncludedHeightKey, bz); err != nil {
235235
s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("failed to persist DA included height")
236236
}
237+
238+
// Delete height cache for that height
239+
// This can only be performed after the height has been persisted to store
240+
s.cache.DeleteHeight(nextHeight)
237241
}
238242
}
239243
}

0 commit comments

Comments
 (0)