@aztec/txe 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a

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 (144) hide show
  1. package/dest/ContractClassRegistry-PII7Q556.js +3 -0
  2. package/dest/ContractClassRegistry-PII7Q556.js.map +7 -0
  3. package/dest/ContractInstanceRegistry-3MHLGFNZ.js +3 -0
  4. package/dest/ContractInstanceRegistry-3MHLGFNZ.js.map +7 -0
  5. package/dest/FeeJuice-TQV5AQK7.js +3 -0
  6. package/dest/FeeJuice-TQV5AQK7.js.map +7 -0
  7. package/dest/MultiCallEntrypoint-SBJUIJY7.js +3 -0
  8. package/dest/MultiCallEntrypoint-SBJUIJY7.js.map +7 -0
  9. package/dest/SchnorrAccount-MQXPIZ44.js +3 -0
  10. package/dest/SchnorrAccount-MQXPIZ44.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 +61 -0
  14. package/dest/bin/index.js +3 -30
  15. package/dest/bin/index.js.map +7 -0
  16. package/dest/bin/oracle_test_server.d.ts +3 -0
  17. package/dest/bin/oracle_test_server.d.ts.map +1 -0
  18. package/dest/bin/oracle_test_server.js +41 -0
  19. package/dest/chunk-2FGZE6YL.js +265 -0
  20. package/dest/chunk-2FGZE6YL.js.map +7 -0
  21. package/dest/chunk-6HRVNWJU.js +304 -0
  22. package/dest/chunk-6HRVNWJU.js.map +7 -0
  23. package/dest/chunk-BJVAAXNA.js +3 -0
  24. package/dest/chunk-BJVAAXNA.js.map +7 -0
  25. package/dest/constants.d.ts +5 -1
  26. package/dest/constants.d.ts.map +1 -1
  27. package/dest/constants.js +8 -0
  28. package/dest/dispatcher_pool.d.ts +67 -0
  29. package/dest/dispatcher_pool.d.ts.map +1 -0
  30. package/dest/dispatcher_pool.js +284 -0
  31. package/dest/index.d.ts +185 -7
  32. package/dest/index.d.ts.map +1 -1
  33. package/dest/index.js +70 -190
  34. package/dest/metafile.json +38642 -0
  35. package/dest/msgpackr_fr_extension.d.ts +2 -0
  36. package/dest/msgpackr_fr_extension.d.ts.map +1 -0
  37. package/dest/msgpackr_fr_extension.js +21 -0
  38. package/dest/oracle/interfaces.d.ts +33 -8
  39. package/dest/oracle/interfaces.d.ts.map +1 -1
  40. package/dest/oracle/test-resolver/fixtures.d.ts +43 -0
  41. package/dest/oracle/test-resolver/fixtures.d.ts.map +1 -0
  42. package/dest/oracle/test-resolver/fixtures.js +39 -0
  43. package/dest/oracle/test-resolver/index.d.ts +9 -0
  44. package/dest/oracle/test-resolver/index.d.ts.map +1 -0
  45. package/dest/oracle/test-resolver/index.js +33 -0
  46. package/dest/oracle/test-resolver/resolver.d.ts +34 -0
  47. package/dest/oracle/test-resolver/resolver.d.ts.map +1 -0
  48. package/dest/oracle/test-resolver/resolver.js +114 -0
  49. package/dest/oracle/txe_oracle_public_context.d.ts +26 -2
  50. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  51. package/dest/oracle/txe_oracle_public_context.js +43 -1
  52. package/dest/oracle/txe_oracle_registry.d.ts +147 -0
  53. package/dest/oracle/txe_oracle_registry.d.ts.map +1 -0
  54. package/dest/oracle/txe_oracle_registry.js +562 -0
  55. package/dest/oracle/txe_oracle_top_level_context.d.ts +27 -13
  56. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  57. package/dest/oracle/txe_oracle_top_level_context.js +129 -52
  58. package/dest/oracle/txe_oracle_version.d.ts +17 -0
  59. package/dest/oracle/txe_oracle_version.d.ts.map +1 -0
  60. package/dest/oracle/txe_oracle_version.js +14 -0
  61. package/dest/oracle/txe_private_execution_oracle.d.ts +17 -0
  62. package/dest/oracle/txe_private_execution_oracle.d.ts.map +1 -0
  63. package/dest/oracle/txe_private_execution_oracle.js +15 -0
  64. package/dest/rpc_server.d.ts +14 -0
  65. package/dest/rpc_server.d.ts.map +1 -0
  66. package/dest/rpc_server.js +78 -0
  67. package/dest/rpc_translator.d.ts +96 -266
  68. package/dest/rpc_translator.d.ts.map +1 -1
  69. package/dest/rpc_translator.js +699 -730
  70. package/dest/server.bundle.js +3 -0
  71. package/dest/server.bundle.js.map +7 -0
  72. package/dest/state_machine/archiver.d.ts +4 -3
  73. package/dest/state_machine/archiver.d.ts.map +1 -1
  74. package/dest/state_machine/archiver.js +24 -14
  75. package/dest/state_machine/dummy_p2p_client.d.ts +11 -6
  76. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  77. package/dest/state_machine/dummy_p2p_client.js +11 -2
  78. package/dest/state_machine/index.d.ts +4 -2
  79. package/dest/state_machine/index.d.ts.map +1 -1
  80. package/dest/state_machine/index.js +16 -2
  81. package/dest/state_machine/mock_epoch_cache.d.ts +1 -3
  82. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  83. package/dest/state_machine/mock_epoch_cache.js +0 -6
  84. package/dest/state_machine/synchronizer.js +1 -1
  85. package/dest/txe_session.d.ts +75 -14
  86. package/dest/txe_session.d.ts.map +1 -1
  87. package/dest/txe_session.js +164 -38
  88. package/dest/utils/encoding.d.ts +191 -0
  89. package/dest/utils/encoding.d.ts.map +1 -0
  90. package/dest/{util → utils}/encoding.js +3 -2
  91. package/dest/{util → utils}/expected_failure_error.d.ts +1 -1
  92. package/dest/utils/expected_failure_error.d.ts.map +1 -0
  93. package/dest/{util → utils}/txe_account_store.d.ts +1 -1
  94. package/dest/utils/txe_account_store.d.ts.map +1 -0
  95. package/dest/utils/txe_artifact_resolver.d.ts +37 -0
  96. package/dest/utils/txe_artifact_resolver.d.ts.map +1 -0
  97. package/dest/utils/txe_artifact_resolver.js +161 -0
  98. package/dest/utils/txe_public_contract_data_source.d.ts +20 -0
  99. package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -0
  100. package/dest/worker.bundle.js +3 -0
  101. package/dest/worker.bundle.js.map +7 -0
  102. package/dest/worker.d.ts +2 -0
  103. package/dest/worker.d.ts.map +1 -0
  104. package/dest/worker.js +92 -0
  105. package/package.json +38 -21
  106. package/src/bin/check_txe_oracle_version.ts +70 -0
  107. package/src/bin/index.ts +11 -2
  108. package/src/bin/oracle_test_server.ts +51 -0
  109. package/src/constants.ts +10 -0
  110. package/src/dispatcher_pool.ts +316 -0
  111. package/src/index.ts +97 -227
  112. package/src/msgpackr_fr_extension.ts +23 -0
  113. package/src/oracle/interfaces.ts +29 -7
  114. package/src/oracle/test-resolver/fixtures.ts +84 -0
  115. package/src/oracle/test-resolver/index.ts +45 -0
  116. package/src/oracle/test-resolver/resolver.ts +165 -0
  117. package/src/oracle/txe_oracle_public_context.ts +60 -0
  118. package/src/oracle/txe_oracle_registry.ts +401 -0
  119. package/src/oracle/txe_oracle_top_level_context.ts +157 -62
  120. package/src/oracle/txe_oracle_version.ts +17 -0
  121. package/src/oracle/txe_private_execution_oracle.ts +30 -0
  122. package/src/rpc_server.ts +87 -0
  123. package/src/rpc_translator.ts +774 -1043
  124. package/src/state_machine/archiver.ts +37 -16
  125. package/src/state_machine/dummy_p2p_client.ts +25 -9
  126. package/src/state_machine/index.ts +17 -4
  127. package/src/state_machine/mock_epoch_cache.ts +0 -8
  128. package/src/state_machine/synchronizer.ts +1 -1
  129. package/src/txe_session.ts +269 -39
  130. package/src/{util → utils}/encoding.ts +3 -2
  131. package/src/utils/txe_artifact_resolver.ts +217 -0
  132. package/src/worker.ts +98 -0
  133. package/dest/util/encoding.d.ts +0 -790
  134. package/dest/util/encoding.d.ts.map +0 -1
  135. package/dest/util/expected_failure_error.d.ts.map +0 -1
  136. package/dest/util/txe_account_store.d.ts.map +0 -1
  137. package/dest/util/txe_public_contract_data_source.d.ts +0 -20
  138. package/dest/util/txe_public_contract_data_source.d.ts.map +0 -1
  139. /package/dest/{util → utils}/expected_failure_error.js +0 -0
  140. /package/dest/{util → utils}/txe_account_store.js +0 -0
  141. /package/dest/{util → utils}/txe_public_contract_data_source.js +0 -0
  142. /package/src/{util → utils}/expected_failure_error.ts +0 -0
  143. /package/src/{util → utils}/txe_account_store.ts +0 -0
  144. /package/src/{util → utils}/txe_public_contract_data_source.ts +0 -0
@@ -1,10 +1,10 @@
1
- import { CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
1
+ import { CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, MAX_PRIVATE_LOGS_PER_TX, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
2
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import { Schnorr } from '@aztec/foundation/crypto/schnorr';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { LogLevels, applyStringFormatting, createLogger } from '@aztec/foundation/log';
6
6
  import { TestDateProvider } from '@aztec/foundation/timer';
7
- import { CapsuleService, ORACLE_VERSION_MAJOR, enrichPublicSimulationError } from '@aztec/pxe/server';
7
+ import { CapsuleService, ORACLE_VERSION_MAJOR, composeHooks, enrichPublicSimulationError } from '@aztec/pxe/server';
8
8
  import { ExecutionNoteCache, ExecutionTaggingIndexCache, HashedValuesCache, Oracle, PrivateExecutionOracle, UtilityExecutionOracle, executePrivateFunction, generateSimulatedProvingResult } from '@aztec/pxe/simulator';
9
9
  import { ExecutionError, WASMSimulator, createSimulationError, extractCallStack, resolveAssertionMessageFromError, toACVMWitness, witnessMapToFields } from '@aztec/simulator/client';
10
10
  import { CppPublicTxSimulator, GuardedMerkleTreeOperations, PublicContractsDB, PublicProcessor } from '@aztec/simulator/server';
@@ -13,17 +13,18 @@ import { AuthWitness } from '@aztec/stdlib/auth-witness';
13
13
  import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
14
14
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
15
15
  import { computePartialAddress } from '@aztec/stdlib/contract';
16
- import { FALLBACK_TEARDOWN_DA_GAS_LIMIT, FALLBACK_TEARDOWN_L2_GAS_LIMIT, Gas, GasFees, GasSettings } from '@aztec/stdlib/gas';
16
+ import { Gas, GasFees } from '@aztec/stdlib/gas';
17
17
  import { computeCalldataHash, computeProtocolNullifier, siloNullifier } from '@aztec/stdlib/hash';
18
18
  import { PartialPrivateTailPublicInputsForPublic, PrivateKernelTailCircuitPublicInputs, PrivateToPublicAccumulatedData, PublicCallRequest } from '@aztec/stdlib/kernel';
19
+ import { hashPublicKey } from '@aztec/stdlib/keys';
19
20
  import { ChonkProof } from '@aztec/stdlib/proofs';
20
21
  import { makeGlobalVariables } from '@aztec/stdlib/testing';
21
22
  import { MerkleTreeId } from '@aztec/stdlib/trees';
22
23
  import { CallContext, HashedValues, PrivateExecutionResult, Tx, TxConstantData, TxContext, TxEffect, TxHash, collectNested } from '@aztec/stdlib/tx';
23
- import { ForkCheckpoint } from '@aztec/world-state';
24
- import { DEFAULT_ADDRESS } from '../constants.js';
25
- import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js';
24
+ import { ForkCheckpoint } from '@aztec/world-state/native';
25
+ import { DEFAULT_ADDRESS, MAX_PRIVATE_EVENTS_PER_TXE_QUERY, MAX_PRIVATE_EVENT_LEN } from '../constants.js';
26
26
  import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from '../utils/block_creation.js';
27
+ import { TXEPublicContractDataSource } from '../utils/txe_public_contract_data_source.js';
27
28
  export class TXEOracleTopLevelContext {
28
29
  stateMachine;
29
30
  contractStore;
@@ -40,11 +41,13 @@ export class TXEOracleTopLevelContext {
40
41
  version;
41
42
  chainId;
42
43
  authwits;
43
- contractSyncService;
44
+ artifactResolver;
45
+ rootPath;
46
+ packageName;
44
47
  isMisc;
45
48
  isTxe;
46
49
  logger;
47
- constructor(stateMachine, contractStore, noteStore, keyStore, addressStore, accountStore, senderTaggingStore, recipientTaggingStore, senderAddressBookStore, capsuleStore, privateEventStore, nextBlockTimestamp, version, chainId, authwits, contractSyncService){
50
+ constructor(stateMachine, contractStore, noteStore, keyStore, addressStore, accountStore, senderTaggingStore, recipientTaggingStore, senderAddressBookStore, capsuleStore, privateEventStore, nextBlockTimestamp, version, chainId, authwits, artifactResolver, rootPath, packageName){
48
51
  this.stateMachine = stateMachine;
49
52
  this.contractStore = contractStore;
50
53
  this.noteStore = noteStore;
@@ -60,7 +63,9 @@ export class TXEOracleTopLevelContext {
60
63
  this.version = version;
61
64
  this.chainId = chainId;
62
65
  this.authwits = authwits;
63
- this.contractSyncService = contractSyncService;
66
+ this.artifactResolver = artifactResolver;
67
+ this.rootPath = rootPath;
68
+ this.packageName = packageName;
64
69
  this.isMisc = true;
65
70
  this.isTxe = true;
66
71
  this.logger = createLogger('txe:top_level_context');
@@ -87,7 +92,7 @@ export class TXEOracleTopLevelContext {
87
92
  return Fr.random();
88
93
  }
89
94
  // We instruct users to debug contracts via this oracle, so it makes sense that they'd expect it to also work in tests
90
- log(level, message, fields) {
95
+ log(level, message, _fieldsSize, fields) {
91
96
  if (!LogLevels[level]) {
92
97
  throw new Error(`Invalid log level: ${level}`);
93
98
  }
@@ -107,20 +112,27 @@ export class TXEOracleTopLevelContext {
107
112
  return Promise.resolve(this.nextBlockTimestamp);
108
113
  }
109
114
  async getLastBlockTimestamp() {
110
- return (await this.stateMachine.node.getBlockHeader('latest')).globalVariables.timestamp;
115
+ return (await this.stateMachine.node.getBlockData('latest')).header.globalVariables.timestamp;
111
116
  }
112
117
  async getLastTxEffects() {
113
118
  const latestBlockNumber = await this.stateMachine.archiver.getBlockNumber();
114
- const block = await this.stateMachine.archiver.getBlock(latestBlockNumber);
119
+ const block = await this.stateMachine.archiver.getBlock({
120
+ number: latestBlockNumber
121
+ });
115
122
  if (block.body.txEffects.length != 1) {
116
123
  // Note that calls like env.mine() will result in blocks with no transactions, hitting this
117
124
  throw new Error(`Expected a single transaction in the last block, found ${block.body.txEffects.length}`);
118
125
  }
119
126
  const txEffects = block.body.txEffects[0];
127
+ const privateLogs = txEffects.privateLogs;
128
+ if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) {
129
+ throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`);
130
+ }
120
131
  return {
121
132
  txHash: txEffects.txHash,
122
133
  noteHashes: txEffects.noteHashes,
123
- nullifiers: txEffects.nullifiers
134
+ nullifiers: txEffects.nullifiers,
135
+ privateLogs
124
136
  };
125
137
  }
126
138
  async syncContractNonOracleMethod(contractAddress, scope, jobId) {
@@ -136,7 +148,7 @@ export class TXEOracleTopLevelContext {
136
148
  ]);
137
149
  }
138
150
  async getPrivateEvents(selector, contractAddress, scope) {
139
- return (await this.privateEventStore.getPrivateEvents(selector, {
151
+ const events = (await this.privateEventStore.getPrivateEvents(selector, {
140
152
  contractAddress,
141
153
  scopes: [
142
154
  scope
@@ -144,6 +156,13 @@ export class TXEOracleTopLevelContext {
144
156
  fromBlock: 0,
145
157
  toBlock: await this.getLastBlockNumber() + 1
146
158
  })).map((e)=>e.packedEvent);
159
+ if (events.length > MAX_PRIVATE_EVENTS_PER_TXE_QUERY) {
160
+ throw new Error(`Array of length ${events.length} larger than maxLen ${MAX_PRIVATE_EVENTS_PER_TXE_QUERY}`);
161
+ }
162
+ if (events.some((e)=>e.length > MAX_PRIVATE_EVENT_LEN)) {
163
+ throw new Error(`Some private event has length larger than maxLen ${MAX_PRIVATE_EVENT_LEN}`);
164
+ }
165
+ return events;
147
166
  }
148
167
  async advanceBlocksBy(blocks) {
149
168
  this.logger.debug(`time traveling ${blocks} blocks`);
@@ -155,22 +174,54 @@ export class TXEOracleTopLevelContext {
155
174
  this.logger.debug(`time traveling ${duration} seconds`);
156
175
  this.nextBlockTimestamp += duration;
157
176
  }
158
- async deploy(artifact, instance, secret) {
177
+ deploymentNullifier(address) {
178
+ return siloNullifier(AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), address.toField());
179
+ }
180
+ async deploy(contractPath, initializer, args, secret, salt, deployer) {
181
+ const { artifact, instance } = await this.artifactResolver.resolveDeployArtifact({
182
+ rootPath: this.rootPath,
183
+ packageName: this.packageName,
184
+ contractPath,
185
+ initializer,
186
+ args,
187
+ secret,
188
+ salt,
189
+ deployer
190
+ });
159
191
  // Emit deployment nullifier
160
192
  await this.mineBlock({
161
193
  nullifiers: [
162
- await siloNullifier(AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), instance.address.toField())
194
+ await this.deploymentNullifier(instance.address)
163
195
  ]
164
196
  });
165
197
  if (!secret.equals(Fr.ZERO)) {
166
- await this.addAccount(artifact, instance, secret);
198
+ await this.registerContractAndAddAccount(artifact, instance, secret);
167
199
  } else {
168
200
  await this.contractStore.addContractInstance(instance);
169
201
  await this.contractStore.addContractArtifact(artifact);
170
202
  this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`);
171
203
  }
204
+ return [
205
+ instance.salt,
206
+ instance.deployer.toField(),
207
+ instance.currentContractClassId,
208
+ instance.initializationHash,
209
+ instance.immutablesHash,
210
+ ...instance.publicKeys.toFields()
211
+ ];
172
212
  }
173
- async addAccount(artifact, instance, secret) {
213
+ /**
214
+ * Mines a single block containing only the deployment nullifiers for the contracts at the given addresses.
215
+ */ async mineDeploymentNullifiers(addresses) {
216
+ await this.mineBlock({
217
+ nullifiers: await Promise.all(addresses.map((address)=>this.deploymentNullifier(address)))
218
+ });
219
+ }
220
+ async addAccount(secret) {
221
+ const { artifact, instance } = await this.artifactResolver.resolveAccountArtifact(secret);
222
+ return this.registerContractAndAddAccount(artifact, instance, secret);
223
+ }
224
+ async registerContractAndAddAccount(artifact, instance, secret) {
174
225
  const partialAddress = await computePartialAddress(instance);
175
226
  this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`);
176
227
  await this.contractStore.addContractInstance(instance);
@@ -191,12 +242,11 @@ export class TXEOracleTopLevelContext {
191
242
  }
192
243
  async addAuthWitness(address, messageHash) {
193
244
  const account = await this.accountStore.getAccount(address);
194
- const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey);
245
+ const ivpkMHash = await hashPublicKey(account.publicKeys.ivpkM);
246
+ const privateKey = await this.keyStore.getMasterSecretKey(ivpkMHash);
195
247
  const schnorr = new Schnorr();
196
- const signature = await schnorr.constructSignature(messageHash.toBuffer(), privateKey);
197
- const authWitness = new AuthWitness(messageHash, [
198
- ...signature.toBuffer()
199
- ]);
248
+ const signature = await schnorr.constructSignature(messageHash, privateKey);
249
+ const authWitness = new AuthWitness(messageHash, signature.toLimbFields());
200
250
  this.authwits.set(authWitness.requestHash.toString(), authWitness);
201
251
  }
202
252
  async mineBlock(options = {}) {
@@ -222,29 +272,26 @@ export class TXEOracleTopLevelContext {
222
272
  this.logger.info(`Created block ${blockNumber} with timestamp ${block.header.globalVariables.timestamp}`);
223
273
  await this.stateMachine.handleL2Block(block);
224
274
  }
225
- async privateCallNewFlow(from, targetContractAddress = AztecAddress.zero(), functionSelector = FunctionSelector.empty(), args, argsHash = Fr.zero(), isStaticCall = false, jobId) {
275
+ async privateCallNewFlow(from, targetContractAddress = AztecAddress.zero(), functionSelector = FunctionSelector.empty(), args, argsHash = Fr.zero(), isStaticCall = false, additionalScopes = [], jobId, authorizedUtilityCallTargets, gasSettings) {
226
276
  this.logger.verbose(`Executing external function ${await this.contractStore.getDebugFunctionName(targetContractAddress, functionSelector)}@${targetContractAddress} isStaticCall=${isStaticCall}`);
227
277
  const artifact = await this.contractStore.getFunctionArtifact(targetContractAddress, functionSelector);
228
278
  if (!artifact) {
229
279
  const message = functionSelector.equals(await FunctionSelector.fromSignature('verify_private_authwit(Field)')) ? 'Found no account contract artifact for a private authwit check - use `create_contract_account` instead of `create_light_account` for authwit support.' : 'Function Artifact does not exist';
230
280
  throw new Error(message);
231
281
  }
232
- // When `from` is the zero address (e.g. when deploying a new account contract), we return an
233
- // empty scope list which acts as deny-all: no notes are visible and no keys are accessible.
234
- const effectiveScopes = from.isZero() ? [] : [
235
- from
282
+ const scopes = from === undefined ? additionalScopes : [
283
+ from,
284
+ ...additionalScopes
236
285
  ];
237
286
  // Sync notes before executing private function to discover notes from previous transactions
238
287
  const utilityExecutor = async (call, execScopes)=>{
239
288
  await this.executeUtilityCall(call, execScopes, jobId);
240
289
  };
241
290
  const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
242
- await this.stateMachine.contractSyncService.ensureContractSynced(targetContractAddress, functionSelector, utilityExecutor, blockHeader, jobId, effectiveScopes);
291
+ await this.stateMachine.contractSyncService.ensureContractSynced(targetContractAddress, functionSelector, utilityExecutor, blockHeader, jobId, scopes);
243
292
  const blockNumber = await this.getNextBlockNumber();
244
- const callContext = new CallContext(from, targetContractAddress, functionSelector, isStaticCall);
245
- const gasLimits = new Gas(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS);
246
- const teardownGasLimits = new Gas(FALLBACK_TEARDOWN_DA_GAS_LIMIT, FALLBACK_TEARDOWN_L2_GAS_LIMIT);
247
- const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
293
+ const msgSender = from ?? AztecAddress.NULL_MSG_SENDER;
294
+ const callContext = new CallContext(msgSender, targetContractAddress, functionSelector, isStaticCall);
248
295
  const txContext = new TxContext(this.chainId, this.version, gasSettings);
249
296
  const protocolNullifier = await computeProtocolNullifier(getSingleTxBlockRequestHash(blockNumber));
250
297
  const noteCache = new ExecutionNoteCache(protocolNullifier);
@@ -276,19 +323,22 @@ export class TXEOracleTopLevelContext {
276
323
  senderTaggingStore: this.senderTaggingStore,
277
324
  recipientTaggingStore: this.recipientTaggingStore,
278
325
  senderAddressBookStore: this.senderAddressBookStore,
279
- capsuleService: new CapsuleService(this.capsuleStore, effectiveScopes),
326
+ capsuleService: new CapsuleService(this.capsuleStore, scopes),
280
327
  privateEventStore: this.privateEventStore,
281
328
  contractSyncService: this.stateMachine.contractSyncService,
282
329
  jobId,
283
330
  totalPublicCalldataCount: 0,
284
331
  sideEffectCounter: minRevertibleSideEffectCounter,
285
- scopes: effectiveScopes,
332
+ scopes,
286
333
  // In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
287
334
  // contract would perform, including setting senderForTags.
288
335
  senderForTags: from,
289
336
  simulator,
290
337
  messageContextService: this.stateMachine.messageContextService,
291
- l2TipsStore: this.stateMachine.node
338
+ l2TipsStore: this.stateMachine.l2TipsProvider,
339
+ hooks: composeHooks({
340
+ authorizeUtilityCall: this.buildAuthorizeUtilityCallHook(isStaticCall ? 'private view' : 'private', authorizedUtilityCallTargets)
341
+ })
292
342
  });
293
343
  // Note: This is a slight modification of simulator.run without any of the checks. Maybe we should modify simulator.run with a boolean value to skip checks.
294
344
  let result;
@@ -360,10 +410,20 @@ export class TXEOracleTopLevelContext {
360
410
  throw new Error('Contract execution has reverted');
361
411
  }
362
412
  }
413
+ // Walk the nested private-call tree and collect every offchain effect the transaction emitted.
414
+ // PXE stores these on each `PrivateCallExecutionResult` and they never reach TXE via the
415
+ // `aztec_utl_emitOffchainEffect` foreign-call path (that path only fires at the top-level), so
416
+ // we pull them out here and the RPC wrapper will hand them to `TXESession` for buffering.
417
+ const offchainEffects = collectNested([
418
+ executionResult
419
+ ], (r)=>r.offchainEffects.map((e)=>e.data));
363
420
  if (isStaticCall) {
364
421
  await checkpoint.revert();
365
422
  await forkedWorldTrees.close();
366
- return executionResult.returnValues ?? [];
423
+ return {
424
+ returnValues: executionResult.returnValues ?? [],
425
+ offchainEffects
426
+ };
367
427
  }
368
428
  const txEffect = TxEffect.empty();
369
429
  txEffect.noteHashes = processedTx.txEffect.noteHashes;
@@ -379,14 +439,14 @@ export class TXEOracleTopLevelContext {
379
439
  ]);
380
440
  await this.stateMachine.handleL2Block(l2Block);
381
441
  await forkedWorldTrees.close();
382
- return executionResult.returnValues ?? [];
442
+ return {
443
+ returnValues: executionResult.returnValues ?? [],
444
+ offchainEffects
445
+ };
383
446
  }
384
- async publicCallNewFlow(from, targetContractAddress, calldata, isStaticCall) {
447
+ async publicCallNewFlow(from, targetContractAddress, calldata, isStaticCall, gasSettings) {
385
448
  this.logger.verbose(`Executing public function ${await this.contractStore.getDebugFunctionName(targetContractAddress, FunctionSelector.fromField(calldata[0]))}@${targetContractAddress} isStaticCall=${isStaticCall}`);
386
449
  const blockNumber = await this.getNextBlockNumber();
387
- const gasLimits = new Gas(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS);
388
- const teardownGasLimits = new Gas(FALLBACK_TEARDOWN_DA_GAS_LIMIT, FALLBACK_TEARDOWN_L2_GAS_LIMIT);
389
- const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
390
450
  const txContext = new TxContext(this.chainId, this.version, gasSettings);
391
451
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
392
452
  const calldataHash = await computeCalldataHash(calldata);
@@ -419,7 +479,7 @@ export class TXEOracleTopLevelContext {
419
479
  // The enqueued public call itself we make be revertible so that the public execution is itself revertible, as tests
420
480
  // may require producing reverts.
421
481
  const revertibleAccumulatedData = PrivateToPublicAccumulatedData.empty();
422
- revertibleAccumulatedData.publicCallRequests[0] = new PublicCallRequest(from, targetContractAddress, isStaticCall, calldataHash);
482
+ revertibleAccumulatedData.publicCallRequests[0] = new PublicCallRequest(from ?? AztecAddress.NULL_MSG_SENDER, targetContractAddress, isStaticCall, calldataHash);
423
483
  const inputsForPublic = new PartialPrivateTailPublicInputsForPublic(nonRevertibleAccumulatedData, revertibleAccumulatedData, PublicCallRequest.empty());
424
484
  const constantData = new TxConstantData(anchorBlockHeader, txContext, Fr.zero(), Fr.zero());
425
485
  const txData = new PrivateKernelTailCircuitPublicInputs(constantData, /*gasUsed=*/ new Gas(0, 0), /*feePayer=*/ AztecAddress.zero(), /*expirationTimestamp=*/ 0n, inputsForPublic, undefined);
@@ -475,7 +535,7 @@ export class TXEOracleTopLevelContext {
475
535
  await forkedWorldTrees.close();
476
536
  return returnValues ?? [];
477
537
  }
478
- async executeUtilityFunction(targetContractAddress, functionSelector, args, jobId) {
538
+ async executeUtilityFunction(targetContractAddress, functionSelector, args, jobId, authorizedUtilityCallTargets) {
479
539
  const artifact = await this.contractStore.getFunctionArtifact(targetContractAddress, functionSelector);
480
540
  if (!artifact) {
481
541
  throw new Error(`Cannot call ${functionSelector} as there is no artifact found at ${targetContractAddress}.`);
@@ -495,9 +555,9 @@ export class TXEOracleTopLevelContext {
495
555
  args,
496
556
  returnTypes: []
497
557
  });
498
- return this.executeUtilityCall(call, await this.keyStore.getAccounts(), jobId);
558
+ return this.executeUtilityCall(call, await this.keyStore.getAccounts(), jobId, authorizedUtilityCallTargets);
499
559
  }
500
- async executeUtilityCall(call, scopes, jobId) {
560
+ async executeUtilityCall(call, scopes, jobId, authorizedUtilityCallTargets = []) {
501
561
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
502
562
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
503
563
  throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
@@ -508,6 +568,10 @@ export class TXEOracleTopLevelContext {
508
568
  });
509
569
  try {
510
570
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
571
+ const simulator = new WASMSimulator();
572
+ const utilityExecutor = async (syncCall, execScopes)=>{
573
+ await this.executeUtilityCall(syncCall, execScopes, jobId);
574
+ };
511
575
  const oracle = new UtilityExecutionOracle({
512
576
  contractAddress: call.to,
513
577
  authWitnesses: [],
@@ -523,12 +587,17 @@ export class TXEOracleTopLevelContext {
523
587
  capsuleService: new CapsuleService(this.capsuleStore, scopes),
524
588
  privateEventStore: this.privateEventStore,
525
589
  messageContextService: this.stateMachine.messageContextService,
526
- contractSyncService: this.contractSyncService,
527
- l2TipsStore: this.stateMachine.node,
590
+ contractSyncService: this.stateMachine.contractSyncService,
591
+ l2TipsStore: this.stateMachine.l2TipsProvider,
528
592
  jobId,
529
- scopes
593
+ scopes,
594
+ simulator,
595
+ hooks: composeHooks({
596
+ authorizeUtilityCall: this.buildAuthorizeUtilityCallHook('utility', authorizedUtilityCallTargets)
597
+ }),
598
+ utilityExecutor
530
599
  });
531
- const acirExecutionResult = await new WASMSimulator().executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback()).catch((err)=>{
600
+ const acirExecutionResult = await simulator.executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback()).catch((err)=>{
532
601
  err.message = resolveAssertionMessageFromError(err, entryPointArtifact);
533
602
  throw new ExecutionError(err.message, {
534
603
  contractAddress: call.to,
@@ -551,7 +620,15 @@ export class TXEOracleTopLevelContext {
551
620
  ];
552
621
  }
553
622
  async getLastBlockNumber() {
554
- const header = await this.stateMachine.node.getBlockHeader('latest');
555
- return header ? header.globalVariables.blockNumber : BlockNumber.ZERO;
623
+ const block = await this.stateMachine.node.getBlock('latest');
624
+ return block ? block.header.globalVariables.blockNumber : BlockNumber.ZERO;
625
+ }
626
+ buildAuthorizeUtilityCallHook(callerContext, authorizedTargets) {
627
+ if (authorizedTargets.length === 0) {
628
+ return undefined;
629
+ }
630
+ return (req)=>Promise.resolve({
631
+ authorized: req.callerContext === callerContext && authorizedTargets.some((t)=>t.equals(req.target))
632
+ });
556
633
  }
557
634
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The TXE oracle version constants are used to check that the oracle interface used for tests is in sync between
3
+ * TXE and Aztec.nr. This is separate from the contract oracle version in `pxe/src/oracle_version.ts`, which covers
4
+ * oracles used during contract execution by PXE.
5
+ *
6
+ * The Noir counterparts are in `noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr`.
7
+ */
8
+ export declare const TXE_ORACLE_VERSION_MAJOR = 1;
9
+ export declare const TXE_ORACLE_VERSION_MINOR = 2;
10
+ /**
11
+ * This hash is computed from the TXE oracle interfaces (IAvmExecutionOracle and ITxeExecutionOracle) and is used to
12
+ * detect when those interfaces change. When it does, bump:
13
+ * - TXE_ORACLE_VERSION_MAJOR (and reset MINOR to 0) for breaking changes, or
14
+ * - TXE_ORACLE_VERSION_MINOR for additive changes (new oracle method added).
15
+ */
16
+ export declare const TXE_ORACLE_INTERFACE_HASH = "9e5f6ad5fd170d1de5ddd417f19cce47b17382567e08360dc6a783154828e218";
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHhlX29yYWNsZV92ZXJzaW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb3JhY2xlL3R4ZV9vcmFjbGVfdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7O0dBTUc7QUFDSCxlQUFPLE1BQU0sd0JBQXdCLElBQUksQ0FBQztBQUMxQyxlQUFPLE1BQU0sd0JBQXdCLElBQUksQ0FBQztBQUUxQzs7Ozs7R0FLRztBQUNILGVBQU8sTUFBTSx5QkFBeUIscUVBQXFFLENBQUMifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"txe_oracle_version.d.ts","sourceRoot":"","sources":["../../src/oracle/txe_oracle_version.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,qEAAqE,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The TXE oracle version constants are used to check that the oracle interface used for tests is in sync between
3
+ * TXE and Aztec.nr. This is separate from the contract oracle version in `pxe/src/oracle_version.ts`, which covers
4
+ * oracles used during contract execution by PXE.
5
+ *
6
+ * The Noir counterparts are in `noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr`.
7
+ */ export const TXE_ORACLE_VERSION_MAJOR = 1;
8
+ export const TXE_ORACLE_VERSION_MINOR = 2;
9
+ /**
10
+ * This hash is computed from the TXE oracle interfaces (IAvmExecutionOracle and ITxeExecutionOracle) and is used to
11
+ * detect when those interfaces change. When it does, bump:
12
+ * - TXE_ORACLE_VERSION_MAJOR (and reset MINOR to 0) for breaking changes, or
13
+ * - TXE_ORACLE_VERSION_MINOR for additive changes (new oracle method added).
14
+ */ export const TXE_ORACLE_INTERFACE_HASH = '9e5f6ad5fd170d1de5ddd417f19cce47b17382567e08360dc6a783154828e218';
@@ -0,0 +1,17 @@
1
+ import type { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { PrivateExecutionOracle } from '@aztec/pxe/simulator';
3
+ import type { FunctionSelector } from '@aztec/stdlib/abi';
4
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
+ /**
6
+ * TXE-specific subclass of PrivateExecutionOracle that forbids operations not supported in
7
+ * TestEnvironment::private_context. TXE uses dedicated oracle flows (e.g. private_call) instead.
8
+ */
9
+ export declare class TXEPrivateExecutionOracle extends PrivateExecutionOracle {
10
+ callPrivateFunction(_targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, _argsHash: Fr, _sideEffectCounter: number, _isStaticCall: boolean): Promise<{
11
+ endSideEffectCounter: Fr;
12
+ returnsHash: Fr;
13
+ }>;
14
+ assertValidPublicCalldata(_calldataHash: Fr): Promise<void>;
15
+ notifyRevertiblePhaseStart(_minRevertibleSideEffectCounter: number): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHhlX3ByaXZhdGVfZXhlY3V0aW9uX29yYWNsZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL29yYWNsZS90eGVfcHJpdmF0ZV9leGVjdXRpb25fb3JhY2xlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ3pELE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDMUQsT0FBTyxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFaEU7OztHQUdHO0FBQ0gscUJBQWEseUJBQTBCLFNBQVEsc0JBQXNCO0lBQzFELG1CQUFtQixDQUMxQixzQkFBc0IsRUFBRSxZQUFZLEVBQ3BDLGlCQUFpQixFQUFFLGdCQUFnQixFQUNuQyxTQUFTLEVBQUUsRUFBRSxFQUNiLGtCQUFrQixFQUFFLE1BQU0sRUFDMUIsYUFBYSxFQUFFLE9BQU8sR0FDckIsT0FBTyxDQUFDO1FBQUUsb0JBQW9CLEVBQUUsRUFBRSxDQUFDO1FBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQTtLQUFFLENBQUMsQ0FJeEQ7SUFFUSx5QkFBeUIsQ0FBQyxhQUFhLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FFbkU7SUFFUSwwQkFBMEIsQ0FBQywrQkFBK0IsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUxRjtDQUNGIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"txe_private_execution_oracle.d.ts","sourceRoot":"","sources":["../../src/oracle/txe_private_execution_oracle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,sBAAsB;IAC1D,mBAAmB,CAC1B,sBAAsB,EAAE,YAAY,EACpC,iBAAiB,EAAE,gBAAgB,EACnC,SAAS,EAAE,EAAE,EACb,kBAAkB,EAAE,MAAM,EAC1B,aAAa,EAAE,OAAO,GACrB,OAAO,CAAC;QAAE,oBAAoB,EAAE,EAAE,CAAC;QAAC,WAAW,EAAE,EAAE,CAAA;KAAE,CAAC,CAIxD;IAEQ,yBAAyB,CAAC,aAAa,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnE;IAEQ,0BAA0B,CAAC,+BAA+B,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;CACF"}
@@ -0,0 +1,15 @@
1
+ import { PrivateExecutionOracle } from '@aztec/pxe/simulator';
2
+ /**
3
+ * TXE-specific subclass of PrivateExecutionOracle that forbids operations not supported in
4
+ * TestEnvironment::private_context. TXE uses dedicated oracle flows (e.g. private_call) instead.
5
+ */ export class TXEPrivateExecutionOracle extends PrivateExecutionOracle {
6
+ callPrivateFunction(_targetContractAddress, _functionSelector, _argsHash, _sideEffectCounter, _isStaticCall) {
7
+ throw new Error('Contract calls are forbidden inside a `TestEnvironment::private_context`, use `private_call` instead');
8
+ }
9
+ assertValidPublicCalldata(_calldataHash) {
10
+ throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context');
11
+ }
12
+ notifyRevertiblePhaseStart(_minRevertibleSideEffectCounter) {
13
+ throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context');
14
+ }
15
+ }
@@ -0,0 +1,14 @@
1
+ import type { Logger } from '@aztec/foundation/log';
2
+ /**
3
+ * Creates the TXE RPC server. With `TXE_WORKERS=1` oracle calls run on the main thread (no
4
+ * worker_threads, no IPC overhead). With any other value oracle calls are
5
+ * routed to a pool of worker threads sized to that value, sticky by `session_id`.
6
+ *
7
+ * Each incoming TCP socket is tagged with the `session_id` of the first oracle call it carries —
8
+ * nargo uses one HTTP client per test, so the socket-to-session mapping is 1:1. When the socket
9
+ * closes (end of test), the dispatcher disposes the session and frees its world state + LMDB.
10
+ *
11
+ * Lives in its own module so the worker bundle does not pull in the HTTP server stack.
12
+ */
13
+ export declare function createTXERpcServer(logger: Logger): Promise<import("@aztec/foundation/json-rpc/server").SafeJsonRpcServer>;
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnBjX3NlcnZlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3JwY19zZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFlcEQ7Ozs7Ozs7Ozs7R0FVRztBQUNILHdCQUFzQixrQkFBa0IsQ0FBQyxNQUFNLEVBQUUsTUFBTSwwRUE0Q3REIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc_server.d.ts","sourceRoot":"","sources":["../src/rpc_server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAepD;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,0EA4CtD"}
@@ -0,0 +1,78 @@
1
+ import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
2
+ import { TXEDispatcherPool, buildSharedContractStore } from './dispatcher_pool.js';
3
+ import { TXEDispatcher, TXEDispatcherApiSchema } from './index.js';
4
+ /**
5
+ * Symbol used to tag an incoming TCP socket with the `session_id` it has been associated with.
6
+ * Hidden under a Symbol so we don't risk colliding with anything koa or http core adds.
7
+ */ const SESSION_SYMBOL = Symbol('txeSessionId');
8
+ /**
9
+ * Creates the TXE RPC server. With `TXE_WORKERS=1` oracle calls run on the main thread (no
10
+ * worker_threads, no IPC overhead). With any other value oracle calls are
11
+ * routed to a pool of worker threads sized to that value, sticky by `session_id`.
12
+ *
13
+ * Each incoming TCP socket is tagged with the `session_id` of the first oracle call it carries —
14
+ * nargo uses one HTTP client per test, so the socket-to-session mapping is 1:1. When the socket
15
+ * closes (end of test), the dispatcher disposes the session and frees its world state + LMDB.
16
+ *
17
+ * Lives in its own module so the worker bundle does not pull in the HTTP server stack.
18
+ */ export async function createTXERpcServer(logger) {
19
+ const workerCount = Number(process.env.TXE_WORKERS);
20
+ let dispatcher;
21
+ if (workerCount === 1) {
22
+ const { dataDir, schnorrClassId } = await buildSharedContractStore();
23
+ dispatcher = new TXEDispatcher(logger, {
24
+ contractStoreSourceDir: dataDir,
25
+ schnorrClassId
26
+ });
27
+ } else {
28
+ dispatcher = new TXEDispatcherPool(logger, {
29
+ workers: Number.isFinite(workerCount) && workerCount > 1 ? workerCount : undefined
30
+ });
31
+ }
32
+ const server = createSafeJsonRpcServer(dispatcher, TXEDispatcherApiSchema, {
33
+ http200OnError: true,
34
+ middlewares: [
35
+ async (ctx, next)=>{
36
+ // The body parser runs further down the chain, so `ctx.request.body` is populated only
37
+ // after `next()` resolves.
38
+ await next();
39
+ const socket = ctx.req.socket;
40
+ if (socket[SESSION_SYMBOL] !== undefined) {
41
+ return;
42
+ }
43
+ const body = ctx.request.body;
44
+ const sessionId = body && typeof body === 'object' && 'params' in body ? extractSessionId(body.params) : undefined;
45
+ if (sessionId === undefined) {
46
+ return;
47
+ }
48
+ socket[SESSION_SYMBOL] = sessionId;
49
+ logger.debug(`Tagged socket with session`, {
50
+ sessionId,
51
+ remoteAddress: socket.remoteAddress,
52
+ remotePort: socket.remotePort
53
+ });
54
+ socket.once('close', ()=>{
55
+ logger.debug(`Disposing session on socket close`, {
56
+ sessionId
57
+ });
58
+ void dispatcher.disposeSession(sessionId);
59
+ });
60
+ }
61
+ ]
62
+ });
63
+ return server;
64
+ }
65
+ // Extracts `session_id` from a JSON-RPC `params` array. Always a `number` because session_id
66
+ // comes off `JSON.parse`, which never produces BigInt; values above MAX_SAFE_INTEGER lose
67
+ // precision but still work as a Map key.
68
+ function extractSessionId(params) {
69
+ if (!Array.isArray(params) || params.length === 0) {
70
+ return undefined;
71
+ }
72
+ const first = params[0];
73
+ if (first && typeof first === 'object' && 'session_id' in first) {
74
+ const sid = first.session_id;
75
+ return typeof sid === 'number' ? sid : undefined;
76
+ }
77
+ return undefined;
78
+ }