From 4687f72ecafd3673ba22193ac5baa3ac87bdb6ca Mon Sep 17 00:00:00 2001 From: Angel Castillo Date: Mon, 27 Jan 2025 16:15:12 +0800 Subject: [PATCH 1/2] feat(cardano-services): blockfrost chain history provider now caches transactions locally --- .../BlockfrostChainHistoryProvider.ts | 20 ++++++++++++++----- .../BlockfrostChainHistoryProvider.test.ts | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/cardano-services-client/src/ChainHistoryProvider/BlockfrostChainHistoryProvider.ts b/packages/cardano-services-client/src/ChainHistoryProvider/BlockfrostChainHistoryProvider.ts index d766a68e376..95e06359543 100644 --- a/packages/cardano-services-client/src/ChainHistoryProvider/BlockfrostChainHistoryProvider.ts +++ b/packages/cardano-services-client/src/ChainHistoryProvider/BlockfrostChainHistoryProvider.ts @@ -34,6 +34,7 @@ type BlockfrostTx = Pick a.block_height - b.block_height || a.tx_index - b.tx_index; export class BlockfrostChainHistoryProvider extends BlockfrostProvider implements ChainHistoryProvider { + private readonly cache: Map = new Map(); private networkInfoProvider: NetworkInfoProvider; constructor(client: BlockfrostClient, networkInfoProvider: NetworkInfoProvider, logger: Logger) { @@ -475,11 +476,20 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement } public async transactionsByHashes({ ids }: TransactionsByIdsArgs): Promise { - try { - return Promise.all(ids.map((id) => this.fetchTransaction(id))); - } catch (error) { - throw this.toProviderError(error); - } + return Promise.all( + ids.map(async (id) => { + if (this.cache.has(id)) { + return this.cache.get(id)!; + } + try { + const fetchedTransaction = await this.fetchTransaction(id); + this.cache.set(id, fetchedTransaction); + return fetchedTransaction; + } catch (error) { + throw this.toProviderError(error); + } + }) + ); } // eslint-disable-next-line sonarjs/cognitive-complexity diff --git a/packages/cardano-services-client/test/ChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts b/packages/cardano-services-client/test/ChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts index b461131e711..4ecb825b725 100644 --- a/packages/cardano-services-client/test/ChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts +++ b/packages/cardano-services-client/test/ChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts @@ -590,5 +590,25 @@ describe('blockfrostChainHistoryProvider', () => { expect(response[0]).toEqual(expectedHydratedTxCBOR); }); }); + + test('caches transactions and returns them from the cache on subsequent calls', async () => { + const firstResponse = await provider.transactionsByHashes({ + ids: ['1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477' as Cardano.TransactionId] + }); + + expect(firstResponse).toHaveLength(1); + expect(firstResponse[0]).toEqual(expectedHydratedTxCBOR); + expect(request).toHaveBeenCalled(); + + request.mockClear(); + + const secondResponse = await provider.transactionsByHashes({ + ids: ['1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477' as Cardano.TransactionId] + }); + + expect(secondResponse).toHaveLength(1); + expect(secondResponse[0]).toEqual(expectedHydratedTxCBOR); + expect(request).not.toHaveBeenCalled(); + }); }); }); From 8cb0dacdee74b9e341af091f88d408003e5f002e Mon Sep 17 00:00:00 2001 From: Angel Castillo Date: Mon, 27 Jan 2025 16:26:05 +0800 Subject: [PATCH 2/2] feat(cardano-services): blockfrost utxo provider now caches utxos locally --- .../UtxoProvider/BlockfrostUtxoProvider.ts | 16 ++++++++++++- .../test/Utxo/BlockfrostUtxoProvider.test.ts | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/cardano-services-client/src/UtxoProvider/BlockfrostUtxoProvider.ts b/packages/cardano-services-client/src/UtxoProvider/BlockfrostUtxoProvider.ts index 28d2fb2c935..c021e547762 100644 --- a/packages/cardano-services-client/src/UtxoProvider/BlockfrostUtxoProvider.ts +++ b/packages/cardano-services-client/src/UtxoProvider/BlockfrostUtxoProvider.ts @@ -3,6 +3,8 @@ import { Cardano, Serialization, UtxoByAddressesArgs, UtxoProvider } from '@card import type { Responses } from '@blockfrost/blockfrost-js'; export class BlockfrostUtxoProvider extends BlockfrostProvider implements UtxoProvider { + private readonly cache: Map = new Map(); + protected async fetchUtxos(addr: Cardano.PaymentAddress, paginationQueryString: string): Promise { const queryString = `addresses/${addr.toString()}/utxos?${paginationQueryString}`; const utxos = await this.request(queryString); @@ -27,7 +29,11 @@ export class BlockfrostUtxoProvider extends BlockfrostProvider implements UtxoPr }); } protected async fetchDetailsFromCBOR(hash: string) { - return this.fetchCBOR(hash) + if (this.cache.has(hash)) { + return this.cache.get(hash); + } + + const result = await this.fetchCBOR(hash) .then((cbor) => { const tx = Serialization.Transaction.fromCbor(Serialization.TxCBOR(cbor)).toCore(); this.logger.info('Fetched details from CBOR for tx', hash); @@ -37,7 +43,15 @@ export class BlockfrostUtxoProvider extends BlockfrostProvider implements UtxoPr this.logger.warn('Failed to fetch details from CBOR for tx', hash, error); return null; }); + + if (!result) { + return null; + } + + this.cache.set(hash, result); + return result; } + public async utxoByAddresses({ addresses }: UtxoByAddressesArgs): Promise { try { const utxoResults = await Promise.all( diff --git a/packages/cardano-services-client/test/Utxo/BlockfrostUtxoProvider.test.ts b/packages/cardano-services-client/test/Utxo/BlockfrostUtxoProvider.test.ts index ed1e7f64b4f..4255cf821af 100644 --- a/packages/cardano-services-client/test/Utxo/BlockfrostUtxoProvider.test.ts +++ b/packages/cardano-services-client/test/Utxo/BlockfrostUtxoProvider.test.ts @@ -79,5 +79,29 @@ describe('blockfrostUtxoProvider', () => { } }); }); + + test('does not call txs/${hash}/cbor when data is cached', async () => { + const txHash = '0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5'; + + mockResponses(request, [ + [`txs/${txHash}/cbor`, 'mockedCBORData'], + [`addresses/${address.toString()}/utxos?page=1&count=100`, generateUtxoResponseMock(1)], + [`addresses/${address.toString()}/utxos?page=2&count=100`, generateUtxoResponseMock(0)] + ]); + + const firstResponse = await provider.utxoByAddresses({ addresses: [address] }); + + expect(firstResponse).toBeTruthy(); + expect(firstResponse.length).toBe(1); + + expect(request).toHaveBeenCalled(); + request.mockClear(); + + const secondResponse = await provider.utxoByAddresses({ addresses: [address] }); + + expect(secondResponse).toEqual(firstResponse); + + expect(request).not.toHaveBeenCalledWith(`txs/${txHash}/cbor`); + }); }); });