@aztec/end-to-end 0.0.1-commit.f650c0a5c → 0.0.1-commit.f7ea82942

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dest/bench/client_flows/client_flows_benchmark.js +2 -2
  2. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +3 -2
  3. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
  4. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +1 -1
  5. package/dest/e2e_epochs/epochs_test.d.ts +16 -1
  6. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  7. package/dest/e2e_epochs/epochs_test.js +53 -5
  8. package/dest/e2e_fees/fees_test.d.ts +1 -1
  9. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  10. package/dest/e2e_fees/fees_test.js +2 -2
  11. package/dest/e2e_p2p/p2p_network.d.ts +6 -3
  12. package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
  13. package/dest/e2e_p2p/p2p_network.js +38 -15
  14. package/dest/e2e_p2p/shared.d.ts +1 -1
  15. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  16. package/dest/e2e_p2p/shared.js +20 -5
  17. package/dest/fixtures/fixtures.d.ts +12 -1
  18. package/dest/fixtures/fixtures.d.ts.map +1 -1
  19. package/dest/fixtures/fixtures.js +10 -0
  20. package/dest/fixtures/ha_setup.d.ts +2 -2
  21. package/dest/fixtures/ha_setup.d.ts.map +1 -1
  22. package/dest/fixtures/ha_setup.js +1 -1
  23. package/dest/fixtures/schnorr_hardcoded_account_contract.d.ts +25 -0
  24. package/dest/fixtures/schnorr_hardcoded_account_contract.d.ts.map +1 -0
  25. package/dest/fixtures/schnorr_hardcoded_account_contract.js +39 -0
  26. package/dest/fixtures/setup.d.ts +13 -7
  27. package/dest/fixtures/setup.d.ts.map +1 -1
  28. package/dest/fixtures/setup.js +22 -7
  29. package/dest/forward-compatibility/wallet_rpc_client.d.ts +7 -0
  30. package/dest/forward-compatibility/wallet_rpc_client.d.ts.map +1 -0
  31. package/dest/forward-compatibility/wallet_rpc_client.js +15 -0
  32. package/dest/forward-compatibility/wallet_service.d.ts +3 -0
  33. package/dest/forward-compatibility/wallet_service.d.ts.map +1 -0
  34. package/dest/forward-compatibility/wallet_service.js +109 -0
  35. package/dest/shared/gas_portal_test_harness.js +1 -1
  36. package/dest/shared/uniswap_l1_l2.d.ts +1 -1
  37. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  38. package/dest/shared/uniswap_l1_l2.js +0 -4
  39. package/dest/test-wallet/test_wallet.d.ts +13 -4
  40. package/dest/test-wallet/test_wallet.d.ts.map +1 -1
  41. package/dest/test-wallet/test_wallet.js +54 -25
  42. package/dest/test-wallet/worker_wallet.d.ts +4 -4
  43. package/dest/test-wallet/worker_wallet.d.ts.map +1 -1
  44. package/dest/test-wallet/worker_wallet_schema.d.ts +7 -2
  45. package/dest/test-wallet/worker_wallet_schema.d.ts.map +1 -1
  46. package/package.json +39 -39
  47. package/src/bench/client_flows/client_flows_benchmark.ts +2 -2
  48. package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +3 -6
  49. package/src/e2e_epochs/epochs_test.ts +53 -4
  50. package/src/e2e_fees/fees_test.ts +4 -2
  51. package/src/e2e_p2p/p2p_network.ts +41 -16
  52. package/src/e2e_p2p/shared.ts +17 -4
  53. package/src/fixtures/fixtures.ts +22 -0
  54. package/src/fixtures/ha_setup.ts +4 -2
  55. package/src/fixtures/schnorr_hardcoded_account_contract.ts +49 -0
  56. package/src/fixtures/setup.ts +35 -8
  57. package/src/forward-compatibility/wallet_rpc_client.ts +14 -0
  58. package/src/forward-compatibility/wallet_service.ts +104 -0
  59. package/src/guides/up_quick_start.sh +0 -2
  60. package/src/legacy-jest-resolver.cjs +2 -2
  61. package/src/shared/gas_portal_test_harness.ts +0 -1
  62. package/src/shared/uniswap_l1_l2.ts +0 -4
  63. package/src/test-wallet/test_wallet.ts +56 -29
  64. package/src/test-wallet/worker_wallet.ts +3 -2
@@ -1,5 +1,7 @@
1
+ import type { InitialAccountData } from '@aztec/accounts/testing';
1
2
  import type { Archiver } from '@aztec/archiver';
2
3
  import { type AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node';
4
+ import { getAccountContractAddress } from '@aztec/aztec.js/account';
3
5
  import { getTimestampRangeForEpoch } from '@aztec/aztec.js/block';
4
6
  import { getContractInstanceFromInstantiationParams } from '@aztec/aztec.js/contracts';
5
7
  import { Fr } from '@aztec/aztec.js/fields';
@@ -34,6 +36,10 @@ import { join } from 'path';
34
36
  import type { Hex } from 'viem';
35
37
  import { privateKeyToAccount } from 'viem/accounts';
36
38
 
39
+ import {
40
+ SCHNORR_HARDCODED_PRIVATE_KEY,
41
+ SchnorrHardcodedKeyAccountContract,
42
+ } from '../fixtures/schnorr_hardcoded_account_contract.js';
37
43
  import {
38
44
  type EndToEndContext,
39
45
  type SetupOptions,
@@ -41,6 +47,7 @@ import {
41
47
  getPrivateKeyFromIndex,
42
48
  setup,
43
49
  } from '../fixtures/utils.js';
50
+ import type { TestWallet } from '../test-wallet/test_wallet.js';
44
51
 
45
52
  export const WORLD_STATE_CHECKPOINT_HISTORY = 2;
46
53
  export const WORLD_STATE_BLOCK_CHECK_INTERVAL = 50;
@@ -51,6 +58,8 @@ export type EpochsTestOpts = Partial<SetupOptions> & {
51
58
  numberOfAccounts?: number;
52
59
  pxeOpts?: Partial<PXEConfig>;
53
60
  aztecSlotDurationInL1Slots?: number;
61
+ /** Skip creating/registering the hardcoded account during setup (for tests that handle accounts themselves). */
62
+ skipHardcodedAccount?: boolean;
54
63
  };
55
64
 
56
65
  export type TrackedSequencerEvent = {
@@ -121,10 +130,18 @@ export class EpochsTestContext {
121
130
  this.L1_BLOCK_TIME_IN_S = ethereumSlotDuration;
122
131
  this.L2_SLOT_DURATION_IN_S = aztecSlotDuration;
123
132
 
133
+ // When skipInitialSequencer is set, auto-create a hardcoded account funded via genesis.
134
+ // This avoids needing to deploy accounts on-chain (which would require a running sequencer).
135
+ const useHardcodedAccount = opts.skipInitialSequencer && !opts.skipHardcodedAccount;
136
+ let hardcodedAccountData: InitialAccountData | undefined;
137
+ if (useHardcodedAccount) {
138
+ hardcodedAccountData = await EpochsTestContext.getHardcodedAccountData(Fr.random(), Fr.random());
139
+ }
140
+
124
141
  // Set up system without any account nor protocol contracts
125
142
  // and with faster block times and shorter epochs.
126
143
  const context = await setup(
127
- opts.numberOfAccounts ?? 0,
144
+ useHardcodedAccount ? 0 : (opts.numberOfAccounts ?? 0),
128
145
  {
129
146
  automineL1Setup: true,
130
147
  checkIntervalMs: 50,
@@ -139,15 +156,13 @@ export class EpochsTestContext {
139
156
  realProofs: false,
140
157
  startProverNode: true,
141
158
  proverTestDelayMs: opts.proverTestDelayMs ?? 0,
142
- // We use numeric incremental prover ids for simplicity, but we can switch to
143
- // using the prover's eth address if the proverId is used for something in the rollup contract
144
- // Use numeric EthAddress for deterministic prover id
145
159
  proverId: EthAddress.fromNumber(1),
146
160
  worldStateCheckpointHistory: WORLD_STATE_CHECKPOINT_HISTORY,
147
161
  exitDelaySeconds: DefaultL1ContractsConfig.exitDelaySeconds,
148
162
  slasherEnabled: false,
149
163
  l1PublishingTime,
150
164
  ...opts,
165
+ ...(hardcodedAccountData ? { initialFundedAccounts: [hardcodedAccountData], numberOfAccounts: 0 } : {}),
151
166
  },
152
167
  // Use checkpointed chain tip for PXE by default to avoid issues with blocks being dropped due to pruned anchor blocks.
153
168
  // Can be overridden via opts.pxeOpts.
@@ -155,6 +170,11 @@ export class EpochsTestContext {
155
170
  );
156
171
 
157
172
  this.context = context;
173
+
174
+ // Register the hardcoded account in PXE (local only, no on-chain deployment needed).
175
+ if (hardcodedAccountData) {
176
+ await this.registerHardcodedAccount(hardcodedAccountData);
177
+ }
158
178
  this.proverNodes = context.proverNode ? [context.proverNode] : [];
159
179
  this.nodes = context.aztecNode ? [context.aztecNode as AztecNodeService] : [];
160
180
  this.logger = context.logger;
@@ -197,6 +217,35 @@ export class EpochsTestContext {
197
217
  await this.context.teardown();
198
218
  }
199
219
 
220
+ /**
221
+ * Computes InitialAccountData for a SchnorrHardcodedKeyAccountContract.
222
+ * This contract has a hardcoded signing key and no initializer, so it can be used without
223
+ * on-chain deployment. Pass the returned data in `initialFundedAccounts` so the address
224
+ * gets funded with fee juice in genesis.
225
+ */
226
+ public static async getHardcodedAccountData(secret: Fr, salt: Fr): Promise<InitialAccountData> {
227
+ const contract = new SchnorrHardcodedKeyAccountContract();
228
+ const address = await getAccountContractAddress(contract, secret, salt);
229
+ const signingKey = SCHNORR_HARDCODED_PRIVATE_KEY;
230
+ return { secret, salt, signingKey, address };
231
+ }
232
+
233
+ /**
234
+ * Registers a SchnorrHardcodedKeyAccountContract in PXE. The account must have been funded
235
+ * at genesis (via getHardcodedAccountData). No on-chain deployment or block mining needed.
236
+ */
237
+ public async registerHardcodedAccount(accountData: InitialAccountData) {
238
+ const contract = new SchnorrHardcodedKeyAccountContract();
239
+ const wallet = this.context.wallet;
240
+ const accountManager = await (wallet as TestWallet).createAccount({
241
+ secret: accountData.secret,
242
+ salt: accountData.salt,
243
+ contract,
244
+ });
245
+ this.context.accounts = [accountManager.address];
246
+ return accountManager.address;
247
+ }
248
+
200
249
  public async createProverNode(opts: { dontStart?: boolean } & Partial<ProverNodeConfig> = {}) {
201
250
  this.logger.warn('Creating and syncing a simulated prover node...');
202
251
  const proverNodePrivateKey = this.getNextPrivateKey();
@@ -23,7 +23,7 @@ import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
23
23
 
24
24
  import { getContract } from 'viem';
25
25
 
26
- import { MNEMONIC } from '../fixtures/fixtures.js';
26
+ import { MNEMONIC, getPaddedMaxFeesPerGas } from '../fixtures/fixtures.js';
27
27
  import {
28
28
  type EndToEndContext,
29
29
  type SetupOptions,
@@ -193,7 +193,9 @@ export class FeesTest {
193
193
  this.wallet = this.context.wallet;
194
194
  this.aztecNode = this.context.aztecNodeService;
195
195
  this.aztecNodeAdmin = this.context.aztecNodeService;
196
- this.gasSettings = GasSettings.fallback({ maxFeesPerGas: (await this.aztecNode.getCurrentMinFees()).mul(2) });
196
+ this.gasSettings = GasSettings.fallback({
197
+ maxFeesPerGas: await getPaddedMaxFeesPerGas(this.aztecNode),
198
+ });
197
199
  this.cheatCodes = this.context.cheatCodes;
198
200
  this.accounts = deployedAccounts.map(a => a.address);
199
201
  this.accounts.forEach((a, i) => this.logger.verbose(`Account ${i} address: ${a}`));
@@ -1,5 +1,6 @@
1
- import type { InitialAccountData } from '@aztec/accounts/testing';
1
+ import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing';
2
2
  import type { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node';
3
+ import { getAccountContractAddress } from '@aztec/aztec.js/account';
3
4
  import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses';
4
5
  import { Fr } from '@aztec/aztec.js/fields';
5
6
  import { getL1ContractsConfigEnvVars } from '@aztec/ethereum/config';
@@ -28,10 +29,13 @@ import getPort from 'get-port';
28
29
  import { type GetContractReturnType, getAddress, getContract } from 'viem';
29
30
  import { privateKeyToAccount } from 'viem/accounts';
30
31
 
32
+ import {
33
+ SCHNORR_HARDCODED_PRIVATE_KEY,
34
+ SchnorrHardcodedKeyAccountContract,
35
+ } from '../fixtures/schnorr_hardcoded_account_contract.js';
31
36
  import {
32
37
  type EndToEndContext,
33
38
  type SetupOptions,
34
- deployAccounts,
35
39
  getPrivateKeyFromIndex,
36
40
  getSponsoredFPCAddress,
37
41
  setup,
@@ -70,7 +74,7 @@ export class P2PNetworkTest {
70
74
  public peerIdPrivateKeys: string[] = [];
71
75
  public validators: Operator[] = [];
72
76
 
73
- public deployedAccounts: InitialAccountData[] = [];
77
+ public hardcodedAccountData!: InitialAccountData;
74
78
  public genesis: GenesisData | undefined;
75
79
 
76
80
  // The re-execution test needs a wallet and a spam contract
@@ -123,7 +127,6 @@ export class P2PNetworkTest {
123
127
  metricsPort: metricsPort,
124
128
  numberOfInitialFundedAccounts: 2,
125
129
  startProverNode,
126
- walletMinFeePadding: 2.0,
127
130
  };
128
131
 
129
132
  this.deployL1ContractsArgs = {
@@ -186,10 +189,10 @@ export class P2PNetworkTest {
186
189
  }
187
190
 
188
191
  get fundedAccount() {
189
- if (!this.deployedAccounts[0]) {
190
- throw new Error('Call setupAccount to create a funded account.');
192
+ if (!this.hardcodedAccountData) {
193
+ throw new Error('Call setup to initialize the hardcoded account.');
191
194
  }
192
- return this.deployedAccounts[0];
195
+ return this.hardcodedAccountData;
193
196
  }
194
197
 
195
198
  async addBootstrapNode() {
@@ -297,17 +300,22 @@ export class P2PNetworkTest {
297
300
  await this._sendDummyTx(this.context.deployL1ContractsValues.l1Client);
298
301
  }
299
302
 
303
+ /** Points the wallet to a P2P-enabled node so transactions can propagate through the network. */
304
+ setupWalletOnNode(node: AztecNodeService) {
305
+ this.logger.info('Pointing wallet to a P2P-enabled node');
306
+ this.context.wallet.updateNode(node);
307
+ }
308
+
309
+ /** Registers the hardcoded account in PXE without on-chain deployment. No sequencer needed. */
300
310
  async setupAccount() {
301
- this.logger.info('Setting up account');
302
- const { deployedAccounts } = await deployAccounts(
303
- 1,
304
- this.logger,
305
- )({
306
- wallet: this.context.wallet,
307
- initialFundedAccounts: this.context.initialFundedAccounts,
311
+ this.logger.info('Registering hardcoded account (no deployment)');
312
+ const contract = new SchnorrHardcodedKeyAccountContract();
313
+ const accountManager = await (this.context.wallet as TestWallet).createAccount({
314
+ secret: this.hardcodedAccountData.secret,
315
+ salt: this.hardcodedAccountData.salt,
316
+ contract,
308
317
  });
309
- this.deployedAccounts = deployedAccounts;
310
- [{ address: this.defaultAccountAddress }] = deployedAccounts;
318
+ this.defaultAccountAddress = accountManager.address;
311
319
  this.wallet = this.context.wallet;
312
320
  }
313
321
 
@@ -348,12 +356,29 @@ export class P2PNetworkTest {
348
356
 
349
357
  async setup() {
350
358
  this.logger.info('Setting up subsystems from fresh');
359
+
360
+ // Pre-compute hardcoded account data so it gets funded in genesis.
361
+ const contract = new SchnorrHardcodedKeyAccountContract();
362
+ const secret = Fr.random();
363
+ const salt = Fr.random();
364
+ this.hardcodedAccountData = {
365
+ secret,
366
+ salt,
367
+ signingKey: SCHNORR_HARDCODED_PRIVATE_KEY,
368
+ address: await getAccountContractAddress(contract, secret, salt),
369
+ };
370
+
371
+ // Generate regular Schnorr accounts for tests that need deployable accounts (e.g. add_rollup).
372
+ const regularAccounts = await generateSchnorrAccounts(this.setupOptions.numberOfInitialFundedAccounts ?? 2);
373
+
351
374
  this.context = await setup(
352
375
  0,
353
376
  {
354
377
  ...this.setupOptions,
355
378
  fundSponsoredFPC: true,
356
379
  skipAccountDeployment: true,
380
+ skipInitialSequencer: true,
381
+ initialFundedAccounts: [...regularAccounts, this.hardcodedAccountData],
357
382
  slasherEnabled: this.setupOptions.slasherEnabled ?? this.deployL1ContractsArgs.slasherEnabled ?? false,
358
383
  aztecTargetCommitteeSize: 0,
359
384
  l1ContractsArgs: this.deployL1ContractsArgs,
@@ -19,6 +19,7 @@ import { getPXEConfig, getPXEConfig as getRpcConfig } from '@aztec/pxe/server';
19
19
  import { getRoundForOffense } from '@aztec/slasher';
20
20
  import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
21
21
 
22
+ import { SchnorrHardcodedKeyAccountContract } from '../fixtures/schnorr_hardcoded_account_contract.js';
22
23
  import { submitTxsTo } from '../shared/submit-transactions.js';
23
24
  import { TestWallet } from '../test-wallet/test_wallet.js';
24
25
  import { type ProvenTx, proveInteraction } from '../test-wallet/utils.js';
@@ -55,10 +56,17 @@ export const submitTransactions = async (
55
56
  rpcConfig.proverEnabled = false;
56
57
  const wallet = await TestWallet.create(
57
58
  node,
58
- { ...getPXEConfig(), proverEnabled: false },
59
+ // Use checkpointed chain tip to avoid anchoring on provisional blocks that the archiver can prune
60
+ // when their slot ends without a checkpoint landing on L1.
61
+ { ...getPXEConfig(), proverEnabled: false, syncChainTip: 'checkpointed' },
59
62
  { loggerActorLabel: 'pxe-tx' },
60
63
  );
61
- const fundedAccountManager = await wallet.createSchnorrAccount(fundedAccount.secret, fundedAccount.salt);
64
+ const contract = new SchnorrHardcodedKeyAccountContract();
65
+ const fundedAccountManager = await wallet.createAccount({
66
+ secret: fundedAccount.secret,
67
+ salt: fundedAccount.salt,
68
+ contract,
69
+ });
62
70
  return submitTxsTo(wallet, fundedAccountManager.address, numTxs, logger);
63
71
  };
64
72
 
@@ -73,10 +81,15 @@ export async function prepareTransactions(
73
81
 
74
82
  const wallet = await TestWallet.create(
75
83
  node,
76
- { ...getPXEConfig(), proverEnabled: false },
84
+ { ...getPXEConfig(), proverEnabled: false, syncChainTip: 'checkpointed' },
77
85
  { loggerActorLabel: 'pxe-tx' },
78
86
  );
79
- const fundedAccountManager = await wallet.createSchnorrAccount(fundedAccount.secret, fundedAccount.salt);
87
+ const accountContract = new SchnorrHardcodedKeyAccountContract();
88
+ const fundedAccountManager = await wallet.createAccount({
89
+ secret: fundedAccount.secret,
90
+ salt: fundedAccount.salt,
91
+ contract: accountContract,
92
+ });
80
93
 
81
94
  const testContractInstance = await getContractInstanceFromInstantiationParams(TestContractArtifact, {
82
95
  salt: Fr.random(),
@@ -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;
@@ -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 = process.env.DATABASE_URL || 'postgresql://aztec:aztec@localhost:5432/aztec_ha_test';
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
+ }
@@ -7,6 +7,8 @@ import {
7
7
  BatchCall,
8
8
  type ContractFunctionInteraction,
9
9
  type ContractMethod,
10
+ type DeployInteractionWaitOptions,
11
+ type DeployOptions,
10
12
  getContractClassFromArtifact,
11
13
  waitForProven,
12
14
  } from '@aztec/aztec.js/contracts';
@@ -15,7 +17,7 @@ import { Fr } from '@aztec/aztec.js/fields';
15
17
  import { type Logger, createLogger } from '@aztec/aztec.js/log';
16
18
  import type { AztecNode } from '@aztec/aztec.js/node';
17
19
  import type { Wallet } from '@aztec/aztec.js/wallet';
18
- import { AnvilTestWatcher, CheatCodes } from '@aztec/aztec/testing';
20
+ import { AnvilTestWatcher, type AnvilTestWatcherOpts, CheatCodes } from '@aztec/aztec/testing';
19
21
  import { SPONSORED_FPC_SALT } from '@aztec/constants';
20
22
  import { isAnvilTestChain } from '@aztec/ethereum/chain';
21
23
  import { createExtendedL1Client } from '@aztec/ethereum/client';
@@ -49,7 +51,7 @@ import type { ProverNodeConfig } from '@aztec/prover-node';
49
51
  import { type PXEConfig, getPXEConfig } from '@aztec/pxe/server';
50
52
  import type { SequencerClient } from '@aztec/sequencer-client';
51
53
  import { type ContractInstanceWithAddress, getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract';
52
- import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
54
+ import type { AztecNodeAdmin, AztecNodeDebug } from '@aztec/stdlib/interfaces/client';
53
55
  import { tryStop } from '@aztec/stdlib/interfaces/server';
54
56
  import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
55
57
  import type { GenesisData } from '@aztec/stdlib/world-state';
@@ -179,8 +181,11 @@ export type SetupOptions = {
179
181
  proverNodeConfig?: Partial<ProverNodeConfig>;
180
182
  /** Whether to use a mock gossip sub network for p2p clients. */
181
183
  mockGossipSubNetwork?: boolean;
184
+ /** Whether to add simulated latency to the mock gossipsub network (in ms) */
185
+ mockGossipSubNetworkLatency?: number;
182
186
  /** Whether to disable the anvil test watcher (can still be manually started) */
183
187
  disableAnvilTestWatcher?: boolean;
188
+ anvilTestWatcherOpts?: AnvilTestWatcherOpts;
184
189
  /** Whether to enable anvil automine during deployment of L1 contracts (consider defaulting this to true). */
185
190
  automineL1Setup?: boolean;
186
191
  /** How many accounts to seed and unlock in anvil. */
@@ -202,8 +207,11 @@ export type SetupOptions = {
202
207
  skipAccountDeployment?: boolean;
203
208
  /** L1 contracts deployment arguments. */
204
209
  l1ContractsArgs?: Partial<DeployAztecL1ContractsArgs>;
205
- /** Wallet minimum fee padding multiplier (defaults to 0.5, which is 50% padding). */
210
+ /** Wallet minimum fee padding multiplier */
206
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;
207
215
  } & Partial<AztecNodeConfig>;
208
216
 
209
217
  /** Context for an end-to-end test as returned by the `setup` function */
@@ -211,7 +219,7 @@ export type EndToEndContext = {
211
219
  /** The Anvil instance (only set if anvil was started locally). */
212
220
  anvil: Anvil | undefined;
213
221
  /** The Aztec Node service or client a connected to it. */
214
- aztecNode: AztecNode;
222
+ aztecNode: AztecNode & AztecNodeDebug;
215
223
  /** The Aztec Node as a service. */
216
224
  aztecNodeService: AztecNodeService;
217
225
  /** Client to the Aztec Node admin interface. */
@@ -440,6 +448,7 @@ export async function setup(
440
448
  deployL1ContractsValues.l1ContractAddresses.rollupAddress,
441
449
  deployL1ContractsValues.l1Client,
442
450
  dateProvider,
451
+ opts.anvilTestWatcherOpts,
443
452
  );
444
453
  if (!opts.disableAnvilTestWatcher) {
445
454
  await watcher.start();
@@ -470,7 +479,7 @@ export async function setup(
470
479
  let p2pClientDeps: P2PClientDeps | undefined = undefined;
471
480
 
472
481
  if (opts.mockGossipSubNetwork) {
473
- mockGossipSubNetwork = new MockGossipSubNetwork();
482
+ mockGossipSubNetwork = new MockGossipSubNetwork(opts.mockGossipSubNetworkLatency);
474
483
  p2pClientDeps = { p2pServiceFactory: getMockPubSubP2PServiceFactory(mockGossipSubNetwork) };
475
484
  }
476
485
 
@@ -498,8 +507,23 @@ export async function setup(
498
507
  }
499
508
  }
500
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
+
501
521
  const aztecNodeService = await withLoggerBindings({ actor: 'node-0' }, () =>
502
- AztecNodeService.createAndSync(config, { dateProvider, telemetry: telemetryClient, p2pClientDeps }, { genesis }),
522
+ AztecNodeService.createAndSync(
523
+ initialNodeConfig,
524
+ { dateProvider, telemetry: telemetryClient, p2pClientDeps },
525
+ { genesis, dontStartSequencer: opts.skipInitialSequencer },
526
+ ),
503
527
  );
504
528
  const sequencerClient = aztecNodeService.getSequencer();
505
529
 
@@ -559,7 +583,9 @@ export async function setup(
559
583
 
560
584
  let accounts: AztecAddress[] = [];
561
585
 
562
- if (shouldDeployAccounts) {
586
+ if (opts.skipInitialSequencer) {
587
+ logger.info('Sequencer not started on initial node, skipping block progression');
588
+ } else if (shouldDeployAccounts) {
563
589
  logger.info(
564
590
  `${numberOfAccounts} accounts are being deployed. Reliably progressing past genesis by setting minTxsPerBlock to 1 and waiting for the accounts to be deployed`,
565
591
  );
@@ -832,7 +858,7 @@ export async function ensureAccountContractsPublished(wallet: Wallet, accountsTo
832
858
  * Returns deployed account data that can be used by tests.
833
859
  */
834
860
  export const deployAccounts =
835
- (numberOfAccounts: number, logger: Logger) =>
861
+ (numberOfAccounts: number, logger: Logger, deployOptions?: Partial<DeployOptions<DeployInteractionWaitOptions>>) =>
836
862
  async ({ wallet, initialFundedAccounts }: { wallet: TestWallet; initialFundedAccounts: InitialAccountData[] }) => {
837
863
  if (initialFundedAccounts.length < numberOfAccounts) {
838
864
  throw new Error(`Cannot deploy more than ${initialFundedAccounts.length} initial accounts.`);
@@ -851,6 +877,7 @@ export const deployAccounts =
851
877
  await deployMethod.send({
852
878
  from: NO_FROM,
853
879
  skipClassPublication: i !== 0, // Publish the contract class at most once.
880
+ ...deployOptions,
854
881
  });
855
882
  }
856
883
 
@@ -0,0 +1,14 @@
1
+ import type { Wallet } from '@aztec/aztec.js/wallet';
2
+ import { WalletSchema } from '@aztec/aztec.js/wallet';
3
+ import { createSafeJsonRpcClient, makeFetch } from '@aztec/foundation/json-rpc/client';
4
+
5
+ /**
6
+ * Creates a JSON-RPC client that connects to a remote wallet service.
7
+ * The returned object implements the {@link Wallet} interface, proxying all calls over HTTP to the specified URL.
8
+ */
9
+ export function createWalletClient(url: string): Wallet {
10
+ return createSafeJsonRpcClient<Wallet>(url, WalletSchema, {
11
+ namespaceMethods: 'wallet',
12
+ fetch: makeFetch([1, 2, 3], false),
13
+ });
14
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env -S node --no-warnings
2
+ /**
3
+ * Standalone entrypoint that spins up a local Aztec network (L1 + node) and exposes a {@link NodeEmbeddedWallet} over
4
+ * JSON-RPC.
5
+ *
6
+ * Intended for forward-compatibility testing: an **old** release image runs this script so that **new** tests can send
7
+ * new artifacts to old runtime code (loadContractArtifact, ACIR simulator, class-ID computation, entrypoint encoding,
8
+ * etc.).
9
+ */
10
+ import { getSchnorrAccountContractAddress } from '@aztec/accounts/schnorr';
11
+ import { getInitialTestAccountsData } from '@aztec/accounts/testing';
12
+ import { createLocalNetwork } from '@aztec/aztec';
13
+ import { Fr } from '@aztec/aztec.js/fields';
14
+ import { WalletSchema } from '@aztec/aztec.js/wallet';
15
+ import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin';
16
+ import { createNamespacedSafeJsonRpcServer, startHttpRpcServer } from '@aztec/foundation/json-rpc/server';
17
+ import { createLogger } from '@aztec/foundation/log';
18
+ import { AztecNodeApiSchema } from '@aztec/stdlib/interfaces/client';
19
+ import { EmbeddedWallet } from '@aztec/wallets/embedded';
20
+
21
+ const logger = createLogger('wallet-service');
22
+
23
+ const { ETHEREUM_HOSTS = 'http://localhost:8545', NODE_PORT = '8080', WALLET_PORT = '8081' } = process.env;
24
+
25
+ async function main() {
26
+ const l1RpcUrls = ETHEREUM_HOSTS.split(',').map(url => url.trim());
27
+
28
+ // Some tests (e.g. AMM) need 4 accounts but only 3 are funded via genesis. Generate deterministic keys for a 4th
29
+ // account so we can compute its address before network startup and include it in genesis funding. We cannot do this
30
+ // in the test because Wallet interface does not expose account creation functionality (only TestWallet exposes that
31
+ // but that's not used in forward compatibility testing).
32
+ const extraAccountSecret = Fr.fromHexString('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef');
33
+ const extraAccountSalt = Fr.ZERO;
34
+ const extraAccountSigningKey = GrumpkinScalar.random();
35
+ const extraAccountAddress = await getSchnorrAccountContractAddress(
36
+ extraAccountSecret,
37
+ extraAccountSalt,
38
+ extraAccountSigningKey,
39
+ );
40
+
41
+ logger.info('Starting wallet service...', { l1RpcUrls });
42
+
43
+ // createLocalNetwork deploys L1 contracts, starts the node, and optionally deploys funded test accounts (when
44
+ // TEST_ACCOUNTS=true via env). We are not proving anything just like is done when local network is started by
45
+ // the `aztecStart` function. The extra account address is passed via prefundAddresses so it gets fee juice at genesis.
46
+ const { node, stop: stopNetwork } = await createLocalNetwork(
47
+ { l1RpcUrls, realProofs: false, prefundAddresses: [extraAccountAddress.toString()] },
48
+ logger.info,
49
+ );
50
+
51
+ // Create an ephemeral embedded wallet backed by the local node.
52
+ const wallet = await EmbeddedWallet.create(node, { ephemeral: true });
53
+
54
+ // Re-register the initial test accounts so they are available via wallet.getAccounts(). createLocalNetwork deploys
55
+ // them onchain but uses a temporary wallet that is then stopped.
56
+ //
57
+ // We use the non-lazy import path (@aztec/accounts/testing, not /lazy) to avoid the dynamic JSON import that is
58
+ // incompatible with Node.js import attribute enforcement.
59
+ const testAccountsData = await getInitialTestAccountsData();
60
+ const accounts = await Promise.all(
61
+ testAccountsData.map(({ secret, salt, signingKey }) => wallet.createSchnorrAccount(secret, salt, signingKey)),
62
+ );
63
+
64
+ // Register and deploy the 4th account.
65
+ const extraAccount = await wallet.createSchnorrAccount(extraAccountSecret, extraAccountSalt, extraAccountSigningKey);
66
+ const deployMethod = await extraAccount.getDeployMethod();
67
+ await deployMethod.send({ from: accounts[0].address });
68
+
69
+ logger.info('Embedded wallet created', {
70
+ accounts: [...accounts, extraAccount].map(a => a.address.toString()),
71
+ });
72
+
73
+ // Contract artifacts are large, so allow generous body sizes for RPC requests.
74
+ const rpcOptions = { maxBodySizeBytes: '50mb' };
75
+
76
+ // Serve node RPC
77
+ const nodeRpcServer = createNamespacedSafeJsonRpcServer({ node: [node, AztecNodeApiSchema] }, rpcOptions);
78
+ const nodeHttpServer = await startHttpRpcServer(nodeRpcServer, { port: NODE_PORT });
79
+ logger.info(`Node JSON-RPC server listening on port ${nodeHttpServer.port}`);
80
+
81
+ // Serve wallet RPC
82
+ const walletRpcServer = createNamespacedSafeJsonRpcServer({ wallet: [wallet, WalletSchema] }, rpcOptions);
83
+ const walletHttpServer = await startHttpRpcServer(walletRpcServer, { port: WALLET_PORT });
84
+ logger.info(`Wallet JSON-RPC server listening on port ${walletHttpServer.port}`);
85
+
86
+ const shutdown = async () => {
87
+ logger.info('Shutting down...');
88
+ nodeHttpServer.close();
89
+ walletHttpServer.close();
90
+ await wallet.stop();
91
+ await stopNetwork();
92
+ process.exit(0);
93
+ };
94
+
95
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
96
+ process.once('SIGINT', shutdown);
97
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
98
+ process.once('SIGTERM', shutdown);
99
+ }
100
+
101
+ main().catch(err => {
102
+ logger.error('Wallet service failed to start', err);
103
+ process.exit(1);
104
+ });
@@ -18,10 +18,8 @@ aztec-wallet() {
18
18
 
19
19
  aztec-wallet import-test-accounts
20
20
 
21
- # docs:start:declare-accounts
22
21
  aztec-wallet create-account -a alice -f test0
23
22
  aztec-wallet create-account -a bob -f test0
24
- # docs:end:declare-accounts
25
23
 
26
24
  aztec-wallet bridge-fee-juice 1000000000000000000000 accounts:alice --mint --no-wait
27
25