@aztec/txe 0.0.1-commit.85d7d01 → 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.
- 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 +39 -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 +36 -20
- package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_top_level_context.js +125 -66
- 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 +567 -243
- 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 +13 -7
- package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
- package/dest/state_machine/dummy_p2p_client.js +16 -4
- 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 +64 -6
- package/dest/txe_session.d.ts.map +1 -1
- package/dest/txe_session.js +159 -31
- 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 +43 -34
- package/src/oracle/txe_oracle_public_context.ts +42 -12
- package/src/oracle/txe_oracle_top_level_context.ts +146 -77
- package/src/oracle/txe_private_execution_oracle.ts +30 -0
- package/src/rpc_server.ts +87 -0
- package/src/rpc_translator.ts +646 -326
- package/src/state_machine/archiver.ts +33 -18
- package/src/state_machine/dummy_p2p_client.ts +31 -11
- 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 +253 -33
- 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
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
|
|
2
1
|
import { type NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js/abi';
|
|
3
2
|
import { AztecAddress } from '@aztec/aztec.js/addresses';
|
|
4
3
|
import {
|
|
@@ -7,12 +6,10 @@ import {
|
|
|
7
6
|
} from '@aztec/aztec.js/contracts';
|
|
8
7
|
import { Fr } from '@aztec/aztec.js/fields';
|
|
9
8
|
import { PublicKeys, deriveKeys } from '@aztec/aztec.js/keys';
|
|
10
|
-
import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
|
|
11
9
|
import type { Logger } from '@aztec/foundation/log';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { ContractStore } from '@aztec/pxe/server';
|
|
10
|
+
import { cloneEphemeralStoreFrom } from '@aztec/kv-store/lmdb-v2';
|
|
11
|
+
import type { ProtocolContractName } from '@aztec/protocol-contracts';
|
|
12
|
+
import { ContractStore } from '@aztec/pxe/client/lazy';
|
|
16
13
|
import { computeArtifactHash } from '@aztec/stdlib/contract';
|
|
17
14
|
import type { ContractArtifactWithHash } from '@aztec/stdlib/contract';
|
|
18
15
|
import type { ApiSchemaFor } from '@aztec/stdlib/schemas';
|
|
@@ -24,6 +21,9 @@ import { readFile, readdir } from 'fs/promises';
|
|
|
24
21
|
import { join, parse } from 'path';
|
|
25
22
|
import { z } from 'zod';
|
|
26
23
|
|
|
24
|
+
// Side-effect import: registers the msgpackr Fr extension for the bundled `Fr` class. Must
|
|
25
|
+
// be loaded before any `sendMessage` call. See msgpackr_fr_extension.ts for the why.
|
|
26
|
+
import './msgpackr_fr_extension.js';
|
|
27
27
|
import { type TXEOracleFunctionName, TXESession } from './txe_session.js';
|
|
28
28
|
import {
|
|
29
29
|
type ForeignCallArgs,
|
|
@@ -32,30 +32,61 @@ import {
|
|
|
32
32
|
type ForeignCallResult,
|
|
33
33
|
ForeignCallResultSchema,
|
|
34
34
|
type ForeignCallSingle,
|
|
35
|
+
addressFromSingle,
|
|
35
36
|
fromArray,
|
|
36
37
|
fromSingle,
|
|
37
38
|
toSingle,
|
|
38
|
-
} from './
|
|
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[] = [];
|
|
39
44
|
|
|
40
45
|
const sessions = new Map<number, TXESession>();
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* The in-flight map coalesces concurrent requests for the same cache key so that
|
|
47
|
-
* computeArtifactHash (very expensive) is only run once even under parallelism.
|
|
47
|
+
/**
|
|
48
|
+
* Cache + in-flight map pair. Lookup hits the cache, then awaits an in-flight `compute()` if one
|
|
49
|
+
* exists, otherwise starts one and stores it. Guarantees `compute()` runs at most once per `key`
|
|
50
|
+
* across concurrent callers, which matters because `computeArtifactHash` is expensive.
|
|
48
51
|
*/
|
|
49
|
-
|
|
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<
|
|
50
81
|
string,
|
|
51
82
|
{ artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }
|
|
52
83
|
>();
|
|
53
|
-
const TXEArtifactsCacheInFlight = new Map<
|
|
54
|
-
string,
|
|
55
|
-
Promise<{ artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }>
|
|
56
|
-
>();
|
|
57
84
|
|
|
58
|
-
|
|
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 = {
|
|
59
90
|
session_id: number;
|
|
60
91
|
function: TXEOracleFunctionName;
|
|
61
92
|
root_path: string;
|
|
@@ -63,10 +94,13 @@ type TXEForeignCallInput = {
|
|
|
63
94
|
inputs: ForeignCallArgs;
|
|
64
95
|
};
|
|
65
96
|
|
|
66
|
-
const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
|
|
97
|
+
export const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
|
|
67
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).
|
|
68
102
|
// eslint-disable-next-line camelcase
|
|
69
|
-
session_id: z.number().
|
|
103
|
+
session_id: z.number().nonnegative(),
|
|
70
104
|
function: z.string() as z.ZodType<TXEOracleFunctionName>,
|
|
71
105
|
// eslint-disable-next-line camelcase
|
|
72
106
|
root_path: z.string(),
|
|
@@ -76,12 +110,56 @@ const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
|
|
|
76
110
|
}),
|
|
77
111
|
);
|
|
78
112
|
|
|
79
|
-
|
|
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 {
|
|
80
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
|
+
}
|
|
81
141
|
|
|
82
|
-
|
|
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
|
+
}
|
|
83
161
|
|
|
84
|
-
private fastHashFile(path: string) {
|
|
162
|
+
private fastHashFile(path: string): Promise<string> {
|
|
85
163
|
return new Promise(resolve => {
|
|
86
164
|
const fd = createReadStream(path);
|
|
87
165
|
const hash = createHash('sha1');
|
|
@@ -89,7 +167,7 @@ class TXEDispatcher {
|
|
|
89
167
|
|
|
90
168
|
fd.on('end', function () {
|
|
91
169
|
hash.end();
|
|
92
|
-
resolve(hash.read());
|
|
170
|
+
resolve(hash.read() as string);
|
|
93
171
|
});
|
|
94
172
|
|
|
95
173
|
fd.pipe(hash);
|
|
@@ -105,6 +183,8 @@ class TXEDispatcher {
|
|
|
105
183
|
|
|
106
184
|
const decodedArgs = fromArray(inputs[3] as ForeignCallArray);
|
|
107
185
|
const secret = fromSingle(inputs[4] as ForeignCallSingle);
|
|
186
|
+
const salt = fromSingle(inputs[5] as ForeignCallSingle);
|
|
187
|
+
const deployer = addressFromSingle(inputs[6] as ForeignCallSingle);
|
|
108
188
|
const publicKeys = secret.equals(Fr.ZERO) ? PublicKeys.default() : (await deriveKeys(secret)).publicKeys;
|
|
109
189
|
const publicKeysHash = await publicKeys.hash();
|
|
110
190
|
|
|
@@ -135,46 +215,31 @@ class TXEDispatcher {
|
|
|
135
215
|
|
|
136
216
|
const cacheKey = `${contractDirectory ?? ''}-${contractFilename}-${initializer}-${decodedArgs
|
|
137
217
|
.map(arg => arg.toString())
|
|
138
|
-
.join('-')}-${publicKeysHash}-${fileHash}`;
|
|
218
|
+
.join('-')}-${publicKeysHash}-${salt}-${deployer}-${fileHash}`;
|
|
139
219
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
skipArgsDecoding: true,
|
|
164
|
-
salt: Fr.ONE,
|
|
165
|
-
publicKeys,
|
|
166
|
-
constructorArtifact: initializer ? initializer : undefined,
|
|
167
|
-
deployer: AztecAddress.ZERO,
|
|
168
|
-
});
|
|
169
|
-
const result = { artifact: computedArtifact, instance: computedInstance };
|
|
170
|
-
TXEArtifactsCache.set(cacheKey, result);
|
|
171
|
-
TXEArtifactsCacheInFlight.delete(cacheKey);
|
|
172
|
-
return result;
|
|
173
|
-
};
|
|
174
|
-
TXEArtifactsCacheInFlight.set(cacheKey, compute());
|
|
175
|
-
}
|
|
176
|
-
({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!);
|
|
177
|
-
}
|
|
220
|
+
const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
|
|
221
|
+
this.logger.debug(`Loading compiled artifact ${artifactPath}`);
|
|
222
|
+
// Inner cache: artifact load + hash depends only on the compiled bytecode (`fileHash`), so
|
|
223
|
+
// subsequent deploys of the same contract — regardless of constructor args / deployer /
|
|
224
|
+
// salt — reuse the same `ContractArtifactWithHash`.
|
|
225
|
+
const computedArtifact = await TXEArtifactsCache.getOrCompute(fileHash, async () => {
|
|
226
|
+
const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
|
|
227
|
+
const artifactWithoutHash = loadContractArtifact(artifactJSON);
|
|
228
|
+
return { ...artifactWithoutHash, artifactHash: await computeArtifactHash(artifactWithoutHash) };
|
|
229
|
+
});
|
|
230
|
+
this.logger.debug(
|
|
231
|
+
`Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
|
|
232
|
+
);
|
|
233
|
+
const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
|
|
234
|
+
constructorArgs: decodedArgs,
|
|
235
|
+
skipArgsDecoding: true,
|
|
236
|
+
salt,
|
|
237
|
+
publicKeys,
|
|
238
|
+
constructorArtifact: initializer ? initializer : undefined,
|
|
239
|
+
deployer,
|
|
240
|
+
});
|
|
241
|
+
return { artifact: computedArtifact, instance: computedInstance };
|
|
242
|
+
});
|
|
178
243
|
|
|
179
244
|
inputs.splice(0, 1, artifact, instance, toSingle(secret));
|
|
180
245
|
}
|
|
@@ -184,43 +249,29 @@ class TXEDispatcher {
|
|
|
184
249
|
|
|
185
250
|
const cacheKey = `SchnorrAccountContract-${secret}`;
|
|
186
251
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const keys = await deriveKeys(secret);
|
|
197
|
-
const args = [
|
|
198
|
-
keys.publicKeys.masterIncomingViewingPublicKey.x,
|
|
199
|
-
keys.publicKeys.masterIncomingViewingPublicKey.y,
|
|
200
|
-
];
|
|
201
|
-
const computedArtifact: ContractArtifactWithHash = {
|
|
202
|
-
...SchnorrAccountContractArtifact,
|
|
203
|
-
// Artifact hash is *very* expensive to compute, so we do it here once
|
|
204
|
-
// and the TXE contract data provider can cache it
|
|
205
|
-
artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact),
|
|
206
|
-
};
|
|
207
|
-
const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
|
|
208
|
-
constructorArgs: args,
|
|
209
|
-
skipArgsDecoding: true,
|
|
210
|
-
salt: Fr.ONE,
|
|
211
|
-
publicKeys: keys.publicKeys,
|
|
212
|
-
constructorArtifact: 'constructor',
|
|
213
|
-
deployer: AztecAddress.ZERO,
|
|
214
|
-
});
|
|
215
|
-
const result = { artifact: computedArtifact, instance: computedInstance };
|
|
216
|
-
TXEArtifactsCache.set(cacheKey, result);
|
|
217
|
-
TXEArtifactsCacheInFlight.delete(cacheKey);
|
|
218
|
-
return result;
|
|
219
|
-
};
|
|
220
|
-
TXEArtifactsCacheInFlight.set(cacheKey, compute());
|
|
252
|
+
const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
|
|
253
|
+
const [artifactFromStore, classWithPreimage] = await Promise.all([
|
|
254
|
+
this.contractStore.getContractArtifact(this.schnorrClassId),
|
|
255
|
+
this.contractStore.getContractClassWithPreimage(this.schnorrClassId),
|
|
256
|
+
]);
|
|
257
|
+
if (!artifactFromStore || !classWithPreimage) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`SchnorrAccount not found in shared contract store at class id ${this.schnorrClassId.toString()}`,
|
|
260
|
+
);
|
|
221
261
|
}
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
const computedArtifact = { ...artifactFromStore, artifactHash: classWithPreimage.artifactHash };
|
|
263
|
+
const keys = await deriveKeys(secret);
|
|
264
|
+
const args = [keys.publicKeys.ivpkM.x, keys.publicKeys.ivpkM.y];
|
|
265
|
+
const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
|
|
266
|
+
constructorArgs: args,
|
|
267
|
+
skipArgsDecoding: true,
|
|
268
|
+
salt: Fr.ONE,
|
|
269
|
+
publicKeys: keys.publicKeys,
|
|
270
|
+
constructorArtifact: 'constructor',
|
|
271
|
+
deployer: AztecAddress.ZERO,
|
|
272
|
+
});
|
|
273
|
+
return { artifact: computedArtifact, instance: computedInstance };
|
|
274
|
+
});
|
|
224
275
|
|
|
225
276
|
inputs.splice(0, 0, artifact, instance);
|
|
226
277
|
}
|
|
@@ -232,26 +283,16 @@ class TXEDispatcher {
|
|
|
232
283
|
|
|
233
284
|
if (!sessions.has(sessionId)) {
|
|
234
285
|
this.logger.debug(`Creating new session ${sessionId}`);
|
|
235
|
-
|
|
236
|
-
const kvStore = await openTmpStore('txe-contracts');
|
|
237
|
-
this.contractStore = new ContractStore(kvStore);
|
|
238
|
-
const provider = new BundledProtocolContractsProvider();
|
|
239
|
-
for (const name of protocolContractNames) {
|
|
240
|
-
const { instance, artifact } = await provider.getProtocolContractArtifact(name);
|
|
241
|
-
await this.contractStore.addContractArtifact(artifact);
|
|
242
|
-
await this.contractStore.addContractInstance(instance);
|
|
243
|
-
}
|
|
244
|
-
this.logger.debug('Registered protocol contracts in shared contract store');
|
|
245
|
-
}
|
|
286
|
+
await this.warmUp();
|
|
246
287
|
sessions.set(sessionId, await TXESession.init(this.contractStore));
|
|
247
288
|
}
|
|
248
289
|
|
|
249
290
|
switch (functionName) {
|
|
250
|
-
case '
|
|
291
|
+
case 'aztec_txe_deploy': {
|
|
251
292
|
await this.#processDeployInputs(callData);
|
|
252
293
|
break;
|
|
253
294
|
}
|
|
254
|
-
case '
|
|
295
|
+
case 'aztec_txe_addAccount': {
|
|
255
296
|
await this.#processAddAccountInputs(callData);
|
|
256
297
|
break;
|
|
257
298
|
}
|
|
@@ -259,20 +300,31 @@ class TXEDispatcher {
|
|
|
259
300
|
|
|
260
301
|
return await sessions.get(sessionId)!.processFunction(functionName, inputs);
|
|
261
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;
|
|
262
323
|
}
|
|
263
324
|
|
|
264
|
-
const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
|
|
325
|
+
export const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
|
|
265
326
|
// eslint-disable-next-line camelcase
|
|
266
|
-
resolve_foreign_call: z.function(
|
|
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() }),
|
|
267
330
|
};
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Creates an RPC server that forwards calls to the TXE.
|
|
271
|
-
* @param logger - Logger to output to
|
|
272
|
-
* @returns A TXE RPC server.
|
|
273
|
-
*/
|
|
274
|
-
export function createTXERpcServer(logger: Logger) {
|
|
275
|
-
return createSafeJsonRpcServer(new TXEDispatcher(logger), TXEDispatcherApiSchema, {
|
|
276
|
-
http200OnError: true,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
@@ -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
|
+
});
|
package/src/oracle/interfaces.ts
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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,46 +51,48 @@ export interface IAvmExecutionOracle {
|
|
|
44
51
|
export interface ITxeExecutionOracle {
|
|
45
52
|
isTxe: true;
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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,
|
|
78
|
+
additionalScopes: AztecAddress[],
|
|
74
79
|
jobId: string,
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
authorizedUtilityCallTargets: AztecAddress[],
|
|
81
|
+
gasSettings: GasSettings,
|
|
82
|
+
): Promise<{ returnValues: Fr[]; offchainEffects: Fr[][] }>;
|
|
83
|
+
executeUtilityFunction(
|
|
77
84
|
targetContractAddress: AztecAddress,
|
|
78
85
|
functionSelector: FunctionSelector,
|
|
79
86
|
args: Fr[],
|
|
80
87
|
jobId: string,
|
|
88
|
+
authorizedUtilityCallTargets: AztecAddress[],
|
|
81
89
|
): Promise<Fr[]>;
|
|
82
|
-
|
|
83
|
-
from: AztecAddress,
|
|
90
|
+
publicCallNewFlow(
|
|
91
|
+
from: AztecAddress | undefined,
|
|
84
92
|
targetContractAddress: AztecAddress,
|
|
85
93
|
calldata: Fr[],
|
|
86
94
|
isStaticCall: boolean,
|
|
95
|
+
gasSettings: GasSettings,
|
|
87
96
|
): Promise<Fr[]>;
|
|
88
97
|
// TODO(F-335): Drop this from here as it's not a real oracle handler - it's only called from
|
|
89
98
|
// RPCTranslator::txeGetPrivateEvents and never from Noir.
|
|
@@ -39,46 +39,46 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
address(): Promise<AztecAddress> {
|
|
43
43
|
return Promise.resolve(this.contractAddress);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
sender(): Promise<AztecAddress> {
|
|
47
47
|
return Promise.resolve(AztecAddress.ZERO); // todo: change?
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
blockNumber(): Promise<BlockNumber> {
|
|
51
51
|
return Promise.resolve(this.globalVariables.blockNumber);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
timestamp(): Promise<bigint> {
|
|
55
55
|
return Promise.resolve(this.globalVariables.timestamp);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
isStaticCall(): Promise<boolean> {
|
|
59
59
|
return Promise.resolve(false);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
chainId(): Promise<Fr> {
|
|
63
63
|
return Promise.resolve(this.globalVariables.chainId);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
version(): Promise<Fr> {
|
|
67
67
|
return Promise.resolve(this.globalVariables.version);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|