Skip to content

Commit 49f4891

Browse files
committed
address+tapdb: add cache for decimal display
To avoid needing to make a DB query for each asset to find out its decimal display value, we cache it upon first request.
1 parent 4657bf0 commit 49f4891

File tree

5 files changed

+168
-1
lines changed

5 files changed

+168
-1
lines changed

address/book.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ type Storage interface {
137137
// transfer comes in later on.
138138
InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
139139
keyType asset.ScriptKeyType) error
140+
141+
// FetchAllAssetMeta attempts to fetch all asset meta known to the
142+
// database.
143+
FetchAllAssetMeta(
144+
ctx context.Context) (map[asset.ID]*proof.MetaReveal, error)
140145
}
141146

142147
// KeyRing is used to create script and internal keys for Taproot Asset
@@ -190,6 +195,14 @@ type Book struct {
190195
// subscriberMtx guards the subscribers map and access to the
191196
// subscriptionID.
192197
subscriberMtx sync.Mutex
198+
199+
// decimalDisplayCache is a cache for the decimal display value of
200+
// assets. This is used to avoid repeated database queries for the same
201+
// asset ID.
202+
decimalDisplayCache map[asset.ID]fn.Option[uint32]
203+
204+
// decDisplayCacheMtx guards the decimalDisplayCache map.
205+
decDisplayCacheMtx sync.Mutex
193206
}
194207

195208
// A compile-time assertion to make sure Book satisfies the
@@ -203,6 +216,7 @@ func NewBook(cfg BookConfig) *Book {
203216
subscribers: make(
204217
map[uint64]*fn.EventReceiver[*AddrWithKeyInfo],
205218
),
219+
decimalDisplayCache: make(map[asset.ID]fn.Option[uint32]),
206220
}
207221
}
208222

@@ -339,13 +353,61 @@ func (b *Book) FetchAssetMetaForAsset(ctx context.Context,
339353
func (b *Book) DecDisplayForAssetID(ctx context.Context,
340354
id asset.ID) (fn.Option[uint32], error) {
341355

356+
b.decDisplayCacheMtx.Lock()
357+
defer b.decDisplayCacheMtx.Unlock()
358+
359+
// If we don't have anything in the cache, we'll attempt to load it.
360+
// This will be re-attempted every time if there are no assets in the
361+
// database. But this isn't expected to remain the case for long.
362+
if len(b.decimalDisplayCache) == 0 {
363+
// If the cache is empty, we'll populate it with all asset
364+
// metas known to the database.
365+
allMeta, err := b.cfg.Store.FetchAllAssetMeta(ctx)
366+
if err != nil {
367+
return fn.None[uint32](), fmt.Errorf("unable to fetch "+
368+
"all asset meta: %v", err)
369+
}
370+
371+
for assetID, meta := range allMeta {
372+
if meta == nil {
373+
continue
374+
}
375+
376+
displayOpt, err := meta.DecDisplayOption()
377+
if err != nil {
378+
return fn.None[uint32](), fmt.Errorf("unable "+
379+
"to extract decimal display option "+
380+
"for asset %v: %v", assetID, err)
381+
}
382+
383+
b.decimalDisplayCache[assetID] = displayOpt
384+
}
385+
}
386+
387+
// If we have the value in the cache, return it from there.
388+
if displayOpt, ok := b.decimalDisplayCache[id]; ok {
389+
return displayOpt, nil
390+
}
391+
392+
// If we don't have the value in the cache, it was added after we filled
393+
// the cache, and we'll attempt to fetch the asset meta from the
394+
// database instead.
342395
meta, err := b.FetchAssetMetaForAsset(ctx, id)
343396
if err != nil {
344397
return fn.None[uint32](), fmt.Errorf("unable to fetch asset "+
345398
"meta for asset_id=%v :%v", id, err)
346399
}
347400

348-
return meta.DecDisplayOption()
401+
opt, err := meta.DecDisplayOption()
402+
if err != nil {
403+
return fn.None[uint32](), fmt.Errorf("unable to extract "+
404+
"decimal display option for asset %v: %v", id, err)
405+
}
406+
407+
// Store the value in the cache for future lookups.
408+
b.decimalDisplayCache[id] = opt
409+
410+
return opt, nil
349411
}
350412

351413
// NewAddress creates a new Taproot Asset address based on the input parameters.

tapdb/addrs.go

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

8181
// AssetMeta is the metadata record for an asset.
8282
AssetMeta = sqlc.FetchAssetMetaForAssetRow
83+
84+
// AllAssetMetaRow is a type alias for fetching all asset metadata
85+
// records.
86+
AllAssetMetaRow = sqlc.FetchAllAssetMetaRow
8387
)
8488

8589
var (
@@ -180,6 +184,10 @@ type AddrBook interface {
180184
// FetchAssetMetaForAsset fetches the asset meta for a given asset.
181185
FetchAssetMetaForAsset(ctx context.Context,
182186
assetID []byte) (AssetMeta, error)
187+
188+
// FetchAllAssetMeta fetches all asset metadata records from the
189+
// database.
190+
FetchAllAssetMeta(ctx context.Context) ([]AllAssetMetaRow, error)
183191
}
184192

185193
// AddrBookTxOptions defines the set of db txn options the AddrBook
@@ -1164,6 +1172,49 @@ func (t *TapAddressBook) FetchAssetMetaForAsset(ctx context.Context,
11641172
return assetMeta, nil
11651173
}
11661174

1175+
// FetchAllAssetMeta attempts to fetch all asset meta known to the database.
1176+
func (t *TapAddressBook) FetchAllAssetMeta(
1177+
ctx context.Context) (map[asset.ID]*proof.MetaReveal, error) {
1178+
1179+
var assetMetas map[asset.ID]*proof.MetaReveal
1180+
1181+
readOpts := NewAssetStoreReadTx()
1182+
dbErr := t.db.ExecTx(ctx, &readOpts, func(q AddrBook) error {
1183+
dbMetas, err := q.FetchAllAssetMeta(ctx)
1184+
if err != nil {
1185+
return err
1186+
}
1187+
1188+
assetMetas = make(map[asset.ID]*proof.MetaReveal, len(dbMetas))
1189+
for _, dbMeta := range dbMetas {
1190+
// If no record is present, we should get a
1191+
// sql.ErrNoRows error
1192+
// above.
1193+
metaOpt, err := parseAssetMetaReveal(dbMeta.AssetsMetum)
1194+
if err != nil {
1195+
return fmt.Errorf("unable to parse asset "+
1196+
"meta: %w", err)
1197+
}
1198+
1199+
metaOpt.WhenSome(func(meta proof.MetaReveal) {
1200+
var id asset.ID
1201+
copy(id[:], dbMeta.AssetID)
1202+
assetMetas[id] = &meta
1203+
})
1204+
}
1205+
1206+
return nil
1207+
})
1208+
switch {
1209+
case errors.Is(dbErr, sql.ErrNoRows):
1210+
return nil, address.ErrAssetMetaNotFound
1211+
case dbErr != nil:
1212+
return nil, dbErr
1213+
}
1214+
1215+
return assetMetas, nil
1216+
}
1217+
11671218
// insertFullAssetGen inserts a new asset genesis and optional asset group
11681219
// into the database. A placeholder for the asset meta inserted as well.
11691220
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
@@ -1034,6 +1034,13 @@ JOIN assets_meta
10341034
ON assets.meta_data_id = assets_meta.meta_id
10351035
WHERE assets.asset_id = $1;
10361036

1037+
-- name: FetchAllAssetMeta :many
1038+
SELECT sqlc.embed(assets_meta), genesis_assets.asset_id
1039+
FROM assets_meta
1040+
JOIN genesis_assets
1041+
ON genesis_assets.meta_data_id = assets_meta.meta_id
1042+
ORDER BY assets_meta.meta_id;
1043+
10371044
-- Upsert a record into the mint_anchor_uni_commitments table.
10381045
-- If a record with the same batch_id and group_key already exists, update the
10391046
-- existing record. Otherwise, insert a new record.

0 commit comments

Comments
 (0)