Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tapgarden: use lru.Cache for various new group caches #543

Merged
merged 1 commit into from
Oct 3, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 64 additions & 43 deletions tapgarden/caretaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/neutrino/cache/lru"

"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
Expand Down Expand Up @@ -1263,81 +1265,103 @@ func GenHeaderVerifier(ctx context.Context,
}
}

// GenGroupVeifier generates a group key verification callback function given
// a DB handle.
// assetGroupCacheSize is the size of the cache for group keys.
const assetGroupCacheSize = 10000

// emptyVal is a simple type def around struct{} to use as a dummy value in in
// the cache.
type emptyVal struct{}

// singleCacheValue is a dummy value that can be used to add an element to the
// cache. This should be used when the cache just needs to worry aobut the
// total number of elements, and not also the size (in bytes) of the elements.
type singleCacheValue[T any] struct {
val T
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't embed type param values, so had to use this here.

}

// Size determines how big this entry would be in the cache.
func (s singleCacheValue[T]) Size() (uint64, error) {
return 1, nil
}

// newSingleValue creates a new single cache value.
func newSingleValue[T any](v T) singleCacheValue[T] {
return singleCacheValue[T]{
val: v,
}
}

// emptyCacheVal is a type def for an empty cache value. In this case the cache
// is used more as a set.
type emptyCacheVal = singleCacheValue[emptyVal]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type alias used here to it inherits all the methods.


// GenGroupVeifier generates a group key verification callback function given a
// DB handle.
func GenGroupVerifier(ctx context.Context,
mintingStore MintingStore) func(*btcec.PublicKey) error {

// Cache known group keys that were previously fetched.
assetGroups := fn.NewSet[asset.SerializedKey]()

// verifierLock is a mutex that must be held when using the group
// anchor verifier.
var verifierLock sync.Mutex
assetGroups := lru.NewCache[
asset.SerializedKey, emptyCacheVal](
assetGroupCacheSize,
)

return func(groupKey *btcec.PublicKey) error {
if groupKey == nil {
return fmt.Errorf("cannot verify empty group key")
}

verifierLock.Lock()
defer verifierLock.Unlock()

assetGroupKey := asset.ToSerialized(groupKey)
ok := assetGroups.Contains(assetGroupKey)
if ok {
_, err := assetGroups.Get(assetGroupKey)
if err == nil {
return nil
}

// This query will err if no stored group has a matching
// tweaked group key.
_, err := mintingStore.FetchGroupByGroupKey(
_, err = mintingStore.FetchGroupByGroupKey(
ctx, groupKey,
)
if err != nil {
return fmt.Errorf("%x: %w", assetGroupKey,
ErrGroupKeyUnknown)
}

assetGroups.Add(assetGroupKey)
_, _ = assetGroups.Put(assetGroupKey, emptyCacheVal{})

return nil
}
}

// GenGroupAnchorVerifier generates a caching group anchor verification callback
// function given a DB handle.
// GenGroupAnchorVerifier generates a caching group anchor verification
// callback function given a DB handle.
func GenGroupAnchorVerifier(ctx context.Context,
mintingStore MintingStore) func(*asset.Genesis,
*asset.GroupKey) error {

// Cache anchors for groups that were previously fetched.
groupAnchors := make(map[asset.SerializedKey]*asset.Genesis)

// verifierLock is a mutex that must be held when using the group
// anchor verifier.
var verifierLock sync.Mutex
groupAnchors := lru.NewCache[
asset.SerializedKey, singleCacheValue[*asset.Genesis]](
assetGroupCacheSize,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC with a 10k element cache this would be a few MB max?

)

return func(gen *asset.Genesis, groupKey *asset.GroupKey) error {
verifierLock.Lock()
defer verifierLock.Unlock()

assetGroupKey := asset.ToSerialized(&groupKey.GroupPubKey)
groupAnchor, ok := groupAnchors[assetGroupKey]

if !ok {
groupAnchor, err := groupAnchors.Get(assetGroupKey)
if err != nil {
storedGroup, err := mintingStore.FetchGroupByGroupKey(
ctx, &groupKey.GroupPubKey,
)
if err != nil {
return ErrGroupKeyUnknown
}

groupAnchors[assetGroupKey] = storedGroup.Genesis
groupAnchor = storedGroup.Genesis
groupAnchor = newSingleValue(storedGroup.Genesis)

_, _ = groupAnchors.Put(assetGroupKey, groupAnchor)
}

if gen.ID() != groupAnchor.ID() {
if gen.ID() != groupAnchor.val.ID() {
return ErrGenesisNotGroupAnchor
}

Expand All @@ -1353,20 +1377,15 @@ func GenRawGroupAnchorVerifier(ctx context.Context) func(*asset.Genesis,
*asset.GroupKey) error {

// Cache group anchors we already verified.
groupAnchors := make(map[asset.SerializedKey]*asset.Genesis)

// verifierLock is a mutex that must be held when using the group
// anchor verifier.
var verifierLock sync.Mutex
groupAnchors := lru.NewCache[
asset.SerializedKey, singleCacheValue[*asset.Genesis]](
assetGroupCacheSize,
)

return func(gen *asset.Genesis, groupKey *asset.GroupKey) error {
verifierLock.Lock()
defer verifierLock.Unlock()

assetGroupKey := asset.ToSerialized(&groupKey.GroupPubKey)
groupAnchor, ok := groupAnchors[assetGroupKey]

if !ok {
groupAnchor, err := groupAnchors.Get(assetGroupKey)
if err != nil {
// TODO(jhb): add tapscript root support
singleTweak := gen.ID()
tweakedGroupKey, err := asset.GroupPubKey(
Expand All @@ -1381,12 +1400,14 @@ func GenRawGroupAnchorVerifier(ctx context.Context) func(*asset.Genesis,
return ErrGenesisNotGroupAnchor
}

groupAnchors[assetGroupKey] = gen
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here groupAnchor wasn't re assigned.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah returns nil anyway, so np.

groupAnchor = newSingleValue(gen)

_, _ = groupAnchors.Put(assetGroupKey, groupAnchor)

return nil
}

if gen.ID() != groupAnchor.ID() {
if gen.ID() != groupAnchor.val.ID() {
return ErrGenesisNotGroupAnchor
}

Expand Down