Skip to content

Commit 548b1b2

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 c2dd6ce commit 548b1b2

File tree

6 files changed

+175
-1
lines changed

6 files changed

+175
-1
lines changed

address/book.go

Lines changed: 63 additions & 1 deletion
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

@@ -428,13 +442,61 @@ func (b *Book) FetchAssetMetaForAsset(ctx context.Context,
428442
func (b *Book) DecDisplayForAssetID(ctx context.Context,
429443
id asset.ID) (fn.Option[uint32], error) {
430444

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.
431484
meta, err := b.FetchAssetMetaForAsset(ctx, id)
432485
if err != nil {
433486
return fn.None[uint32](), fmt.Errorf("unable to fetch asset "+
434487
"meta for asset_id=%v :%v", id, err)
435488
}
436489

437-
return meta.DecDisplayOption()
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
438500
}
439501

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

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

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)