Skip to content

Commit 71afab0

Browse files
feat: integrate persistent cache with blockchain providers (#1697)
1 parent 85f7456 commit 71afab0

File tree

11 files changed

+347
-229
lines changed

11 files changed

+347
-229
lines changed

apps/browser-extension-wallet/package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@
4141
},
4242
"dependencies": {
4343
"@ant-design/icons": "^4.7.0",
44-
"@cardano-sdk/cardano-services-client": "0.26.2",
45-
"@cardano-sdk/core": "0.45.1",
46-
"@cardano-sdk/dapp-connector": "0.13.4",
47-
"@cardano-sdk/input-selection": "0.14.2",
48-
"@cardano-sdk/tx-construction": "0.26.1",
49-
"@cardano-sdk/util": "0.15.6",
50-
"@cardano-sdk/util-rxjs": "0.9.5",
51-
"@cardano-sdk/wallet": "0.51.9",
52-
"@cardano-sdk/web-extension": "0.38.10",
44+
"@cardano-sdk/cardano-services-client": "0.26.3",
45+
"@cardano-sdk/core": "0.45.2",
46+
"@cardano-sdk/dapp-connector": "0.13.5",
47+
"@cardano-sdk/input-selection": "0.14.3",
48+
"@cardano-sdk/tx-construction": "0.26.2",
49+
"@cardano-sdk/util": "0.15.7",
50+
"@cardano-sdk/util-rxjs": "0.9.6",
51+
"@cardano-sdk/wallet": "0.51.10",
52+
"@cardano-sdk/web-extension": "0.38.14",
5353
"@emurgo/cip14-js": "~3.0.1",
5454
"@input-output-hk/lace-ui-toolkit": "3.2.1",
5555
"@lace/cardano": "0.1.0",
@@ -104,7 +104,7 @@
104104
"zustand": "3.5.14"
105105
},
106106
"devDependencies": {
107-
"@cardano-sdk/hardware-ledger": "0.15.2",
107+
"@cardano-sdk/hardware-ledger": "0.15.3",
108108
"@emurgo/cardano-message-signing-asmjs": "1.0.1",
109109
"@openpgp/web-stream-tools": "0.0.11-patch-0",
110110
"@pdfme/common": "^4.0.2",

apps/browser-extension-wallet/src/lib/scripts/background/config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { storage } from 'webextension-polyfill';
12
import axiosFetchAdapter from '@shiroyasha9/axios-fetch-adapter';
23
import { Wallet } from '@lace/cardano';
34
import { RemoteApiProperties, RemoteApiPropertyType } from '@cardano-sdk/web-extension';
@@ -62,7 +63,8 @@ export const getProviders = async (chainName: Wallet.ChainName): Promise<Wallet.
6263
logger,
6364
experiments: {
6465
useWebSocket: isExperimentEnabled(ExperimentName.WEBSOCKET_API)
65-
}
66+
},
67+
extensionLocalStorage: storage.local
6668
});
6769
};
6870

packages/cardano/package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@
4040
"watch": "yarn build --watch"
4141
},
4242
"dependencies": {
43-
"@cardano-sdk/cardano-services-client": "0.26.2",
44-
"@cardano-sdk/core": "0.45.1",
45-
"@cardano-sdk/crypto": "0.2.1",
46-
"@cardano-sdk/hardware-ledger": "0.15.2",
47-
"@cardano-sdk/hardware-trezor": "0.7.1",
48-
"@cardano-sdk/key-management": "0.27.1",
49-
"@cardano-sdk/tx-construction": "0.26.1",
50-
"@cardano-sdk/util": "0.15.6",
51-
"@cardano-sdk/wallet": "0.51.9",
52-
"@cardano-sdk/web-extension": "0.38.10",
43+
"@cardano-sdk/cardano-services-client": "0.26.3",
44+
"@cardano-sdk/core": "0.45.2",
45+
"@cardano-sdk/crypto": "0.2.2",
46+
"@cardano-sdk/hardware-ledger": "0.15.3",
47+
"@cardano-sdk/hardware-trezor": "0.7.2",
48+
"@cardano-sdk/key-management": "0.27.2",
49+
"@cardano-sdk/tx-construction": "0.26.2",
50+
"@cardano-sdk/util": "0.15.7",
51+
"@cardano-sdk/wallet": "0.51.10",
52+
"@cardano-sdk/web-extension": "0.38.14",
5353
"@lace/common": "0.1.0",
5454
"@ledgerhq/devices": "^8.4.4",
5555
"@stablelib/chacha20poly1305": "1.0.1",
@@ -73,7 +73,7 @@
7373
},
7474
"devDependencies": {
7575
"@blockfrost/blockfrost-js": "^5.5.0",
76-
"@cardano-sdk/util-dev": "0.25.4",
76+
"@cardano-sdk/util-dev": "0.25.5",
7777
"@emurgo/cardano-message-signing-browser": "1.0.1",
7878
"@types/webextension-polyfill": "0.10.0",
7979
"axios": "^1.7.4",

packages/cardano/src/wallet/lib/__tests__/blockfrost-input-resolver.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,25 @@ describe('BlockfrostInputResolver', () => {
2222
warn: jest.fn()
2323
} as unknown as jest.Mocked<Logger>;
2424

25-
resolver = new BlockfrostInputResolver(clientMock, loggerMock);
25+
// eslint-disable-next-line unicorn/consistent-function-scoping
26+
const createProviderCache = () => {
27+
const cache = new Map();
28+
return {
29+
async get(key: string) {
30+
return cache.get(key);
31+
},
32+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
33+
async set(key: string, val: any) {
34+
cache.set(key, val);
35+
}
36+
};
37+
};
38+
39+
resolver = new BlockfrostInputResolver({
40+
cache: createProviderCache(),
41+
client: clientMock,
42+
logger: loggerMock
43+
});
2644
});
2745

2846
afterEach(() => {

packages/cardano/src/wallet/lib/blockfrost-input-resolver.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable unicorn/no-null, @typescript-eslint/no-non-null-assertion */
22
import { Cardano } from '@cardano-sdk/core';
33
import { BlockfrostClient, BlockfrostError, BlockfrostToCore } from '@cardano-sdk/cardano-services-client';
4+
import type { Cache } from '@cardano-sdk/util';
45
import { Logger } from 'ts-log';
56
import { Responses } from '@blockfrost/blockfrost-js';
67

@@ -14,21 +15,29 @@ const NOT_FOUND_STATUS = 404;
1415
*/
1516
const txInToId = (txIn: Cardano.TxIn): string => `${txIn.txId}#${txIn.index}`;
1617

18+
type BlockfrostInputResolverDependencies = {
19+
cache: Cache<Cardano.TxOut>;
20+
client: BlockfrostClient;
21+
logger: Logger;
22+
};
23+
1724
/**
1825
* A resolver class to fetch and resolve transaction inputs using Blockfrost API.
1926
*/
2027
export class BlockfrostInputResolver implements Cardano.InputResolver {
2128
readonly #logger: Logger;
2229
readonly #client: BlockfrostClient;
23-
readonly #txCache = new Map<string, Cardano.TxOut>();
30+
readonly #txCache: Cache<Cardano.TxOut>;
2431

2532
/**
2633
* Constructs a new BlockfrostInputResolver.
2734
*
35+
* @param cache - A caching interface.
2836
* @param client - The Blockfrost client instance to interact with the Blockfrost API.
2937
* @param logger - The logger instance to log messages to.
3038
*/
31-
constructor(client: BlockfrostClient, logger: Logger) {
39+
constructor({ cache, client, logger }: BlockfrostInputResolverDependencies) {
40+
this.#txCache = cache;
3241
this.#client = client;
3342
this.#logger = logger;
3443
}
@@ -44,9 +53,10 @@ export class BlockfrostInputResolver implements Cardano.InputResolver {
4453
public async resolveInput(input: Cardano.TxIn, options?: Cardano.ResolveOptions): Promise<Cardano.TxOut | null> {
4554
this.#logger.debug(`Resolving input ${input.txId}#${input.index}`);
4655

47-
if (this.#txCache.has(txInToId(input))) {
56+
const cached = await this.#txCache.get(txInToId(input));
57+
if (cached) {
4858
this.#logger.debug(`Resolved input ${input.txId}#${input.index} from cache`);
49-
return this.#txCache.get(txInToId(input))!;
59+
return cached;
5060
}
5161

5262
const resolved = this.resolveFromHints(input, options);
@@ -69,7 +79,7 @@ export class BlockfrostInputResolver implements Cardano.InputResolver {
6979
for (const hint of options.hints.transactions) {
7080
if (input.txId === hint.id && hint.body.outputs.length > input.index) {
7181
this.#logger.debug(`Resolved input ${input.txId}#${input.index} from hint`);
72-
this.#txCache.set(txInToId(input), hint.body.outputs[input.index]);
82+
void this.#txCache.set(txInToId(input), hint.body.outputs[input.index]);
7383

7484
return hint.body.outputs[input.index];
7585
}
@@ -80,7 +90,7 @@ export class BlockfrostInputResolver implements Cardano.InputResolver {
8090
for (const utxo of options.hints.utxos) {
8191
if (input.txId === utxo[0].txId && input.index === utxo[0].index) {
8292
this.#logger.debug(`Resolved input ${input.txId}#${input.index} from hint`);
83-
this.#txCache.set(txInToId(input), utxo[1]);
93+
void this.#txCache.set(txInToId(input), utxo[1]);
8494

8595
return utxo[1];
8696
}
@@ -119,7 +129,7 @@ export class BlockfrostInputResolver implements Cardano.InputResolver {
119129

120130
const coreTxOut = BlockfrostToCore.txOut(blockfrostUTxO);
121131

122-
this.#txCache.set(txInToId(txIn), coreTxOut);
132+
void this.#txCache.set(txInToId(txIn), coreTxOut);
123133

124134
this.#logger.debug(`Resolved input ${txIn.txId}#${txIn.index} from Blockfrost`);
125135
return coreTxOut;

packages/cardano/src/wallet/lib/providers.ts

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
/* eslint-disable no-new, complexity, sonarjs/cognitive-complexity */
3+
import { Storage } from 'webextension-polyfill';
34
import { AxiosAdapter } from 'axios';
45
import { Logger } from 'ts-log';
56
import {
@@ -33,7 +34,7 @@ import {
3334
BlockfrostNetworkInfoProvider,
3435
BlockfrostRewardAccountInfoProvider
3536
} from '@cardano-sdk/cardano-services-client';
36-
import { RemoteApiProperties, RemoteApiPropertyType } from '@cardano-sdk/web-extension';
37+
import { RemoteApiProperties, RemoteApiPropertyType, createPersistentCacheStorage } from '@cardano-sdk/web-extension';
3738
import { BlockfrostAddressDiscovery } from '@wallet/lib/blockfrost-address-discovery';
3839
import { WalletProvidersDependencies } from './cardano-wallet';
3940
import { BlockfrostInputResolver } from './blockfrost-input-resolver';
@@ -86,6 +87,7 @@ interface ProvidersConfig {
8687
experiments: {
8788
useWebSocket?: boolean;
8889
};
90+
extensionLocalStorage: Storage.LocalStorageArea;
8991
}
9092

9193
/**
@@ -94,11 +96,41 @@ interface ProvidersConfig {
9496
* If a new one needs to be created (ex. on network change) the previous instance needs to be closed. */
9597
let wsProvider: CardanoWsClient;
9698

99+
enum CacheName {
100+
chainHistoryProvider = 'chain-history-provider-cache',
101+
inputResolver = 'input-resolver-cache',
102+
utxoProvider = 'utxo-provider-cache'
103+
}
104+
105+
// eslint-disable-next-line no-magic-numbers
106+
const sizeOf1mb = 1024 * 1024;
107+
108+
// The count values have been calculated by filling the cache by impersonating a few
109+
// rich wallets and then getting the average size of a single item per each cache collection
110+
const cacheAssignment: Record<CacheName, { count: number; size: number }> = {
111+
[CacheName.chainHistoryProvider]: {
112+
count: 5_180_160_021,
113+
// eslint-disable-next-line no-magic-numbers
114+
size: 30 * sizeOf1mb
115+
},
116+
[CacheName.inputResolver]: {
117+
count: 65_529_512_340,
118+
// eslint-disable-next-line no-magic-numbers
119+
size: 30 * sizeOf1mb
120+
},
121+
[CacheName.utxoProvider]: {
122+
count: 6_530_251_302,
123+
// eslint-disable-next-line no-magic-numbers
124+
size: 30 * sizeOf1mb
125+
}
126+
};
127+
97128
export const createProviders = ({
98129
axiosAdapter,
99130
env: { baseCardanoServicesUrl: baseUrl, customSubmitTxUrl, blockfrostConfig },
100131
logger = console,
101-
experiments: { useWebSocket }
132+
experiments: { useWebSocket },
133+
extensionLocalStorage
102134
}: ProvidersConfig): WalletProvidersDependencies => {
103135
const httpProviderConfig: CreateHttpProviderConfig<Provider> = { baseUrl, logger, adapter: axiosAdapter };
104136

@@ -107,7 +139,17 @@ export const createProviders = ({
107139
});
108140
const assetProvider = new BlockfrostAssetProvider(blockfrostClient, logger);
109141
const networkInfoProvider = new BlockfrostNetworkInfoProvider(blockfrostClient, logger);
110-
const chainHistoryProvider = new BlockfrostChainHistoryProvider(blockfrostClient, networkInfoProvider, logger);
142+
const chainHistoryProvider = new BlockfrostChainHistoryProvider({
143+
client: blockfrostClient,
144+
cache: createPersistentCacheStorage({
145+
extensionLocalStorage,
146+
fallbackMaxCollectionItemsGuard: cacheAssignment[CacheName.chainHistoryProvider].count,
147+
resourceName: CacheName.chainHistoryProvider,
148+
quotaInBytes: cacheAssignment[CacheName.chainHistoryProvider].size
149+
}),
150+
networkInfoProvider,
151+
logger
152+
});
111153
const rewardsProvider = new BlockfrostRewardsProvider(blockfrostClient, logger);
112154
const stakePoolProvider = stakePoolHttpProvider(httpProviderConfig);
113155
const txSubmitProvider = createTxSubmitProvider(blockfrostClient, httpProviderConfig, customSubmitTxUrl);
@@ -122,7 +164,16 @@ export const createProviders = ({
122164
stakePoolProvider
123165
});
124166

125-
const inputResolver = new BlockfrostInputResolver(blockfrostClient, logger);
167+
const inputResolver = new BlockfrostInputResolver({
168+
cache: createPersistentCacheStorage({
169+
extensionLocalStorage,
170+
fallbackMaxCollectionItemsGuard: cacheAssignment[CacheName.inputResolver].count,
171+
resourceName: CacheName.inputResolver,
172+
quotaInBytes: cacheAssignment[CacheName.inputResolver].size
173+
}),
174+
client: blockfrostClient,
175+
logger
176+
});
126177

127178
if (useWebSocket) {
128179
const url = new URL(baseUrl);
@@ -152,7 +203,16 @@ export const createProviders = ({
152203
};
153204
}
154205

155-
const utxoProvider = new BlockfrostUtxoProvider(blockfrostClient, logger);
206+
const utxoProvider = new BlockfrostUtxoProvider({
207+
cache: createPersistentCacheStorage({
208+
extensionLocalStorage,
209+
fallbackMaxCollectionItemsGuard: cacheAssignment[CacheName.utxoProvider].count,
210+
resourceName: CacheName.utxoProvider,
211+
quotaInBytes: cacheAssignment[CacheName.utxoProvider].size
212+
}),
213+
client: blockfrostClient,
214+
logger
215+
});
156216

157217
return {
158218
assetProvider,

packages/common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"watch": "yarn build --watch"
4040
},
4141
"dependencies": {
42-
"@cardano-sdk/util": "0.15.6",
42+
"@cardano-sdk/util": "0.15.7",
4343
"antd": "^4.24.10",
4444
"classnames": "^2.3.1",
4545
"jdenticon": "3.1.0",

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
},
4444
"dependencies": {
4545
"@ant-design/icons": "^4.7.0",
46-
"@cardano-sdk/wallet": "0.51.9",
47-
"@cardano-sdk/web-extension": "0.38.10",
46+
"@cardano-sdk/wallet": "0.51.10",
47+
"@cardano-sdk/web-extension": "0.38.14",
4848
"@input-output-hk/lace-ui-toolkit": "1.19.0",
4949
"@lace/cardano": "0.1.0",
5050
"@lace/common": "0.1.0",

packages/nami/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@
5757
},
5858
"dependencies": {
5959
"@biglup/is-cid": "^1.0.3",
60-
"@cardano-sdk/core": "0.45.1",
61-
"@cardano-sdk/crypto": "0.2.1",
62-
"@cardano-sdk/tx-construction": "0.26.1",
63-
"@cardano-sdk/util": "0.15.6",
64-
"@cardano-sdk/web-extension": "0.38.10",
60+
"@cardano-sdk/core": "0.45.2",
61+
"@cardano-sdk/crypto": "0.2.2",
62+
"@cardano-sdk/tx-construction": "0.26.2",
63+
"@cardano-sdk/util": "0.15.7",
64+
"@cardano-sdk/web-extension": "0.38.14",
6565
"@chakra-ui/css-reset": "1.0.0",
6666
"@chakra-ui/icons": "1.0.13",
6767
"@chakra-ui/react": "1.6.4",

packages/staking/package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@
7676
},
7777
"devDependencies": {
7878
"@babel/core": "^7.21.0",
79-
"@cardano-sdk/core": "0.45.1",
80-
"@cardano-sdk/input-selection": "0.14.2",
81-
"@cardano-sdk/tx-construction": "0.26.1",
82-
"@cardano-sdk/util": "0.15.6",
83-
"@cardano-sdk/wallet": "0.51.9",
84-
"@cardano-sdk/web-extension": "0.38.10",
79+
"@cardano-sdk/core": "0.45.2",
80+
"@cardano-sdk/input-selection": "0.14.3",
81+
"@cardano-sdk/tx-construction": "0.26.2",
82+
"@cardano-sdk/util": "0.15.7",
83+
"@cardano-sdk/wallet": "0.51.10",
84+
"@cardano-sdk/web-extension": "0.38.14",
8585
"@storybook/addon-actions": "^7.6.7",
8686
"@storybook/addon-essentials": "^7.6.7",
8787
"@storybook/addon-interactions": "^7.6.7",
@@ -127,11 +127,11 @@
127127
"wait-on": "^7.0.1"
128128
},
129129
"peerDependencies": {
130-
"@cardano-sdk/input-selection": "0.14.2",
131-
"@cardano-sdk/tx-construction": "0.26.1",
132-
"@cardano-sdk/util": "0.15.6",
133-
"@cardano-sdk/wallet": "0.51.9",
134-
"@cardano-sdk/web-extension": "0.38.10",
130+
"@cardano-sdk/input-selection": "0.14.3",
131+
"@cardano-sdk/tx-construction": "0.26.2",
132+
"@cardano-sdk/util": "0.15.7",
133+
"@cardano-sdk/wallet": "0.51.10",
134+
"@cardano-sdk/web-extension": "0.38.14",
135135
"@lace/cardano": "^0.1.0",
136136
"@lace/common": "^0.1.0",
137137
"@lace/core": "0.1.0",

0 commit comments

Comments
 (0)