Skip to content

Commit 362970f

Browse files
committed
tapdb: add TestUniverseProofCache unit tests
1 parent c4c9bc2 commit 362970f

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

tapdb/multiverse_cache_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/dustin/go-humanize"
910
"github.com/lightninglabs/taproot-assets/asset"
1011
"github.com/lightninglabs/taproot-assets/internal/test"
1112
"github.com/lightninglabs/taproot-assets/mssmt"
@@ -230,6 +231,111 @@ func TestSyncerCacheMemoryUsage(t *testing.T) {
230231
}
231232
}
232233

234+
// TestUniverseProofCache exercises the basic behaviors of the universe proof
235+
// cache to ensure inserts, fetches, evictions, and removals behave as
236+
// expected.
237+
func TestUniverseProofCache(t *testing.T) {
238+
t.Parallel()
239+
240+
// 1 MiB cache for most subtests.
241+
const testCacheSizeBytes = 1 << 20
242+
243+
t.Run("insert and fetch", func(t *testing.T) {
244+
cache := newUniverseProofCache(testCacheSizeBytes)
245+
246+
id := randUniverseID(t, false)
247+
leafKey := randLeafKey(t)
248+
proofs := newTestUniverseProofs(t, 1)
249+
250+
// The cache should miss until an entry is inserted.
251+
require.Nil(t, cache.fetchProof(id, leafKey))
252+
require.EqualValues(t, 0, cache.hit.Load())
253+
require.EqualValues(t, 1, cache.miss.Load())
254+
255+
cache.insertProofs(id, leafKey, proofs)
256+
257+
cached := cache.fetchProof(id, leafKey)
258+
require.Equal(t, proofs, cached)
259+
require.EqualValues(t, 1, cache.hit.Load())
260+
require.EqualValues(t, 1, cache.miss.Load())
261+
})
262+
263+
t.Run("eviction respects capacity", func(t *testing.T) {
264+
// Configure the cache to hold only two entries worth of data so
265+
// the eviction order is deterministic once a third entry
266+
// arrives.
267+
baseProofs := newTestUniverseProofs(t, 1)
268+
proofSize := proofCacheEntrySize(t, baseProofs)
269+
maxBytes := proofSize * 2
270+
cache := newUniverseProofCache(maxBytes)
271+
272+
id := randUniverseID(t, false)
273+
keys := []universe.LeafKey{
274+
randLeafKey(t),
275+
randLeafKey(t),
276+
randLeafKey(t),
277+
}
278+
279+
for _, key := range keys {
280+
cache.insertProofs(id, key, cloneProofSlice(baseProofs))
281+
}
282+
283+
require.Equal(t, 2, cache.cache.Len())
284+
require.LessOrEqual(t, cache.cache.Size(), maxBytes)
285+
286+
// The oldest entry should have been evicted while the most
287+
// recently inserted entries remain.
288+
require.Nil(t, cache.fetchProof(id, keys[0]))
289+
require.NotNil(t, cache.fetchProof(id, keys[1]))
290+
require.NotNil(t, cache.fetchProof(id, keys[2]))
291+
})
292+
293+
t.Run("removals", func(t *testing.T) {
294+
cache := newUniverseProofCache(testCacheSizeBytes)
295+
296+
id1 := randUniverseID(t, false)
297+
id2 := randUniverseID(t, false)
298+
299+
leaf1 := randLeafKey(t)
300+
leaf2 := randLeafKey(t)
301+
302+
cache.insertProofs(id1, leaf1, newTestUniverseProofs(t, 1))
303+
cache.insertProofs(id1, leaf2, newTestUniverseProofs(t, 1))
304+
cache.insertProofs(id2, leaf1, newTestUniverseProofs(t, 1))
305+
306+
cache.RemoveLeafKeyProofs(id1, leaf1)
307+
require.Nil(t, cache.fetchProof(id1, leaf1))
308+
require.NotNil(t, cache.fetchProof(id1, leaf2))
309+
require.NotNil(t, cache.fetchProof(id2, leaf1))
310+
311+
cache.RemoveUniverseProofs(id1)
312+
require.Nil(t, cache.fetchProof(id1, leaf2))
313+
require.NotNil(t, cache.fetchProof(id2, leaf1))
314+
})
315+
316+
t.Run("cache logger default size formatting", func(t *testing.T) {
317+
cache := newUniverseProofCache(testCacheSizeBytes)
318+
319+
require.NotNil(t, cache.cacheLogger.cacheSize)
320+
require.Equal(
321+
t, humanize.Bytes(0), cache.cacheLogger.cacheSize(),
322+
)
323+
324+
id := randUniverseID(t, false)
325+
leafKey := randLeafKey(t)
326+
proofs := newTestUniverseProofs(t, 1)
327+
328+
cache.insertProofs(id, leafKey, proofs)
329+
require.Equal(t, humanize.Bytes(cache.cache.Size()),
330+
cache.cacheLogger.cacheSize())
331+
332+
cache.RemoveLeafKeyProofs(id, leafKey)
333+
require.Equal(
334+
t, humanize.Bytes(0), cache.cacheLogger.cacheSize(),
335+
)
336+
})
337+
}
338+
233339
func queryRoots(t *testing.T, multiverse *MultiverseStore,
234340
pageSize int32) []universe.Root {
235341

@@ -275,3 +381,49 @@ func assertAllLeavesInRoots(t *testing.T, allLeaves []*universe.Item,
275381
"idx %d", leaf.ID.StringForLog(), idx)
276382
}
277383
}
384+
385+
// newTestUniverseProofs returns a slice of random universe proofs for use in
386+
// cache tests.
387+
func newTestUniverseProofs(t *testing.T, count int) []*universe.Proof {
388+
t.Helper()
389+
390+
proofs := make([]*universe.Proof, count)
391+
for i := 0; i < count; i++ {
392+
leaf := randMintingLeaf(
393+
t, asset.RandGenesis(t, asset.Normal), nil,
394+
)
395+
leafCopy := leaf
396+
397+
proofs[i] = &universe.Proof{
398+
Leaf: &leafCopy,
399+
LeafKey: randLeafKey(t),
400+
UniverseRoot: leafCopy.SmtLeafNode(),
401+
UniverseInclusionProof: &mssmt.Proof{},
402+
MultiverseRoot: leafCopy.SmtLeafNode(),
403+
MultiverseInclusionProof: &mssmt.Proof{},
404+
}
405+
}
406+
407+
return proofs
408+
}
409+
410+
// proofCacheEntrySize returns the computed cache size for a single cache entry.
411+
func proofCacheEntrySize(t *testing.T, proofs []*universe.Proof) uint64 {
412+
t.Helper()
413+
414+
cached := cachedProofs(proofs)
415+
size, err := (&cached).Size()
416+
require.NoError(t, err)
417+
require.NotZero(t, size)
418+
419+
return size
420+
}
421+
422+
// cloneProofSlice returns a shallow copy of the provided proof slice so that
423+
// callers can reuse deterministic proof fixtures without sharing slice headers.
424+
func cloneProofSlice(proofs []*universe.Proof) []*universe.Proof {
425+
cloned := make([]*universe.Proof, len(proofs))
426+
copy(cloned, proofs)
427+
428+
return cloned
429+
}

0 commit comments

Comments
 (0)