Skip to content

Commit d48dbfe

Browse files
committed
refactor(wallets): narrow stub-class cache to just the class id
Callers only ever consume the `id` field from `getContractClassFromArtifact`, so cache `Map<AccountType, Promise<Fr>>` instead of the full `ContractClassWithId & ContractClassIdPreimage`. Method renamed `#getStubClass` → `#getStubClassId`, map renamed `#stubClasses` → `#stubClassIds`, callers drop the destructure. Drop the verbose Promise-as-value rationale comment — the idiom is established in this codebase (see ContractSyncService) and the PXE precedents document the cache contract briefly without explaining the dedupe pattern.
1 parent e572e63 commit d48dbfe

5 files changed

Lines changed: 88 additions & 81 deletions

File tree

yarn-project/cli-wallet/src/utils/wallet.ts

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import type { PXEConfig } from '@aztec/pxe/config';
1717
import type { PXE } from '@aztec/pxe/server';
1818
import { createPXE, getPXEConfig } from '@aztec/pxe/server';
1919
import { AztecAddress } from '@aztec/stdlib/aztec-address';
20-
import type { ContractClassIdPreimage, ContractClassWithId } from '@aztec/stdlib/contract';
2120
import { deriveSigningKey } from '@aztec/stdlib/keys';
2221
import { NoteDao } from '@aztec/stdlib/note';
2322
import type { SimulationOverrides, TxExecutionRequest, TxProvingResult } from '@aztec/stdlib/tx';
@@ -31,13 +30,9 @@ import { printGasEstimates } from './options/fees.js';
3130

3231
export class CLIWallet extends BaseWallet {
3332
private accountCache = new Map<string, Account>();
34-
/**
35-
* Per-account-type cache of the stub class id and preimage. The Promise is stored (not the resolved
36-
* value) so concurrent first-time callers dedupe on the same hashing + registration work. ECDSA
37-
* variants all map to the same stub artifact but get their own cache slot, so we may hash + register
38-
* the same artifact one extra time per variant on first miss; that cost is bounded and one-time.
39-
*/
40-
#stubClasses = new Map<AccountType, Promise<ContractClassWithId & ContractClassIdPreimage>>();
33+
// Stub class ids, populated on wallet startup
34+
// to avoid redundant work per simulation
35+
private stubClassIds = new Map<AccountType, Fr>();
4136

4237
constructor(
4338
pxe: PXE,
@@ -57,7 +52,27 @@ export class CLIWallet extends BaseWallet {
5752
): Promise<CLIWallet> {
5853
const pxeConfig = Object.assign(getPXEConfig(), overridePXEConfig);
5954
const pxe = await createPXE(node, pxeConfig);
60-
return new CLIWallet(pxe, node, log, db);
55+
const wallet = new CLIWallet(pxe, node, log, db);
56+
await wallet.initStubClasses();
57+
return wallet;
58+
}
59+
60+
/**
61+
* Hashes and registers the stub class for every supported account type with PXE, populating
62+
* stubClassIds. Called on wallet initialization.
63+
*/
64+
private async initStubClasses(): Promise<void> {
65+
const { id: schnorrClassId } = await getContractClassFromArtifact(StubSchnorrAccountContractArtifact);
66+
await this.pxe.registerContractClass(StubSchnorrAccountContractArtifact);
67+
68+
// ecdsa stubs share the same class id
69+
const { id: ecdsaClassId } = await getContractClassFromArtifact(StubEcdsaAccountContractArtifact);
70+
await this.pxe.registerContractClass(StubEcdsaAccountContractArtifact);
71+
72+
this.stubClassIds.set('schnorr', schnorrClassId);
73+
this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId);
74+
this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId);
75+
this.stubClassIds.set('ecdsasecp256r1ssh', ecdsaClassId);
6176
}
6277

6378
override async getAccounts(): Promise<Aliased<AztecAddress>[]> {
@@ -208,29 +223,16 @@ export class CLIWallet extends BaseWallet {
208223
const { type } = await this.db!.retrieveAccount(address);
209224
const stubAccount =
210225
type === 'schnorr' ? createStubSchnorrAccount(originalAddress) : createStubEcdsaAccount(originalAddress);
211-
const { id: stubClassId } = await this.#getStubClass(type);
226+
const stubClassId = this.stubClassIds.get(type);
227+
if (!stubClassId) {
228+
throw new Error(
229+
`Stub class for account type '${type}' was not registered at wallet init. This is a bug — initStubClasses should cover every supported AccountType.`,
230+
);
231+
}
212232
const instance = { ...contractInstance, currentContractClassId: stubClassId };
213233
return { account: stubAccount, instance };
214234
}
215235

216-
/**
217-
* Lazily hashes and registers the stub class for the given account type, caching the result so
218-
* subsequent simulations skip the artifact-hashing + registration round-trip.
219-
*/
220-
#getStubClass(type: AccountType): Promise<ContractClassWithId & ContractClassIdPreimage> {
221-
let cached = this.#stubClasses.get(type);
222-
if (!cached) {
223-
cached = (async () => {
224-
const artifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact;
225-
const stubClass = await getContractClassFromArtifact(artifact);
226-
await this.pxe.registerContractClass(artifact);
227-
return stubClass;
228-
})();
229-
this.#stubClasses.set(type, cached);
230-
}
231-
return cached;
232-
}
233-
234236
override async simulateTx(
235237
executionPayload: ExecutionPayload,
236238
opts: SimulateOptions,

yarn-project/end-to-end/src/test-wallet/test_wallet.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ import { type PXEConfig, getPXEConfig } from '@aztec/pxe/config';
2525
import { PXE, type PXECreationOptions, createPXE } from '@aztec/pxe/server';
2626
import { AuthWitness } from '@aztec/stdlib/auth-witness';
2727
import { AztecAddress } from '@aztec/stdlib/aztec-address';
28-
import {
29-
type ContractClassIdPreimage,
30-
type ContractClassWithId,
31-
getContractClassFromArtifact,
32-
} from '@aztec/stdlib/contract';
28+
import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
3329
import { deriveSigningKey } from '@aztec/stdlib/keys';
3430
import type { NoteDao } from '@aztec/stdlib/note';
3531
import {
@@ -82,7 +78,9 @@ export class TestWallet extends BaseWallet {
8278
...overridePXEConfig,
8379
});
8480
const pxe = await createPXE(nodeRef, pxeConfig, options);
85-
return new TestWallet(pxe, nodeRef);
81+
const wallet = new TestWallet(pxe, nodeRef);
82+
await wallet.initStubClasses();
83+
return wallet;
8684
}
8785

8886
/**
@@ -116,28 +114,25 @@ export class TestWallet extends BaseWallet {
116114
});
117115
}
118116

119-
/**
120-
* Per-account-type cache of the stub class id and preimage. The Promise is stored (not the resolved
121-
* value) so concurrent first-time callers dedupe on the same hashing + registration work.
122-
*/
123-
#stubClasses = new Map<AccountType, Promise<ContractClassWithId & ContractClassIdPreimage>>();
117+
// Stub class ids, populated on wallet startup
118+
// to avoid redundant work per simulation
119+
private stubClassIds = new Map<AccountType, Fr>();
124120

125121
/**
126-
* Lazily hashes and registers the stub class for the given account type, caching the result so
127-
* subsequent simulations skip the artifact-hashing + registration round-trip.
122+
* Hashes and registers the stub class for every supported account type with PXE, populating
123+
* stubClassIds. Called on wallet initialization.
128124
*/
129-
#getStubClass(type: AccountType): Promise<ContractClassWithId & ContractClassIdPreimage> {
130-
let cached = this.#stubClasses.get(type);
131-
if (!cached) {
132-
cached = (async () => {
133-
const stubArtifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact;
134-
const stubClass = await getContractClassFromArtifact(stubArtifact);
135-
await this.pxe.registerContractClass(stubArtifact);
136-
return stubClass;
137-
})();
138-
this.#stubClasses.set(type, cached);
139-
}
140-
return cached;
125+
private async initStubClasses(): Promise<void> {
126+
const { id: schnorrClassId } = await getContractClassFromArtifact(StubSchnorrAccountContractArtifact);
127+
await this.pxe.registerContractClass(StubSchnorrAccountContractArtifact);
128+
129+
// ecdsa stubs share the same class id
130+
const { id: ecdsaClassId } = await getContractClassFromArtifact(StubEcdsaAccountContractArtifact);
131+
await this.pxe.registerContractClass(StubEcdsaAccountContractArtifact);
132+
133+
this.stubClassIds.set('schnorr', schnorrClassId);
134+
this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId);
135+
this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId);
141136
}
142137

143138
/**
@@ -160,7 +155,13 @@ export class TestWallet extends BaseWallet {
160155
);
161156
}
162157

163-
const { id: stubClassId } = await this.#getStubClass(this.getTypeFor(address));
158+
const type = this.getTypeFor(address);
159+
const stubClassId = this.stubClassIds.get(type);
160+
if (!stubClassId) {
161+
throw new Error(
162+
`Stub class for account type '${type}' was not registered at wallet init. This is a bug — initStubClasses should cover every supported AccountType.`,
163+
);
164+
}
164165

165166
contracts[address.toString()] = {
166167
instance: { ...contractInstance, currentContractClassId: stubClassId },

yarn-project/wallets/src/embedded/embedded_wallet.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ import type { AztecAsyncKVStore } from '@aztec/kv-store';
1111
import type { PXEConfig, PXECreationOptions } from '@aztec/pxe/client/lazy';
1212
import type { PXE } from '@aztec/pxe/server';
1313
import { AztecAddress } from '@aztec/stdlib/aztec-address';
14-
import {
15-
type ContractClassIdPreimage,
16-
type ContractClassWithId,
17-
getContractClassFromArtifact,
18-
} from '@aztec/stdlib/contract';
14+
import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
1915
import { GasSettings } from '@aztec/stdlib/gas';
2016
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
2117
import { deriveSigningKey } from '@aztec/stdlib/keys';
@@ -80,11 +76,9 @@ const DEFAULT_ESTIMATED_GAS_PADDING = 0.1;
8076
export class EmbeddedWallet extends BaseWallet {
8177
protected estimatedGasPadding = DEFAULT_ESTIMATED_GAS_PADDING;
8278

83-
/**
84-
* Per-account-type cache of the stub class id and preimage. The Promise is stored (not the resolved
85-
* value) so concurrent first-time callers dedupe on the same hashing + registration work.
86-
*/
87-
#stubClasses = new Map<AccountType, Promise<ContractClassWithId & ContractClassIdPreimage>>();
79+
// Stub class ids, populated on wallet startup
80+
// to avoid redundant work per simulation
81+
protected stubClassIds = new Map<AccountType, Fr>();
8882

8983
constructor(
9084
pxe: PXE,
@@ -203,21 +197,22 @@ export class EmbeddedWallet extends BaseWallet {
203197
}
204198

205199
/**
206-
* Lazily hashes and registers the stub class for the given account type, caching the result so
207-
* subsequent simulations skip the artifact-hashing + registration round-trip.
200+
* Hashes and registers the stub class for every supported account type with PXE, populating
201+
* stubClassIds. Called on wallet initialization.
208202
*/
209-
#getStubClass(type: AccountType): Promise<ContractClassWithId & ContractClassIdPreimage> {
210-
let cached = this.#stubClasses.get(type);
211-
if (!cached) {
212-
cached = (async () => {
213-
const stubArtifact = await this.accountContracts.getStubAccountContractArtifact(type);
214-
const stubClass = await getContractClassFromArtifact(stubArtifact);
215-
await this.pxe.registerContractClass(stubArtifact);
216-
return stubClass;
217-
})();
218-
this.#stubClasses.set(type, cached);
219-
}
220-
return cached;
203+
async initStubClasses(): Promise<void> {
204+
const schnorrArtifact = await this.accountContracts.getStubAccountContractArtifact('schnorr');
205+
const { id: schnorrClassId } = await getContractClassFromArtifact(schnorrArtifact);
206+
await this.pxe.registerContractClass(schnorrArtifact);
207+
208+
// ecdsa stubs share the same class id
209+
const ecdsaArtifact = await this.accountContracts.getStubAccountContractArtifact('ecdsasecp256r1');
210+
const { id: ecdsaClassId } = await getContractClassFromArtifact(ecdsaArtifact);
211+
await this.pxe.registerContractClass(ecdsaArtifact);
212+
213+
this.stubClassIds.set('schnorr', schnorrClassId);
214+
this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId);
215+
this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId);
221216
}
222217

223218
/**
@@ -233,7 +228,12 @@ export class EmbeddedWallet extends BaseWallet {
233228
for (const account of filtered) {
234229
const address = account.item;
235230
const { type } = await this.walletDB.retrieveAccount(address);
236-
const { id: stubClassId } = await this.#getStubClass(type);
231+
const stubClassId = this.stubClassIds.get(type);
232+
if (!stubClassId) {
233+
throw new Error(
234+
`Stub class for account type '${type}' was not registered at wallet init. This is a bug — initStubClasses should cover every supported AccountType.`,
235+
);
236+
}
237237

238238
const originalAccount = await this.getAccountFromAddress(address);
239239
const completeAddress = originalAccount.getCompleteAddress();

yarn-project/wallets/src/embedded/entrypoints/browser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ export class BrowserEmbeddedWallet extends EmbeddedWallet {
6969
));
7070
const walletDB = new WalletDB(walletDBStore, rootLogger.createChild('wallet:db').info);
7171

72-
return new this(pxe, aztecNode, walletDB, new LazyAccountContractsProvider(), rootLogger) as T;
72+
const wallet = new this(pxe, aztecNode, walletDB, new LazyAccountContractsProvider(), rootLogger) as T;
73+
await wallet.initStubClasses();
74+
return wallet;
7375
}
7476
}
7577

yarn-project/wallets/src/embedded/entrypoints/node.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ export class NodeEmbeddedWallet extends EmbeddedWallet {
7676
));
7777
const walletDB = new WalletDB(walletDBStore, rootLogger.createChild('wallet:db').info);
7878

79-
return new this(pxe, aztecNode, walletDB, new BundleAccountContractsProvider(), rootLogger) as T;
79+
const wallet = new this(pxe, aztecNode, walletDB, new BundleAccountContractsProvider(), rootLogger) as T;
80+
await wallet.initStubClasses();
81+
return wallet;
8082
}
8183
}
8284

0 commit comments

Comments
 (0)