@aztec/txe 0.0.1-commit.04852196a → 0.0.1-commit.04d373f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dest/ContractClassRegistry-KKNT527R.js +3 -0
  2. package/dest/ContractClassRegistry-KKNT527R.js.map +7 -0
  3. package/dest/ContractInstanceRegistry-GSKBNL3Q.js +3 -0
  4. package/dest/ContractInstanceRegistry-GSKBNL3Q.js.map +7 -0
  5. package/dest/FeeJuice-GUJ5O6GB.js +3 -0
  6. package/dest/FeeJuice-GUJ5O6GB.js.map +7 -0
  7. package/dest/MultiCallEntrypoint-ZQPCKVKL.js +3 -0
  8. package/dest/MultiCallEntrypoint-ZQPCKVKL.js.map +7 -0
  9. package/dest/SchnorrAccount-QIXE5DXA.js +3 -0
  10. package/dest/SchnorrAccount-QIXE5DXA.js.map +7 -0
  11. package/dest/bin/check_txe_oracle_version.d.ts +2 -0
  12. package/dest/bin/check_txe_oracle_version.d.ts.map +1 -0
  13. package/dest/bin/check_txe_oracle_version.js +32 -0
  14. package/dest/bin/index.js +3 -30
  15. package/dest/bin/index.js.map +7 -0
  16. package/dest/chunk-BJVAAXNA.js +3 -0
  17. package/dest/chunk-BJVAAXNA.js.map +7 -0
  18. package/dest/chunk-DF6RMOC6.js +304 -0
  19. package/dest/chunk-DF6RMOC6.js.map +7 -0
  20. package/dest/chunk-PPJTNNUG.js +265 -0
  21. package/dest/chunk-PPJTNNUG.js.map +7 -0
  22. package/dest/constants.d.ts +3 -1
  23. package/dest/constants.d.ts.map +1 -1
  24. package/dest/constants.js +5 -0
  25. package/dest/dispatcher_pool.d.ts +67 -0
  26. package/dest/dispatcher_pool.d.ts.map +1 -0
  27. package/dest/dispatcher_pool.js +284 -0
  28. package/dest/index.d.ts +188 -7
  29. package/dest/index.d.ts.map +1 -1
  30. package/dest/index.js +155 -116
  31. package/dest/metafile.json +38147 -0
  32. package/dest/msgpackr_fr_extension.d.ts +2 -0
  33. package/dest/msgpackr_fr_extension.d.ts.map +1 -0
  34. package/dest/msgpackr_fr_extension.js +21 -0
  35. package/dest/oracle/interfaces.d.ts +15 -4
  36. package/dest/oracle/interfaces.d.ts.map +1 -1
  37. package/dest/oracle/txe_oracle_public_context.d.ts +6 -1
  38. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  39. package/dest/oracle/txe_oracle_public_context.js +15 -0
  40. package/dest/oracle/txe_oracle_top_level_context.d.ts +22 -6
  41. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  42. package/dest/oracle/txe_oracle_top_level_context.js +106 -47
  43. package/dest/oracle/txe_private_execution_oracle.d.ts +17 -0
  44. package/dest/oracle/txe_private_execution_oracle.d.ts.map +1 -0
  45. package/dest/oracle/txe_private_execution_oracle.js +15 -0
  46. package/dest/rpc_server.d.ts +14 -0
  47. package/dest/rpc_server.d.ts.map +1 -0
  48. package/dest/rpc_server.js +78 -0
  49. package/dest/rpc_translator.d.ts +90 -34
  50. package/dest/rpc_translator.d.ts.map +1 -1
  51. package/dest/rpc_translator.js +396 -153
  52. package/dest/server.bundle.js +3 -0
  53. package/dest/server.bundle.js.map +7 -0
  54. package/dest/state_machine/archiver.d.ts +4 -4
  55. package/dest/state_machine/archiver.d.ts.map +1 -1
  56. package/dest/state_machine/archiver.js +22 -20
  57. package/dest/state_machine/dummy_p2p_client.d.ts +13 -7
  58. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  59. package/dest/state_machine/dummy_p2p_client.js +16 -4
  60. package/dest/state_machine/global_variable_builder.d.ts +9 -4
  61. package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
  62. package/dest/state_machine/global_variable_builder.js +9 -3
  63. package/dest/state_machine/index.d.ts +7 -3
  64. package/dest/state_machine/index.d.ts.map +1 -1
  65. package/dest/state_machine/index.js +23 -5
  66. package/dest/state_machine/mock_epoch_cache.d.ts +18 -3
  67. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  68. package/dest/state_machine/mock_epoch_cache.js +35 -2
  69. package/dest/state_machine/synchronizer.d.ts +5 -5
  70. package/dest/state_machine/synchronizer.d.ts.map +1 -1
  71. package/dest/state_machine/synchronizer.js +4 -4
  72. package/dest/txe_oracle_version.d.ts +17 -0
  73. package/dest/txe_oracle_version.d.ts.map +1 -0
  74. package/dest/txe_oracle_version.js +14 -0
  75. package/dest/txe_session.d.ts +64 -6
  76. package/dest/txe_session.d.ts.map +1 -1
  77. package/dest/txe_session.js +157 -29
  78. package/dest/utils/encoding.d.ts +191 -0
  79. package/dest/utils/encoding.d.ts.map +1 -0
  80. package/dest/{util → utils}/encoding.js +7 -2
  81. package/dest/{util → utils}/expected_failure_error.d.ts +1 -1
  82. package/dest/utils/expected_failure_error.d.ts.map +1 -0
  83. package/dest/{util → utils}/txe_account_store.d.ts +1 -1
  84. package/dest/utils/txe_account_store.d.ts.map +1 -0
  85. package/dest/utils/txe_public_contract_data_source.d.ts +20 -0
  86. package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -0
  87. package/dest/{util → utils}/txe_public_contract_data_source.js +1 -3
  88. package/dest/worker.bundle.js +3 -0
  89. package/dest/worker.bundle.js.map +7 -0
  90. package/dest/worker.d.ts +2 -0
  91. package/dest/worker.d.ts.map +1 -0
  92. package/dest/worker.js +92 -0
  93. package/package.json +37 -21
  94. package/src/bin/check_txe_oracle_version.ts +37 -0
  95. package/src/bin/index.ts +11 -2
  96. package/src/constants.ts +6 -0
  97. package/src/dispatcher_pool.ts +316 -0
  98. package/src/index.ts +172 -123
  99. package/src/msgpackr_fr_extension.ts +23 -0
  100. package/src/oracle/interfaces.ts +16 -3
  101. package/src/oracle/txe_oracle_public_context.ts +30 -0
  102. package/src/oracle/txe_oracle_top_level_context.ts +124 -55
  103. package/src/oracle/txe_private_execution_oracle.ts +30 -0
  104. package/src/rpc_server.ts +87 -0
  105. package/src/rpc_translator.ts +459 -222
  106. package/src/state_machine/archiver.ts +33 -18
  107. package/src/state_machine/dummy_p2p_client.ts +31 -11
  108. package/src/state_machine/global_variable_builder.ts +18 -3
  109. package/src/state_machine/index.ts +25 -6
  110. package/src/state_machine/mock_epoch_cache.ts +46 -3
  111. package/src/state_machine/synchronizer.ts +5 -5
  112. package/src/txe_oracle_version.ts +17 -0
  113. package/src/txe_session.ts +251 -31
  114. package/src/{util → utils}/encoding.ts +8 -2
  115. package/src/{util → utils}/txe_public_contract_data_source.ts +0 -2
  116. package/src/worker.ts +98 -0
  117. package/dest/util/encoding.d.ts +0 -720
  118. package/dest/util/encoding.d.ts.map +0 -1
  119. package/dest/util/expected_failure_error.d.ts.map +0 -1
  120. package/dest/util/txe_account_store.d.ts.map +0 -1
  121. package/dest/util/txe_public_contract_data_source.d.ts +0 -20
  122. package/dest/util/txe_public_contract_data_source.d.ts.map +0 -1
  123. /package/dest/{util → utils}/expected_failure_error.js +0 -0
  124. /package/dest/{util → utils}/txe_account_store.js +0 -0
  125. /package/src/{util → utils}/expected_failure_error.ts +0 -0
  126. /package/src/{util → utils}/txe_account_store.ts +0 -0
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
2
1
  import { type NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js/abi';
3
2
  import { AztecAddress } from '@aztec/aztec.js/addresses';
4
3
  import {
@@ -7,12 +6,10 @@ import {
7
6
  } from '@aztec/aztec.js/contracts';
8
7
  import { Fr } from '@aztec/aztec.js/fields';
9
8
  import { PublicKeys, deriveKeys } from '@aztec/aztec.js/keys';
10
- import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
11
9
  import type { Logger } from '@aztec/foundation/log';
12
- import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
13
- import { protocolContractNames } from '@aztec/protocol-contracts';
14
- import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
15
- import { ContractStore } from '@aztec/pxe/server';
10
+ import { cloneEphemeralStoreFrom } from '@aztec/kv-store/lmdb-v2';
11
+ import type { ProtocolContractName } from '@aztec/protocol-contracts';
12
+ import { ContractStore } from '@aztec/pxe/client/lazy';
16
13
  import { computeArtifactHash } from '@aztec/stdlib/contract';
17
14
  import type { ContractArtifactWithHash } from '@aztec/stdlib/contract';
18
15
  import type { ApiSchemaFor } from '@aztec/stdlib/schemas';
@@ -24,6 +21,9 @@ import { readFile, readdir } from 'fs/promises';
24
21
  import { join, parse } from 'path';
25
22
  import { z } from 'zod';
26
23
 
24
+ // Side-effect import: registers the msgpackr Fr extension for the bundled `Fr` class. Must
25
+ // be loaded before any `sendMessage` call. See msgpackr_fr_extension.ts for the why.
26
+ import './msgpackr_fr_extension.js';
27
27
  import { type TXEOracleFunctionName, TXESession } from './txe_session.js';
28
28
  import {
29
29
  type ForeignCallArgs,
@@ -36,27 +36,57 @@ import {
36
36
  fromArray,
37
37
  fromSingle,
38
38
  toSingle,
39
- } from './util/encoding.js';
39
+ } from './utils/encoding.js';
40
+
41
+ // Protocol contracts TXE registers in its contract store. Only AuthRegistry is needed for the
42
+ // current test suites; add a contract here if a lookup against a `0x000…00X` address fails.
43
+ export const TXE_REQUIRED_PROTOCOL_CONTRACTS: ProtocolContractName[] = [];
40
44
 
41
45
  const sessions = new Map<number, TXESession>();
42
46
 
43
- /*
44
- * TXE typically has to load the same contract artifacts over and over again for multiple tests,
45
- * so we cache them here to avoid loading from disk repeatedly.
46
- *
47
- * The in-flight map coalesces concurrent requests for the same cache key so that
48
- * computeArtifactHash (very expensive) is only run once even under parallelism.
47
+ /**
48
+ * Cache + in-flight map pair. Lookup hits the cache, then awaits an in-flight `compute()` if one
49
+ * exists, otherwise starts one and stores it. Guarantees `compute()` runs at most once per `key`
50
+ * across concurrent callers, which matters because `computeArtifactHash` is expensive.
49
51
  */
50
- const TXEArtifactsCache = new Map<
52
+ class AsyncCache<K, V> {
53
+ private readonly cache = new Map<K, V>();
54
+ private readonly inFlight = new Map<K, Promise<V>>();
55
+
56
+ getOrCompute(key: K, compute: () => Promise<V>): Promise<V> {
57
+ const cached = this.cache.get(key);
58
+ if (cached !== undefined) {
59
+ return Promise.resolve(cached);
60
+ }
61
+ let pending = this.inFlight.get(key);
62
+ if (!pending) {
63
+ pending = (async () => {
64
+ try {
65
+ const value = await compute();
66
+ this.cache.set(key, value);
67
+ return value;
68
+ } finally {
69
+ this.inFlight.delete(key);
70
+ }
71
+ })();
72
+ this.inFlight.set(key, pending);
73
+ }
74
+ return pending;
75
+ }
76
+ }
77
+
78
+ // Full deploys (artifact + computed instance), keyed by the full deploy context (contract +
79
+ // constructor args + publicKeys + salt + deployer). Hits on repeated identical deploys.
80
+ const TXEDeploymentsCache = new AsyncCache<
51
81
  string,
52
82
  { artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }
53
83
  >();
54
- const TXEArtifactsCacheInFlight = new Map<
55
- string,
56
- Promise<{ artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }>
57
- >();
58
84
 
59
- type TXEForeignCallInput = {
85
+ // Loaded + hashed contract artifact, keyed by compiled-bytecode hash. Hits across deploys of the
86
+ // same contract when constructor args / salt / deployer differ.
87
+ const TXEArtifactsCache = new AsyncCache<string, ContractArtifactWithHash>();
88
+
89
+ export type TXEForeignCallInput = {
60
90
  session_id: number;
61
91
  function: TXEOracleFunctionName;
62
92
  root_path: string;
@@ -64,10 +94,13 @@ type TXEForeignCallInput = {
64
94
  inputs: ForeignCallArgs;
65
95
  };
66
96
 
67
- const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
97
+ export const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
68
98
  z.object({
99
+ // Nargo generates session_id as a u64, which may exceed Number.MAX_SAFE_INTEGER.
100
+ // Zod 4's `.int()` enforces the safe-integer bound, so we drop it here and only require
101
+ // the value to be a non-negative number (it is used solely as a Map key).
69
102
  // eslint-disable-next-line camelcase
70
- session_id: z.number().int().nonnegative(),
103
+ session_id: z.number().nonnegative(),
71
104
  function: z.string() as z.ZodType<TXEOracleFunctionName>,
72
105
  // eslint-disable-next-line camelcase
73
106
  root_path: z.string(),
@@ -77,12 +110,56 @@ const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
77
110
  }),
78
111
  );
79
112
 
80
- class TXEDispatcher {
113
+ export interface TXEDispatcherOptions {
114
+ /**
115
+ * Path to an LMDB directory holding the required protocol contracts (see
116
+ * {@link TXE_REQUIRED_PROTOCOL_CONTRACTS}) and the SchnorrAccount artifact. When set, the
117
+ * dispatcher clones this directory into a fresh tmpdir on first use instead of registering
118
+ * the contracts itself.
119
+ */
120
+ contractStoreSourceDir: string;
121
+ /**
122
+ * Class id (hex) of the SchnorrAccount artifact pre-registered in the shared LMDB. When set,
123
+ * `#processAddAccountInputs` looks the artifact up from the cloned store instead of
124
+ * recomputing it via `getSchnorrAccountContractArtifact()` + `computeArtifactHash()`.
125
+ */
126
+ schnorrClassId: string;
127
+ }
128
+
129
+ export class TXEDispatcher {
81
130
  private contractStore!: ContractStore;
131
+ private readonly contractStoreSourceDir: string;
132
+ private readonly schnorrClassId: Fr;
133
+
134
+ constructor(
135
+ private logger: Logger,
136
+ opts: TXEDispatcherOptions,
137
+ ) {
138
+ this.contractStoreSourceDir = opts.contractStoreSourceDir;
139
+ this.schnorrClassId = Fr.fromString(opts.schnorrClassId);
140
+ }
82
141
 
83
- constructor(private logger: Logger) {}
142
+ /**
143
+ * Clones the pre-populated LMDB at `contractStoreSourceDir` into a fresh per-instance tmpdir
144
+ * on first use, so this dispatcher has a writable store already containing the required
145
+ * protocol contracts + SchnorrAccount. Idempotent — subsequent calls are no-ops.
146
+ */
147
+ private async warmUp(): Promise<void> {
148
+ if (this.contractStore) {
149
+ return;
150
+ }
151
+ const t0 = Date.now();
152
+ const kvStore = await cloneEphemeralStoreFrom(
153
+ join(this.contractStoreSourceDir, 'data.mdb'),
154
+ 'txe-contracts',
155
+ undefined,
156
+ 2,
157
+ );
158
+ this.contractStore = new ContractStore(kvStore);
159
+ this.logger.debug('Cloned shared protocol-contracts store', { totalMs: Date.now() - t0 });
160
+ }
84
161
 
85
- private fastHashFile(path: string) {
162
+ private fastHashFile(path: string): Promise<string> {
86
163
  return new Promise(resolve => {
87
164
  const fd = createReadStream(path);
88
165
  const hash = createHash('sha1');
@@ -90,7 +167,7 @@ class TXEDispatcher {
90
167
 
91
168
  fd.on('end', function () {
92
169
  hash.end();
93
- resolve(hash.read());
170
+ resolve(hash.read() as string);
94
171
  });
95
172
 
96
173
  fd.pipe(hash);
@@ -140,44 +217,29 @@ class TXEDispatcher {
140
217
  .map(arg => arg.toString())
141
218
  .join('-')}-${publicKeysHash}-${salt}-${deployer}-${fileHash}`;
142
219
 
143
- let instance;
144
- let artifact: ContractArtifactWithHash;
145
-
146
- if (TXEArtifactsCache.has(cacheKey)) {
147
- this.logger.debug(`Using cached artifact for ${cacheKey}`);
148
- ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
149
- } else {
150
- if (!TXEArtifactsCacheInFlight.has(cacheKey)) {
151
- this.logger.debug(`Loading compiled artifact ${artifactPath}`);
152
- const compute = async () => {
153
- const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
154
- const artifactWithoutHash = loadContractArtifact(artifactJSON);
155
- const computedArtifact: ContractArtifactWithHash = {
156
- ...artifactWithoutHash,
157
- // Artifact hash is *very* expensive to compute, so we do it here once
158
- // and the TXE contract data provider can cache it
159
- artifactHash: await computeArtifactHash(artifactWithoutHash),
160
- };
161
- this.logger.debug(
162
- `Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
163
- );
164
- const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
165
- constructorArgs: decodedArgs,
166
- skipArgsDecoding: true,
167
- salt,
168
- publicKeys,
169
- constructorArtifact: initializer ? initializer : undefined,
170
- deployer,
171
- });
172
- const result = { artifact: computedArtifact, instance: computedInstance };
173
- TXEArtifactsCache.set(cacheKey, result);
174
- TXEArtifactsCacheInFlight.delete(cacheKey);
175
- return result;
176
- };
177
- TXEArtifactsCacheInFlight.set(cacheKey, compute());
178
- }
179
- ({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!);
180
- }
220
+ const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
221
+ this.logger.debug(`Loading compiled artifact ${artifactPath}`);
222
+ // Inner cache: artifact load + hash depends only on the compiled bytecode (`fileHash`), so
223
+ // subsequent deploys of the same contract — regardless of constructor args / deployer /
224
+ // salt reuse the same `ContractArtifactWithHash`.
225
+ const computedArtifact = await TXEArtifactsCache.getOrCompute(fileHash, async () => {
226
+ const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
227
+ const artifactWithoutHash = loadContractArtifact(artifactJSON);
228
+ return { ...artifactWithoutHash, artifactHash: await computeArtifactHash(artifactWithoutHash) };
229
+ });
230
+ this.logger.debug(
231
+ `Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
232
+ );
233
+ const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
234
+ constructorArgs: decodedArgs,
235
+ skipArgsDecoding: true,
236
+ salt,
237
+ publicKeys,
238
+ constructorArtifact: initializer ? initializer : undefined,
239
+ deployer,
240
+ });
241
+ return { artifact: computedArtifact, instance: computedInstance };
242
+ });
181
243
 
182
244
  inputs.splice(0, 1, artifact, instance, toSingle(secret));
183
245
  }
@@ -187,43 +249,29 @@ class TXEDispatcher {
187
249
 
188
250
  const cacheKey = `SchnorrAccountContract-${secret}`;
189
251
 
190
- let artifact: ContractArtifactWithHash;
191
- let instance;
192
-
193
- if (TXEArtifactsCache.has(cacheKey)) {
194
- this.logger.debug(`Using cached artifact for ${cacheKey}`);
195
- ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
196
- } else {
197
- if (!TXEArtifactsCacheInFlight.has(cacheKey)) {
198
- const compute = async () => {
199
- const keys = await deriveKeys(secret);
200
- const args = [
201
- keys.publicKeys.masterIncomingViewingPublicKey.x,
202
- keys.publicKeys.masterIncomingViewingPublicKey.y,
203
- ];
204
- const computedArtifact: ContractArtifactWithHash = {
205
- ...SchnorrAccountContractArtifact,
206
- // Artifact hash is *very* expensive to compute, so we do it here once
207
- // and the TXE contract data provider can cache it
208
- artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact),
209
- };
210
- const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
211
- constructorArgs: args,
212
- skipArgsDecoding: true,
213
- salt: Fr.ONE,
214
- publicKeys: keys.publicKeys,
215
- constructorArtifact: 'constructor',
216
- deployer: AztecAddress.ZERO,
217
- });
218
- const result = { artifact: computedArtifact, instance: computedInstance };
219
- TXEArtifactsCache.set(cacheKey, result);
220
- TXEArtifactsCacheInFlight.delete(cacheKey);
221
- return result;
222
- };
223
- TXEArtifactsCacheInFlight.set(cacheKey, compute());
252
+ const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
253
+ const [artifactFromStore, classWithPreimage] = await Promise.all([
254
+ this.contractStore.getContractArtifact(this.schnorrClassId),
255
+ this.contractStore.getContractClassWithPreimage(this.schnorrClassId),
256
+ ]);
257
+ if (!artifactFromStore || !classWithPreimage) {
258
+ throw new Error(
259
+ `SchnorrAccount not found in shared contract store at class id ${this.schnorrClassId.toString()}`,
260
+ );
224
261
  }
225
- ({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!);
226
- }
262
+ const computedArtifact = { ...artifactFromStore, artifactHash: classWithPreimage.artifactHash };
263
+ const keys = await deriveKeys(secret);
264
+ const args = [keys.publicKeys.ivpkM.x, keys.publicKeys.ivpkM.y];
265
+ const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
266
+ constructorArgs: args,
267
+ skipArgsDecoding: true,
268
+ salt: Fr.ONE,
269
+ publicKeys: keys.publicKeys,
270
+ constructorArtifact: 'constructor',
271
+ deployer: AztecAddress.ZERO,
272
+ });
273
+ return { artifact: computedArtifact, instance: computedInstance };
274
+ });
227
275
 
228
276
  inputs.splice(0, 0, artifact, instance);
229
277
  }
@@ -235,17 +283,7 @@ class TXEDispatcher {
235
283
 
236
284
  if (!sessions.has(sessionId)) {
237
285
  this.logger.debug(`Creating new session ${sessionId}`);
238
- if (!this.contractStore) {
239
- const kvStore = await openTmpStore('txe-contracts');
240
- this.contractStore = new ContractStore(kvStore);
241
- const provider = new BundledProtocolContractsProvider();
242
- for (const name of protocolContractNames) {
243
- const { instance, artifact } = await provider.getProtocolContractArtifact(name);
244
- await this.contractStore.addContractArtifact(artifact);
245
- await this.contractStore.addContractInstance(instance);
246
- }
247
- this.logger.debug('Registered protocol contracts in shared contract store');
248
- }
286
+ await this.warmUp();
249
287
  sessions.set(sessionId, await TXESession.init(this.contractStore));
250
288
  }
251
289
 
@@ -262,20 +300,31 @@ class TXEDispatcher {
262
300
 
263
301
  return await sessions.get(sessionId)!.processFunction(functionName, inputs);
264
302
  }
303
+
304
+ /**
305
+ * Releases a session and its resources (per-session LMDB + `NativeWorldStateService`).
306
+ * Called by the dispatcher pool when nargo closes its TCP connection for a test (see
307
+ * `rpc_server.ts`'s socket tracker). No-op if the session was never created — that happens
308
+ * when nargo opens a connection but errors before sending a request.
309
+ */
310
+ async disposeSession(sessionId: number): Promise<void> {
311
+ const session = sessions.get(sessionId);
312
+ if (!session) {
313
+ return;
314
+ }
315
+ sessions.delete(sessionId);
316
+ await session.dispose();
317
+ }
318
+ }
319
+
320
+ /** Diagnostic-only: number of sessions currently held by this worker. */
321
+ export function activeSessionCount(): number {
322
+ return sessions.size;
265
323
  }
266
324
 
267
- const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
325
+ export const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
268
326
  // eslint-disable-next-line camelcase
269
- resolve_foreign_call: z.function().args(TXEForeignCallInputSchema).returns(ForeignCallResultSchema),
327
+ resolve_foreign_call: z.function({ input: z.tuple([TXEForeignCallInputSchema]), output: ForeignCallResultSchema }),
328
+ // disposeSession is invoked over IPC from the worker, not via RPC; required by ApiSchemaFor.
329
+ disposeSession: z.function({ input: z.tuple([z.number().nonnegative()]), output: z.void() }),
270
330
  };
271
-
272
- /**
273
- * Creates an RPC server that forwards calls to the TXE.
274
- * @param logger - Logger to output to
275
- * @returns A TXE RPC server.
276
- */
277
- export function createTXERpcServer(logger: Logger) {
278
- return createSafeJsonRpcServer(new TXEDispatcher(logger), TXEDispatcherApiSchema, {
279
- http200OnError: true,
280
- });
281
- }
@@ -0,0 +1,23 @@
1
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
+
3
+ import { addExtension } from 'msgpackr';
4
+
5
+ // `@aztec/native/msgpack_channel` registers a msgpackr extension that packs `Fr` instances to
6
+ // their raw 32-byte buffer for the C++ world-state side to deserialize. Because `@aztec/native`
7
+ // is externalized in TXE's esbuild config (it loads a `.node` binary that can't be bundled),
8
+ // the `Fr` class the extension is keyed on belongs to the *external* `@aztec/foundation`
9
+ // module instance — a different class identity than the `Fr` bundled into the TXE worker /
10
+ // bin. msgpackr's extension match uses `instanceof`, so bundled `Fr` instances slip through
11
+ // and msgpackr falls back to `Fr.toJSON()` (returns `Fr.toString()` — a `0x...` hex string).
12
+ // The C++ side then sees a string where a 32-byte binary was expected and throws
13
+ // `msgpack::type_error` whose `what()` is literally `"std::bad_cast"`.
14
+ //
15
+ // Fix: register the same extension under the BUNDLED `Fr` class identity. msgpackr's extension
16
+ // table holds an entry per `Class`, so both Fr classes now resolve to the same `write` callback
17
+ // and produce the same wire format. Imported as a side-effect from `./index.js` so both the
18
+ // worker (via `./worker.ts`) and the main-thread RPC server (via `./rpc_server.ts`) get the
19
+ // registration before any `sendMessage` runs.
20
+ addExtension({
21
+ Class: Fr,
22
+ write: (fr: Fr) => fr.toBuffer(),
23
+ });
@@ -6,6 +6,8 @@ import { BlockNumber } from '@aztec/foundation/branded-types';
6
6
  import type { Fr } from '@aztec/foundation/curves/bn254';
7
7
  import type { EventSelector, FunctionSelector } from '@aztec/stdlib/abi';
8
8
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
+ import type { GasSettings } from '@aztec/stdlib/gas';
10
+ import type { PrivateLog } from '@aztec/stdlib/logs';
9
11
  import type { UInt64 } from '@aztec/stdlib/types';
10
12
 
11
13
  // These interfaces complement the ones defined in PXE, and combined with those contain the full list of oracles used by
@@ -36,6 +38,11 @@ export interface IAvmExecutionOracle {
36
38
  nullifierExists(siloedNullifier: Fr): Promise<boolean>;
37
39
  storageWrite(slot: Fr, value: Fr): Promise<void>;
38
40
  storageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr>;
41
+ returndataSize(): Promise<Fr>;
42
+ returndataCopy(rdOffset: number, copySize: number): Promise<Fr[]>;
43
+ call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<Fr[]>;
44
+ staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<Fr[]>;
45
+ successCopy(): Promise<Fr>;
39
46
  }
40
47
 
41
48
  /**
@@ -58,28 +65,34 @@ export interface ITxeExecutionOracle {
58
65
  txHash: TxHash;
59
66
  noteHashes: Fr[];
60
67
  nullifiers: Fr[];
68
+ privateLogs: PrivateLog[];
61
69
  }>;
62
70
  getPrivateEvents(selector: EventSelector, contractAddress: AztecAddress, scope: AztecAddress): Promise<Fr[][]>;
63
71
  privateCallNewFlow(
64
- from: AztecAddress,
72
+ from: AztecAddress | undefined,
65
73
  targetContractAddress: AztecAddress,
66
74
  functionSelector: FunctionSelector,
67
75
  args: Fr[],
68
76
  argsHash: Fr,
69
77
  isStaticCall: boolean,
78
+ additionalScopes: AztecAddress[],
70
79
  jobId: string,
71
- ): Promise<Fr[]>;
80
+ authorizedUtilityCallTargets: AztecAddress[],
81
+ gasSettings: GasSettings,
82
+ ): Promise<{ returnValues: Fr[]; offchainEffects: Fr[][] }>;
72
83
  executeUtilityFunction(
73
84
  targetContractAddress: AztecAddress,
74
85
  functionSelector: FunctionSelector,
75
86
  args: Fr[],
76
87
  jobId: string,
88
+ authorizedUtilityCallTargets: AztecAddress[],
77
89
  ): Promise<Fr[]>;
78
90
  publicCallNewFlow(
79
- from: AztecAddress,
91
+ from: AztecAddress | undefined,
80
92
  targetContractAddress: AztecAddress,
81
93
  calldata: Fr[],
82
94
  isStaticCall: boolean,
95
+ gasSettings: GasSettings,
83
96
  ): Promise<Fr[]>;
84
97
  // TODO(F-335): Drop this from here as it's not a real oracle handler - it's only called from
85
98
  // RPCTranslator::txeGetPrivateEvents and never from Noir.
@@ -122,6 +122,36 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
122
122
  return value;
123
123
  }
124
124
 
125
+ returndataSize(): Promise<Fr> {
126
+ throw new Error(
127
+ 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
128
+ );
129
+ }
130
+
131
+ returndataCopy(_rdOffset: number, _copySize: number): Promise<Fr[]> {
132
+ throw new Error(
133
+ 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
134
+ );
135
+ }
136
+
137
+ call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise<Fr[]> {
138
+ throw new Error(
139
+ 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
140
+ );
141
+ }
142
+
143
+ staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise<Fr[]> {
144
+ throw new Error(
145
+ 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
146
+ );
147
+ }
148
+
149
+ successCopy(): Promise<Fr> {
150
+ throw new Error(
151
+ 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
152
+ );
153
+ }
154
+
125
155
  async close(): Promise<L2Block> {
126
156
  this.logger.debug('Exiting Public Context, building block with collected side effects', {
127
157
  blockNumber: this.globalVariables.blockNumber,