Skip to content

Commit 40568fa

Browse files
committed
tapdb: improve proof cache key handling and size estimation
- Replace `ProofKey` with `UniverseProofKey` for clearer cache key distinction, enabling removal of individual proofs by leaf key instead of all proofs for a universe ID. - Refactor proof cache methods: add `RemoveLeafKeyProofs` and enhance `RemoveUniverseProofs` for more granular control. - Implement deep size estimation in the `Size` method to improve memory management accuracy. - Change cache size limit from number of proofs to total memory size of all proofs, as proof sizes can vary due to inclusion of parent proof files.
1 parent 03024f2 commit 40568fa

File tree

3 files changed

+95
-63
lines changed

3 files changed

+95
-63
lines changed

tapdb/multiverse.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ func (b *MultiverseStore) UpsertProofLeaf(ctx context.Context,
834834

835835
// Invalidate the cache since we just updated the root.
836836
b.rootNodeCache.wipeCache()
837-
b.proofCache.delProofsForAsset(id)
837+
b.proofCache.RemoveLeafKeyProofs(id, key)
838838
b.leafKeysCache.wipeCache(id.String())
839839
b.syncerCache.addOrReplace(universe.Root{
840840
ID: id,
@@ -952,10 +952,14 @@ func (b *MultiverseStore) UpsertProofLeafBatch(ctx context.Context,
952952
)
953953

954954
for id := range idsToDelete {
955-
b.proofCache.Delete(id)
956955
b.leafKeysCache.wipeCache(id)
957956
}
958957

958+
for idx := range items {
959+
item := items[idx]
960+
b.proofCache.RemoveLeafKeyProofs(item.ID, item.Key)
961+
}
962+
959963
return nil
960964
}
961965

@@ -990,7 +994,7 @@ func (b *MultiverseStore) DeleteUniverse(ctx context.Context,
990994
// Wipe the cache items from this node.
991995
b.rootNodeCache.wipeCache()
992996

993-
b.proofCache.Delete(id.String())
997+
b.proofCache.RemoveUniverseProofs(id)
994998
b.leafKeysCache.wipeCache(id.String())
995999
b.syncerCache.remove(id.Key())
9961000

tapdb/multiverse_cache.go

Lines changed: 87 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ package tapdb
22

33
import (
44
"bytes"
5-
"crypto/sha256"
65
"slices"
76
"sort"
87
"sync"
98
"sync/atomic"
109

1110
"github.com/lightninglabs/neutrino/cache/lru"
12-
"github.com/lightninglabs/taproot-assets/fn"
1311
"github.com/lightninglabs/taproot-assets/universe"
1412
"github.com/lightningnetwork/lnd/lnutils"
1513
)
@@ -67,80 +65,86 @@ func DefaultMultiverseCacheConfig() MultiverseCacheConfig {
6765
}
6866
}
6967

70-
// ProofKey is used to uniquely identify a proof within a universe. This is
71-
// used for the LRU cache for the proofs themselves, which are considered to be
72-
// immutable.
73-
type ProofKey [32]byte
74-
75-
// NewProofKey takes a universe identifier and leaf key, and returns a proof
76-
// key.
77-
func NewProofKey(id universe.Identifier, key universe.LeafKey) ProofKey {
78-
idBytes := id.Bytes()
79-
leafKeyBytes := key.UniverseKey()
80-
81-
// The proof key maps down the ID and the leaf key into a single
82-
// 32-byte value: sha256(id || leaf_key)..
83-
h := sha256.New()
84-
h.Write(idBytes[:])
85-
h.Write(leafKeyBytes[:])
86-
87-
return fn.ToArray[ProofKey](h.Sum(nil))
88-
}
89-
9068
// cachedProofs is a list of cached proof leaves.
9169
type cachedProofs []*universe.Proof
9270

93-
// Size just returns 1 as we're limiting based on the total number of different
94-
// leaf keys we query by. So we might store more than one proof per cache entry
95-
// if the universe key's script key isn't set. But we only want a certain number
96-
// of different keys stored in the cache.
71+
// Size returns the total byte size of all cached proofs.
9772
func (c *cachedProofs) Size() (uint64, error) {
98-
return 1, nil
73+
if c == nil {
74+
return 0, nil
75+
}
76+
77+
totalBytes := uint64(0)
78+
for _, proof := range *c {
79+
if proof == nil {
80+
continue
81+
}
82+
83+
totalBytes += proof.LowerBoundByteSize()
84+
}
85+
86+
return totalBytes, nil
9987
}
10088

10189
// newProofCache creates a new leaf proof cache.
102-
func newProofCache(proofCacheSize uint64) *lru.Cache[ProofKey, *cachedProofs] {
103-
return lru.NewCache[ProofKey, *cachedProofs](proofCacheSize)
90+
//
91+
// nolint: lll
92+
func newProofCache(totalCacheBytesSize uint64) *lru.Cache[UniverseProofKey, *cachedProofs] {
93+
return lru.NewCache[UniverseProofKey, *cachedProofs](
94+
totalCacheBytesSize,
95+
)
10496
}
10597

10698
// universeIDKey is a cache key used to uniquely identify a universe within a
10799
// multiverse tree cache.
108100
type universeIDKey = string
109101

102+
// UniverseProofKey houses the components of a universe proof key. All fields
103+
// must be comparable.
104+
type UniverseProofKey struct {
105+
// uniIDKey is the universe ID key to which the proof belongs.
106+
uniIDKey universe.IdentifierKey
107+
108+
// leafKey is the leaf key of the proof.
109+
leafKeyBytes [32]byte
110+
}
111+
112+
// NewUniverseProofKey creates a new universe proof key.
113+
func NewUniverseProofKey(uniID universe.Identifier,
114+
leafKey universe.LeafKey) UniverseProofKey {
115+
116+
return UniverseProofKey{
117+
uniIDKey: uniID.Key(),
118+
leafKeyBytes: leafKey.UniverseKey(),
119+
}
120+
}
121+
110122
// universeProofCache a map of proof caches for each proof type.
111123
type universeProofCache struct {
112-
proofsPerUniverse uint64
124+
// maxCacheByteSize is the maximum size of the cache in bytes.
125+
maxCacheByteSize uint64
113126

114-
lnutils.SyncMap[universeIDKey, *lru.Cache[ProofKey, *cachedProofs]]
127+
// cache is the LRU cache for the proofs themselves.
128+
cache *lru.Cache[UniverseProofKey, *cachedProofs]
115129

116130
*cacheLogger
117131
}
118132

119133
// newUniverseProofCache creates a new proof cache.
120-
func newUniverseProofCache(proofsPerUniverse uint64) *universeProofCache {
134+
func newUniverseProofCache(maxCacheByteSize uint64) *universeProofCache {
121135
return &universeProofCache{
122-
proofsPerUniverse: proofsPerUniverse,
123-
SyncMap: lnutils.SyncMap[
124-
universeIDKey, *lru.Cache[ProofKey, *cachedProofs],
125-
]{},
126-
cacheLogger: newCacheLogger("universe_proofs"),
136+
maxCacheByteSize: maxCacheByteSize,
137+
cache: newProofCache(maxCacheByteSize),
138+
cacheLogger: newCacheLogger("universe_proofs"),
127139
}
128140
}
129141

130142
// fetchProof reads the cached proof for the given ID and leaf key.
131143
func (p *universeProofCache) fetchProof(id universe.Identifier,
132144
leafKey universe.LeafKey) []*universe.Proof {
133145

134-
// First, get the sub-cache for this universe ID from the map of
135-
// caches.
136-
assetProofCache, _ := p.LoadOrStore(
137-
id.String(), newProofCache(p.proofsPerUniverse),
138-
)
139-
140-
// With that lower level cache obtained, we can check to see if we have
141-
// a hit or not.
142-
proofKey := NewProofKey(id, leafKey)
143-
proofFromCache, err := assetProofCache.Get(proofKey)
146+
uniProofKey := NewUniverseProofKey(id, leafKey)
147+
proofFromCache, err := p.cache.Get(uniProofKey)
144148
if err == nil {
145149
p.Hit()
146150
return *proofFromCache
@@ -155,28 +159,52 @@ func (p *universeProofCache) fetchProof(id universe.Identifier,
155159
func (p *universeProofCache) insertProofs(id universe.Identifier,
156160
leafKey universe.LeafKey, proofs []*universe.Proof) {
157161

158-
assetProofCache, _ := p.LoadOrStore(
159-
id.String(), newProofCache(p.proofsPerUniverse),
160-
)
161-
162-
proofKey := NewProofKey(id, leafKey)
162+
uniProofKey := NewUniverseProofKey(id, leafKey)
163163

164164
log.Debugf("Storing proof(s) in cache (universe_id=%v, leaf_key=%v, "+
165-
"proof_key=%x, count=%d)", id.StringForLog(), leafKey,
166-
proofKey[:], len(proofs))
165+
"count=%d)", id.StringForLog(), leafKey, len(proofs))
167166

168167
proofVal := cachedProofs(proofs)
169-
if _, err := assetProofCache.Put(proofKey, &proofVal); err != nil {
168+
if _, err := p.cache.Put(uniProofKey, &proofVal); err != nil {
170169
log.Errorf("Unable to insert proof into universe proof "+
171170
"cache: %v", err)
172171
}
173172
}
174173

175-
// delProofsForAsset deletes all the proofs for the given asset.
176-
func (p *universeProofCache) delProofsForAsset(id universe.Identifier) {
177-
log.Debugf("Wiping universe proof cache (universe_id=%v)", id)
174+
// RemoveUniverseProofs deletes all the proofs for the given universe ID.
175+
func (p *universeProofCache) RemoveUniverseProofs(id universe.Identifier) {
176+
log.Debugf("Removing universe proofs (universe_id=%s)",
177+
id.StringForLog())
178178

179-
p.Delete(id.String())
179+
targetIDKey := id.Key()
180+
p.cache.Range(
181+
func(key UniverseProofKey, proofs *cachedProofs) bool {
182+
if key.uniIDKey == targetIDKey {
183+
p.cache.Delete(key)
184+
}
185+
return true
186+
},
187+
)
188+
}
189+
190+
// RemoveLeafKeyProofs deletes all the proofs for the given universe ID and leaf
191+
// key.
192+
func (p *universeProofCache) RemoveLeafKeyProofs(id universe.Identifier,
193+
leafKey universe.LeafKey) {
194+
195+
log.Debugf("Removing leaf key proofs (universe_id=%s, leaf_key=%v)",
196+
id.StringForLog(), leafKey)
197+
198+
targetCacheKey := NewUniverseProofKey(id, leafKey)
199+
200+
p.cache.Range(
201+
func(key UniverseProofKey, proofs *cachedProofs) bool {
202+
if key == targetCacheKey {
203+
p.cache.Delete(key)
204+
}
205+
return true
206+
},
207+
)
180208
}
181209

182210
// rootPageQueryKey is a cache key that wraps around a query to fetch all the

tapdb/universe_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ func TestUniverseTreeIsolation(t *testing.T) {
620620
// TODO(roasbeef): need base universe -> universe cache
621621
// invalidation or delete thru multiverse
622622
multiverse.rootNodeCache.wipeCache()
623-
multiverse.proofCache.delProofsForAsset(rootNode.ID)
623+
multiverse.proofCache.RemoveUniverseProofs(rootNode.ID)
624624
}
625625

626626
// The deleted universe should not be present in the multiverse.

0 commit comments

Comments
 (0)