@aztec/end-to-end 2.0.0-nightly.20250826 → 2.0.0-nightly.20250828

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.
@@ -0,0 +1,264 @@
1
+ import type { AztecAddress } from '@aztec/aztec.js';
2
+ import { getAddressFromPrivateKey } from '@aztec/ethereum';
3
+ import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import type { EthPrivateKey } from '@aztec/node-keystore';
5
+
6
+ import { writeFile } from 'fs/promises';
7
+ import { createServer } from 'http';
8
+ import { signMessage, signTypedData } from 'viem/accounts';
9
+
10
+ // Create a mock JSON RPC signer
11
+ // Only supports signing messages and type data
12
+
13
+ const SUPPORTED_METHODS = ['eth_sign', 'eth_signTypedData_v4'];
14
+
15
+ export function createJSONRPCSigner(keyLookup: Map<string, EthPrivateKey>, stats: Map<string, number>) {
16
+ return createServer((req, res) => {
17
+ if (req.method === 'POST') {
18
+ let body = '';
19
+ req.on('data', chunk => {
20
+ body += chunk.toString();
21
+ });
22
+ req.on('end', () => {
23
+ try {
24
+ const jsonRequest = JSON.parse(body);
25
+ res.writeHead(200, { 'Content-Type': 'application/json' });
26
+
27
+ if (!SUPPORTED_METHODS.includes(jsonRequest.method)) {
28
+ res.end(
29
+ JSON.stringify({
30
+ jsonrpc: '2.0',
31
+ id: jsonRequest.id,
32
+ error: { code: -32601, message: 'Method not supported' },
33
+ }),
34
+ );
35
+ return;
36
+ }
37
+
38
+ // Get the address sending the transaction
39
+ const [address, data] = jsonRequest.params;
40
+
41
+ const lowerCaseAddress = address.toLowerCase();
42
+ stats.set(lowerCaseAddress, (stats.get(lowerCaseAddress) ?? 0) + 1);
43
+
44
+ // Find the private key for the address
45
+ const privateKey = keyLookup.get(address.toLowerCase());
46
+ if (!privateKey) {
47
+ res.end(
48
+ JSON.stringify({
49
+ jsonrpc: '2.0',
50
+ id: jsonRequest.id,
51
+ error: { code: -32602, message: `No private key found for address ${address}` },
52
+ }),
53
+ );
54
+ return;
55
+ }
56
+
57
+ const promise =
58
+ jsonRequest.method === 'eth_sign'
59
+ ? signMessage({ message: { raw: data as `0x${string}` }, privateKey })
60
+ : signTypedData({
61
+ privateKey,
62
+ ...data,
63
+ });
64
+
65
+ void promise.then(signature => {
66
+ res.end(
67
+ JSON.stringify({
68
+ jsonrpc: '2.0',
69
+ id: jsonRequest.id,
70
+ result: signature,
71
+ }),
72
+ );
73
+ });
74
+ } catch (err) {
75
+ res.end(
76
+ JSON.stringify({
77
+ jsonrpc: '2.0',
78
+ id: 1,
79
+ error: { code: -32603, message: `${err}` },
80
+ }),
81
+ );
82
+ }
83
+ });
84
+ } else {
85
+ res.writeHead(405);
86
+ res.end('Method not allowed');
87
+ }
88
+ });
89
+ }
90
+
91
+ // Functions for creating file based key stores for the e2e_multi_validator_node_key_store test
92
+ export async function createKeyFile1(
93
+ fileName: string,
94
+ mnemonic: string,
95
+ validatorIndex: number,
96
+ publisher1Key: EthPrivateKey,
97
+ publisher2Key: EthPrivateKey,
98
+ coinbase: EthAddress,
99
+ feeRecipient: AztecAddress,
100
+ ) {
101
+ const obj = {
102
+ schemaVersion: 1,
103
+ validators: [
104
+ {
105
+ attester: {
106
+ mnemonic: mnemonic,
107
+ accountIndex: 0,
108
+ addressIndex: validatorIndex,
109
+ addressCount: 1,
110
+ },
111
+ coinbase: coinbase.toChecksumString(),
112
+ publisher: [publisher1Key, publisher2Key],
113
+ feeRecipient: feeRecipient.toString(),
114
+ },
115
+ ],
116
+ };
117
+ await writeFile(fileName, JSON.stringify(obj, null, 2));
118
+ }
119
+
120
+ export async function createKeyFile2(
121
+ fileName: string,
122
+ validatorKey: EthPrivateKey,
123
+ publisherMnemonic: string,
124
+ publisher1Index: number,
125
+ coinbase: EthAddress,
126
+ feeRecipient: AztecAddress,
127
+ ) {
128
+ const obj = {
129
+ schemaVersion: 1,
130
+ validators: [
131
+ {
132
+ attester: validatorKey,
133
+ coinbase: coinbase.toChecksumString(),
134
+ publisher: {
135
+ mnemonic: publisherMnemonic,
136
+ accountIndex: 0,
137
+ addressIndex: publisher1Index,
138
+ addressCount: 2,
139
+ },
140
+ feeRecipient: feeRecipient.toString(),
141
+ },
142
+ ],
143
+ };
144
+ await writeFile(fileName, JSON.stringify(obj, null, 2));
145
+ }
146
+
147
+ export async function createKeyFile3(
148
+ fileName: string,
149
+ validatorAddress: EthAddress,
150
+ publisher1Key: EthPrivateKey,
151
+ publisher2Key: EthPrivateKey,
152
+ coinbase: EthAddress,
153
+ remoteSignerUrl: string,
154
+ feeRecipient: AztecAddress,
155
+ ) {
156
+ const obj = {
157
+ schemaVersion: 1,
158
+ validators: [
159
+ {
160
+ attester: {
161
+ address: validatorAddress.toChecksumString(),
162
+ },
163
+ coinbase: coinbase.toChecksumString(),
164
+ publisher: [publisher1Key, publisher2Key],
165
+ feeRecipient: feeRecipient.toString(),
166
+ remoteSigner: {
167
+ remoteSignerUrl: remoteSignerUrl,
168
+ },
169
+ },
170
+ ],
171
+ };
172
+ await writeFile(fileName, JSON.stringify(obj, null, 2));
173
+ }
174
+
175
+ export async function createKeyFile4(
176
+ fileName: string,
177
+ validator1Address: EthAddress,
178
+ validator2Address: EthAddress,
179
+ publisher1Index: number,
180
+ publisher2Key: EthPrivateKey,
181
+ mnemonic: string,
182
+ publisher3Key: EthPrivateKey,
183
+ coinbase1: EthAddress,
184
+ coinbase2: EthAddress,
185
+ remoteSignerUrl: string,
186
+ feeRecipient1: AztecAddress,
187
+ feeRecipient2: AztecAddress,
188
+ ) {
189
+ const obj = {
190
+ schemaVersion: 1,
191
+ remoteSigner: {
192
+ remoteSignerUrl: remoteSignerUrl,
193
+ },
194
+ validators: [
195
+ {
196
+ attester: {
197
+ address: validator1Address.toChecksumString(),
198
+ },
199
+ coinbase: coinbase1.toChecksumString(),
200
+ publisher: {
201
+ mnemonic: mnemonic,
202
+ accountIndex: 0,
203
+ addressIndex: publisher1Index,
204
+ addressCount: 2,
205
+ },
206
+ feeRecipient: feeRecipient1.toString(),
207
+ },
208
+ {
209
+ attester: {
210
+ address: validator2Address.toChecksumString(),
211
+ },
212
+ coinbase: coinbase2.toChecksumString(),
213
+ publisher: [publisher2Key, publisher3Key],
214
+ feeRecipient: feeRecipient2.toString(),
215
+ },
216
+ ],
217
+ };
218
+ await writeFile(fileName, JSON.stringify(obj, null, 2));
219
+ }
220
+
221
+ export async function createKeyFile5(fileName: string, proverAddress: EthAddress, remoteSignerUrl: string) {
222
+ const obj = {
223
+ schemaVersion: 1,
224
+ prover: {
225
+ id: '0x1234567890123456789012345678901234567890',
226
+ publisher: [
227
+ {
228
+ address: proverAddress.toChecksumString(),
229
+ remoteSignerUrl: remoteSignerUrl,
230
+ },
231
+ ],
232
+ },
233
+ };
234
+ await writeFile(fileName, JSON.stringify(obj, null, 2));
235
+ }
236
+
237
+ export async function createKeyFile6(
238
+ fileName: string,
239
+ mnemonic: string,
240
+ validator1Index: number,
241
+ coinbase: EthAddress,
242
+ feeRecipient: AztecAddress,
243
+ ) {
244
+ const obj = {
245
+ schemaVersion: 1,
246
+ validators: [
247
+ {
248
+ attester: {
249
+ mnemonic: mnemonic,
250
+ accountIndex: 0,
251
+ addressIndex: validator1Index,
252
+ addressCount: 2,
253
+ },
254
+ coinbase: coinbase.toChecksumString(),
255
+ feeRecipient: feeRecipient.toString(),
256
+ },
257
+ ],
258
+ };
259
+ await writeFile(fileName, JSON.stringify(obj, null, 2));
260
+ }
261
+
262
+ export function addressForPrivateKey(privateKey: EthPrivateKey): EthAddress {
263
+ return EthAddress.fromString(getAddressFromPrivateKey(privateKey));
264
+ }
@@ -3,20 +3,22 @@ import type { InitialAccountData } from '@aztec/accounts/testing';
3
3
  import type { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node';
4
4
  import { type AccountWalletWithSecretKey, AztecAddress, EthAddress, Fr } from '@aztec/aztec.js';
5
5
  import {
6
+ type EmpireSlashingProposerContract,
6
7
  type ExtendedViemWalletClient,
7
8
  GSEContract,
8
- L1TxUtils,
9
9
  MultiAdderArtifact,
10
10
  type Operator,
11
11
  RollupContract,
12
+ type TallySlashingProposerContract,
12
13
  type ViemClient,
14
+ createL1TxUtilsFromViemWallet,
13
15
  deployL1Contract,
14
16
  getL1ContractsConfigEnvVars,
15
17
  } from '@aztec/ethereum';
16
18
  import { ChainMonitor } from '@aztec/ethereum/test';
17
19
  import { SecretValue } from '@aztec/foundation/config';
18
20
  import { type Logger, createLogger } from '@aztec/foundation/log';
19
- import { EmpireSlashingProposerAbi, RollupAbi, SlasherAbi, TestERC20Abi } from '@aztec/l1-artifacts';
21
+ import { RollupAbi, SlasherAbi, TestERC20Abi } from '@aztec/l1-artifacts';
20
22
  import { SpamContract } from '@aztec/noir-test-contracts.js/Spam';
21
23
  import type { BootstrapNode } from '@aztec/p2p/bootstrap';
22
24
  import { createBootstrapNodeFromPrivateKey, getBootstrapNodeEnr } from '@aztec/p2p/test-helpers';
@@ -71,6 +73,7 @@ export class P2PNetworkTest {
71
73
 
72
74
  public deployedAccounts: InitialAccountData[] = [];
73
75
  public prefilledPublicData: PublicDataTreeLeaf[] = [];
76
+
74
77
  // The re-execution test needs a wallet and a spam contract
75
78
  public wallet?: AccountWalletWithSecretKey;
76
79
  public defaultAccountAddress?: AztecAddress;
@@ -349,7 +352,7 @@ export class P2PNetworkTest {
349
352
  }
350
353
 
351
354
  private async _sendDummyTx(l1Client: ExtendedViemWalletClient) {
352
- const l1TxUtils = new L1TxUtils(l1Client);
355
+ const l1TxUtils = createL1TxUtilsFromViemWallet(l1Client);
353
356
  return await l1TxUtils.sendAndMonitorTransaction({
354
357
  to: l1Client.account!.address,
355
358
  value: 1n,
@@ -392,7 +395,7 @@ export class P2PNetworkTest {
392
395
  async getContracts(): Promise<{
393
396
  rollup: RollupContract;
394
397
  slasherContract: GetContractReturnType<typeof SlasherAbi, ViemClient>;
395
- slashingProposer: GetContractReturnType<typeof EmpireSlashingProposerAbi, ViemClient>;
398
+ slashingProposer: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
396
399
  slashFactory: SlashFactoryContract;
397
400
  }> {
398
401
  if (!this.ctx.deployL1ContractsValues) {
@@ -410,11 +413,8 @@ export class P2PNetworkTest {
410
413
  client: this.ctx.deployL1ContractsValues.l1Client,
411
414
  });
412
415
 
413
- const slashingProposer = getContract({
414
- address: getAddress(await slasherContract.read.PROPOSER()),
415
- abi: EmpireSlashingProposerAbi,
416
- client: this.ctx.deployL1ContractsValues.l1Client,
417
- });
416
+ // Get the actual slashing proposer from rollup (which handles both empire and tally)
417
+ const slashingProposer = await rollup.getSlashingProposer();
418
418
 
419
419
  const slashFactory = new SlashFactoryContract(
420
420
  this.ctx.deployL1ContractsValues.l1Client,
@@ -12,16 +12,16 @@ import {
12
12
  retryUntil,
13
13
  } from '@aztec/aztec.js';
14
14
  import type { RollupCheatCodes } from '@aztec/aztec/testing';
15
- import type { RollupContract, ViemClient } from '@aztec/ethereum';
15
+ import type { EmpireSlashingProposerContract, RollupContract, TallySlashingProposerContract } from '@aztec/ethereum';
16
16
  import { timesAsync, unique } from '@aztec/foundation/collection';
17
- import type { EmpireSlashingProposerAbi } from '@aztec/l1-artifacts/EmpireSlashingProposerAbi';
17
+ import type { TestDateProvider } from '@aztec/foundation/timer';
18
18
  import type { SpamContract } from '@aztec/noir-test-contracts.js/Spam';
19
19
  import { TestContract, TestContractArtifact } from '@aztec/noir-test-contracts.js/Test';
20
20
  import { PXEService, createPXEService, getPXEServiceConfig as getRpcConfig } from '@aztec/pxe/server';
21
+ import { getRoundForOffense } from '@aztec/slasher';
22
+ import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
21
23
  import type { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
22
24
 
23
- import type { GetContractReturnType } from 'viem';
24
-
25
25
  import type { NodeContext } from '../fixtures/setup_p2p_test.js';
26
26
  import { submitTxsTo } from '../shared/submit-transactions.js';
27
27
 
@@ -107,25 +107,36 @@ export async function createPXEServiceAndPrepareTransactions(
107
107
  return { txs, pxeService: pxe, node };
108
108
  }
109
109
 
110
- export async function awaitProposalExecution(
111
- slashingProposer: GetContractReturnType<typeof EmpireSlashingProposerAbi, ViemClient>,
110
+ export function awaitProposalExecution(
111
+ slashingProposer: EmpireSlashingProposerContract | TallySlashingProposerContract,
112
112
  timeoutSeconds: number,
113
- ) {
114
- await retryUntil(
115
- async () => {
116
- const events = await slashingProposer.getEvents.PayloadSubmitted();
117
- if (events.length === 0) {
118
- return false;
119
- }
120
- const event = events[0];
121
- const roundNumber = event.args.round;
122
- const payload = event.args.payload;
123
- return roundNumber && payload;
124
- },
125
- 'payload submitted',
126
- timeoutSeconds,
127
- 1,
128
- );
113
+ logger: Logger,
114
+ ): Promise<bigint> {
115
+ return new Promise<bigint>((resolve, reject) => {
116
+ const timeout = setTimeout(() => {
117
+ logger.warn(`Timed out waiting for proposal execution`);
118
+ reject(new Error(`Timeout waiting for proposal execution after ${timeoutSeconds}s`));
119
+ }, timeoutSeconds * 1000);
120
+
121
+ if (slashingProposer.type === 'empire') {
122
+ const unwatch = slashingProposer.listenToPayloadSubmitted(args => {
123
+ logger.warn(`Proposal ${args.payload} from round ${args.round} executed`);
124
+ clearTimeout(timeout);
125
+ unwatch();
126
+ resolve(args.round);
127
+ });
128
+ } else if (slashingProposer.type === 'tally') {
129
+ const unwatch = slashingProposer.listenToRoundExecuted(args => {
130
+ logger.warn(`Slash from round ${args.round} executed`);
131
+ clearTimeout(timeout);
132
+ unwatch();
133
+ resolve(args.round);
134
+ });
135
+ } else {
136
+ clearTimeout(timeout);
137
+ reject(new Error(`Unknown slashing proposer type: ${(slashingProposer as any).type}`));
138
+ }
139
+ });
129
140
  }
130
141
 
131
142
  export async function awaitCommitteeExists({
@@ -148,6 +159,35 @@ export async function awaitCommitteeExists({
148
159
  return committee!;
149
160
  }
150
161
 
162
+ export async function awaitOffenseDetected({
163
+ logger,
164
+ nodeAdmin,
165
+ slashingRoundSize,
166
+ epochDuration,
167
+ }: {
168
+ nodeAdmin: AztecNodeAdmin;
169
+ logger: Logger;
170
+ slashingRoundSize: number;
171
+ epochDuration: number;
172
+ }) {
173
+ logger.info(`Waiting for an offense to be detected`);
174
+ const offenses = await retryUntil(
175
+ async () => {
176
+ const offenses = await nodeAdmin.getSlashOffenses('all');
177
+ if (offenses.length > 0) {
178
+ return offenses;
179
+ }
180
+ },
181
+ 'non-empty offenses',
182
+ 60,
183
+ );
184
+ logger.info(
185
+ `Hit ${offenses.length} offenses on rounds ${unique(offenses.map(o => getRoundForOffense(o, { slashingRoundSize, epochDuration })))}`,
186
+ offenses,
187
+ );
188
+ return offenses;
189
+ }
190
+
151
191
  /**
152
192
  * Await the committee to be slashed out of the validator set.
153
193
  * Currently assumes that the committee is the same size as the validator set.
@@ -161,52 +201,58 @@ export async function awaitCommitteeKicked({
161
201
  slashingRoundSize,
162
202
  aztecSlotDuration,
163
203
  logger,
164
- sendDummyTx,
204
+ dateProvider,
165
205
  }: {
166
206
  rollup: RollupContract;
167
207
  cheatCodes: RollupCheatCodes;
168
208
  committee: readonly `0x${string}`[];
169
209
  slashFactory: SlashFactoryContract;
170
- slashingProposer: GetContractReturnType<typeof EmpireSlashingProposerAbi, ViemClient>;
210
+ slashingProposer: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
171
211
  slashingRoundSize: number;
172
212
  aztecSlotDuration: number;
213
+ dateProvider: TestDateProvider;
173
214
  logger: Logger;
174
- sendDummyTx: () => Promise<void>;
175
215
  }) {
176
- logger.info(`Advancing epochs so slash payload gets deployed`);
177
- await cheatCodes.debugRollup();
178
- await cheatCodes.advanceToNextEpoch();
179
- await cheatCodes.advanceToNextEpoch();
216
+ if (!slashingProposer) {
217
+ throw new Error('No slashing proposer configured. Cannot test slashing.');
218
+ }
180
219
 
181
- // Await for the slash payload to be created and check that all committee members are slashed
182
- const slashPayloadEvents = await retryUntil(
183
- async () => {
184
- const events = await slashFactory.getSlashPayloadCreatedEvents();
185
- return events.length > 0 ? events : undefined;
186
- },
187
- 'slash payload created',
188
- 120,
189
- 1,
190
- );
191
- expect(slashPayloadEvents.length).toBe(1);
220
+ logger.info(`Advancing epochs so we start slashing`);
221
+ await cheatCodes.debugRollup();
222
+ await cheatCodes.advanceToNextEpoch({ updateDateProvider: dateProvider });
223
+ await cheatCodes.advanceToNextEpoch({ updateDateProvider: dateProvider });
192
224
 
193
- // 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)
194
- expect(unique(slashPayloadEvents[0].slashes.map(slash => slash.validator.toString()))).toHaveLength(committee.length);
225
+ // Await for the slash payload to be created if empire (no payload is created on tally until execution time)
226
+ if (slashingProposer.type === 'empire') {
227
+ const slashPayloadEvents = await retryUntil(
228
+ async () => {
229
+ const events = await slashFactory.getSlashPayloadCreatedEvents();
230
+ return events.length > 0 ? events : undefined;
231
+ },
232
+ 'slash payload created',
233
+ 120,
234
+ 1,
235
+ );
236
+ expect(slashPayloadEvents.length).toBe(1);
237
+ // 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)
238
+ expect(unique(slashPayloadEvents[0].slashes.map(slash => slash.validator.toString()))).toHaveLength(
239
+ committee.length,
240
+ );
241
+ }
195
242
 
196
243
  const attestersPre = await rollup.getAttesters();
197
244
  expect(attestersPre.length).toBe(committee.length);
198
245
 
199
246
  for (const attester of attestersPre) {
200
247
  const attesterInfo = await rollup.getAttesterView(attester);
201
- // Check that status isValidating
202
- expect(attesterInfo.status).toEqual(1);
248
+ expect(attesterInfo.status).toEqual(1); // Validating
203
249
  }
204
250
 
205
- logger.info(`Waiting for slash proposal to be executed`);
206
- await awaitProposalExecution(slashingProposer, slashingRoundSize * 2 * aztecSlotDuration);
251
+ const timeout = slashingRoundSize * 2 * aztecSlotDuration;
252
+ logger.info(`Waiting for slash to be executed (timeout ${timeout}s)`);
253
+ await awaitProposalExecution(slashingProposer, timeout, logger);
207
254
 
208
- // The attesters should still form the committee
209
- // but they should be reduced to the "living" status
255
+ // The attesters should still form the committee but they should be reduced to the "living" status
210
256
  await cheatCodes.debugRollup();
211
257
  const committeePostSlashing = await rollup.getCurrentEpochCommittee();
212
258
  expect(committeePostSlashing?.length).toBe(attestersPre.length);
@@ -214,18 +260,15 @@ export async function awaitCommitteeKicked({
214
260
  const attestersPostSlashing = await rollup.getAttesters();
215
261
  expect(attestersPostSlashing.length).toBe(0);
216
262
 
217
- // TODO(palla/slash): Reinstate this check if applies
218
- // for (const attester of attestersPre) {
219
- // const attesterInfo = await rollup.getAttesterView(attester);
220
- // // Check that status is Living
221
- // expect(attesterInfo.status).toEqual(2);
222
- // }
263
+ for (const attester of attestersPre) {
264
+ const attesterInfo = await rollup.getAttesterView(attester);
265
+ expect(attesterInfo.status).toEqual(2); // Living
266
+ }
223
267
 
268
+ logger.info(`Advancing two epochs to check current committee`);
224
269
  await cheatCodes.debugRollup();
225
- await cheatCodes.advanceToNextEpoch();
226
- await sendDummyTx();
227
- await cheatCodes.advanceToNextEpoch();
228
- await sendDummyTx();
270
+ await cheatCodes.advanceToNextEpoch({ updateDateProvider: dateProvider });
271
+ await cheatCodes.advanceToNextEpoch({ updateDateProvider: dateProvider });
229
272
  await cheatCodes.debugRollup();
230
273
 
231
274
  const committeeNextEpoch = await rollup.getCurrentEpochCommittee();
@@ -296,10 +296,10 @@ export class FullProverTest {
296
296
  ...this.context.aztecNodeConfig,
297
297
  txCollectionNodeRpcUrls: [],
298
298
  dataDirectory: undefined,
299
- proverId: this.proverAddress.toField(),
299
+ proverId: this.proverAddress,
300
300
  realProofs: this.realProofs,
301
301
  proverAgentCount: 2,
302
- publisherPrivateKey: new SecretValue(`0x${proverNodePrivateKey!.toString('hex')}` as const),
302
+ publisherPrivateKeys: [new SecretValue(`0x${proverNodePrivateKey!.toString('hex')}` as const)],
303
303
  proverNodeMaxPendingJobs: 100,
304
304
  proverNodeMaxParallelBlocksPerEpoch: 32,
305
305
  proverNodePollingIntervalMs: 100,
@@ -154,7 +154,7 @@ export async function createValidatorConfig(
154
154
  const attesterPrivateKey = bufferToHex(getPrivateKeyFromIndex(ATTESTER_PRIVATE_KEYS_START_INDEX + addressIndex)!);
155
155
 
156
156
  config.validatorPrivateKeys = new SecretValue([attesterPrivateKey]);
157
- config.publisherPrivateKey = new SecretValue(attesterPrivateKey);
157
+ config.publisherPrivateKeys = [new SecretValue(attesterPrivateKey)];
158
158
 
159
159
  const nodeConfig: AztecNodeConfig = {
160
160
  ...config,
@@ -8,6 +8,7 @@ import {
8
8
  type CompleteAddress,
9
9
  type ContractFunctionInteraction,
10
10
  DefaultWaitForProvenOpts,
11
+ EthAddress,
11
12
  type Logger,
12
13
  type PXE,
13
14
  type Wallet,
@@ -342,11 +343,14 @@ async function setupFromFresh(
342
343
  const publisherPrivKeyRaw = hdAccount.getHdKey().privateKey;
343
344
  const publisherPrivKey = publisherPrivKeyRaw === null ? null : Buffer.from(publisherPrivKeyRaw);
344
345
 
346
+ const l1Client = createExtendedL1Client([aztecNodeConfig.l1RpcUrls[0]], hdAccount, foundry);
347
+
345
348
  const validatorPrivKey = getPrivateKeyFromIndex(0);
346
349
  const proverNodePrivateKey = getPrivateKeyFromIndex(0);
347
350
 
348
- aztecNodeConfig.publisherPrivateKey = new SecretValue<`0x${string}`>(`0x${publisherPrivKey!.toString('hex')}`);
351
+ aztecNodeConfig.publisherPrivateKeys = [new SecretValue<`0x${string}`>(`0x${publisherPrivKey!.toString('hex')}`)];
349
352
  aztecNodeConfig.validatorPrivateKeys = new SecretValue([`0x${validatorPrivKey!.toString('hex')}`]);
353
+ aztecNodeConfig.coinbase = opts.coinbase ?? EthAddress.fromString(`${hdAccount.address}`);
350
354
 
351
355
  const ethCheatCodes = new EthCheatCodesWithState(aztecNodeConfig.l1RpcUrls);
352
356
 
@@ -361,7 +365,6 @@ async function setupFromFresh(
361
365
  opts.initialAccountFeeJuice,
362
366
  );
363
367
 
364
- const l1Client = createExtendedL1Client([aztecNodeConfig.l1RpcUrls[0]], hdAccount, foundry);
365
368
  await deployMulticall3(l1Client, logger);
366
369
 
367
370
  const deployL1ContractsValues = await setupL1Contracts(aztecNodeConfig.l1RpcUrls[0], hdAccount, logger, {
@@ -439,7 +442,7 @@ async function setupFromFresh(
439
442
  );
440
443
  await blobSink.start();
441
444
 
442
- logger.verbose('Creating and synching an aztec node...');
445
+ logger.info('Creating and synching an aztec node...');
443
446
  const aztecNode = await AztecNodeService.createAndSync(
444
447
  aztecNodeConfig,
445
448
  { telemetry, dateProvider },