@aztec/end-to-end 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dest/bench/client_flows/benchmark.d.ts +15 -1
  2. package/dest/bench/client_flows/benchmark.d.ts.map +1 -1
  3. package/dest/bench/client_flows/benchmark.js +17 -0
  4. package/dest/bench/client_flows/client_flows_benchmark.d.ts +2 -2
  5. package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
  6. package/dest/bench/client_flows/client_flows_benchmark.js +13 -9
  7. package/dest/bench/utils.d.ts +1 -1
  8. package/dest/bench/utils.d.ts.map +1 -1
  9. package/dest/bench/utils.js +2 -0
  10. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +3 -3
  11. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
  12. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +13 -4
  13. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +15 -4
  14. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  15. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +39 -7
  16. package/dest/e2e_deploy_contract/deploy_test.d.ts +3 -3
  17. package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
  18. package/dest/e2e_deploy_contract/deploy_test.js +2 -1
  19. package/dest/e2e_epochs/epochs_test.d.ts +8 -2
  20. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  21. package/dest/e2e_epochs/epochs_test.js +30 -7
  22. package/dest/e2e_fees/fees_test.d.ts +5 -2
  23. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  24. package/dest/e2e_fees/fees_test.js +29 -3
  25. package/dest/e2e_nested_contract/nested_contract_test.d.ts +3 -3
  26. package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
  27. package/dest/e2e_nested_contract/nested_contract_test.js +2 -1
  28. package/dest/e2e_p2p/inactivity_slash_test.d.ts +1 -1
  29. package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -1
  30. package/dest/e2e_p2p/inactivity_slash_test.js +1 -0
  31. package/dest/e2e_p2p/reqresp/utils.d.ts +1 -1
  32. package/dest/e2e_p2p/reqresp/utils.d.ts.map +1 -1
  33. package/dest/e2e_p2p/reqresp/utils.js +28 -12
  34. package/dest/e2e_p2p/shared.d.ts +19 -8
  35. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  36. package/dest/e2e_p2p/shared.js +33 -13
  37. package/dest/e2e_token_contract/token_contract_test.d.ts +3 -3
  38. package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
  39. package/dest/e2e_token_contract/token_contract_test.js +4 -2
  40. package/dest/fixtures/e2e_prover_test.d.ts +3 -3
  41. package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
  42. package/dest/fixtures/e2e_prover_test.js +2 -1
  43. package/dest/fixtures/fixtures.d.ts +59 -1
  44. package/dest/fixtures/fixtures.d.ts.map +1 -1
  45. package/dest/fixtures/fixtures.js +55 -0
  46. package/dest/fixtures/schnorr_hardcoded_account_contract.js +2 -4
  47. package/dest/fixtures/setup.d.ts +30 -4
  48. package/dest/fixtures/setup.d.ts.map +1 -1
  49. package/dest/fixtures/setup.js +117 -7
  50. package/dest/fixtures/setup_p2p_test.d.ts +5 -1
  51. package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
  52. package/dest/fixtures/utils.d.ts +2 -2
  53. package/dest/fixtures/utils.d.ts.map +1 -1
  54. package/dest/fixtures/utils.js +1 -1
  55. package/dest/install_legacy_contracts.d.cts +10 -0
  56. package/dest/install_legacy_contracts.d.cts.map +1 -0
  57. package/dest/legacy-jest-resolver.d.cts +1 -1
  58. package/dest/legacy-jest-resolver.d.cts.map +1 -1
  59. package/dest/shared/cross_chain_test_harness.d.ts +4 -2
  60. package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
  61. package/dest/shared/cross_chain_test_harness.js +9 -5
  62. package/dest/shared/gas_portal_test_harness.d.ts +8 -5
  63. package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
  64. package/dest/shared/gas_portal_test_harness.js +16 -7
  65. package/dest/shared/uniswap_l1_l2.d.ts +1 -1
  66. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  67. package/dest/shared/uniswap_l1_l2.js +50 -28
  68. package/dest/shared/wait_for_l1_to_l2_message.d.ts +13 -0
  69. package/dest/shared/wait_for_l1_to_l2_message.d.ts.map +1 -0
  70. package/dest/shared/wait_for_l1_to_l2_message.js +10 -0
  71. package/dest/simulators/lending_simulator.d.ts +10 -3
  72. package/dest/simulators/lending_simulator.d.ts.map +1 -1
  73. package/dest/simulators/lending_simulator.js +23 -11
  74. package/dest/spartan/setup_test_wallets.d.ts +1 -1
  75. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  76. package/dest/spartan/setup_test_wallets.js +9 -3
  77. package/dest/spartan/tx_metrics.d.ts +18 -4
  78. package/dest/spartan/tx_metrics.d.ts.map +1 -1
  79. package/dest/spartan/tx_metrics.js +73 -20
  80. package/dest/spartan/utils/config.d.ts +9 -35
  81. package/dest/spartan/utils/config.d.ts.map +1 -1
  82. package/dest/spartan/utils/nodes.js +2 -2
  83. package/dest/test-wallet/test_wallet.d.ts +3 -2
  84. package/dest/test-wallet/test_wallet.d.ts.map +1 -1
  85. package/dest/test-wallet/test_wallet.js +49 -27
  86. package/dest/test-wallet/utils.d.ts +3 -3
  87. package/dest/test-wallet/utils.d.ts.map +1 -1
  88. package/dest/test-wallet/utils.js +2 -7
  89. package/dest/test-wallet/wallet_worker_script.js +7 -2
  90. package/dest/test-wallet/worker_wallet.d.ts +2 -1
  91. package/dest/test-wallet/worker_wallet.d.ts.map +1 -1
  92. package/dest/test-wallet/worker_wallet.js +6 -2
  93. package/dest/test-wallet/worker_wallet_schema.d.ts +110 -229
  94. package/dest/test-wallet/worker_wallet_schema.d.ts.map +1 -1
  95. package/dest/test-wallet/worker_wallet_schema.js +14 -2
  96. package/package.json +45 -41
  97. package/src/bench/client_flows/benchmark.ts +19 -0
  98. package/src/bench/client_flows/client_flows_benchmark.ts +32 -30
  99. package/src/bench/utils.ts +2 -1
  100. package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +21 -4
  101. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +49 -11
  102. package/src/e2e_deploy_contract/deploy_test.ts +3 -2
  103. package/src/e2e_epochs/epochs_test.ts +32 -5
  104. package/src/e2e_fees/fees_test.ts +33 -2
  105. package/src/e2e_nested_contract/nested_contract_test.ts +3 -1
  106. package/src/e2e_p2p/inactivity_slash_test.ts +1 -0
  107. package/src/e2e_p2p/reqresp/utils.ts +29 -11
  108. package/src/e2e_p2p/shared.ts +31 -12
  109. package/src/e2e_token_contract/token_contract_test.ts +12 -2
  110. package/src/fixtures/e2e_prover_test.ts +3 -1
  111. package/src/fixtures/fixtures.ts +61 -0
  112. package/src/fixtures/schnorr_hardcoded_account_contract.ts +2 -2
  113. package/src/fixtures/setup.ts +134 -10
  114. package/src/fixtures/setup_p2p_test.ts +4 -0
  115. package/src/fixtures/utils.ts +2 -0
  116. package/src/install_legacy_contracts.cjs +75 -0
  117. package/src/legacy-jest-resolver.cjs +25 -48
  118. package/src/shared/cross_chain_test_harness.ts +14 -4
  119. package/src/shared/gas_portal_test_harness.ts +20 -9
  120. package/src/shared/uniswap_l1_l2.ts +84 -36
  121. package/src/shared/wait_for_l1_to_l2_message.ts +23 -0
  122. package/src/simulators/lending_simulator.ts +27 -13
  123. package/src/spartan/setup_test_wallets.ts +6 -4
  124. package/src/spartan/tx_metrics.ts +81 -23
  125. package/src/spartan/utils/nodes.ts +2 -2
  126. package/src/test-wallet/test_wallet.ts +46 -23
  127. package/src/test-wallet/utils.ts +3 -7
  128. package/src/test-wallet/wallet_worker_script.ts +5 -2
  129. package/src/test-wallet/worker_wallet.ts +7 -3
  130. package/src/test-wallet/worker_wallet_schema.ts +2 -2
@@ -56,7 +56,9 @@ export const submitTransactions = async (
56
56
  rpcConfig.proverEnabled = false;
57
57
  const wallet = await TestWallet.create(
58
58
  node,
59
- { ...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' },
60
62
  { loggerActorLabel: 'pxe-tx' },
61
63
  );
62
64
  const contract = new SchnorrHardcodedKeyAccountContract();
@@ -79,7 +81,7 @@ export async function prepareTransactions(
79
81
 
80
82
  const wallet = await TestWallet.create(
81
83
  node,
82
- { ...getPXEConfig(), proverEnabled: false },
84
+ { ...getPXEConfig(), proverEnabled: false, syncChainTip: 'checkpointed' },
83
85
  { loggerActorLabel: 'pxe-tx' },
84
86
  );
85
87
  const accountContract = new SchnorrHardcodedKeyAccountContract();
@@ -146,12 +148,21 @@ export async function awaitCommitteeExists({
146
148
  }
147
149
 
148
150
  /**
149
- * Advance epochs until we find one where the target proposer is selected for at least one slot,
150
- * then stop one epoch before it. This leaves time for the caller to start sequencers before
151
- * warping to the target epoch, avoiding the race where the target epoch passes before sequencers
152
- * are ready.
151
+ * Advance epochs until we find one where the target proposer is selected for a slot at least
152
+ * `warmupSlots` into the epoch, then stop one epoch before it. This leaves time for the caller to
153
+ * start sequencers before warping to the target epoch, avoiding the race where the target epoch
154
+ * passes before sequencers are ready.
153
155
  *
154
- * Returns the target epoch number so the caller can warp to it after starting sequencers.
156
+ * The first `warmupSlots` slots of the epoch are skipped on purpose. Callers warp to one slot
157
+ * before the target epoch and, under proposer pipelining, the proposer begins building one slot
158
+ * before its proposal slot. If the proposer were in the first slot of the epoch, that build would
159
+ * begin at the exact instant of the warp, leaving the freshly-started sequencer no warm-up margin;
160
+ * it then serializes its (often AVM-heavy) proposal past the slot boundary and honest receivers
161
+ * reject it as late. Picking a slot at least `warmupSlots` into the epoch guarantees that many full
162
+ * slots of wall-clock between the warp and the start of the proposer's build.
163
+ *
164
+ * Returns the target epoch and the concrete target slot so the caller can warp to it after starting
165
+ * sequencers.
155
166
  */
156
167
  export async function advanceToEpochBeforeProposer({
157
168
  epochCache,
@@ -159,13 +170,15 @@ export async function advanceToEpochBeforeProposer({
159
170
  targetProposer,
160
171
  logger,
161
172
  maxAttempts = 20,
173
+ warmupSlots = 1,
162
174
  }: {
163
175
  epochCache: EpochCacheInterface;
164
176
  cheatCodes: RollupCheatCodes;
165
177
  targetProposer: EthAddress;
166
178
  logger: Logger;
167
179
  maxAttempts?: number;
168
- }): Promise<{ targetEpoch: EpochNumber }> {
180
+ warmupSlots?: number;
181
+ }): Promise<{ targetEpoch: EpochNumber; targetSlot: SlotNumber }> {
169
182
  const { epochDuration } = await cheatCodes.getConfig();
170
183
 
171
184
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -173,8 +186,11 @@ export async function advanceToEpochBeforeProposer({
173
186
  // Check the NEXT epoch's slots so we stay one epoch before the target,
174
187
  // giving the caller time to start sequencers before the target epoch arrives.
175
188
  const nextEpoch = Number(currentEpoch) + 1;
176
- const startSlot = nextEpoch * Number(epochDuration);
177
- const endSlot = startSlot + Number(epochDuration);
189
+ const epochStartSlot = nextEpoch * Number(epochDuration);
190
+ // Skip the first `warmupSlots` slots so the caller keeps a warm-up margin after warping to one
191
+ // slot before the epoch (see the doc comment above).
192
+ const startSlot = epochStartSlot + warmupSlots;
193
+ const endSlot = epochStartSlot + Number(epochDuration);
178
194
 
179
195
  logger.info(
180
196
  `Checking next epoch ${nextEpoch} (slots ${startSlot}-${endSlot - 1}) for proposer ${targetProposer} (current epoch: ${currentEpoch})`,
@@ -186,7 +202,7 @@ export async function advanceToEpochBeforeProposer({
186
202
  logger.warn(
187
203
  `Found target proposer ${targetProposer} in slot ${s} of epoch ${nextEpoch}. Staying at epoch ${currentEpoch} to allow sequencer startup.`,
188
204
  );
189
- return { targetEpoch: EpochNumber(nextEpoch) };
205
+ return { targetEpoch: EpochNumber(nextEpoch), targetSlot: SlotNumber(s) };
190
206
  }
191
207
  }
192
208
 
@@ -279,7 +295,10 @@ export async function awaitCommitteeKicked({
279
295
  expect(attesterInfo.status).toEqual(1); // Validating
280
296
  }
281
297
 
282
- const timeout = slashingRoundSize * 2 * aztecSlotDuration + 30;
298
+ // Allow up to four round-lengths so that under proposer pipelining, where individual rounds
299
+ // sometimes fail to gather quorum because parts of the committee miss votes due to chain-state
300
+ // races, we still see a later round execute the slash.
301
+ const timeout = slashingRoundSize * 4 * aztecSlotDuration + 30;
283
302
  logger.info(`Waiting for slash to be executed (timeout ${timeout}s)`);
284
303
  await awaitProposalExecution(slashingProposer, timeout, logger);
285
304
 
@@ -7,7 +7,15 @@ import { InvalidAccountContract } from '@aztec/noir-test-contracts.js/InvalidAcc
7
7
 
8
8
  import { jest } from '@jest/globals';
9
9
 
10
- import { type EndToEndContext, deployAccounts, publicDeployAccounts, setup, teardown } from '../fixtures/setup.js';
10
+ import {
11
+ type EndToEndContext,
12
+ type SetupOptions,
13
+ deployAccounts,
14
+ ensureAuthRegistryPublished,
15
+ publicDeployAccounts,
16
+ setup,
17
+ teardown,
18
+ } from '../fixtures/setup.js';
11
19
  import { mintTokensToPrivate } from '../fixtures/token_utils.js';
12
20
  import { TokenSimulator } from '../simulators/token_simulator.js';
13
21
  import type { TestWallet } from '../test-wallet/test_wallet.js';
@@ -77,6 +85,7 @@ export class TokenContractTest {
77
85
  [this.adminAddress, this.account1Address, this.account2Address] = deployedAccounts.map(acc => acc.address);
78
86
 
79
87
  this.logger.info('Applying base setup - deploying token contract');
88
+ await ensureAuthRegistryPublished(this.wallet, this.adminAddress);
80
89
  this.logger.verbose(`Public deploy accounts...`);
81
90
  await publicDeployAccounts(this.wallet, [this.adminAddress, this.account1Address]);
82
91
 
@@ -115,8 +124,9 @@ export class TokenContractTest {
115
124
  );
116
125
  }
117
126
 
118
- async setup() {
127
+ async setup(opts: Partial<SetupOptions> = {}) {
119
128
  this.context = await setup(0, {
129
+ ...opts,
120
130
  metricsPort: this.metricsPort,
121
131
  fundSponsoredFPC: true,
122
132
  skipAccountDeployment: true,
@@ -24,6 +24,7 @@ import { getACVMConfig } from './get_acvm_config.js';
24
24
  import { getBBConfig } from './get_bb_config.js';
25
25
  import {
26
26
  type EndToEndContext,
27
+ type SetupOptions,
27
28
  deployAccounts,
28
29
  getPrivateKeyFromIndex,
29
30
  getSponsoredFPCAddress,
@@ -124,9 +125,10 @@ export class FullProverTest {
124
125
  );
125
126
  }
126
127
 
127
- async setup() {
128
+ async setup(opts: Partial<SetupOptions> = {}) {
128
129
  this.logger.info('Setting up subsystems from fresh');
129
130
  this.context = await setup(0, {
131
+ ...opts,
130
132
  startProverNode: true,
131
133
  coinbase: this.coinbase,
132
134
  fundSponsoredFPC: true,
@@ -12,6 +12,67 @@ export const DEFAULT_MIN_FEE_PADDING = 5;
12
12
  */
13
13
  export const LARGE_MIN_FEE_PADDING = 15;
14
14
 
15
+ /**
16
+ * Fee padding used by tests running under proposer pipelining. Under pipelining the fee-asset
17
+ * price modifier evolves faster across the build/publish gap, so client-set maxFeesPerGas (sized
18
+ * for the default 5x padding) was getting bumped past by the time the tx mined a few slots later.
19
+ * Observed worst case in CI: fee evolved ~20x between PXE snapshot and inclusion, exceeding even
20
+ * LARGE_MIN_FEE_PADDING (15x).
21
+ */
22
+ export const PIPELINED_FEE_PADDING = 30;
23
+
24
+ /**
25
+ * Setup option preset that opts a test into proposer pipelining. Use with `setup()`:
26
+ *
27
+ * await setup(N, { ...PIPELINING_SETUP_OPTS, ...otherOpts });
28
+ *
29
+ * The preset sets:
30
+ * - `inboxLag: 2` so the sequencer sources L1->L2 messages from checkpoint N-1 (already sealed),
31
+ * avoiding `L1ToL2MessagesNotReadyError` when building for slot N during slot N-1.
32
+ * - `minTxsPerBlock: 0` so empty checkpoints land even when a tx arrives late in the build window
33
+ * (otherwise the chain stalls on alternating slots).
34
+ * - `aztecSlotDuration: 12` / `ethereumSlotDuration: 4` so the pipelined cycle fits inside the
35
+ * default 300s Jest hook budget. Tests that depend on the env-default 72s/12s should override.
36
+ * - `walletMinFeePadding: PIPELINED_FEE_PADDING` (30x) to absorb the wider fee evolution window.
37
+ */
38
+ export const PIPELINING_SETUP_OPTS = {
39
+ inboxLag: 2,
40
+ minTxsPerBlock: 0,
41
+ aztecSlotDuration: 12,
42
+ ethereumSlotDuration: 4,
43
+ walletMinFeePadding: PIPELINED_FEE_PADDING,
44
+ } as const;
45
+
46
+ /**
47
+ * Setup option preset that opts a test into the deterministic AutomineSequencer path.
48
+ * Use only for single-sequencer tests that don't exercise block-building or consensus
49
+ * (e.g. e2e_token, e2e_amm, e2e_authwit). Not compatible with `e2e_p2p/*`,
50
+ * `e2e_epochs/*`, `e2e_slashing/*`, `e2e_block_building`, or any multi-validator suite.
51
+ *
52
+ * await setup(N, { ...AUTOMINE_E2E_OPTS, ...otherOpts });
53
+ *
54
+ * The preset:
55
+ * - Swaps the production Sequencer for an AutomineSequencer that builds one block per
56
+ * submitted tx, publishes synchronously to L1, and owns all time control through a
57
+ * serial queue (see `sequencer-client/src/sequencer/automine/automine_sequencer.ts`).
58
+ * - Disables the validator client and AnvilTestWatcher (the AutomineSequencer needs
59
+ * neither).
60
+ * - Uses `inboxLag: 1` (synchronous) since the AutomineSequencer publishes one block per tx.
61
+ * - Switches anvil into automine mode at setup time (no interval mining); each L1 tx
62
+ * mines an L1 block immediately.
63
+ *
64
+ * Requires `aztecTargetCommitteeSize: 0`, which is the e2e default at `setup.ts:317`.
65
+ */
66
+ export const AUTOMINE_E2E_OPTS = {
67
+ useAutomineSequencer: true,
68
+ disableAnvilTestWatcher: true,
69
+ inboxLag: 1,
70
+ minTxsPerBlock: 0,
71
+ aztecSlotDuration: 12,
72
+ ethereumSlotDuration: 4,
73
+ walletMinFeePadding: PIPELINED_FEE_PADDING,
74
+ } as const;
75
+
15
76
  /** Returns worst-case predicted min fees with padding applied, mirroring the BaseWallet pattern. */
16
77
  export async function getPaddedMaxFeesPerGas(node: AztecNode, padding = DEFAULT_MIN_FEE_PADDING): Promise<GasFees> {
17
78
  const predicted = await node.getPredictedMinFees();
@@ -41,8 +41,8 @@ export class SchnorrHardcodedKeyAccountContract extends DefaultAccountContract {
41
41
  return {
42
42
  async createAuthWit(messageHash: Fr): Promise<AuthWitness> {
43
43
  const signer = new Schnorr();
44
- const signature = await signer.constructSignature(messageHash.toBuffer(), privateKey);
45
- return new AuthWitness(messageHash, [...signature.toBuffer()]);
44
+ const signature = await signer.constructSignature(messageHash, privateKey);
45
+ return new AuthWitness(messageHash, signature.toLimbFields());
46
46
  },
47
47
  };
48
48
  }
@@ -7,8 +7,8 @@ import {
7
7
  BatchCall,
8
8
  type ContractFunctionInteraction,
9
9
  type ContractMethod,
10
- type DeployInteractionWaitOptions,
11
10
  type DeployOptions,
11
+ type InteractionWaitOptions,
12
12
  getContractClassFromArtifact,
13
13
  waitForProven,
14
14
  } from '@aztec/aztec.js/contracts';
@@ -48,8 +48,12 @@ import type { P2PClientDeps } from '@aztec/p2p';
48
48
  import { MockGossipSubNetwork, getMockPubSubP2PServiceFactory } from '@aztec/p2p/test-helpers';
49
49
  import { protocolContractsHash } from '@aztec/protocol-contracts';
50
50
  import type { ProverNodeConfig } from '@aztec/prover-node';
51
- import { type PXEConfig, getPXEConfig } from '@aztec/pxe/server';
51
+ import { type PXEConfig, type PXECreationOptions, getPXEConfig } from '@aztec/pxe/server';
52
52
  import type { SequencerClient } from '@aztec/sequencer-client';
53
+ import { AuthRegistryArtifact, getStandardAuthRegistry } from '@aztec/standard-contracts/auth-registry';
54
+ import { HandshakeRegistryArtifact, getStandardHandshakeRegistry } from '@aztec/standard-contracts/handshake-registry';
55
+ import { PublicChecksArtifact, getStandardPublicChecks } from '@aztec/standard-contracts/public-checks';
56
+ import { ARTIFACT_VERSION_BEFORE_INJECTION } from '@aztec/stdlib/abi';
53
57
  import { type ContractInstanceWithAddress, getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract';
54
58
  import type { AztecNodeAdmin, AztecNodeDebug } from '@aztec/stdlib/interfaces/client';
55
59
  import { tryStop } from '@aztec/stdlib/interfaces/server';
@@ -212,6 +216,11 @@ export type SetupOptions = {
212
216
  /** Whether the initial node should be a lightweight RPC-only node (no sequencer, no validator).
213
217
  * Use for tests that create their own validator nodes and don't need the initial sequencer. */
214
218
  skipInitialSequencer?: boolean;
219
+ /** Whether to swap the production Sequencer for the minimal AutomineSequencer.
220
+ * Use only for single-sequencer non-block-building tests. See AUTOMINE_E2E_OPTS in `fixtures.ts`. */
221
+ useAutomineSequencer?: boolean;
222
+ /** Options forwarded to PXE creation (e.g. execution hooks). */
223
+ pxeCreationOptions?: PXECreationOptions;
215
224
  } & Partial<AztecNodeConfig>;
216
225
 
217
226
  /** Context for an end-to-end test as returned by the `setup` function */
@@ -270,6 +279,32 @@ export type EndToEndContext = {
270
279
  teardown: () => Promise<void>;
271
280
  };
272
281
 
282
+ /**
283
+ * When CONTRACT_ARTIFACTS_VERSION is set (backwards compatibility testing), asserts that the loaded artifact's
284
+ * aztecVersion matches the expected version. This is a sanity check verifying that the legacy artifact resolver
285
+ * actually swapped in the correct version.
286
+ */
287
+ function assertContractArtifactsVersion() {
288
+ const expected = process.env.CONTRACT_ARTIFACTS_VERSION;
289
+ if (!expected) {
290
+ return;
291
+ }
292
+ const { aztecVersion } = SponsoredFPCContract.artifact;
293
+ // TODO(F-557): Remove this bypass once pre-version artifacts are no longer tested.
294
+ if (aztecVersion === ARTIFACT_VERSION_BEFORE_INJECTION) {
295
+ createLogger('e2e:setup').info(
296
+ `Skipping artifact version check: artifact predates version injection (CONTRACT_ARTIFACTS_VERSION=${expected})`,
297
+ );
298
+ return;
299
+ }
300
+ if (aztecVersion !== expected) {
301
+ throw new Error(
302
+ `Artifact version mismatch: expected ${expected} but got ${aztecVersion}. ` +
303
+ `The legacy artifact resolver may not have swapped in the correct version.`,
304
+ );
305
+ }
306
+ }
307
+
273
308
  /**
274
309
  * Sets up the environment for the end-to-end tests.
275
310
  * @param numberOfAccounts - The number of new accounts to be created once the PXE is initiated.
@@ -282,6 +317,7 @@ export async function setup(
282
317
  pxeOpts: Partial<PXEConfig> = {},
283
318
  chain: Chain = foundry,
284
319
  ): Promise<EndToEndContext> {
320
+ assertContractArtifactsVersion();
285
321
  let anvil: Anvil | undefined;
286
322
  try {
287
323
  opts.aztecTargetCommitteeSize ??= 0;
@@ -424,9 +460,27 @@ export async function setup(
424
460
  },
425
461
  );
426
462
 
427
- config.l1Contracts = deployL1ContractsValues.l1ContractAddresses;
463
+ Object.assign(config, deployL1ContractsValues.l1ContractAddresses);
428
464
  config.rollupVersion = deployL1ContractsValues.rollupVersion;
429
465
 
466
+ // Propagate L1-contracts-config overrides back to the node config so the archiver's
467
+ // `l1Constants` (and any other node-side consumer) agrees with what was actually deployed.
468
+ // Without this, a per-test override like `aztecEpochDuration: 4` lands on the rollup contract
469
+ // but the node config keeps the default (32), so `archiver.isEpochComplete(0)` computes
470
+ // `endSlot=31` and `EpochTestSettler`/`EpochMonitor` never fires — letting the
471
+ // `aztecProofSubmissionEpochs` window expire mid-test and prune the pending chain.
472
+ // Skip undefined values: callers (e.g. `P2PNetworkTest`) sometimes build `l1ContractsArgs`
473
+ // by spreading a partial `AztecNodeConfig`, which leaves unset fields (notably `dataDirectory`)
474
+ // as `undefined`. A blind Object.assign would then clobber the temp `dataDirectory` set earlier
475
+ // in this function and crash `setupSharedBlobStorage`.
476
+ if (opts.l1ContractsArgs) {
477
+ for (const [key, value] of Object.entries(opts.l1ContractsArgs)) {
478
+ if (value !== undefined) {
479
+ (config as unknown as Record<string, unknown>)[key] = value;
480
+ }
481
+ }
482
+ }
483
+
430
484
  if (enableAutomine) {
431
485
  await ethCheatCodes.setAutomine(false);
432
486
  await ethCheatCodes.setIntervalMining(config.ethereumSlotDuration);
@@ -492,11 +546,17 @@ export async function setup(
492
546
  throw new Error('minTxsPerBlock is undefined in e2e test setup');
493
547
  }
494
548
 
495
- // Only set minTxsPerBlock=1 if we're going to deploy accounts and need reliable block inclusion
549
+ // Whether we're deploying accounts (and therefore need reliable block inclusion past genesis)
496
550
  const shouldDeployAccounts = numberOfAccounts > 0 && !opts.skipAccountDeployment;
497
551
  // Only set minTxsPerBlock=0 if we need an empty block (no accounts at all, not skipped deployment)
498
552
  const needsEmptyBlock = numberOfAccounts === 0 && !opts.skipAccountDeployment;
499
- config.minTxsPerBlock = shouldDeployAccounts ? 1 : needsEmptyBlock ? 0 : originalMinTxsPerBlock;
553
+ // Pipelining is always on: the proposer builds during slot N-1 for slot N. A tx submitted at
554
+ // slot N start arrives after that build, so forcing minTxsPerBlock=1 would stall the chain on
555
+ // alternating slots -- hence empty checkpoints are allowed (minTxsPerBlock=0) for account
556
+ // deployment. Automine is unaffected: its runBuild clamps mempool builds to
557
+ // Math.max(minTxsPerBlock ?? 1, 1) and still requires minValidTxs: 1.
558
+ const accountsDeployMinTxs = 0;
559
+ config.minTxsPerBlock = shouldDeployAccounts ? accountsDeployMinTxs : needsEmptyBlock ? 0 : originalMinTxsPerBlock;
500
560
 
501
561
  config.p2pEnabled = opts.mockGossipSubNetwork || config.p2pEnabled;
502
562
  config.p2pIp = opts.p2pIp ?? config.p2pIp ?? '127.0.0.1';
@@ -559,13 +619,21 @@ export async function setup(
559
619
  pxeConfig.dataDirectory = path.join(directoryToCleanup, randomBytes(8).toString('hex'));
560
620
  // For tests we only want proving enabled if specifically requested
561
621
  pxeConfig.proverEnabled = !!pxeOpts.proverEnabled;
562
- const wallet = await TestWallet.create(aztecNodeService, pxeConfig, { loggerActorLabel: 'pxe-0' });
622
+ const wallet = await TestWallet.create(aztecNodeService, pxeConfig, {
623
+ loggerActorLabel: 'pxe-0',
624
+ ...opts.pxeCreationOptions,
625
+ });
563
626
 
564
627
  if (opts.walletMinFeePadding !== undefined) {
565
628
  wallet.setMinFeePadding(opts.walletMinFeePadding);
566
629
  }
567
630
 
568
- const cheatCodes = await CheatCodes.create(config.l1RpcUrls, aztecNodeService, dateProvider);
631
+ const cheatCodes = await CheatCodes.create(
632
+ config.l1RpcUrls,
633
+ aztecNodeService,
634
+ dateProvider,
635
+ aztecNodeService.getAutomineSequencer(),
636
+ );
569
637
 
570
638
  if (
571
639
  (opts.aztecTargetCommitteeSize && opts.aztecTargetCommitteeSize > 0) ||
@@ -587,13 +655,18 @@ export async function setup(
587
655
  logger.info('Sequencer not started on initial node, skipping block progression');
588
656
  } else if (shouldDeployAccounts) {
589
657
  logger.info(
590
- `${numberOfAccounts} accounts are being deployed. Reliably progressing past genesis by setting minTxsPerBlock to 1 and waiting for the accounts to be deployed`,
658
+ `${numberOfAccounts} accounts are being deployed. Reliably progressing past genesis by waiting for the accounts to be deployed`,
591
659
  );
592
660
  const accountsData = initialFundedAccounts.slice(0, numberOfAccounts);
593
661
  const accountManagers = await deployFundedSchnorrAccounts(wallet, accountsData);
594
662
  accounts = accountManagers.map(accountManager => accountManager.address);
595
663
  } else if (needsEmptyBlock) {
596
664
  logger.info('No accounts are being deployed, waiting for an empty block 1 to be mined');
665
+ // AutomineSequencer only builds on tx arrival; explicitly request an empty block.
666
+ const automine = aztecNodeService.getAutomineSequencer();
667
+ if (automine) {
668
+ await automine.buildEmptyBlock();
669
+ }
597
670
  while ((await aztecNodeService.getBlockNumber()) === 0) {
598
671
  await sleep(2000);
599
672
  }
@@ -734,7 +807,7 @@ export async function waitForProvenChain(node: AztecNode, targetBlock?: BlockNum
734
807
  targetBlock ??= await node.getBlockNumber();
735
808
 
736
809
  await retryUntil(
737
- async () => (await node.getProvenBlockNumber()) >= targetBlock,
810
+ async () => (await node.getBlockNumber('proven')) >= targetBlock,
738
811
  'proven chain status',
739
812
  timeoutSec,
740
813
  intervalSec,
@@ -824,6 +897,57 @@ export async function expectMappingDelta<K, V extends number | bigint>(
824
897
  expect(diffs).toEqual(expectedDiffs);
825
898
  }
826
899
 
900
+ /**
901
+ * Registers the auth_registry contract class and publishes its standard instance if not already
902
+ * present, and registers the artifact with PXE. Publishing is required before exercising the public
903
+ * authwit path (which relies on the AVM's deployment-nullifier check); the PXE-side registration is
904
+ * required so revert messages from AuthRegistry calls can be enriched (otherwise assertion strings
905
+ * surface as generic "Assertion failed:" and tests that match on the real message fail).
906
+ */
907
+ export async function ensureAuthRegistryPublished(wallet: Wallet, from: AztecAddress) {
908
+ const { instance, contractClass } = await getStandardAuthRegistry();
909
+ if (!(await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered) {
910
+ await (await publishContractClass(wallet, AuthRegistryArtifact)).send({ from });
911
+ }
912
+ if (!(await wallet.getContractMetadata(instance.address)).isContractPublished) {
913
+ await publishInstance(wallet, instance).send({ from });
914
+ }
915
+ await wallet.registerContract(instance, AuthRegistryArtifact);
916
+ }
917
+
918
+ /**
919
+ * Registers the public_checks contract class and publishes its standard instance if not already
920
+ * present, and registers the artifact with PXE. Required for any contract that calls
921
+ * `privately_check_timestamp` / `privately_check_block_number` (which dispatch into the
922
+ * deployed PublicChecks contract via an enqueued public call).
923
+ */
924
+ export async function ensurePublicChecksPublished(wallet: Wallet, from: AztecAddress) {
925
+ const { instance, contractClass } = await getStandardPublicChecks();
926
+ if (!(await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered) {
927
+ await (await publishContractClass(wallet, PublicChecksArtifact)).send({ from });
928
+ }
929
+ if (!(await wallet.getContractMetadata(instance.address)).isContractPublished) {
930
+ await publishInstance(wallet, instance).send({ from });
931
+ }
932
+ await wallet.registerContract(instance, PublicChecksArtifact);
933
+ }
934
+
935
+ /**
936
+ * Registers the handshake_registry contract class and publishes its standard instance if not
937
+ * already present, and registers the artifact with PXE. Required for constrained-delivery flows
938
+ * that call into the HandshakeRegistry at its well-known address.
939
+ */
940
+ export async function ensureHandshakeRegistryPublished(wallet: Wallet, from: AztecAddress) {
941
+ const { instance, contractClass } = await getStandardHandshakeRegistry();
942
+ if (!(await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered) {
943
+ await (await publishContractClass(wallet, HandshakeRegistryArtifact)).send({ from });
944
+ }
945
+ if (!(await wallet.getContractMetadata(instance.address)).isContractPublished) {
946
+ await publishInstance(wallet, instance).send({ from });
947
+ }
948
+ await wallet.registerContract(instance, HandshakeRegistryArtifact);
949
+ }
950
+
827
951
  /**
828
952
  * Registers the contract class used for test accounts and publicly deploys the instances requested.
829
953
  * Use this when you need to make a public call to an account contract, such as for requesting a public authwit.
@@ -858,7 +982,7 @@ export async function ensureAccountContractsPublished(wallet: Wallet, accountsTo
858
982
  * Returns deployed account data that can be used by tests.
859
983
  */
860
984
  export const deployAccounts =
861
- (numberOfAccounts: number, logger: Logger, deployOptions?: Partial<DeployOptions<DeployInteractionWaitOptions>>) =>
985
+ (numberOfAccounts: number, logger: Logger, deployOptions?: Partial<DeployOptions<InteractionWaitOptions>>) =>
862
986
  async ({ wallet, initialFundedAccounts }: { wallet: TestWallet; initialFundedAccounts: InitialAccountData[] }) => {
863
987
  if (initialFundedAccounts.length < numberOfAccounts) {
864
988
  throw new Error(`Cannot deploy more than ${initialFundedAccounts.length} initial accounts.`);
@@ -88,6 +88,10 @@ export type CreateNodeConfig = AztecNodeConfig & {
88
88
  dontStartSequencer?: boolean;
89
89
  /** Override the private key (instead of deriving from addressIndex). */
90
90
  validatorPrivateKey?: `0x${string}`;
91
+ /** Corrupt only the block proposal at this indexWithinCheckpoint (testing only). */
92
+ invalidBlockProposalIndexWithinCheckpoint?: number;
93
+ /** Accept proposal gossip regardless of slot timing (testing only). */
94
+ skipProposalSlotValidation?: boolean;
91
95
  };
92
96
 
93
97
  /** Creates a P2P enabled instance of Aztec Node Service with a validator. */
@@ -9,6 +9,8 @@ export {
9
9
  createAndSyncProverNode,
10
10
  deployAccounts,
11
11
  ensureAccountContractsPublished,
12
+ ensureAuthRegistryPublished,
13
+ ensurePublicChecksPublished,
12
14
  expectMapping,
13
15
  expectMappingDelta,
14
16
  getBalancesFn,
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ // Installs pinned legacy @aztec/* contract-artifact packages into .legacy-contracts/<version>/.
3
+ //
4
+ // Called from two places:
5
+ // - bootstrap.sh ci-compat-e2e: pre-populates the cache on the host before hermetic test
6
+ // containers launch. The containers run with --net=none (see ci3/docker_isolate), so on-demand
7
+ // installs from inside them fail with EAI_AGAIN.
8
+ // - legacy-jest-resolver.cjs: on-demand install for local dev, where jest runs with network.
9
+ //
10
+ // Idempotent: no-op when all packages are already present.
11
+ /* eslint-disable @typescript-eslint/no-require-imports */
12
+
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+ const { execFileSync } = require('child_process');
16
+
17
+ const REDIRECTED = ['@aztec/noir-contracts.js', '@aztec/noir-test-contracts.js', '@aztec/accounts'];
18
+
19
+ const e2eRoot = path.resolve(__dirname, '..');
20
+
21
+ function cacheRoot(version) {
22
+ return path.join(e2eRoot, '.legacy-contracts', version);
23
+ }
24
+
25
+ function pkgJsonPath(version, pkg) {
26
+ return path.join(cacheRoot(version), 'node_modules', pkg, 'package.json');
27
+ }
28
+
29
+ function installLegacyContracts(version) {
30
+ if (!version) {
31
+ throw new Error('installLegacyContracts: version is required');
32
+ }
33
+ if (REDIRECTED.every(pkg => fs.existsSync(pkgJsonPath(version, pkg)))) {
34
+ return;
35
+ }
36
+
37
+ const cacheDir = cacheRoot(version);
38
+ fs.mkdirSync(cacheDir, { recursive: true });
39
+
40
+ // Seed a standalone package.json so `npm install --prefix` treats cacheRoot as its own project. Without this, npm
41
+ // walks up and finds the yarn-project workspace root, which breaks on `workspace:` protocol deps and risks
42
+ // clobbering the monorepo's node_modules.
43
+ const seed = path.join(cacheDir, 'package.json');
44
+ if (!fs.existsSync(seed)) {
45
+ fs.writeFileSync(seed, JSON.stringify({ name: 'legacy-contracts-cache', private: true }));
46
+ }
47
+
48
+ const specs = REDIRECTED.map(pkg => `${pkg}@${version}`);
49
+ process.stderr.write(`[legacy-contracts] installing ${specs.join(' ')} into ${cacheDir}\n`);
50
+ // --prefix: install into cacheRoot instead of cwd, so the cache is isolated from the monorepo.
51
+ // --no-save: don't write the installed packages back to the seeded package.json.
52
+ // --ignore-scripts: skip lifecycle scripts (preinstall/postinstall) of the legacy packages and their transitive
53
+ // deps; we only want the files on disk, not to run any build steps.
54
+ // --legacy-peer-deps: tolerate peer-dependency mismatches between the pinned legacy @aztec/* graph and whatever
55
+ // current versions npm would otherwise try to reconcile.
56
+ execFileSync(
57
+ 'npm',
58
+ ['install', '--prefix', cacheDir, '--no-save', '--ignore-scripts', '--legacy-peer-deps', ...specs],
59
+ { stdio: 'inherit' },
60
+ );
61
+
62
+ // Verify versions on disk match the requested version.
63
+ for (const pkg of REDIRECTED) {
64
+ const onDisk = JSON.parse(fs.readFileSync(pkgJsonPath(version, pkg), 'utf8')).version;
65
+ if (onDisk !== version) {
66
+ throw new Error(`[legacy-contracts] ${pkg} on disk is ${onDisk}, expected ${version}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ module.exports = { installLegacyContracts, REDIRECTED, cacheRoot };
72
+
73
+ if (require.main === module) {
74
+ installLegacyContracts(process.argv[2]);
75
+ }