@aztec/end-to-end 0.0.1-commit.b1c78909e → 0.0.1-commit.b2a5d0dd1
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/README.md +27 -0
- package/dest/bench/client_flows/client_flows_benchmark.d.ts +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.js +15 -30
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +3 -2
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +1 -1
- package/dest/e2e_epochs/epochs_test.d.ts +18 -1
- package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
- package/dest/e2e_epochs/epochs_test.js +59 -9
- package/dest/e2e_fees/fees_test.d.ts +1 -1
- package/dest/e2e_fees/fees_test.d.ts.map +1 -1
- package/dest/e2e_fees/fees_test.js +3 -3
- package/dest/e2e_p2p/inactivity_slash_test.js +3 -3
- package/dest/e2e_p2p/p2p_network.d.ts +10 -9
- package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
- package/dest/e2e_p2p/p2p_network.js +46 -27
- package/dest/e2e_p2p/reqresp/utils.js +1 -1
- package/dest/e2e_p2p/shared.d.ts +5 -7
- package/dest/e2e_p2p/shared.d.ts.map +1 -1
- package/dest/e2e_p2p/shared.js +29 -44
- package/dest/fixtures/authwit_proxy.d.ts +1 -1
- package/dest/fixtures/authwit_proxy.d.ts.map +1 -1
- package/dest/fixtures/authwit_proxy.js +4 -0
- package/dest/fixtures/e2e_prover_test.d.ts +4 -3
- package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
- package/dest/fixtures/e2e_prover_test.js +7 -12
- package/dest/fixtures/fixtures.d.ts +12 -1
- package/dest/fixtures/fixtures.d.ts.map +1 -1
- package/dest/fixtures/fixtures.js +10 -0
- package/dest/fixtures/get_bb_config.d.ts +1 -1
- package/dest/fixtures/get_bb_config.d.ts.map +1 -1
- package/dest/fixtures/get_bb_config.js +5 -5
- package/dest/fixtures/ha_setup.d.ts +2 -2
- package/dest/fixtures/ha_setup.d.ts.map +1 -1
- package/dest/fixtures/ha_setup.js +1 -1
- package/dest/fixtures/schnorr_hardcoded_account_contract.d.ts +25 -0
- package/dest/fixtures/schnorr_hardcoded_account_contract.d.ts.map +1 -0
- package/dest/fixtures/schnorr_hardcoded_account_contract.js +39 -0
- package/dest/fixtures/setup.d.ts +17 -10
- package/dest/fixtures/setup.d.ts.map +1 -1
- package/dest/fixtures/setup.js +40 -21
- package/dest/fixtures/setup_p2p_test.d.ts +6 -6
- package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
- package/dest/fixtures/setup_p2p_test.js +8 -8
- package/dest/fixtures/token_utils.d.ts +1 -1
- package/dest/fixtures/token_utils.d.ts.map +1 -1
- package/dest/fixtures/token_utils.js +2 -5
- package/dest/forward-compatibility/wallet_rpc_client.d.ts +7 -0
- package/dest/forward-compatibility/wallet_rpc_client.d.ts.map +1 -0
- package/dest/forward-compatibility/wallet_rpc_client.js +15 -0
- package/dest/forward-compatibility/wallet_service.d.ts +3 -0
- package/dest/forward-compatibility/wallet_service.d.ts.map +1 -0
- package/dest/forward-compatibility/wallet_service.js +109 -0
- package/dest/legacy-jest-resolver.d.cts +3 -0
- package/dest/legacy-jest-resolver.d.cts.map +1 -0
- package/dest/shared/gas_portal_test_harness.js +1 -1
- package/dest/shared/uniswap_l1_l2.d.ts +1 -1
- package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
- package/dest/shared/uniswap_l1_l2.js +9 -16
- package/dest/simulators/lending_simulator.d.ts +1 -1
- package/dest/simulators/lending_simulator.d.ts.map +1 -1
- package/dest/simulators/lending_simulator.js +2 -2
- package/dest/simulators/token_simulator.js +1 -1
- package/dest/spartan/setup_test_wallets.d.ts +1 -1
- package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.js +63 -45
- package/dest/test-wallet/test_wallet.d.ts +24 -23
- package/dest/test-wallet/test_wallet.d.ts.map +1 -1
- package/dest/test-wallet/test_wallet.js +118 -81
- package/dest/test-wallet/worker_wallet.d.ts +4 -4
- package/dest/test-wallet/worker_wallet.d.ts.map +1 -1
- package/dest/test-wallet/worker_wallet_schema.d.ts +9 -4
- package/dest/test-wallet/worker_wallet_schema.d.ts.map +1 -1
- package/package.json +40 -40
- package/src/bench/client_flows/client_flows_benchmark.ts +33 -24
- package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +3 -6
- package/src/e2e_epochs/epochs_test.ts +69 -9
- package/src/e2e_fees/fees_test.ts +4 -2
- package/src/e2e_p2p/inactivity_slash_test.ts +3 -3
- package/src/e2e_p2p/p2p_network.ts +57 -39
- package/src/e2e_p2p/reqresp/utils.ts +1 -1
- package/src/e2e_p2p/shared.ts +29 -59
- package/src/fixtures/authwit_proxy.ts +4 -0
- package/src/fixtures/e2e_prover_test.ts +12 -17
- package/src/fixtures/fixtures.ts +22 -0
- package/src/fixtures/get_bb_config.ts +7 -6
- package/src/fixtures/ha_setup.ts +4 -2
- package/src/fixtures/schnorr_hardcoded_account_contract.ts +49 -0
- package/src/fixtures/setup.ts +54 -25
- package/src/fixtures/setup_p2p_test.ts +9 -9
- package/src/fixtures/token_utils.ts +1 -4
- package/src/forward-compatibility/wallet_rpc_client.ts +14 -0
- package/src/forward-compatibility/wallet_service.ts +104 -0
- package/src/guides/up_quick_start.sh +0 -2
- package/src/legacy-jest-resolver.cjs +135 -0
- package/src/shared/gas_portal_test_harness.ts +0 -1
- package/src/shared/uniswap_l1_l2.ts +29 -28
- package/src/simulators/lending_simulator.ts +4 -2
- package/src/simulators/token_simulator.ts +1 -1
- package/src/spartan/setup_test_wallets.ts +63 -39
- package/src/test-wallet/test_wallet.ts +147 -100
- package/src/test-wallet/worker_wallet.ts +3 -2
package/src/e2e_p2p/shared.ts
CHANGED
|
@@ -7,11 +7,7 @@ import type { Logger } from '@aztec/aztec.js/log';
|
|
|
7
7
|
import { TxHash } from '@aztec/aztec.js/tx';
|
|
8
8
|
import type { RollupCheatCodes } from '@aztec/aztec/testing';
|
|
9
9
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
10
|
-
import type {
|
|
11
|
-
EmpireSlashingProposerContract,
|
|
12
|
-
RollupContract,
|
|
13
|
-
TallySlashingProposerContract,
|
|
14
|
-
} from '@aztec/ethereum/contracts';
|
|
10
|
+
import type { RollupContract, SlashingProposerContract } from '@aztec/ethereum/contracts';
|
|
15
11
|
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
16
12
|
import { timesAsync, unique } from '@aztec/foundation/collection';
|
|
17
13
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -22,8 +18,8 @@ import { TestContract, TestContractArtifact } from '@aztec/noir-test-contracts.j
|
|
|
22
18
|
import { getPXEConfig, getPXEConfig as getRpcConfig } from '@aztec/pxe/server';
|
|
23
19
|
import { getRoundForOffense } from '@aztec/slasher';
|
|
24
20
|
import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
|
|
25
|
-
import type { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
26
21
|
|
|
22
|
+
import { SchnorrHardcodedKeyAccountContract } from '../fixtures/schnorr_hardcoded_account_contract.js';
|
|
27
23
|
import { submitTxsTo } from '../shared/submit-transactions.js';
|
|
28
24
|
import { TestWallet } from '../test-wallet/test_wallet.js';
|
|
29
25
|
import { type ProvenTx, proveInteraction } from '../test-wallet/utils.js';
|
|
@@ -63,7 +59,12 @@ export const submitTransactions = async (
|
|
|
63
59
|
{ ...getPXEConfig(), proverEnabled: false },
|
|
64
60
|
{ loggerActorLabel: 'pxe-tx' },
|
|
65
61
|
);
|
|
66
|
-
const
|
|
62
|
+
const contract = new SchnorrHardcodedKeyAccountContract();
|
|
63
|
+
const fundedAccountManager = await wallet.createAccount({
|
|
64
|
+
secret: fundedAccount.secret,
|
|
65
|
+
salt: fundedAccount.salt,
|
|
66
|
+
contract,
|
|
67
|
+
});
|
|
67
68
|
return submitTxsTo(wallet, fundedAccountManager.address, numTxs, logger);
|
|
68
69
|
};
|
|
69
70
|
|
|
@@ -81,7 +82,12 @@ export async function prepareTransactions(
|
|
|
81
82
|
{ ...getPXEConfig(), proverEnabled: false },
|
|
82
83
|
{ loggerActorLabel: 'pxe-tx' },
|
|
83
84
|
);
|
|
84
|
-
const
|
|
85
|
+
const accountContract = new SchnorrHardcodedKeyAccountContract();
|
|
86
|
+
const fundedAccountManager = await wallet.createAccount({
|
|
87
|
+
secret: fundedAccount.secret,
|
|
88
|
+
salt: fundedAccount.salt,
|
|
89
|
+
contract: accountContract,
|
|
90
|
+
});
|
|
85
91
|
|
|
86
92
|
const testContractInstance = await getContractInstanceFromInstantiationParams(TestContractArtifact, {
|
|
87
93
|
salt: Fr.random(),
|
|
@@ -99,7 +105,7 @@ export async function prepareTransactions(
|
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
export function awaitProposalExecution(
|
|
102
|
-
slashingProposer:
|
|
108
|
+
slashingProposer: SlashingProposerContract,
|
|
103
109
|
timeoutSeconds: number,
|
|
104
110
|
logger: Logger,
|
|
105
111
|
): Promise<bigint> {
|
|
@@ -109,24 +115,12 @@ export function awaitProposalExecution(
|
|
|
109
115
|
reject(new Error(`Timeout waiting for proposal execution after ${timeoutSeconds}s`));
|
|
110
116
|
}, timeoutSeconds * 1000);
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
logger.warn(`Proposal ${args.payload} from round ${args.round} executed`);
|
|
115
|
-
clearTimeout(timeout);
|
|
116
|
-
unwatch();
|
|
117
|
-
resolve(args.round);
|
|
118
|
-
});
|
|
119
|
-
} else if (slashingProposer.type === 'tally') {
|
|
120
|
-
const unwatch = slashingProposer.listenToRoundExecuted(args => {
|
|
121
|
-
logger.warn(`Slash from round ${args.round} executed`);
|
|
122
|
-
clearTimeout(timeout);
|
|
123
|
-
unwatch();
|
|
124
|
-
resolve(args.round);
|
|
125
|
-
});
|
|
126
|
-
} else {
|
|
118
|
+
const unwatch = slashingProposer.listenToRoundExecuted(args => {
|
|
119
|
+
logger.warn(`Slash from round ${args.round} executed`);
|
|
127
120
|
clearTimeout(timeout);
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
unwatch();
|
|
122
|
+
resolve(args.round);
|
|
123
|
+
});
|
|
130
124
|
});
|
|
131
125
|
}
|
|
132
126
|
|
|
@@ -245,7 +239,6 @@ export async function awaitCommitteeKicked({
|
|
|
245
239
|
rollup,
|
|
246
240
|
cheatCodes,
|
|
247
241
|
committee,
|
|
248
|
-
slashFactory,
|
|
249
242
|
slashingProposer,
|
|
250
243
|
slashingRoundSize,
|
|
251
244
|
aztecSlotDuration,
|
|
@@ -256,8 +249,7 @@ export async function awaitCommitteeKicked({
|
|
|
256
249
|
rollup: RollupContract;
|
|
257
250
|
cheatCodes: RollupCheatCodes;
|
|
258
251
|
committee: readonly `0x${string}`[];
|
|
259
|
-
|
|
260
|
-
slashingProposer: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
252
|
+
slashingProposer: SlashingProposerContract | undefined;
|
|
261
253
|
slashingRoundSize: number;
|
|
262
254
|
aztecSlotDuration: number;
|
|
263
255
|
aztecEpochDuration: number;
|
|
@@ -270,36 +262,14 @@ export async function awaitCommitteeKicked({
|
|
|
270
262
|
|
|
271
263
|
await cheatCodes.debugRollup();
|
|
272
264
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const events = await slashFactory.getSlashPayloadCreatedEvents();
|
|
282
|
-
return events.length > 0 ? events : undefined;
|
|
283
|
-
},
|
|
284
|
-
'slash payload created',
|
|
285
|
-
120,
|
|
286
|
-
1,
|
|
287
|
-
);
|
|
288
|
-
expect(slashPayloadEvents.length).toBe(1);
|
|
289
|
-
// The uniqueness check is needed since a validator may be slashed more than once on the same round (eg because they let two epochs be pruned)
|
|
290
|
-
expect(unique(slashPayloadEvents[0].slashes.map(slash => slash.validator.toString()))).toHaveLength(
|
|
291
|
-
committee.length,
|
|
292
|
-
);
|
|
293
|
-
} else {
|
|
294
|
-
// Use the slash offset to ensure we are in the right epoch for tally
|
|
295
|
-
const slashOffsetInRounds = await slashingProposer.getSlashOffsetInRounds();
|
|
296
|
-
const slashingRoundSizeInEpochs = slashingRoundSize / aztecEpochDuration;
|
|
297
|
-
const slashingOffsetInEpochs = Number(slashOffsetInRounds) * slashingRoundSizeInEpochs;
|
|
298
|
-
const firstEpochInOffenseRound = offenseEpoch - (offenseEpoch % slashingRoundSizeInEpochs);
|
|
299
|
-
const targetEpoch = firstEpochInOffenseRound + slashingOffsetInEpochs;
|
|
300
|
-
logger.info(`Advancing to epoch ${targetEpoch} so we start slashing`);
|
|
301
|
-
await cheatCodes.advanceToEpoch(EpochNumber(targetEpoch), { offset: -aztecSlotDuration / 2 });
|
|
302
|
-
}
|
|
265
|
+
// Use the slash offset to ensure we are in the right epoch for tally
|
|
266
|
+
const slashOffsetInRounds = await slashingProposer.getSlashOffsetInRounds();
|
|
267
|
+
const slashingRoundSizeInEpochs = slashingRoundSize / aztecEpochDuration;
|
|
268
|
+
const slashingOffsetInEpochs = Number(slashOffsetInRounds) * slashingRoundSizeInEpochs;
|
|
269
|
+
const firstEpochInOffenseRound = offenseEpoch - (offenseEpoch % slashingRoundSizeInEpochs);
|
|
270
|
+
const targetEpoch = firstEpochInOffenseRound + slashingOffsetInEpochs;
|
|
271
|
+
logger.info(`Advancing to epoch ${targetEpoch} so we start slashing`);
|
|
272
|
+
await cheatCodes.advanceToEpoch(EpochNumber(targetEpoch), { offset: -aztecSlotDuration / 2 });
|
|
303
273
|
|
|
304
274
|
const attestersPre = await rollup.getAttesters();
|
|
305
275
|
expect(attestersPre.length).toBe(committee.length);
|
|
@@ -17,6 +17,10 @@ async function buildProxyCall(proxy: GenericProxyContract, action: ContractFunct
|
|
|
17
17
|
return proxy.methods.forward_private_3(call.to, call.selector, call.args);
|
|
18
18
|
} else if (argCount === 4) {
|
|
19
19
|
return proxy.methods.forward_private_4(call.to, call.selector, call.args);
|
|
20
|
+
} else if (argCount === 5) {
|
|
21
|
+
return proxy.methods.forward_private_5(call.to, call.selector, call.args);
|
|
22
|
+
} else if (argCount === 6) {
|
|
23
|
+
return proxy.methods.forward_private_6(call.to, call.selector, call.args);
|
|
20
24
|
}
|
|
21
25
|
throw new Error(`No forward_private_${argCount} method on proxy`);
|
|
22
26
|
}
|
|
@@ -4,12 +4,7 @@ import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses';
|
|
|
4
4
|
import { type Logger, createLogger } from '@aztec/aztec.js/log';
|
|
5
5
|
import type { AztecNode } from '@aztec/aztec.js/node';
|
|
6
6
|
import { CheatCodes } from '@aztec/aztec/testing';
|
|
7
|
-
import {
|
|
8
|
-
BBCircuitVerifier,
|
|
9
|
-
type ClientProtocolCircuitVerifier,
|
|
10
|
-
QueuedIVCVerifier,
|
|
11
|
-
TestCircuitVerifier,
|
|
12
|
-
} from '@aztec/bb-prover';
|
|
7
|
+
import type { ClientProtocolCircuitVerifier } from '@aztec/bb-prover';
|
|
13
8
|
import { BackendType, Barretenberg } from '@aztec/bb.js';
|
|
14
9
|
import type { DeployAztecL1ContractsReturnType } from '@aztec/ethereum/deploy-aztec-l1-contracts';
|
|
15
10
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
@@ -68,7 +63,10 @@ export class FullProverTest {
|
|
|
68
63
|
private provenComponents: ProvenSetup[] = [];
|
|
69
64
|
private bbConfigCleanup?: () => Promise<void>;
|
|
70
65
|
private acvmConfigCleanup?: () => Promise<void>;
|
|
71
|
-
|
|
66
|
+
/** Returns the proof verifier from the prover node (for test assertions). */
|
|
67
|
+
get circuitProofVerifier(): ClientProtocolCircuitVerifier | undefined {
|
|
68
|
+
return this.proverAztecNode?.getProofVerifier();
|
|
69
|
+
}
|
|
72
70
|
provenAsset!: TokenContract;
|
|
73
71
|
context!: EndToEndContext;
|
|
74
72
|
private proverAztecNode!: AztecNodeService;
|
|
@@ -106,15 +104,13 @@ export class FullProverTest {
|
|
|
106
104
|
await publicDeployAccounts(this.wallet, this.accounts.slice(0, 2));
|
|
107
105
|
|
|
108
106
|
this.logger.info('Applying base setup: deploying token contract');
|
|
109
|
-
const {
|
|
110
|
-
receipt: { contract: asset, instance },
|
|
111
|
-
} = await TokenContract.deploy(
|
|
107
|
+
const { contract: asset, instance } = await TokenContract.deploy(
|
|
112
108
|
this.wallet,
|
|
113
109
|
this.accounts[0],
|
|
114
110
|
FullProverTest.TOKEN_NAME,
|
|
115
111
|
FullProverTest.TOKEN_SYMBOL,
|
|
116
112
|
FullProverTest.TOKEN_DECIMALS,
|
|
117
|
-
).send({ from: this.accounts[0]
|
|
113
|
+
).send({ from: this.accounts[0] });
|
|
118
114
|
this.logger.verbose(`Token deployed to ${asset.address}`);
|
|
119
115
|
|
|
120
116
|
this.fakeProofsAsset = asset;
|
|
@@ -170,9 +166,6 @@ export class FullProverTest {
|
|
|
170
166
|
|
|
171
167
|
await Barretenberg.initSingleton({ backend: BackendType.NativeUnixSocket });
|
|
172
168
|
|
|
173
|
-
const verifier = await BBCircuitVerifier.new(bbConfig);
|
|
174
|
-
this.circuitProofVerifier = new QueuedIVCVerifier(bbConfig, verifier);
|
|
175
|
-
|
|
176
169
|
this.logger.debug(`Configuring the node for real proofs...`);
|
|
177
170
|
await this.aztecNodeAdmin.setConfig({
|
|
178
171
|
realProofs: true,
|
|
@@ -180,7 +173,6 @@ export class FullProverTest {
|
|
|
180
173
|
});
|
|
181
174
|
} else {
|
|
182
175
|
this.logger.debug(`Configuring the node min txs per block ${this.minNumberOfTxsPerBlock}...`);
|
|
183
|
-
this.circuitProofVerifier = new TestCircuitVerifier();
|
|
184
176
|
await this.aztecNodeAdmin.setConfig({
|
|
185
177
|
minTxsPerBlock: this.minNumberOfTxsPerBlock,
|
|
186
178
|
});
|
|
@@ -231,8 +223,11 @@ export class FullProverTest {
|
|
|
231
223
|
|
|
232
224
|
this.logger.verbose('Starting prover node');
|
|
233
225
|
const sponsoredFPCAddress = await getSponsoredFPCAddress();
|
|
234
|
-
const {
|
|
226
|
+
const { genesis } = await getGenesisValues(
|
|
235
227
|
this.context.initialFundedAccounts.map(a => a.address).concat(sponsoredFPCAddress),
|
|
228
|
+
undefined,
|
|
229
|
+
undefined,
|
|
230
|
+
this.context.genesis!.genesisTimestamp,
|
|
236
231
|
);
|
|
237
232
|
|
|
238
233
|
const proverNodeConfig: Parameters<typeof AztecNodeService.createAndSync>[0] = {
|
|
@@ -260,7 +255,7 @@ export class FullProverTest {
|
|
|
260
255
|
this.proverAztecNode = await AztecNodeService.createAndSync(
|
|
261
256
|
proverNodeConfig,
|
|
262
257
|
{ dateProvider: this.context.dateProvider, p2pClientDeps: { rpcTxProviders: [this.aztecNode] } },
|
|
263
|
-
{
|
|
258
|
+
{ genesis },
|
|
264
259
|
);
|
|
265
260
|
this.logger.warn(`Proofs are now enabled`, { realProofs: this.realProofs });
|
|
266
261
|
return this;
|
package/src/fixtures/fixtures.ts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
|
+
import type { AztecNode } from '@aztec/aztec.js/node';
|
|
2
|
+
import type { GasFees } from '@aztec/stdlib/gas';
|
|
3
|
+
|
|
1
4
|
export const METRICS_PORT = 4318;
|
|
2
5
|
|
|
6
|
+
/** Default fee padding applied to predicted min fees in e2e tests. */
|
|
7
|
+
export const DEFAULT_MIN_FEE_PADDING = 5;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Large fee padding for txs that may be mined significantly later than when they were created,
|
|
11
|
+
* such as cloned txs in throughput/capacity benchmarks, where fees may spike between creation and mining.
|
|
12
|
+
*/
|
|
13
|
+
export const LARGE_MIN_FEE_PADDING = 15;
|
|
14
|
+
|
|
15
|
+
/** Returns worst-case predicted min fees with padding applied, mirroring the BaseWallet pattern. */
|
|
16
|
+
export async function getPaddedMaxFeesPerGas(node: AztecNode, padding = DEFAULT_MIN_FEE_PADDING): Promise<GasFees> {
|
|
17
|
+
const predicted = await node.getPredictedMinFees();
|
|
18
|
+
const worstCase =
|
|
19
|
+
predicted.length > 0
|
|
20
|
+
? predicted.reduce((worst, fees) => (fees.feePerL2Gas > worst.feePerL2Gas ? fees : worst))
|
|
21
|
+
: await node.getCurrentMinFees();
|
|
22
|
+
return worstCase.mul(1 + padding);
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
export const shouldCollectMetrics = () => {
|
|
4
26
|
if (process.env.COLLECT_METRICS) {
|
|
5
27
|
return METRICS_PORT;
|
|
@@ -13,8 +13,10 @@ const {
|
|
|
13
13
|
BB_SKIP_CLEANUP = '',
|
|
14
14
|
TEMP_DIR = tmpdir(),
|
|
15
15
|
BB_WORKING_DIRECTORY = '',
|
|
16
|
-
BB_NUM_IVC_VERIFIERS = '
|
|
16
|
+
BB_NUM_IVC_VERIFIERS = '8',
|
|
17
17
|
BB_IVC_CONCURRENCY = '1',
|
|
18
|
+
BB_CHONK_VERIFY_MAX_BATCH = '16',
|
|
19
|
+
BB_CHONK_VERIFY_BATCH_CONCURRENCY = '6',
|
|
18
20
|
} = process.env;
|
|
19
21
|
|
|
20
22
|
export const getBBConfig = async (
|
|
@@ -41,16 +43,15 @@ export const getBBConfig = async (
|
|
|
41
43
|
const bbSkipCleanup = ['1', 'true'].includes(BB_SKIP_CLEANUP);
|
|
42
44
|
const cleanup = bbSkipCleanup ? () => Promise.resolve() : () => tryRmDir(directoryToCleanup);
|
|
43
45
|
|
|
44
|
-
const numIvcVerifiers = Number(BB_NUM_IVC_VERIFIERS);
|
|
45
|
-
const ivcConcurrency = Number(BB_IVC_CONCURRENCY);
|
|
46
|
-
|
|
47
46
|
return {
|
|
48
47
|
bbSkipCleanup,
|
|
49
48
|
bbBinaryPath,
|
|
50
49
|
bbWorkingDirectory,
|
|
51
50
|
cleanup,
|
|
52
|
-
numConcurrentIVCVerifiers:
|
|
53
|
-
bbIVCConcurrency:
|
|
51
|
+
numConcurrentIVCVerifiers: Number(BB_NUM_IVC_VERIFIERS),
|
|
52
|
+
bbIVCConcurrency: Number(BB_IVC_CONCURRENCY),
|
|
53
|
+
bbChonkVerifyMaxBatch: Number(BB_CHONK_VERIFY_MAX_BATCH),
|
|
54
|
+
bbChonkVerifyConcurrency: Number(BB_CHONK_VERIFY_BATCH_CONCURRENCY),
|
|
54
55
|
};
|
|
55
56
|
} catch (err) {
|
|
56
57
|
logger.error(`Native BB not available, error: ${err}`);
|
package/src/fixtures/ha_setup.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { privateKeyToAccount } from 'viem/accounts';
|
|
|
11
11
|
*/
|
|
12
12
|
export interface HADatabaseConfig {
|
|
13
13
|
/** PostgreSQL connection URL */
|
|
14
|
-
databaseUrl: string
|
|
14
|
+
databaseUrl: SecretValue<string>;
|
|
15
15
|
/** Node ID for HA coordination */
|
|
16
16
|
nodeId: string;
|
|
17
17
|
/** Enable HA signing */
|
|
@@ -28,7 +28,9 @@ export interface HADatabaseConfig {
|
|
|
28
28
|
* Get database configuration from environment variables
|
|
29
29
|
*/
|
|
30
30
|
export function createHADatabaseConfig(nodeId: string): HADatabaseConfig {
|
|
31
|
-
const databaseUrl =
|
|
31
|
+
const databaseUrl = new SecretValue(
|
|
32
|
+
process.env.DATABASE_URL || 'postgresql://aztec:aztec@localhost:5432/aztec_ha_test',
|
|
33
|
+
);
|
|
32
34
|
|
|
33
35
|
return {
|
|
34
36
|
databaseUrl,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DefaultAccountContract } from '@aztec/accounts/defaults';
|
|
2
|
+
import type { ContractArtifact } from '@aztec/aztec.js/abi';
|
|
3
|
+
import type { AuthWitnessProvider } from '@aztec/aztec.js/account';
|
|
4
|
+
import type { CompleteAddress } from '@aztec/aztec.js/addresses';
|
|
5
|
+
import { AuthWitness } from '@aztec/aztec.js/authorization';
|
|
6
|
+
import type { Fr } from '@aztec/aztec.js/fields';
|
|
7
|
+
import { Schnorr } from '@aztec/foundation/crypto/schnorr';
|
|
8
|
+
import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin';
|
|
9
|
+
import { SchnorrHardcodedAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrHardcodedAccount';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The private key that matches the hardcoded public key in the SchnorrHardcodedAccountContract.
|
|
13
|
+
* The corresponding public key is baked into the Noir contract as a global constant.
|
|
14
|
+
*/
|
|
15
|
+
export const SCHNORR_HARDCODED_PRIVATE_KEY = GrumpkinScalar.fromHexString(
|
|
16
|
+
'0xd35d743ac0dfe3d6dbe6be8c877cb524a00ab1e3d52d7bada095dfc8894ccfa',
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Account contract backed by the SchnorrHardcodedAccount Noir contract.
|
|
21
|
+
* This contract verifies Schnorr signatures against a public key that is hardcoded
|
|
22
|
+
* in the contract artifact (not stored in a note), so it does not require on-chain
|
|
23
|
+
* deployment or initialization. Useful for tests that need a working account without
|
|
24
|
+
* mining any blocks.
|
|
25
|
+
*/
|
|
26
|
+
export class SchnorrHardcodedKeyAccountContract extends DefaultAccountContract {
|
|
27
|
+
constructor(private privateKey: GrumpkinScalar = SCHNORR_HARDCODED_PRIVATE_KEY) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override getContractArtifact(): Promise<ContractArtifact> {
|
|
32
|
+
return Promise.resolve(SchnorrHardcodedAccountContractArtifact);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getInitializationFunctionAndArgs() {
|
|
36
|
+
return Promise.resolve(undefined);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getAuthWitnessProvider(_address: CompleteAddress): AuthWitnessProvider {
|
|
40
|
+
const privateKey = this.privateKey;
|
|
41
|
+
return {
|
|
42
|
+
async createAuthWit(messageHash: Fr): Promise<AuthWitness> {
|
|
43
|
+
const signer = new Schnorr();
|
|
44
|
+
const signature = await signer.constructSignature(messageHash.toBuffer(), privateKey);
|
|
45
|
+
return new AuthWitness(messageHash, [...signature.toBuffer()]);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/fixtures/setup.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
|
|
2
2
|
import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing';
|
|
3
3
|
import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
|
|
4
|
+
import { NO_FROM } from '@aztec/aztec.js/account';
|
|
4
5
|
import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses';
|
|
5
6
|
import {
|
|
6
7
|
BatchCall,
|
|
7
8
|
type ContractFunctionInteraction,
|
|
8
9
|
type ContractMethod,
|
|
10
|
+
type DeployInteractionWaitOptions,
|
|
11
|
+
type DeployOptions,
|
|
9
12
|
getContractClassFromArtifact,
|
|
10
13
|
waitForProven,
|
|
11
14
|
} from '@aztec/aztec.js/contracts';
|
|
@@ -14,7 +17,7 @@ import { Fr } from '@aztec/aztec.js/fields';
|
|
|
14
17
|
import { type Logger, createLogger } from '@aztec/aztec.js/log';
|
|
15
18
|
import type { AztecNode } from '@aztec/aztec.js/node';
|
|
16
19
|
import type { Wallet } from '@aztec/aztec.js/wallet';
|
|
17
|
-
import { AnvilTestWatcher, CheatCodes } from '@aztec/aztec/testing';
|
|
20
|
+
import { AnvilTestWatcher, type AnvilTestWatcherOpts, CheatCodes } from '@aztec/aztec/testing';
|
|
18
21
|
import { SPONSORED_FPC_SALT } from '@aztec/constants';
|
|
19
22
|
import { isAnvilTestChain } from '@aztec/ethereum/chain';
|
|
20
23
|
import { createExtendedL1Client } from '@aztec/ethereum/client';
|
|
@@ -48,9 +51,10 @@ import type { ProverNodeConfig } from '@aztec/prover-node';
|
|
|
48
51
|
import { type PXEConfig, getPXEConfig } from '@aztec/pxe/server';
|
|
49
52
|
import type { SequencerClient } from '@aztec/sequencer-client';
|
|
50
53
|
import { type ContractInstanceWithAddress, getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract';
|
|
51
|
-
import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
|
|
54
|
+
import type { AztecNodeAdmin, AztecNodeDebug } from '@aztec/stdlib/interfaces/client';
|
|
52
55
|
import { tryStop } from '@aztec/stdlib/interfaces/server';
|
|
53
56
|
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
57
|
+
import type { GenesisData } from '@aztec/stdlib/world-state';
|
|
54
58
|
import {
|
|
55
59
|
type TelemetryClient,
|
|
56
60
|
type TelemetryClientConfig,
|
|
@@ -177,8 +181,11 @@ export type SetupOptions = {
|
|
|
177
181
|
proverNodeConfig?: Partial<ProverNodeConfig>;
|
|
178
182
|
/** Whether to use a mock gossip sub network for p2p clients. */
|
|
179
183
|
mockGossipSubNetwork?: boolean;
|
|
184
|
+
/** Whether to add simulated latency to the mock gossipsub network (in ms) */
|
|
185
|
+
mockGossipSubNetworkLatency?: number;
|
|
180
186
|
/** Whether to disable the anvil test watcher (can still be manually started) */
|
|
181
187
|
disableAnvilTestWatcher?: boolean;
|
|
188
|
+
anvilTestWatcherOpts?: AnvilTestWatcherOpts;
|
|
182
189
|
/** Whether to enable anvil automine during deployment of L1 contracts (consider defaulting this to true). */
|
|
183
190
|
automineL1Setup?: boolean;
|
|
184
191
|
/** How many accounts to seed and unlock in anvil. */
|
|
@@ -200,8 +207,11 @@ export type SetupOptions = {
|
|
|
200
207
|
skipAccountDeployment?: boolean;
|
|
201
208
|
/** L1 contracts deployment arguments. */
|
|
202
209
|
l1ContractsArgs?: Partial<DeployAztecL1ContractsArgs>;
|
|
203
|
-
/** Wallet minimum fee padding multiplier
|
|
210
|
+
/** Wallet minimum fee padding multiplier */
|
|
204
211
|
walletMinFeePadding?: number;
|
|
212
|
+
/** Whether the initial node should be a lightweight RPC-only node (no sequencer, no validator).
|
|
213
|
+
* Use for tests that create their own validator nodes and don't need the initial sequencer. */
|
|
214
|
+
skipInitialSequencer?: boolean;
|
|
205
215
|
} & Partial<AztecNodeConfig>;
|
|
206
216
|
|
|
207
217
|
/** Context for an end-to-end test as returned by the `setup` function */
|
|
@@ -209,7 +219,7 @@ export type EndToEndContext = {
|
|
|
209
219
|
/** The Anvil instance (only set if anvil was started locally). */
|
|
210
220
|
anvil: Anvil | undefined;
|
|
211
221
|
/** The Aztec Node service or client a connected to it. */
|
|
212
|
-
aztecNode: AztecNode;
|
|
222
|
+
aztecNode: AztecNode & AztecNodeDebug;
|
|
213
223
|
/** The Aztec Node as a service. */
|
|
214
224
|
aztecNodeService: AztecNodeService;
|
|
215
225
|
/** Client to the Aztec Node admin interface. */
|
|
@@ -248,8 +258,8 @@ export type EndToEndContext = {
|
|
|
248
258
|
sequencerDelayer: Delayer | undefined;
|
|
249
259
|
/** Delayer for prover node L1 txs (only when enableDelayer and startProverNode are true). */
|
|
250
260
|
proverDelayer: Delayer | undefined;
|
|
251
|
-
/**
|
|
252
|
-
|
|
261
|
+
/** Genesis data used for setting up nodes. */
|
|
262
|
+
genesis: GenesisData | undefined;
|
|
253
263
|
/** ACVM config (only set if running locally). */
|
|
254
264
|
acvmConfig: Awaited<ReturnType<typeof getACVMConfig>>;
|
|
255
265
|
/** BB config (only set if running locally). */
|
|
@@ -275,7 +285,7 @@ export async function setup(
|
|
|
275
285
|
let anvil: Anvil | undefined;
|
|
276
286
|
try {
|
|
277
287
|
opts.aztecTargetCommitteeSize ??= 0;
|
|
278
|
-
opts.
|
|
288
|
+
opts.slasherEnabled ??= false;
|
|
279
289
|
|
|
280
290
|
const config: AztecNodeConfig & SetupOptions = { ...getConfigEnvVars(), ...opts };
|
|
281
291
|
// use initialValidators for the node config
|
|
@@ -302,6 +312,8 @@ export async function setup(
|
|
|
302
312
|
config.dataDirectory = directoryToCleanup;
|
|
303
313
|
}
|
|
304
314
|
|
|
315
|
+
const dateProvider = new TestDateProvider();
|
|
316
|
+
|
|
305
317
|
if (!config.l1RpcUrls?.length) {
|
|
306
318
|
if (!isAnvilTestChain(chain.id)) {
|
|
307
319
|
throw new Error(`No ETHEREUM_HOSTS set but non anvil chain requested`);
|
|
@@ -311,6 +323,7 @@ export async function setup(
|
|
|
311
323
|
accounts: opts.anvilAccounts,
|
|
312
324
|
port: opts.anvilPort ?? (process.env.ANVIL_PORT ? parseInt(process.env.ANVIL_PORT) : undefined),
|
|
313
325
|
slotsInAnEpoch: opts.anvilSlotsInAnEpoch,
|
|
326
|
+
dateProvider,
|
|
314
327
|
});
|
|
315
328
|
anvil = res.anvil;
|
|
316
329
|
config.l1RpcUrls = [res.rpcUrl];
|
|
@@ -322,8 +335,6 @@ export async function setup(
|
|
|
322
335
|
logger.info(`Logging metrics to ${filename}`);
|
|
323
336
|
setupMetricsLogger(filename);
|
|
324
337
|
}
|
|
325
|
-
|
|
326
|
-
const dateProvider = new TestDateProvider();
|
|
327
338
|
const ethCheatCodes = new EthCheatCodesWithState(config.l1RpcUrls, dateProvider);
|
|
328
339
|
|
|
329
340
|
if (opts.stateLoad) {
|
|
@@ -373,10 +384,12 @@ export async function setup(
|
|
|
373
384
|
addressesToFund.push(sponsoredFPCAddress);
|
|
374
385
|
}
|
|
375
386
|
|
|
376
|
-
const
|
|
387
|
+
const genesisTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
388
|
+
const { genesisArchiveRoot, genesis, fundingNeeded } = await getGenesisValues(
|
|
377
389
|
addressesToFund,
|
|
378
390
|
opts.initialAccountFeeJuice,
|
|
379
391
|
opts.genesisPublicData,
|
|
392
|
+
genesisTimestamp,
|
|
380
393
|
);
|
|
381
394
|
|
|
382
395
|
const wasAutomining = await ethCheatCodes.isAutoMining();
|
|
@@ -419,11 +432,12 @@ export async function setup(
|
|
|
419
432
|
await ethCheatCodes.setIntervalMining(config.ethereumSlotDuration);
|
|
420
433
|
}
|
|
421
434
|
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
435
|
+
// In compose mode (no local anvil), sync dateProvider to L1 time since it may have drifted
|
|
436
|
+
// ahead of system time due to the local-network watcher warping time forward on each filled slot.
|
|
437
|
+
// When running with a local anvil, the dateProvider is kept in sync via the stdout listener.
|
|
438
|
+
if (!anvil) {
|
|
439
|
+
dateProvider.setTime((await ethCheatCodes.lastBlockTimestamp()) * 1000);
|
|
440
|
+
}
|
|
427
441
|
|
|
428
442
|
if (opts.l2StartTime) {
|
|
429
443
|
await ethCheatCodes.warp(opts.l2StartTime, { resetBlockInterval: true });
|
|
@@ -434,6 +448,7 @@ export async function setup(
|
|
|
434
448
|
deployL1ContractsValues.l1ContractAddresses.rollupAddress,
|
|
435
449
|
deployL1ContractsValues.l1Client,
|
|
436
450
|
dateProvider,
|
|
451
|
+
opts.anvilTestWatcherOpts,
|
|
437
452
|
);
|
|
438
453
|
if (!opts.disableAnvilTestWatcher) {
|
|
439
454
|
await watcher.start();
|
|
@@ -464,7 +479,7 @@ export async function setup(
|
|
|
464
479
|
let p2pClientDeps: P2PClientDeps | undefined = undefined;
|
|
465
480
|
|
|
466
481
|
if (opts.mockGossipSubNetwork) {
|
|
467
|
-
mockGossipSubNetwork = new MockGossipSubNetwork();
|
|
482
|
+
mockGossipSubNetwork = new MockGossipSubNetwork(opts.mockGossipSubNetworkLatency);
|
|
468
483
|
p2pClientDeps = { p2pServiceFactory: getMockPubSubP2PServiceFactory(mockGossipSubNetwork) };
|
|
469
484
|
}
|
|
470
485
|
|
|
@@ -492,11 +507,22 @@ export async function setup(
|
|
|
492
507
|
}
|
|
493
508
|
}
|
|
494
509
|
|
|
510
|
+
// When skipInitialSequencer is set, the initial node is a lightweight RPC-only node.
|
|
511
|
+
// We apply these overrides to a copy so they don't leak into the returned config.
|
|
512
|
+
// Keep P2P enabled if mockGossipSubNetwork is used (needed for tx propagation to validators).
|
|
513
|
+
const initialNodeConfig = opts.skipInitialSequencer
|
|
514
|
+
? {
|
|
515
|
+
...config,
|
|
516
|
+
disableValidator: true,
|
|
517
|
+
...(opts.mockGossipSubNetwork ? {} : { p2pEnabled: false, bootstrapNodes: [] as string[] }),
|
|
518
|
+
}
|
|
519
|
+
: config;
|
|
520
|
+
|
|
495
521
|
const aztecNodeService = await withLoggerBindings({ actor: 'node-0' }, () =>
|
|
496
522
|
AztecNodeService.createAndSync(
|
|
497
|
-
|
|
523
|
+
initialNodeConfig,
|
|
498
524
|
{ dateProvider, telemetry: telemetryClient, p2pClientDeps },
|
|
499
|
-
{
|
|
525
|
+
{ genesis, dontStartSequencer: opts.skipInitialSequencer },
|
|
500
526
|
),
|
|
501
527
|
);
|
|
502
528
|
const sequencerClient = aztecNodeService.getSequencer();
|
|
@@ -521,7 +547,7 @@ export async function setup(
|
|
|
521
547
|
dataDirectory: proverNodeDataDirectory,
|
|
522
548
|
},
|
|
523
549
|
{ dateProvider, p2pClientDeps, telemetry: telemetryClient },
|
|
524
|
-
{
|
|
550
|
+
{ genesis },
|
|
525
551
|
));
|
|
526
552
|
}
|
|
527
553
|
|
|
@@ -557,7 +583,9 @@ export async function setup(
|
|
|
557
583
|
|
|
558
584
|
let accounts: AztecAddress[] = [];
|
|
559
585
|
|
|
560
|
-
if (
|
|
586
|
+
if (opts.skipInitialSequencer) {
|
|
587
|
+
logger.info('Sequencer not started on initial node, skipping block progression');
|
|
588
|
+
} else if (shouldDeployAccounts) {
|
|
561
589
|
logger.info(
|
|
562
590
|
`${numberOfAccounts} accounts are being deployed. Reliably progressing past genesis by setting minTxsPerBlock to 1 and waiting for the accounts to be deployed`,
|
|
563
591
|
);
|
|
@@ -626,7 +654,7 @@ export async function setup(
|
|
|
626
654
|
initialFundedAccounts,
|
|
627
655
|
logger,
|
|
628
656
|
mockGossipSubNetwork,
|
|
629
|
-
|
|
657
|
+
genesis,
|
|
630
658
|
proverNode,
|
|
631
659
|
sequencerDelayer,
|
|
632
660
|
proverDelayer,
|
|
@@ -726,7 +754,7 @@ export function createAndSyncProverNode(
|
|
|
726
754
|
dateProvider: DateProvider;
|
|
727
755
|
p2pClientDeps?: P2PClientDeps;
|
|
728
756
|
},
|
|
729
|
-
options: {
|
|
757
|
+
options: { genesis?: GenesisData; dontStart?: boolean },
|
|
730
758
|
): Promise<{ proverNode: AztecNodeService }> {
|
|
731
759
|
return withLoggerBindings({ actor: 'prover-0' }, async () => {
|
|
732
760
|
const proverNode = await AztecNodeService.createAndSync(
|
|
@@ -739,7 +767,7 @@ export function createAndSyncProverNode(
|
|
|
739
767
|
proverPublisherPrivateKeys: [new SecretValue(proverNodePrivateKey)],
|
|
740
768
|
},
|
|
741
769
|
deps,
|
|
742
|
-
{
|
|
770
|
+
{ genesis: options.genesis, dontStartProverNode: options.dontStart },
|
|
743
771
|
);
|
|
744
772
|
|
|
745
773
|
if (!proverNode.getProverNode()) {
|
|
@@ -830,7 +858,7 @@ export async function ensureAccountContractsPublished(wallet: Wallet, accountsTo
|
|
|
830
858
|
* Returns deployed account data that can be used by tests.
|
|
831
859
|
*/
|
|
832
860
|
export const deployAccounts =
|
|
833
|
-
(numberOfAccounts: number, logger: Logger) =>
|
|
861
|
+
(numberOfAccounts: number, logger: Logger, deployOptions?: Partial<DeployOptions<DeployInteractionWaitOptions>>) =>
|
|
834
862
|
async ({ wallet, initialFundedAccounts }: { wallet: TestWallet; initialFundedAccounts: InitialAccountData[] }) => {
|
|
835
863
|
if (initialFundedAccounts.length < numberOfAccounts) {
|
|
836
864
|
throw new Error(`Cannot deploy more than ${initialFundedAccounts.length} initial accounts.`);
|
|
@@ -847,8 +875,9 @@ export const deployAccounts =
|
|
|
847
875
|
);
|
|
848
876
|
const deployMethod = await accountManager.getDeployMethod();
|
|
849
877
|
await deployMethod.send({
|
|
850
|
-
from:
|
|
878
|
+
from: NO_FROM,
|
|
851
879
|
skipClassPublication: i !== 0, // Publish the contract class at most once.
|
|
880
|
+
...deployOptions,
|
|
852
881
|
});
|
|
853
882
|
}
|
|
854
883
|
|