@aztec/txe 0.0.1-commit.86469d5 → 0.0.1-commit.8655d4a

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 (134) 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 +148 -75
  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 +40 -28
  36. package/dest/oracle/interfaces.d.ts.map +1 -1
  37. package/dest/oracle/txe_oracle_public_context.d.ts +18 -13
  38. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  39. package/dest/oracle/txe_oracle_public_context.js +27 -12
  40. package/dest/oracle/txe_oracle_top_level_context.d.ts +39 -24
  41. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  42. package/dest/oracle/txe_oracle_top_level_context.js +205 -78
  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 +140 -84
  50. package/dest/rpc_translator.d.ts.map +1 -1
  51. package/dest/rpc_translator.js +587 -239
  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 +24 -20
  57. package/dest/state_machine/dummy_p2p_client.d.ts +26 -16
  58. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  59. package/dest/state_machine/dummy_p2p_client.js +44 -20
  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 +10 -6
  64. package/dest/state_machine/index.d.ts.map +1 -1
  65. package/dest/state_machine/index.js +35 -12
  66. package/dest/state_machine/mock_epoch_cache.d.ts +20 -3
  67. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  68. package/dest/state_machine/mock_epoch_cache.js +39 -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 +71 -10
  76. package/dest/txe_session.d.ts.map +1 -1
  77. package/dest/txe_session.js +227 -40
  78. package/dest/utils/block_creation.d.ts +1 -1
  79. package/dest/utils/block_creation.d.ts.map +1 -1
  80. package/dest/utils/block_creation.js +3 -1
  81. package/dest/utils/encoding.d.ts +191 -0
  82. package/dest/utils/encoding.d.ts.map +1 -0
  83. package/dest/{util → utils}/encoding.js +7 -2
  84. package/dest/{util → utils}/expected_failure_error.d.ts +1 -1
  85. package/dest/utils/expected_failure_error.d.ts.map +1 -0
  86. package/dest/{util → utils}/txe_account_store.d.ts +1 -1
  87. package/dest/utils/txe_account_store.d.ts.map +1 -0
  88. package/dest/utils/txe_public_contract_data_source.d.ts +20 -0
  89. package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -0
  90. package/dest/{util → utils}/txe_public_contract_data_source.js +6 -25
  91. package/dest/worker.bundle.js +3 -0
  92. package/dest/worker.bundle.js.map +7 -0
  93. package/dest/worker.d.ts +2 -0
  94. package/dest/worker.d.ts.map +1 -0
  95. package/dest/worker.js +92 -0
  96. package/package.json +37 -21
  97. package/src/bin/check_txe_oracle_version.ts +37 -0
  98. package/src/bin/index.ts +11 -2
  99. package/src/constants.ts +6 -0
  100. package/src/dispatcher_pool.ts +316 -0
  101. package/src/index.ts +166 -80
  102. package/src/msgpackr_fr_extension.ts +23 -0
  103. package/src/oracle/interfaces.ts +48 -34
  104. package/src/oracle/txe_oracle_public_context.ts +42 -12
  105. package/src/oracle/txe_oracle_top_level_context.ts +231 -144
  106. package/src/oracle/txe_private_execution_oracle.ts +30 -0
  107. package/src/rpc_server.ts +87 -0
  108. package/src/rpc_translator.ts +662 -316
  109. package/src/state_machine/archiver.ts +35 -18
  110. package/src/state_machine/dummy_p2p_client.ts +70 -32
  111. package/src/state_machine/global_variable_builder.ts +18 -3
  112. package/src/state_machine/index.ts +48 -13
  113. package/src/state_machine/mock_epoch_cache.ts +51 -3
  114. package/src/state_machine/synchronizer.ts +5 -5
  115. package/src/txe_oracle_version.ts +17 -0
  116. package/src/txe_session.ts +324 -90
  117. package/src/utils/block_creation.ts +3 -1
  118. package/src/{util → utils}/encoding.ts +8 -2
  119. package/src/{util → utils}/txe_public_contract_data_source.ts +10 -38
  120. package/src/worker.ts +98 -0
  121. package/dest/util/encoding.d.ts +0 -720
  122. package/dest/util/encoding.d.ts.map +0 -1
  123. package/dest/util/expected_failure_error.d.ts.map +0 -1
  124. package/dest/util/txe_account_store.d.ts.map +0 -1
  125. package/dest/util/txe_contract_store.d.ts +0 -12
  126. package/dest/util/txe_contract_store.d.ts.map +0 -1
  127. package/dest/util/txe_contract_store.js +0 -22
  128. package/dest/util/txe_public_contract_data_source.d.ts +0 -21
  129. package/dest/util/txe_public_contract_data_source.d.ts.map +0 -1
  130. package/src/util/txe_contract_store.ts +0 -36
  131. /package/dest/{util → utils}/expected_failure_error.js +0 -0
  132. /package/dest/{util → utils}/txe_account_store.js +0 -0
  133. /package/src/{util → utils}/expected_failure_error.ts +0 -0
  134. /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,11 +6,12 @@ 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 { type ProtocolContract, protocolContractNames } from '@aztec/protocol-contracts';
13
- import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
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';
14
13
  import { computeArtifactHash } from '@aztec/stdlib/contract';
14
+ import type { ContractArtifactWithHash } from '@aztec/stdlib/contract';
15
15
  import type { ApiSchemaFor } from '@aztec/stdlib/schemas';
16
16
  import { zodFor } from '@aztec/stdlib/schemas';
17
17
 
@@ -21,6 +21,9 @@ import { readFile, readdir } from 'fs/promises';
21
21
  import { join, parse } from 'path';
22
22
  import { z } from 'zod';
23
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';
24
27
  import { type TXEOracleFunctionName, TXESession } from './txe_session.js';
25
28
  import {
26
29
  type ForeignCallArgs,
@@ -29,24 +32,61 @@ import {
29
32
  type ForeignCallResult,
30
33
  ForeignCallResultSchema,
31
34
  type ForeignCallSingle,
35
+ addressFromSingle,
32
36
  fromArray,
33
37
  fromSingle,
34
38
  toSingle,
35
- } from './util/encoding.js';
36
- import type { ContractArtifactWithHash } from './util/txe_contract_store.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[] = [];
37
44
 
38
45
  const sessions = new Map<number, TXESession>();
39
46
 
40
- /*
41
- * TXE typically has to load the same contract artifacts over and over again for multiple tests,
42
- * so we cache them here to avoid both loading them from disk repeatedly and computing their artifact hashes
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.
43
51
  */
44
- 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<
45
81
  string,
46
82
  { artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }
47
83
  >();
48
84
 
49
- 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 = {
50
90
  session_id: number;
51
91
  function: TXEOracleFunctionName;
52
92
  root_path: string;
@@ -54,10 +94,13 @@ type TXEForeignCallInput = {
54
94
  inputs: ForeignCallArgs;
55
95
  };
56
96
 
57
- const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
97
+ export const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
58
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).
59
102
  // eslint-disable-next-line camelcase
60
- session_id: z.number().int().nonnegative(),
103
+ session_id: z.number().nonnegative(),
61
104
  function: z.string() as z.ZodType<TXEOracleFunctionName>,
62
105
  // eslint-disable-next-line camelcase
63
106
  root_path: z.string(),
@@ -67,12 +110,56 @@ const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
67
110
  }),
68
111
  );
69
112
 
70
- class TXEDispatcher {
71
- private protocolContracts!: ProtocolContract[];
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 {
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
+ }
72
141
 
73
- 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
+ }
74
161
 
75
- private fastHashFile(path: string) {
162
+ private fastHashFile(path: string): Promise<string> {
76
163
  return new Promise(resolve => {
77
164
  const fd = createReadStream(path);
78
165
  const hash = createHash('sha1');
@@ -80,7 +167,7 @@ class TXEDispatcher {
80
167
 
81
168
  fd.on('end', function () {
82
169
  hash.end();
83
- resolve(hash.read());
170
+ resolve(hash.read() as string);
84
171
  });
85
172
 
86
173
  fd.pipe(hash);
@@ -96,6 +183,8 @@ class TXEDispatcher {
96
183
 
97
184
  const decodedArgs = fromArray(inputs[3] as ForeignCallArray);
98
185
  const secret = fromSingle(inputs[4] as ForeignCallSingle);
186
+ const salt = fromSingle(inputs[5] as ForeignCallSingle);
187
+ const deployer = addressFromSingle(inputs[6] as ForeignCallSingle);
99
188
  const publicKeys = secret.equals(Fr.ZERO) ? PublicKeys.default() : (await deriveKeys(secret)).publicKeys;
100
189
  const publicKeysHash = await publicKeys.hash();
101
190
 
@@ -126,39 +215,31 @@ class TXEDispatcher {
126
215
 
127
216
  const cacheKey = `${contractDirectory ?? ''}-${contractFilename}-${initializer}-${decodedArgs
128
217
  .map(arg => arg.toString())
129
- .join('-')}-${publicKeysHash}-${fileHash}`;
130
-
131
- let instance;
132
- let artifact: ContractArtifactWithHash;
218
+ .join('-')}-${publicKeysHash}-${salt}-${deployer}-${fileHash}`;
133
219
 
134
- if (TXEArtifactsCache.has(cacheKey)) {
135
- this.logger.debug(`Using cached artifact for ${cacheKey}`);
136
- ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
137
- } else {
220
+ const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
138
221
  this.logger.debug(`Loading compiled artifact ${artifactPath}`);
139
- const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
140
- const artifactWithoutHash = loadContractArtifact(artifactJSON);
141
- artifact = {
142
- ...artifactWithoutHash,
143
- // Artifact hash is *very* expensive to compute, so we do it here once
144
- // and the TXE contract data provider can cache it
145
- artifactHash: await computeArtifactHash(artifactWithoutHash),
146
- };
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
+ });
147
230
  this.logger.debug(
148
- `Deploy ${
149
- artifact.name
150
- } with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
231
+ `Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
151
232
  );
152
- instance = await getContractInstanceFromInstantiationParams(artifact, {
233
+ const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
153
234
  constructorArgs: decodedArgs,
154
235
  skipArgsDecoding: true,
155
- salt: Fr.ONE,
236
+ salt,
156
237
  publicKeys,
157
238
  constructorArtifact: initializer ? initializer : undefined,
158
- deployer: AztecAddress.ZERO,
239
+ deployer,
159
240
  });
160
- TXEArtifactsCache.set(cacheKey, { artifact, instance });
161
- }
241
+ return { artifact: computedArtifact, instance: computedInstance };
242
+ });
162
243
 
163
244
  inputs.splice(0, 1, artifact, instance, toSingle(secret));
164
245
  }
@@ -168,22 +249,20 @@ class TXEDispatcher {
168
249
 
169
250
  const cacheKey = `SchnorrAccountContract-${secret}`;
170
251
 
171
- let artifact: ContractArtifactWithHash;
172
- let instance;
173
-
174
- if (TXEArtifactsCache.has(cacheKey)) {
175
- this.logger.debug(`Using cached artifact for ${cacheKey}`);
176
- ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
177
- } else {
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
+ );
261
+ }
262
+ const computedArtifact = { ...artifactFromStore, artifactHash: classWithPreimage.artifactHash };
178
263
  const keys = await deriveKeys(secret);
179
- const args = [keys.publicKeys.masterIncomingViewingPublicKey.x, keys.publicKeys.masterIncomingViewingPublicKey.y];
180
- artifact = {
181
- ...SchnorrAccountContractArtifact,
182
- // Artifact hash is *very* expensive to compute, so we do it here once
183
- // and the TXE contract data provider can cache it
184
- artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact),
185
- };
186
- instance = await getContractInstanceFromInstantiationParams(artifact, {
264
+ const args = [keys.publicKeys.ivpkM.x, keys.publicKeys.ivpkM.y];
265
+ const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
187
266
  constructorArgs: args,
188
267
  skipArgsDecoding: true,
189
268
  salt: Fr.ONE,
@@ -191,8 +270,8 @@ class TXEDispatcher {
191
270
  constructorArtifact: 'constructor',
192
271
  deployer: AztecAddress.ZERO,
193
272
  });
194
- TXEArtifactsCache.set(cacheKey, { artifact, instance });
195
- }
273
+ return { artifact: computedArtifact, instance: computedInstance };
274
+ });
196
275
 
197
276
  inputs.splice(0, 0, artifact, instance);
198
277
  }
@@ -204,20 +283,16 @@ class TXEDispatcher {
204
283
 
205
284
  if (!sessions.has(sessionId)) {
206
285
  this.logger.debug(`Creating new session ${sessionId}`);
207
- if (!this.protocolContracts) {
208
- this.protocolContracts = await Promise.all(
209
- protocolContractNames.map(name => new BundledProtocolContractsProvider().getProtocolContractArtifact(name)),
210
- );
211
- }
212
- sessions.set(sessionId, await TXESession.init(this.protocolContracts));
286
+ await this.warmUp();
287
+ sessions.set(sessionId, await TXESession.init(this.contractStore));
213
288
  }
214
289
 
215
290
  switch (functionName) {
216
- case 'txeDeploy': {
291
+ case 'aztec_txe_deploy': {
217
292
  await this.#processDeployInputs(callData);
218
293
  break;
219
294
  }
220
- case 'txeAddAccount': {
295
+ case 'aztec_txe_addAccount': {
221
296
  await this.#processAddAccountInputs(callData);
222
297
  break;
223
298
  }
@@ -225,20 +300,31 @@ class TXEDispatcher {
225
300
 
226
301
  return await sessions.get(sessionId)!.processFunction(functionName, inputs);
227
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;
228
323
  }
229
324
 
230
- const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
325
+ export const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
231
326
  // eslint-disable-next-line camelcase
232
- 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() }),
233
330
  };
234
-
235
- /**
236
- * Creates an RPC server that forwards calls to the TXE.
237
- * @param logger - Logger to output to
238
- * @returns A TXE RPC server.
239
- */
240
- export function createTXERpcServer(logger: Logger) {
241
- return createSafeJsonRpcServer(new TXEDispatcher(logger), TXEDispatcherApiSchema, {
242
- http200OnError: true,
243
- });
244
- }
@@ -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
@@ -24,18 +26,23 @@ import type { UInt64 } from '@aztec/stdlib/types';
24
26
  export interface IAvmExecutionOracle {
25
27
  isAvm: true;
26
28
 
27
- avmOpcodeAddress(): Promise<AztecAddress>;
28
- avmOpcodeSender(): Promise<AztecAddress>;
29
- avmOpcodeBlockNumber(): Promise<BlockNumber>;
30
- avmOpcodeTimestamp(): Promise<bigint>;
31
- avmOpcodeIsStaticCall(): Promise<boolean>;
32
- avmOpcodeChainId(): Promise<Fr>;
33
- avmOpcodeVersion(): Promise<Fr>;
34
- avmOpcodeEmitNullifier(nullifier: Fr): Promise<void>;
35
- avmOpcodeEmitNoteHash(noteHash: Fr): Promise<void>;
36
- avmOpcodeNullifierExists(siloedNullifier: Fr): Promise<boolean>;
37
- avmOpcodeStorageWrite(slot: Fr, value: Fr): Promise<void>;
38
- avmOpcodeStorageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr>;
29
+ address(): Promise<AztecAddress>;
30
+ sender(): Promise<AztecAddress>;
31
+ blockNumber(): Promise<BlockNumber>;
32
+ timestamp(): Promise<bigint>;
33
+ isStaticCall(): Promise<boolean>;
34
+ chainId(): Promise<Fr>;
35
+ version(): Promise<Fr>;
36
+ emitNullifier(nullifier: Fr): Promise<void>;
37
+ emitNoteHash(noteHash: Fr): Promise<void>;
38
+ nullifierExists(siloedNullifier: Fr): Promise<boolean>;
39
+ storageWrite(slot: Fr, value: Fr): Promise<void>;
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
  /**
@@ -44,43 +51,50 @@ export interface IAvmExecutionOracle {
44
51
  export interface ITxeExecutionOracle {
45
52
  isTxe: true;
46
53
 
47
- txeGetDefaultAddress(): AztecAddress;
48
- txeGetNextBlockNumber(): Promise<BlockNumber>;
49
- txeGetNextBlockTimestamp(): Promise<UInt64>;
50
- txeAdvanceBlocksBy(blocks: number): Promise<void>;
51
- txeAdvanceTimestampBy(duration: UInt64): void;
52
- txeDeploy(artifact: ContractArtifact, instance: ContractInstanceWithAddress, foreignSecret: Fr): Promise<void>;
53
- txeCreateAccount(secret: Fr): Promise<CompleteAddress>;
54
- txeAddAccount(
55
- artifact: ContractArtifact,
56
- instance: ContractInstanceWithAddress,
57
- secret: Fr,
58
- ): Promise<CompleteAddress>;
59
- txeAddAuthWitness(address: AztecAddress, messageHash: Fr): Promise<void>;
60
- txeGetLastBlockTimestamp(): Promise<bigint>;
61
- txeGetLastTxEffects(): Promise<{
54
+ getDefaultAddress(): AztecAddress;
55
+ getNextBlockNumber(): Promise<BlockNumber>;
56
+ getNextBlockTimestamp(): Promise<UInt64>;
57
+ advanceBlocksBy(blocks: number): Promise<void>;
58
+ advanceTimestampBy(duration: UInt64): void;
59
+ deploy(artifact: ContractArtifact, instance: ContractInstanceWithAddress, foreignSecret: Fr): Promise<void>;
60
+ createAccount(secret: Fr): Promise<CompleteAddress>;
61
+ addAccount(artifact: ContractArtifact, instance: ContractInstanceWithAddress, secret: Fr): Promise<CompleteAddress>;
62
+ addAuthWitness(address: AztecAddress, messageHash: Fr): Promise<void>;
63
+ getLastBlockTimestamp(): Promise<bigint>;
64
+ getLastTxEffects(): Promise<{
62
65
  txHash: TxHash;
63
66
  noteHashes: Fr[];
64
67
  nullifiers: Fr[];
68
+ privateLogs: PrivateLog[];
65
69
  }>;
66
- txeGetPrivateEvents(selector: EventSelector, contractAddress: AztecAddress, scope: AztecAddress): Promise<Fr[][]>;
67
- txePrivateCallNewFlow(
68
- from: AztecAddress,
70
+ getPrivateEvents(selector: EventSelector, contractAddress: AztecAddress, scope: AztecAddress): Promise<Fr[][]>;
71
+ privateCallNewFlow(
72
+ from: AztecAddress | undefined,
69
73
  targetContractAddress: AztecAddress,
70
74
  functionSelector: FunctionSelector,
71
75
  args: Fr[],
72
76
  argsHash: Fr,
73
77
  isStaticCall: boolean,
74
- ): Promise<Fr[]>;
75
- txeSimulateUtilityFunction(
78
+ additionalScopes: AztecAddress[],
79
+ jobId: string,
80
+ authorizedUtilityCallTargets: AztecAddress[],
81
+ gasSettings: GasSettings,
82
+ ): Promise<{ returnValues: Fr[]; offchainEffects: Fr[][] }>;
83
+ executeUtilityFunction(
76
84
  targetContractAddress: AztecAddress,
77
85
  functionSelector: FunctionSelector,
78
86
  args: Fr[],
87
+ jobId: string,
88
+ authorizedUtilityCallTargets: AztecAddress[],
79
89
  ): Promise<Fr[]>;
80
- txePublicCallNewFlow(
81
- from: AztecAddress,
90
+ publicCallNewFlow(
91
+ from: AztecAddress | undefined,
82
92
  targetContractAddress: AztecAddress,
83
93
  calldata: Fr[],
84
94
  isStaticCall: boolean,
95
+ gasSettings: GasSettings,
85
96
  ): Promise<Fr[]>;
97
+ // TODO(F-335): Drop this from here as it's not a real oracle handler - it's only called from
98
+ // RPCTranslator::txeGetPrivateEvents and never from Noir.
99
+ syncContractNonOracleMethod(contractAddress: AztecAddress, scope: AztecAddress, jobId: string): Promise<void>;
86
100
  }
@@ -39,46 +39,46 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
39
39
  });
40
40
  }
41
41
 
42
- avmOpcodeAddress(): Promise<AztecAddress> {
42
+ address(): Promise<AztecAddress> {
43
43
  return Promise.resolve(this.contractAddress);
44
44
  }
45
45
 
46
- avmOpcodeSender(): Promise<AztecAddress> {
46
+ sender(): Promise<AztecAddress> {
47
47
  return Promise.resolve(AztecAddress.ZERO); // todo: change?
48
48
  }
49
49
 
50
- avmOpcodeBlockNumber(): Promise<BlockNumber> {
50
+ blockNumber(): Promise<BlockNumber> {
51
51
  return Promise.resolve(this.globalVariables.blockNumber);
52
52
  }
53
53
 
54
- avmOpcodeTimestamp(): Promise<bigint> {
54
+ timestamp(): Promise<bigint> {
55
55
  return Promise.resolve(this.globalVariables.timestamp);
56
56
  }
57
57
 
58
- avmOpcodeIsStaticCall(): Promise<boolean> {
58
+ isStaticCall(): Promise<boolean> {
59
59
  return Promise.resolve(false);
60
60
  }
61
61
 
62
- avmOpcodeChainId(): Promise<Fr> {
62
+ chainId(): Promise<Fr> {
63
63
  return Promise.resolve(this.globalVariables.chainId);
64
64
  }
65
65
 
66
- avmOpcodeVersion(): Promise<Fr> {
66
+ version(): Promise<Fr> {
67
67
  return Promise.resolve(this.globalVariables.version);
68
68
  }
69
69
 
70
- async avmOpcodeEmitNullifier(nullifier: Fr) {
70
+ async emitNullifier(nullifier: Fr) {
71
71
  const siloedNullifier = await siloNullifier(this.contractAddress, nullifier);
72
72
  this.transientSiloedNullifiers.push(siloedNullifier);
73
73
  }
74
74
 
75
- async avmOpcodeEmitNoteHash(noteHash: Fr) {
75
+ async emitNoteHash(noteHash: Fr) {
76
76
  const siloedNoteHash = await siloNoteHash(this.contractAddress, noteHash);
77
77
  // TODO: make the note hash unique - they are only siloed right now
78
78
  this.transientUniqueNoteHashes.push(siloedNoteHash);
79
79
  }
80
80
 
81
- async avmOpcodeNullifierExists(siloedNullifier: Fr): Promise<boolean> {
81
+ async nullifierExists(siloedNullifier: Fr): Promise<boolean> {
82
82
  const treeIndex = (
83
83
  await this.forkedWorldTrees.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()])
84
84
  )[0];
@@ -87,7 +87,7 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
87
87
  return treeIndex !== undefined || transientIndex !== undefined;
88
88
  }
89
89
 
90
- async avmOpcodeStorageWrite(slot: Fr, value: Fr) {
90
+ async storageWrite(slot: Fr, value: Fr) {
91
91
  this.logger.debug('AVM storage write', { slot, value });
92
92
 
93
93
  const dataWrite = new PublicDataWrite(await computePublicDataTreeLeafSlot(this.contractAddress, slot), value);
@@ -99,7 +99,7 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
99
99
  ]);
100
100
  }
101
101
 
102
- async avmOpcodeStorageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr> {
102
+ async storageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr> {
103
103
  const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot);
104
104
 
105
105
  const lowLeafResult = await this.forkedWorldTrees.getPreviousValueIndex(
@@ -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,