Skip to content

Commit 8dce848

Browse files
authored
Merge pull request #1650 from lightninglabs/uni-stats-meta-cache
universe: fix asset stats being extremely slow
2 parents a101573 + 548b1b2 commit 8dce848

File tree

8 files changed

+203
-23
lines changed

8 files changed

+203
-23
lines changed

address/book.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ type Storage interface {
150150
// transfer comes in later on.
151151
InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
152152
keyType asset.ScriptKeyType) error
153+
154+
// FetchAllAssetMeta attempts to fetch all asset meta known to the
155+
// database.
156+
FetchAllAssetMeta(
157+
ctx context.Context) (map[asset.ID]*proof.MetaReveal, error)
153158
}
154159

155160
// KeyRing is used to create script and internal keys for Taproot Asset
@@ -203,6 +208,14 @@ type Book struct {
203208
// subscriberMtx guards the subscribers map and access to the
204209
// subscriptionID.
205210
subscriberMtx sync.Mutex
211+
212+
// decimalDisplayCache is a cache for the decimal display value of
213+
// assets. This is used to avoid repeated database queries for the same
214+
// asset ID.
215+
decimalDisplayCache map[asset.ID]fn.Option[uint32]
216+
217+
// decDisplayCacheMtx guards the decimalDisplayCache map.
218+
decDisplayCacheMtx sync.Mutex
206219
}
207220

208221
// A compile-time assertion to make sure Book satisfies the
@@ -216,6 +229,7 @@ func NewBook(cfg BookConfig) *Book {
216229
subscribers: make(
217230
map[uint64]*fn.EventReceiver[*AddrWithKeyInfo],
218231
),
232+
decimalDisplayCache: make(map[asset.ID]fn.Option[uint32]),
219233
}
220234
}
221235

@@ -423,6 +437,68 @@ func (b *Book) FetchAssetMetaForAsset(ctx context.Context,
423437
return meta, nil
424438
}
425439

440+
// DecDisplayForAssetID attempts to fetch the meta reveal for a specific asset
441+
// ID and extract the decimal display value from it.
442+
func (b *Book) DecDisplayForAssetID(ctx context.Context,
443+
id asset.ID) (fn.Option[uint32], error) {
444+
445+
b.decDisplayCacheMtx.Lock()
446+
defer b.decDisplayCacheMtx.Unlock()
447+
448+
// If we don't have anything in the cache, we'll attempt to load it.
449+
// This will be re-attempted every time if there are no assets in the
450+
// database. But this isn't expected to remain the case for long.
451+
if len(b.decimalDisplayCache) == 0 {
452+
// If the cache is empty, we'll populate it with all asset
453+
// metas known to the database.
454+
allMeta, err := b.cfg.Store.FetchAllAssetMeta(ctx)
455+
if err != nil {
456+
return fn.None[uint32](), fmt.Errorf("unable to fetch "+
457+
"all asset meta: %v", err)
458+
}
459+
460+
for assetID, meta := range allMeta {
461+
if meta == nil {
462+
continue
463+
}
464+
465+
displayOpt, err := meta.DecDisplayOption()
466+
if err != nil {
467+
return fn.None[uint32](), fmt.Errorf("unable "+
468+
"to extract decimal display option "+
469+
"for asset %v: %v", assetID, err)
470+
}
471+
472+
b.decimalDisplayCache[assetID] = displayOpt
473+
}
474+
}
475+
476+
// If we have the value in the cache, return it from there.
477+
if displayOpt, ok := b.decimalDisplayCache[id]; ok {
478+
return displayOpt, nil
479+
}
480+
481+
// If we don't have the value in the cache, it was added after we filled
482+
// the cache, and we'll attempt to fetch the asset meta from the
483+
// database instead.
484+
meta, err := b.FetchAssetMetaForAsset(ctx, id)
485+
if err != nil {
486+
return fn.None[uint32](), fmt.Errorf("unable to fetch asset "+
487+
"meta for asset_id=%v :%v", id, err)
488+
}
489+
490+
opt, err := meta.DecDisplayOption()
491+
if err != nil {
492+
return fn.None[uint32](), fmt.Errorf("unable to extract "+
493+
"decimal display option for asset %v: %v", id, err)
494+
}
495+
496+
// Store the value in the cache for future lookups.
497+
b.decimalDisplayCache[id] = opt
498+
499+
return opt, nil
500+
}
501+
426502
// NewAddress creates a new Taproot Asset address based on the input parameters.
427503
func (b *Book) NewAddress(ctx context.Context, addrVersion Version,
428504
specifier asset.Specifier, amount uint64,

address/book_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ type MockStorage struct {
175175
mock.Mock
176176
}
177177

178+
func (m *MockStorage) FetchAllAssetMeta(
179+
ctx context.Context) (map[asset.ID]*proof.MetaReveal, error) {
180+
181+
args := m.Called(ctx)
182+
return args.Get(0).(map[asset.ID]*proof.MetaReveal), args.Error(1)
183+
}
184+
178185
func (m *MockStorage) AddrByScriptKeyAndVersion(ctx context.Context,
179186
key *btcec.PublicKey, version Version) (*AddrWithKeyInfo, error) {
180187

chain_bridge.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import (
2727

2828
const (
2929
// maxNumBlocksInCache is the maximum number of blocks we'll cache
30-
// timestamps for. With 100k blocks we should only take up approximately
31-
// 800kB of memory (4 bytes for the block height and 4 bytes for the
30+
// timestamps for. With 400k blocks we should only take up approximately
31+
// 3200kB of memory (4 bytes for the block height and 4 bytes for the
3232
// timestamp, not including any map/cache overhead).
33-
maxNumBlocksInCache = 100_000
33+
maxNumBlocksInCache = 400_000
3434
)
3535

3636
// cacheableTimestamp is a wrapper around an uint32 that can be used as a value

rpcserver.go

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,9 @@ func (r *rpcServer) MarshalChainAsset(ctx context.Context, a asset.ChainAsset,
12291229
case meta != nil:
12301230
decDisplay, err = meta.DecDisplayOption()
12311231
default:
1232-
decDisplay, err = r.DecDisplayForAssetID(ctx, a.ID())
1232+
decDisplay, err = r.cfg.AddrBook.DecDisplayForAssetID(
1233+
ctx, a.ID(),
1234+
)
12331235
}
12341236
if err != nil {
12351237
return nil, err
@@ -5475,7 +5477,9 @@ func (r *rpcServer) AssetLeaves(ctx context.Context,
54755477
for i, assetLeaf := range assetLeaves {
54765478
assetLeaf := assetLeaf
54775479

5478-
decDisplay, err := r.DecDisplayForAssetID(ctx, assetLeaf.ID())
5480+
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(
5481+
ctx, assetLeaf.ID(),
5482+
)
54795483
if err != nil {
54805484
return nil, err
54815485
}
@@ -5579,7 +5583,9 @@ func (r *rpcServer) marshalUniverseProofLeaf(ctx context.Context,
55795583
return nil, err
55805584
}
55815585

5582-
decDisplay, err := r.DecDisplayForAssetID(ctx, proof.Leaf.ID())
5586+
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(
5587+
ctx, proof.Leaf.ID(),
5588+
)
55835589
if err != nil {
55845590
return nil, err
55855591
}
@@ -6047,7 +6053,7 @@ func (r *rpcServer) marshalUniverseDiff(ctx context.Context,
60476053

60486054
leaves := make([]*unirpc.AssetLeaf, len(diff.NewLeafProofs))
60496055
for i, leaf := range diff.NewLeafProofs {
6050-
decDisplay, err := r.DecDisplayForAssetID(
6056+
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(
60516057
ctx, leaf.ID(),
60526058
)
60536059
if err != nil {
@@ -6542,7 +6548,7 @@ func (r *rpcServer) marshalAssetSyncSnapshot(ctx context.Context,
65426548
AnchorPoint: a.AnchorPoint.String(),
65436549
}
65446550

6545-
decDisplay, err := r.DecDisplayForAssetID(ctx, a.AssetID)
6551+
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(ctx, a.AssetID)
65466552
if err == nil {
65476553
decDisplay.WhenSome(func(u uint32) {
65486554
rpcAsset.DecimalDisplay = u
@@ -8559,20 +8565,6 @@ func encodeVirtualPackets(packets []*tappsbt.VPacket) ([][]byte, error) {
85598565
return rawPackets, nil
85608566
}
85618567

8562-
// DecDisplayForAssetID attempts to fetch the meta reveal for a specific asset
8563-
// ID and extract the decimal display value from it.
8564-
func (r *rpcServer) DecDisplayForAssetID(ctx context.Context,
8565-
id asset.ID) (fn.Option[uint32], error) {
8566-
8567-
meta, err := r.cfg.AddrBook.FetchAssetMetaForAsset(ctx, id)
8568-
if err != nil {
8569-
return fn.None[uint32](), fmt.Errorf("unable to fetch asset "+
8570-
"meta for asset_id=%v :%v", id, err)
8571-
}
8572-
8573-
return meta.DecDisplayOption()
8574-
}
8575-
85768568
// getInboundPolicy returns the policy of the given channel that points towards
85778569
// our node, so it's the policy set by the remote peer.
85788570
func (r *rpcServer) getInboundPolicy(ctx context.Context, chanID uint64,
@@ -8776,7 +8768,7 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
87768768

87778769
// The final piece of information we need is the decimal display
87788770
// information for this asset ID.
8779-
decDisplay, err := r.DecDisplayForAssetID(ctx, assetID)
8771+
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(ctx, assetID)
87808772
if err != nil {
87818773
return nil, err
87828774
}

tapdb/addrs.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ type (
8686

8787
// AssetMeta is the metadata record for an asset.
8888
AssetMeta = sqlc.FetchAssetMetaForAssetRow
89+
90+
// AllAssetMetaRow is a type alias for fetching all asset metadata
91+
// records.
92+
AllAssetMetaRow = sqlc.FetchAllAssetMetaRow
8993
)
9094

9195
var (
@@ -186,6 +190,10 @@ type AddrBook interface {
186190
// FetchAssetMetaForAsset fetches the asset meta for a given asset.
187191
FetchAssetMetaForAsset(ctx context.Context,
188192
assetID []byte) (AssetMeta, error)
193+
194+
// FetchAllAssetMeta fetches all asset metadata records from the
195+
// database.
196+
FetchAllAssetMeta(ctx context.Context) ([]AllAssetMetaRow, error)
189197
}
190198

191199
// AddrBookTxOptions defines the set of db txn options the AddrBook
@@ -1252,6 +1260,49 @@ func (t *TapAddressBook) FetchAssetMetaForAsset(ctx context.Context,
12521260
return assetMeta, nil
12531261
}
12541262

1263+
// FetchAllAssetMeta attempts to fetch all asset meta known to the database.
1264+
func (t *TapAddressBook) FetchAllAssetMeta(
1265+
ctx context.Context) (map[asset.ID]*proof.MetaReveal, error) {
1266+
1267+
var assetMetas map[asset.ID]*proof.MetaReveal
1268+
1269+
readOpts := NewAssetStoreReadTx()
1270+
dbErr := t.db.ExecTx(ctx, &readOpts, func(q AddrBook) error {
1271+
dbMetas, err := q.FetchAllAssetMeta(ctx)
1272+
if err != nil {
1273+
return err
1274+
}
1275+
1276+
assetMetas = make(map[asset.ID]*proof.MetaReveal, len(dbMetas))
1277+
for _, dbMeta := range dbMetas {
1278+
// If no record is present, we should get a
1279+
// sql.ErrNoRows error
1280+
// above.
1281+
metaOpt, err := parseAssetMetaReveal(dbMeta.AssetsMetum)
1282+
if err != nil {
1283+
return fmt.Errorf("unable to parse asset "+
1284+
"meta: %w", err)
1285+
}
1286+
1287+
metaOpt.WhenSome(func(meta proof.MetaReveal) {
1288+
var id asset.ID
1289+
copy(id[:], dbMeta.AssetID)
1290+
assetMetas[id] = &meta
1291+
})
1292+
}
1293+
1294+
return nil
1295+
})
1296+
switch {
1297+
case errors.Is(dbErr, sql.ErrNoRows):
1298+
return nil, address.ErrAssetMetaNotFound
1299+
case dbErr != nil:
1300+
return nil, dbErr
1301+
}
1302+
1303+
return assetMetas, nil
1304+
}
1305+
12551306
// insertFullAssetGen inserts a new asset genesis and optional asset group
12561307
// into the database. A placeholder for the asset meta inserted as well.
12571308
func insertFullAssetGen(ctx context.Context,

tapdb/sqlc/assets.sql.go

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/queries/assets.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,13 @@ JOIN assets_meta
10461046
ON assets.meta_data_id = assets_meta.meta_id
10471047
WHERE assets.asset_id = $1;
10481048

1049+
-- name: FetchAllAssetMeta :many
1050+
SELECT sqlc.embed(assets_meta), genesis_assets.asset_id
1051+
FROM assets_meta
1052+
JOIN genesis_assets
1053+
ON genesis_assets.meta_data_id = assets_meta.meta_id
1054+
ORDER BY assets_meta.meta_id;
1055+
10491056
-- name: UpsertMintAnchorUniCommitment :one
10501057
-- Upsert a record into the mint_anchor_uni_commitments table.
10511058
-- If a record with the same batch ID and tx output index already exists, update

0 commit comments

Comments
 (0)