Skip to content

Commit

Permalink
Allow fetching metadata by any property of AssetID
Browse files Browse the repository at this point in the history
  • Loading branch information
jessepinho committed Mar 12, 2024
1 parent 6ef9463 commit 88312f0
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/router/src/grpc/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface IndexedDbMock {
subscribe?: (table: string) => Partial<AsyncIterable<Mock>>;
getSwapByCommitment?: Mock;
getEpochByHeight?: Mock;
saveAssetsMetadata?: Mock;
}
export interface TendermintMock {
broadcastTx?: Mock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('AssetMetadataById request handler', () => {

mockIndexedDb = {
getAssetsMetadata: vi.fn(),
saveAssetsMetadata: vi.fn(),
};
mockShieldedPool = {
assetMetadata: vi.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import { getAssetMetadata } from './helpers';

export const assetMetadataById: Impl['assetMetadataById'] = async (req, ctx) => {
if (!req.assetId) throw new Error('No asset id passed in request');
if (!req.assetId.altBaseDenom && !req.assetId.altBech32m && !req.assetId.inner.length) {
throw new Error(
'Either `inner` or `altBaseDenom` must be set on the asset ID passed in the `assetMetadataById` request',
);
}

const services = ctx.values.get(servicesCtx);
const { indexedDb, querier } = await services.getWalletServices();

const denomMetadata = await getAssetMetadata(req.assetId, indexedDb, querier);

return { denomMetadata };
Expand Down
14 changes: 12 additions & 2 deletions packages/router/src/grpc/view-protocol-server/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ export const getAssetMetadata = async (
const localMetadata = await indexedDb.getAssetsMetadata(targetAsset);
if (localMetadata) return localMetadata;

// If not available locally, query the metadata from the node.
// If not available locally, query the metadata from the node and save it to
// the internal database.
const nodeMetadata = await querier.shieldedPool.assetMetadata(targetAsset);
if (nodeMetadata) return nodeMetadata;
if (nodeMetadata) {
/**
* @todo: If possible, save asset metadata proactively if we might need it
* for a token that the current user doesn't hold. For example, validator
* delegation tokens could be generated and saved to the database at each
* epoch boundary.
*/
void indexedDb.saveAssetsMetadata(nodeMetadata);
return nodeMetadata;
}

// If the metadata is not found, throw an error with details about the asset.
throw new Error(`No denom metadata found for asset: ${JSON.stringify(targetAsset.toJson())}`);
Expand Down
40 changes: 35 additions & 5 deletions packages/storage/src/indexed-db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { IdbCursorSource } from './stream';

import '@penumbra-zone/polyfills/ReadableStream[Symbol.asyncIterator]';
import { ValidatorInfo } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb';
import { getIdentityKeyFromValidatorInfo } from '@penumbra-zone/getters';
import { bech32AssetId, getIdentityKeyFromValidatorInfo } from '@penumbra-zone/getters';
import { Transaction } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb';

interface IndexedDbProps {
Expand Down Expand Up @@ -177,11 +177,41 @@ export class IndexedDb implements IndexedDbInterface {
});
}

/**
* Gets metadata by asset ID.
*
* If possible, pass an `AssetId` with a populated `inner` property, as that
* is by far the fastest way to retrieve metadata. However, you can also pass
* an `AssetId` with either the `altBaseDenom` or `altBech32m` properties
* populated. In those cases, `getAssetsMetadata` will iterate over every
* metadata in the `ASSETS` table until it finds a match.
*/
async getAssetsMetadata(assetId: AssetId): Promise<Metadata | undefined> {
const key = uint8ArrayToBase64(assetId.inner);
const json = await this.db.get('ASSETS', key);
if (!json) return undefined;
return Metadata.fromJson(json);
if (!assetId.inner.length && !assetId.altBaseDenom && !assetId.altBech32m) return undefined;

if (assetId.inner.length) {
const key = uint8ArrayToBase64(assetId.inner);
const json = await this.db.get('ASSETS', key);
if (!json) return undefined;
return Metadata.fromJson(json);
}

if (assetId.altBaseDenom || assetId.altBech32m) {
for await (const cursor of this.db.transaction('ASSETS').store) {
const metadata = Metadata.fromJson(cursor.value);

if (metadata.base === assetId.altBaseDenom) return metadata;

if (
metadata.penumbraAssetId &&
bech32AssetId(metadata.penumbraAssetId) === assetId.altBech32m
) {
return metadata;
}
}
}

return undefined;
}

async *iterateAssetsMetadata() {
Expand Down

0 comments on commit 88312f0

Please sign in to comment.