@aztec/txe 0.0.1-commit.7b97ef96e → 0.0.1-commit.7cbc774
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.
- package/dest/ContractClassRegistry-KKNT527R.js +3 -0
- package/dest/ContractClassRegistry-KKNT527R.js.map +7 -0
- package/dest/ContractInstanceRegistry-GSKBNL3Q.js +3 -0
- package/dest/ContractInstanceRegistry-GSKBNL3Q.js.map +7 -0
- package/dest/FeeJuice-GUJ5O6GB.js +3 -0
- package/dest/FeeJuice-GUJ5O6GB.js.map +7 -0
- package/dest/MultiCallEntrypoint-ZQPCKVKL.js +3 -0
- package/dest/MultiCallEntrypoint-ZQPCKVKL.js.map +7 -0
- package/dest/SchnorrAccount-QIXE5DXA.js +3 -0
- package/dest/SchnorrAccount-QIXE5DXA.js.map +7 -0
- package/dest/bin/check_txe_oracle_version.d.ts +2 -0
- package/dest/bin/check_txe_oracle_version.d.ts.map +1 -0
- package/dest/bin/check_txe_oracle_version.js +32 -0
- package/dest/bin/index.js +3 -30
- package/dest/bin/index.js.map +7 -0
- package/dest/chunk-BJVAAXNA.js +3 -0
- package/dest/chunk-BJVAAXNA.js.map +7 -0
- package/dest/chunk-DF6RMOC6.js +304 -0
- package/dest/chunk-DF6RMOC6.js.map +7 -0
- package/dest/chunk-PPJTNNUG.js +265 -0
- package/dest/chunk-PPJTNNUG.js.map +7 -0
- package/dest/constants.d.ts +3 -1
- package/dest/constants.d.ts.map +1 -1
- package/dest/constants.js +5 -0
- package/dest/dispatcher_pool.d.ts +67 -0
- package/dest/dispatcher_pool.d.ts.map +1 -0
- package/dest/dispatcher_pool.js +284 -0
- package/dest/index.d.ts +188 -7
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +160 -119
- package/dest/metafile.json +38147 -0
- package/dest/msgpackr_fr_extension.d.ts +2 -0
- package/dest/msgpackr_fr_extension.d.ts.map +1 -0
- package/dest/msgpackr_fr_extension.js +21 -0
- package/dest/oracle/interfaces.d.ts +40 -28
- package/dest/oracle/interfaces.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_public_context.d.ts +18 -13
- package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_public_context.js +27 -12
- package/dest/oracle/txe_oracle_top_level_context.d.ts +38 -22
- package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_top_level_context.js +141 -72
- package/dest/oracle/txe_private_execution_oracle.d.ts +17 -0
- package/dest/oracle/txe_private_execution_oracle.d.ts.map +1 -0
- package/dest/oracle/txe_private_execution_oracle.js +15 -0
- package/dest/rpc_server.d.ts +14 -0
- package/dest/rpc_server.d.ts.map +1 -0
- package/dest/rpc_server.js +78 -0
- package/dest/rpc_translator.d.ts +139 -83
- package/dest/rpc_translator.d.ts.map +1 -1
- package/dest/rpc_translator.js +574 -238
- package/dest/server.bundle.js +3 -0
- package/dest/server.bundle.js.map +7 -0
- package/dest/state_machine/archiver.d.ts +4 -4
- package/dest/state_machine/archiver.d.ts.map +1 -1
- package/dest/state_machine/archiver.js +22 -20
- package/dest/state_machine/dummy_p2p_client.d.ts +14 -8
- package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
- package/dest/state_machine/dummy_p2p_client.js +17 -5
- package/dest/state_machine/global_variable_builder.d.ts +9 -4
- package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
- package/dest/state_machine/global_variable_builder.js +9 -3
- package/dest/state_machine/index.d.ts +7 -3
- package/dest/state_machine/index.d.ts.map +1 -1
- package/dest/state_machine/index.js +23 -5
- package/dest/state_machine/mock_epoch_cache.d.ts +18 -3
- package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
- package/dest/state_machine/mock_epoch_cache.js +35 -2
- package/dest/state_machine/synchronizer.d.ts +5 -5
- package/dest/state_machine/synchronizer.d.ts.map +1 -1
- package/dest/state_machine/synchronizer.js +4 -4
- package/dest/txe_oracle_version.d.ts +17 -0
- package/dest/txe_oracle_version.d.ts.map +1 -0
- package/dest/txe_oracle_version.js +14 -0
- package/dest/txe_session.d.ts +69 -6
- package/dest/txe_session.d.ts.map +1 -1
- package/dest/txe_session.js +170 -35
- package/dest/utils/encoding.d.ts +191 -0
- package/dest/utils/encoding.d.ts.map +1 -0
- package/dest/{util → utils}/encoding.js +7 -2
- package/dest/{util → utils}/expected_failure_error.d.ts +1 -1
- package/dest/utils/expected_failure_error.d.ts.map +1 -0
- package/dest/{util → utils}/txe_account_store.d.ts +1 -1
- package/dest/utils/txe_account_store.d.ts.map +1 -0
- package/dest/utils/txe_public_contract_data_source.d.ts +20 -0
- package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -0
- package/dest/{util → utils}/txe_public_contract_data_source.js +1 -3
- package/dest/worker.bundle.js +3 -0
- package/dest/worker.bundle.js.map +7 -0
- package/dest/worker.d.ts +2 -0
- package/dest/worker.d.ts.map +1 -0
- package/dest/worker.js +92 -0
- package/package.json +37 -21
- package/src/bin/check_txe_oracle_version.ts +37 -0
- package/src/bin/index.ts +11 -2
- package/src/constants.ts +6 -0
- package/src/dispatcher_pool.ts +316 -0
- package/src/index.ts +178 -126
- package/src/msgpackr_fr_extension.ts +23 -0
- package/src/oracle/interfaces.ts +48 -34
- package/src/oracle/txe_oracle_public_context.ts +42 -12
- package/src/oracle/txe_oracle_top_level_context.ts +176 -83
- package/src/oracle/txe_private_execution_oracle.ts +30 -0
- package/src/rpc_server.ts +87 -0
- package/src/rpc_translator.ts +654 -319
- package/src/state_machine/archiver.ts +33 -18
- package/src/state_machine/dummy_p2p_client.ts +32 -12
- package/src/state_machine/global_variable_builder.ts +18 -3
- package/src/state_machine/index.ts +25 -6
- package/src/state_machine/mock_epoch_cache.ts +46 -3
- package/src/state_machine/synchronizer.ts +5 -5
- package/src/txe_oracle_version.ts +17 -0
- package/src/txe_session.ts +269 -37
- package/src/{util → utils}/encoding.ts +8 -2
- package/src/{util → utils}/txe_public_contract_data_source.ts +0 -2
- package/src/worker.ts +98 -0
- package/dest/util/encoding.d.ts +0 -720
- package/dest/util/encoding.d.ts.map +0 -1
- package/dest/util/expected_failure_error.d.ts.map +0 -1
- package/dest/util/txe_account_store.d.ts.map +0 -1
- package/dest/util/txe_public_contract_data_source.d.ts +0 -20
- package/dest/util/txe_public_contract_data_source.d.ts.map +0 -1
- /package/dest/{util → utils}/expected_failure_error.js +0 -0
- /package/dest/{util → utils}/txe_account_store.js +0 -0
- /package/src/{util → utils}/expected_failure_error.ts +0 -0
- /package/src/{util → utils}/txe_account_store.ts +0 -0
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS,
|
|
3
|
-
|
|
4
|
-
DEFAULT_L2_GAS_LIMIT,
|
|
5
|
-
DEFAULT_TEARDOWN_DA_GAS_LIMIT,
|
|
6
|
-
DEFAULT_TEARDOWN_L2_GAS_LIMIT,
|
|
3
|
+
MAX_PRIVATE_LOGS_PER_TX,
|
|
7
4
|
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
|
|
8
5
|
} from '@aztec/constants';
|
|
9
6
|
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
@@ -12,17 +9,19 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
12
9
|
import { LogLevels, type Logger, applyStringFormatting, createLogger } from '@aztec/foundation/log';
|
|
13
10
|
import { TestDateProvider } from '@aztec/foundation/timer';
|
|
14
11
|
import type { KeyStore } from '@aztec/key-store';
|
|
15
|
-
import type { AccessScopes } from '@aztec/pxe/client/lazy';
|
|
16
12
|
import {
|
|
17
13
|
AddressStore,
|
|
14
|
+
CapsuleService,
|
|
18
15
|
CapsuleStore,
|
|
19
16
|
type ContractStore,
|
|
17
|
+
type ExecutionHooks,
|
|
20
18
|
NoteStore,
|
|
21
|
-
|
|
19
|
+
ORACLE_VERSION_MAJOR,
|
|
22
20
|
PrivateEventStore,
|
|
23
21
|
RecipientTaggingStore,
|
|
24
22
|
SenderAddressBookStore,
|
|
25
23
|
SenderTaggingStore,
|
|
24
|
+
composeHooks,
|
|
26
25
|
enrichPublicSimulationError,
|
|
27
26
|
} from '@aztec/pxe/server';
|
|
28
27
|
import {
|
|
@@ -64,6 +63,7 @@ import {
|
|
|
64
63
|
PrivateToPublicAccumulatedData,
|
|
65
64
|
PublicCallRequest,
|
|
66
65
|
} from '@aztec/stdlib/kernel';
|
|
66
|
+
import { hashPublicKey } from '@aztec/stdlib/keys';
|
|
67
67
|
import { ChonkProof } from '@aztec/stdlib/proofs';
|
|
68
68
|
import { makeGlobalVariables } from '@aztec/stdlib/testing';
|
|
69
69
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
@@ -80,13 +80,13 @@ import {
|
|
|
80
80
|
collectNested,
|
|
81
81
|
} from '@aztec/stdlib/tx';
|
|
82
82
|
import type { UInt64 } from '@aztec/stdlib/types';
|
|
83
|
-
import { ForkCheckpoint } from '@aztec/world-state';
|
|
83
|
+
import { ForkCheckpoint } from '@aztec/world-state/native';
|
|
84
84
|
|
|
85
85
|
import { DEFAULT_ADDRESS } from '../constants.js';
|
|
86
86
|
import type { TXEStateMachine } from '../state_machine/index.js';
|
|
87
|
-
import type { TXEAccountStore } from '../util/txe_account_store.js';
|
|
88
|
-
import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js';
|
|
89
87
|
import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from '../utils/block_creation.js';
|
|
88
|
+
import type { TXEAccountStore } from '../utils/txe_account_store.js';
|
|
89
|
+
import { TXEPublicContractDataSource } from '../utils/txe_public_contract_data_source.js';
|
|
90
90
|
import type { ITxeExecutionOracle } from './interfaces.js';
|
|
91
91
|
|
|
92
92
|
export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracle {
|
|
@@ -107,7 +107,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
107
107
|
private senderAddressBookStore: SenderAddressBookStore,
|
|
108
108
|
private capsuleStore: CapsuleStore,
|
|
109
109
|
private privateEventStore: PrivateEventStore,
|
|
110
|
-
private jobId: string,
|
|
111
110
|
private nextBlockTimestamp: bigint,
|
|
112
111
|
private version: Fr,
|
|
113
112
|
private chainId: Fr,
|
|
@@ -117,22 +116,34 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
117
116
|
this.logger.debug('Entering Top Level Context');
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
private contractOracleVersion: { major: number; minor: number } | undefined;
|
|
120
|
+
|
|
121
|
+
assertCompatibleOracleVersion(major: number, minor: number): void {
|
|
122
|
+
if (major !== ORACLE_VERSION_MAJOR) {
|
|
123
|
+
const hint =
|
|
124
|
+
major > ORACLE_VERSION_MAJOR
|
|
125
|
+
? 'The contract was compiled with a newer version of Aztec.nr than this aztec cli version supports. Upgrade your aztec cli version to a compatible version.'
|
|
126
|
+
: 'The contract was compiled with an older version of Aztec.nr than this aztec cli version supports. Recompile the contract with a compatible version of Aztec.nr.';
|
|
122
127
|
throw new Error(
|
|
123
|
-
`Incompatible
|
|
128
|
+
`Incompatible aztec cli version: ${hint} See https://docs.aztec.network/errors/8 (expected oracle major version ${ORACLE_VERSION_MAJOR}, got ${major})`,
|
|
124
129
|
);
|
|
125
130
|
}
|
|
131
|
+
this.contractOracleVersion = { major, minor };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Prefixed with "nonOracleFunction" as it is not used as an oracle handler.
|
|
135
|
+
nonOracleFunctionGetContractOracleVersion(): { major: number; minor: number } | undefined {
|
|
136
|
+
return this.contractOracleVersion;
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
// This is typically only invoked in private contexts, but it is convenient to also have it in top-level for testing
|
|
129
140
|
// setup.
|
|
130
|
-
|
|
141
|
+
getRandomField(): Fr {
|
|
131
142
|
return Fr.random();
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
// We instruct users to debug contracts via this oracle, so it makes sense that they'd expect it to also work in tests
|
|
135
|
-
|
|
146
|
+
log(level: number, message: string, fields: Fr[]): Promise<void> {
|
|
136
147
|
if (!LogLevels[level]) {
|
|
137
148
|
throw new Error(`Invalid log level: ${level}`);
|
|
138
149
|
}
|
|
@@ -142,25 +153,25 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
142
153
|
return Promise.resolve();
|
|
143
154
|
}
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
getDefaultAddress(): AztecAddress {
|
|
146
157
|
return DEFAULT_ADDRESS;
|
|
147
158
|
}
|
|
148
159
|
|
|
149
|
-
async
|
|
160
|
+
async getNextBlockNumber(): Promise<BlockNumber> {
|
|
150
161
|
return BlockNumber((await this.getLastBlockNumber()) + 1);
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
|
|
164
|
+
getNextBlockTimestamp(): Promise<bigint> {
|
|
154
165
|
return Promise.resolve(this.nextBlockTimestamp);
|
|
155
166
|
}
|
|
156
167
|
|
|
157
|
-
async
|
|
158
|
-
return (await this.stateMachine.node.
|
|
168
|
+
async getLastBlockTimestamp() {
|
|
169
|
+
return (await this.stateMachine.node.getBlockData('latest'))!.header.globalVariables.timestamp;
|
|
159
170
|
}
|
|
160
171
|
|
|
161
|
-
async
|
|
172
|
+
async getLastTxEffects() {
|
|
162
173
|
const latestBlockNumber = await this.stateMachine.archiver.getBlockNumber();
|
|
163
|
-
const block = await this.stateMachine.archiver.getBlock(latestBlockNumber);
|
|
174
|
+
const block = await this.stateMachine.archiver.getBlock({ number: latestBlockNumber });
|
|
164
175
|
|
|
165
176
|
if (block!.body.txEffects.length != 1) {
|
|
166
177
|
// Note that calls like env.mine() will result in blocks with no transactions, hitting this
|
|
@@ -169,10 +180,39 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
169
180
|
|
|
170
181
|
const txEffects = block!.body.txEffects[0];
|
|
171
182
|
|
|
172
|
-
|
|
183
|
+
const privateLogs = txEffects.privateLogs;
|
|
184
|
+
if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) {
|
|
185
|
+
throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
txHash: txEffects.txHash,
|
|
190
|
+
noteHashes: txEffects.noteHashes,
|
|
191
|
+
nullifiers: txEffects.nullifiers,
|
|
192
|
+
privateLogs,
|
|
193
|
+
};
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
async
|
|
196
|
+
async syncContractNonOracleMethod(contractAddress: AztecAddress, scope: AztecAddress, jobId: string) {
|
|
197
|
+
if (contractAddress.equals(DEFAULT_ADDRESS)) {
|
|
198
|
+
this.logger.debug(`Skipping sync in getPrivateEvents because the events correspond to the default address.`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
|
|
203
|
+
await this.stateMachine.contractSyncService.ensureContractSynced(
|
|
204
|
+
contractAddress,
|
|
205
|
+
null,
|
|
206
|
+
async (call, execScopes) => {
|
|
207
|
+
await this.executeUtilityCall(call, execScopes, jobId);
|
|
208
|
+
},
|
|
209
|
+
blockHeader,
|
|
210
|
+
jobId,
|
|
211
|
+
[scope],
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async getPrivateEvents(selector: EventSelector, contractAddress: AztecAddress, scope: AztecAddress) {
|
|
176
216
|
return (
|
|
177
217
|
await this.privateEventStore.getPrivateEvents(selector, {
|
|
178
218
|
contractAddress,
|
|
@@ -183,7 +223,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
183
223
|
).map(e => e.packedEvent);
|
|
184
224
|
}
|
|
185
225
|
|
|
186
|
-
async
|
|
226
|
+
async advanceBlocksBy(blocks: number) {
|
|
187
227
|
this.logger.debug(`time traveling ${blocks} blocks`);
|
|
188
228
|
|
|
189
229
|
for (let i = 0; i < blocks; i++) {
|
|
@@ -191,24 +231,23 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
191
231
|
}
|
|
192
232
|
}
|
|
193
233
|
|
|
194
|
-
|
|
234
|
+
advanceTimestampBy(duration: UInt64) {
|
|
195
235
|
this.logger.debug(`time traveling ${duration} seconds`);
|
|
196
236
|
this.nextBlockTimestamp += duration;
|
|
197
237
|
}
|
|
198
238
|
|
|
199
|
-
|
|
239
|
+
private deploymentNullifier(address: AztecAddress): Promise<Fr> {
|
|
240
|
+
return siloNullifier(AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), address.toField());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async deploy(artifact: ContractArtifact, instance: ContractInstanceWithAddress, secret: Fr) {
|
|
200
244
|
// Emit deployment nullifier
|
|
201
245
|
await this.mineBlock({
|
|
202
|
-
nullifiers: [
|
|
203
|
-
await siloNullifier(
|
|
204
|
-
AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS),
|
|
205
|
-
instance.address.toField(),
|
|
206
|
-
),
|
|
207
|
-
],
|
|
246
|
+
nullifiers: [await this.deploymentNullifier(instance.address)],
|
|
208
247
|
});
|
|
209
248
|
|
|
210
249
|
if (!secret.equals(Fr.ZERO)) {
|
|
211
|
-
await this.
|
|
250
|
+
await this.addAccount(artifact, instance, secret);
|
|
212
251
|
} else {
|
|
213
252
|
await this.contractStore.addContractInstance(instance);
|
|
214
253
|
await this.contractStore.addContractArtifact(artifact);
|
|
@@ -216,7 +255,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
216
255
|
}
|
|
217
256
|
}
|
|
218
257
|
|
|
219
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Mines a single block containing only the deployment nullifiers for the contracts at the given addresses.
|
|
260
|
+
*/
|
|
261
|
+
async mineDeploymentNullifiers(addresses: AztecAddress[]) {
|
|
262
|
+
await this.mineBlock({
|
|
263
|
+
nullifiers: await Promise.all(addresses.map(address => this.deploymentNullifier(address))),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async addAccount(artifact: ContractArtifact, instance: ContractInstanceWithAddress, secret: Fr) {
|
|
220
268
|
const partialAddress = await computePartialAddress(instance);
|
|
221
269
|
|
|
222
270
|
this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`);
|
|
@@ -231,7 +279,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
231
279
|
return completeAddress;
|
|
232
280
|
}
|
|
233
281
|
|
|
234
|
-
async
|
|
282
|
+
async createAccount(secret: Fr) {
|
|
235
283
|
// This is a foot gun !
|
|
236
284
|
const completeAddress = await this.keyStore.addAccount(secret, secret);
|
|
237
285
|
await this.accountStore.setAccount(completeAddress.address, completeAddress);
|
|
@@ -241,20 +289,21 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
241
289
|
return completeAddress;
|
|
242
290
|
}
|
|
243
291
|
|
|
244
|
-
async
|
|
292
|
+
async addAuthWitness(address: AztecAddress, messageHash: Fr) {
|
|
245
293
|
const account = await this.accountStore.getAccount(address);
|
|
246
|
-
const
|
|
294
|
+
const ivpkMHash = await hashPublicKey(account.publicKeys.ivpkM);
|
|
295
|
+
const privateKey = await this.keyStore.getMasterSecretKey(ivpkMHash);
|
|
247
296
|
|
|
248
297
|
const schnorr = new Schnorr();
|
|
249
|
-
const signature = await schnorr.constructSignature(messageHash
|
|
298
|
+
const signature = await schnorr.constructSignature(messageHash, privateKey);
|
|
250
299
|
|
|
251
|
-
const authWitness = new AuthWitness(messageHash,
|
|
300
|
+
const authWitness = new AuthWitness(messageHash, signature.toLimbFields());
|
|
252
301
|
|
|
253
302
|
this.authwits.set(authWitness.requestHash.toString(), authWitness);
|
|
254
303
|
}
|
|
255
304
|
|
|
256
305
|
async mineBlock(options: { nullifiers?: Fr[] } = {}) {
|
|
257
|
-
const blockNumber = await this.
|
|
306
|
+
const blockNumber = await this.getNextBlockNumber();
|
|
258
307
|
|
|
259
308
|
const txEffect = TxEffect.empty();
|
|
260
309
|
txEffect.nullifiers = [getSingleTxBlockRequestHash(blockNumber), ...(options.nullifiers ?? [])];
|
|
@@ -278,13 +327,17 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
278
327
|
await this.stateMachine.handleL2Block(block);
|
|
279
328
|
}
|
|
280
329
|
|
|
281
|
-
async
|
|
282
|
-
from: AztecAddress,
|
|
330
|
+
async privateCallNewFlow(
|
|
331
|
+
from: AztecAddress | undefined,
|
|
283
332
|
targetContractAddress: AztecAddress = AztecAddress.zero(),
|
|
284
333
|
functionSelector: FunctionSelector = FunctionSelector.empty(),
|
|
285
334
|
args: Fr[],
|
|
286
335
|
argsHash: Fr = Fr.zero(),
|
|
287
336
|
isStaticCall: boolean = false,
|
|
337
|
+
additionalScopes: AztecAddress[] = [],
|
|
338
|
+
jobId: string,
|
|
339
|
+
authorizedUtilityCallTargets: AztecAddress[],
|
|
340
|
+
gasSettings: GasSettings,
|
|
288
341
|
) {
|
|
289
342
|
this.logger.verbose(
|
|
290
343
|
`Executing external function ${await this.contractStore.getDebugFunctionName(targetContractAddress, functionSelector)}@${targetContractAddress} isStaticCall=${isStaticCall}`,
|
|
@@ -298,13 +351,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
298
351
|
throw new Error(message);
|
|
299
352
|
}
|
|
300
353
|
|
|
301
|
-
|
|
302
|
-
// empty scope list which acts as deny-all: no notes are visible and no keys are accessible.
|
|
303
|
-
const effectiveScopes = from.isZero() ? [] : [from];
|
|
354
|
+
const scopes = from === undefined ? additionalScopes : [from, ...additionalScopes];
|
|
304
355
|
|
|
305
356
|
// Sync notes before executing private function to discover notes from previous transactions
|
|
306
|
-
const utilityExecutor = async (call: FunctionCall, execScopes:
|
|
307
|
-
await this.executeUtilityCall(call, execScopes);
|
|
357
|
+
const utilityExecutor = async (call: FunctionCall, execScopes: AztecAddress[]) => {
|
|
358
|
+
await this.executeUtilityCall(call, execScopes, jobId);
|
|
308
359
|
};
|
|
309
360
|
|
|
310
361
|
const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
|
|
@@ -313,17 +364,14 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
313
364
|
functionSelector,
|
|
314
365
|
utilityExecutor,
|
|
315
366
|
blockHeader,
|
|
316
|
-
|
|
317
|
-
|
|
367
|
+
jobId,
|
|
368
|
+
scopes,
|
|
318
369
|
);
|
|
319
370
|
|
|
320
|
-
const blockNumber = await this.
|
|
321
|
-
|
|
322
|
-
const callContext = new CallContext(from, targetContractAddress, functionSelector, isStaticCall);
|
|
371
|
+
const blockNumber = await this.getNextBlockNumber();
|
|
323
372
|
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
|
|
373
|
+
const msgSender = from ?? AztecAddress.NULL_MSG_SENDER;
|
|
374
|
+
const callContext = new CallContext(msgSender, targetContractAddress, functionSelector, isStaticCall);
|
|
327
375
|
|
|
328
376
|
const txContext = new TxContext(this.chainId, this.version, gasSettings);
|
|
329
377
|
|
|
@@ -357,17 +405,25 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
357
405
|
senderTaggingStore: this.senderTaggingStore,
|
|
358
406
|
recipientTaggingStore: this.recipientTaggingStore,
|
|
359
407
|
senderAddressBookStore: this.senderAddressBookStore,
|
|
360
|
-
|
|
408
|
+
capsuleService: new CapsuleService(this.capsuleStore, scopes),
|
|
361
409
|
privateEventStore: this.privateEventStore,
|
|
362
410
|
contractSyncService: this.stateMachine.contractSyncService,
|
|
363
|
-
jobId
|
|
411
|
+
jobId,
|
|
364
412
|
totalPublicCalldataCount: 0,
|
|
365
413
|
sideEffectCounter: minRevertibleSideEffectCounter,
|
|
366
|
-
scopes
|
|
414
|
+
scopes,
|
|
367
415
|
// In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
|
|
368
416
|
// contract would perform, including setting senderForTags.
|
|
369
417
|
senderForTags: from,
|
|
370
418
|
simulator,
|
|
419
|
+
messageContextService: this.stateMachine.messageContextService,
|
|
420
|
+
l2TipsStore: this.stateMachine.l2TipsProvider,
|
|
421
|
+
hooks: composeHooks({
|
|
422
|
+
authorizeUtilityCall: this.buildAuthorizeUtilityCallHook(
|
|
423
|
+
isStaticCall ? 'private view' : 'private',
|
|
424
|
+
authorizedUtilityCallTargets,
|
|
425
|
+
),
|
|
426
|
+
}),
|
|
371
427
|
});
|
|
372
428
|
|
|
373
429
|
// 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.
|
|
@@ -390,7 +446,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
390
446
|
);
|
|
391
447
|
const publicFunctionsCalldata = await Promise.all(
|
|
392
448
|
publicCallRequests.map(async r => {
|
|
393
|
-
const calldata = await privateExecutionOracle.
|
|
449
|
+
const calldata = await privateExecutionOracle.getHashPreimage(r.calldataHash);
|
|
394
450
|
return new HashedValues(calldata, r.calldataHash);
|
|
395
451
|
}),
|
|
396
452
|
);
|
|
@@ -475,11 +531,17 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
475
531
|
}
|
|
476
532
|
}
|
|
477
533
|
|
|
534
|
+
// Walk the nested private-call tree and collect every offchain effect the transaction emitted.
|
|
535
|
+
// PXE stores these on each `PrivateCallExecutionResult` and they never reach TXE via the
|
|
536
|
+
// `aztec_utl_emitOffchainEffect` foreign-call path (that path only fires at the top-level), so
|
|
537
|
+
// we pull them out here and the RPC wrapper will hand them to `TXESession` for buffering.
|
|
538
|
+
const offchainEffects = collectNested([executionResult], r => r.offchainEffects.map(e => e.data));
|
|
539
|
+
|
|
478
540
|
if (isStaticCall) {
|
|
479
541
|
await checkpoint!.revert();
|
|
480
542
|
|
|
481
543
|
await forkedWorldTrees.close();
|
|
482
|
-
return executionResult.returnValues ?? [];
|
|
544
|
+
return { returnValues: executionResult.returnValues ?? [], offchainEffects };
|
|
483
545
|
}
|
|
484
546
|
|
|
485
547
|
const txEffect = TxEffect.empty();
|
|
@@ -501,26 +563,21 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
501
563
|
|
|
502
564
|
await forkedWorldTrees.close();
|
|
503
565
|
|
|
504
|
-
return executionResult.returnValues ?? [];
|
|
566
|
+
return { returnValues: executionResult.returnValues ?? [], offchainEffects };
|
|
505
567
|
}
|
|
506
568
|
|
|
507
|
-
async
|
|
508
|
-
from: AztecAddress,
|
|
569
|
+
async publicCallNewFlow(
|
|
570
|
+
from: AztecAddress | undefined,
|
|
509
571
|
targetContractAddress: AztecAddress,
|
|
510
572
|
calldata: Fr[],
|
|
511
573
|
isStaticCall: boolean,
|
|
574
|
+
gasSettings: GasSettings,
|
|
512
575
|
) {
|
|
513
576
|
this.logger.verbose(
|
|
514
577
|
`Executing public function ${await this.contractStore.getDebugFunctionName(targetContractAddress, FunctionSelector.fromField(calldata[0]))}@${targetContractAddress} isStaticCall=${isStaticCall}`,
|
|
515
578
|
);
|
|
516
579
|
|
|
517
|
-
const blockNumber = await this.
|
|
518
|
-
|
|
519
|
-
const gasLimits = new Gas(DEFAULT_DA_GAS_LIMIT, DEFAULT_L2_GAS_LIMIT);
|
|
520
|
-
|
|
521
|
-
const teardownGasLimits = new Gas(DEFAULT_TEARDOWN_DA_GAS_LIMIT, DEFAULT_TEARDOWN_L2_GAS_LIMIT);
|
|
522
|
-
|
|
523
|
-
const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
|
|
580
|
+
const blockNumber = await this.getNextBlockNumber();
|
|
524
581
|
|
|
525
582
|
const txContext = new TxContext(this.chainId, this.version, gasSettings);
|
|
526
583
|
|
|
@@ -573,7 +630,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
573
630
|
// may require producing reverts.
|
|
574
631
|
const revertibleAccumulatedData = PrivateToPublicAccumulatedData.empty();
|
|
575
632
|
revertibleAccumulatedData.publicCallRequests[0] = new PublicCallRequest(
|
|
576
|
-
from,
|
|
633
|
+
from ?? AztecAddress.NULL_MSG_SENDER,
|
|
577
634
|
targetContractAddress,
|
|
578
635
|
isStaticCall,
|
|
579
636
|
calldataHash,
|
|
@@ -659,7 +716,13 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
659
716
|
return returnValues ?? [];
|
|
660
717
|
}
|
|
661
718
|
|
|
662
|
-
async
|
|
719
|
+
async executeUtilityFunction(
|
|
720
|
+
targetContractAddress: AztecAddress,
|
|
721
|
+
functionSelector: FunctionSelector,
|
|
722
|
+
args: Fr[],
|
|
723
|
+
jobId: string,
|
|
724
|
+
authorizedUtilityCallTargets: AztecAddress[],
|
|
725
|
+
) {
|
|
663
726
|
const artifact = await this.contractStore.getFunctionArtifact(targetContractAddress, functionSelector);
|
|
664
727
|
if (!artifact) {
|
|
665
728
|
throw new Error(`Cannot call ${functionSelector} as there is no artifact found at ${targetContractAddress}.`);
|
|
@@ -671,11 +734,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
671
734
|
targetContractAddress,
|
|
672
735
|
functionSelector,
|
|
673
736
|
async (call, execScopes) => {
|
|
674
|
-
await this.executeUtilityCall(call, execScopes);
|
|
737
|
+
await this.executeUtilityCall(call, execScopes, jobId);
|
|
675
738
|
},
|
|
676
739
|
blockHeader,
|
|
677
|
-
|
|
678
|
-
|
|
740
|
+
jobId,
|
|
741
|
+
await this.keyStore.getAccounts(),
|
|
679
742
|
);
|
|
680
743
|
|
|
681
744
|
const call = FunctionCall.from({
|
|
@@ -689,10 +752,15 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
689
752
|
returnTypes: [],
|
|
690
753
|
});
|
|
691
754
|
|
|
692
|
-
return this.executeUtilityCall(call,
|
|
755
|
+
return this.executeUtilityCall(call, await this.keyStore.getAccounts(), jobId, authorizedUtilityCallTargets);
|
|
693
756
|
}
|
|
694
757
|
|
|
695
|
-
private async executeUtilityCall(
|
|
758
|
+
private async executeUtilityCall(
|
|
759
|
+
call: FunctionCall,
|
|
760
|
+
scopes: AztecAddress[],
|
|
761
|
+
jobId: string,
|
|
762
|
+
authorizedUtilityCallTargets: AztecAddress[] = [],
|
|
763
|
+
): Promise<Fr[]> {
|
|
696
764
|
const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
|
|
697
765
|
if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
|
|
698
766
|
throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
|
|
@@ -705,6 +773,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
705
773
|
|
|
706
774
|
try {
|
|
707
775
|
const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
|
|
776
|
+
const simulator = new WASMSimulator();
|
|
777
|
+
const utilityExecutor = async (syncCall: FunctionCall, execScopes: AztecAddress[]) => {
|
|
778
|
+
await this.executeUtilityCall(syncCall, execScopes, jobId);
|
|
779
|
+
};
|
|
708
780
|
const oracle = new UtilityExecutionOracle({
|
|
709
781
|
contractAddress: call.to,
|
|
710
782
|
authWitnesses: [],
|
|
@@ -717,12 +789,20 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
717
789
|
aztecNode: this.stateMachine.node,
|
|
718
790
|
recipientTaggingStore: this.recipientTaggingStore,
|
|
719
791
|
senderAddressBookStore: this.senderAddressBookStore,
|
|
720
|
-
|
|
792
|
+
capsuleService: new CapsuleService(this.capsuleStore, scopes),
|
|
721
793
|
privateEventStore: this.privateEventStore,
|
|
722
|
-
|
|
794
|
+
messageContextService: this.stateMachine.messageContextService,
|
|
795
|
+
contractSyncService: this.stateMachine.contractSyncService,
|
|
796
|
+
l2TipsStore: this.stateMachine.l2TipsProvider,
|
|
797
|
+
jobId,
|
|
723
798
|
scopes,
|
|
799
|
+
simulator,
|
|
800
|
+
hooks: composeHooks({
|
|
801
|
+
authorizeUtilityCall: this.buildAuthorizeUtilityCallHook('utility', authorizedUtilityCallTargets),
|
|
802
|
+
}),
|
|
803
|
+
utilityExecutor,
|
|
724
804
|
});
|
|
725
|
-
const acirExecutionResult = await
|
|
805
|
+
const acirExecutionResult = await simulator
|
|
726
806
|
.executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
|
|
727
807
|
.catch((err: Error) => {
|
|
728
808
|
err.message = resolveAssertionMessageFromError(err, entryPointArtifact);
|
|
@@ -750,7 +830,20 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
|
|
|
750
830
|
}
|
|
751
831
|
|
|
752
832
|
private async getLastBlockNumber(): Promise<BlockNumber> {
|
|
753
|
-
const
|
|
754
|
-
return
|
|
833
|
+
const block = await this.stateMachine.node.getBlock('latest');
|
|
834
|
+
return block ? block.header.globalVariables.blockNumber : BlockNumber.ZERO;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private buildAuthorizeUtilityCallHook(
|
|
838
|
+
callerContext: 'private' | 'private view' | 'utility',
|
|
839
|
+
authorizedTargets: AztecAddress[],
|
|
840
|
+
): ExecutionHooks['authorizeUtilityCall'] | undefined {
|
|
841
|
+
if (authorizedTargets.length === 0) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
return req =>
|
|
845
|
+
Promise.resolve({
|
|
846
|
+
authorized: req.callerContext === callerContext && authorizedTargets.some(t => t.equals(req.target)),
|
|
847
|
+
});
|
|
755
848
|
}
|
|
756
849
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* TXE-specific subclass of PrivateExecutionOracle that forbids operations not supported in
|
|
8
|
+
* TestEnvironment::private_context. TXE uses dedicated oracle flows (e.g. private_call) instead.
|
|
9
|
+
*/
|
|
10
|
+
export class TXEPrivateExecutionOracle extends PrivateExecutionOracle {
|
|
11
|
+
override callPrivateFunction(
|
|
12
|
+
_targetContractAddress: AztecAddress,
|
|
13
|
+
_functionSelector: FunctionSelector,
|
|
14
|
+
_argsHash: Fr,
|
|
15
|
+
_sideEffectCounter: number,
|
|
16
|
+
_isStaticCall: boolean,
|
|
17
|
+
): Promise<{ endSideEffectCounter: Fr; returnsHash: Fr }> {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'Contract calls are forbidden inside a `TestEnvironment::private_context`, use `private_call` instead',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override assertValidPublicCalldata(_calldataHash: Fr): Promise<void> {
|
|
24
|
+
throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override notifyRevertiblePhaseStart(_minRevertibleSideEffectCounter: number): Promise<void> {
|
|
28
|
+
throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
import type { Socket } from 'node:net';
|
|
5
|
+
|
|
6
|
+
import { TXEDispatcherPool, buildSharedContractStore } from './dispatcher_pool.js';
|
|
7
|
+
import { TXEDispatcher, TXEDispatcherApiSchema } from './index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Symbol used to tag an incoming TCP socket with the `session_id` it has been associated with.
|
|
11
|
+
* Hidden under a Symbol so we don't risk colliding with anything koa or http core adds.
|
|
12
|
+
*/
|
|
13
|
+
const SESSION_SYMBOL = Symbol('txeSessionId');
|
|
14
|
+
|
|
15
|
+
type TaggedSocket = Socket & { [SESSION_SYMBOL]?: number };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates the TXE RPC server. With `TXE_WORKERS=1` oracle calls run on the main thread (no
|
|
19
|
+
* worker_threads, no IPC overhead). With any other value oracle calls are
|
|
20
|
+
* routed to a pool of worker threads sized to that value, sticky by `session_id`.
|
|
21
|
+
*
|
|
22
|
+
* Each incoming TCP socket is tagged with the `session_id` of the first oracle call it carries —
|
|
23
|
+
* nargo uses one HTTP client per test, so the socket-to-session mapping is 1:1. When the socket
|
|
24
|
+
* closes (end of test), the dispatcher disposes the session and frees its world state + LMDB.
|
|
25
|
+
*
|
|
26
|
+
* Lives in its own module so the worker bundle does not pull in the HTTP server stack.
|
|
27
|
+
*/
|
|
28
|
+
export async function createTXERpcServer(logger: Logger) {
|
|
29
|
+
const workerCount = Number(process.env.TXE_WORKERS);
|
|
30
|
+
let dispatcher: TXEDispatcher | TXEDispatcherPool;
|
|
31
|
+
if (workerCount === 1) {
|
|
32
|
+
const { dataDir, schnorrClassId } = await buildSharedContractStore();
|
|
33
|
+
dispatcher = new TXEDispatcher(logger, { contractStoreSourceDir: dataDir, schnorrClassId });
|
|
34
|
+
} else {
|
|
35
|
+
dispatcher = new TXEDispatcherPool(logger, {
|
|
36
|
+
workers: Number.isFinite(workerCount) && workerCount > 1 ? workerCount : undefined,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const server = createSafeJsonRpcServer(dispatcher, TXEDispatcherApiSchema, {
|
|
40
|
+
http200OnError: true,
|
|
41
|
+
middlewares: [
|
|
42
|
+
async (ctx, next) => {
|
|
43
|
+
// The body parser runs further down the chain, so `ctx.request.body` is populated only
|
|
44
|
+
// after `next()` resolves.
|
|
45
|
+
await next();
|
|
46
|
+
const socket = ctx.req.socket as TaggedSocket;
|
|
47
|
+
if (socket[SESSION_SYMBOL] !== undefined) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const body = (ctx.request as { body?: unknown }).body;
|
|
51
|
+
const sessionId =
|
|
52
|
+
body && typeof body === 'object' && 'params' in body
|
|
53
|
+
? extractSessionId((body as { params: unknown }).params)
|
|
54
|
+
: undefined;
|
|
55
|
+
if (sessionId === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
socket[SESSION_SYMBOL] = sessionId;
|
|
59
|
+
logger.debug(`Tagged socket with session`, {
|
|
60
|
+
sessionId,
|
|
61
|
+
remoteAddress: socket.remoteAddress,
|
|
62
|
+
remotePort: socket.remotePort,
|
|
63
|
+
});
|
|
64
|
+
socket.once('close', () => {
|
|
65
|
+
logger.debug(`Disposing session on socket close`, { sessionId });
|
|
66
|
+
void dispatcher.disposeSession(sessionId);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
return server;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Extracts `session_id` from a JSON-RPC `params` array. Always a `number` because session_id
|
|
75
|
+
// comes off `JSON.parse`, which never produces BigInt; values above MAX_SAFE_INTEGER lose
|
|
76
|
+
// precision but still work as a Map key.
|
|
77
|
+
function extractSessionId(params: unknown): number | undefined {
|
|
78
|
+
if (!Array.isArray(params) || params.length === 0) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const first = params[0];
|
|
82
|
+
if (first && typeof first === 'object' && 'session_id' in first) {
|
|
83
|
+
const sid = (first as { session_id: unknown }).session_id;
|
|
84
|
+
return typeof sid === 'number' ? sid : undefined;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|