From e4f02b38a3890f48b11c2fc3d9af7dcf621213d3 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 3 Jun 2026 10:20:31 +0100 Subject: [PATCH 1/7] fix: wrap rpc errors (#23794) Fix A-1126 --- .../src/l1/calldata_retriever.test.ts | 5 +- .../end-to-end/src/e2e_cheat_codes.test.ts | 7 ++- yarn-project/ethereum/src/client.test.ts | 55 +++++++++++++++++++ yarn-project/ethereum/src/client.ts | 40 +++++++++++++- .../ethereum/src/contracts/rollup.test.ts | 26 ++++++++- yarn-project/ethereum/src/contracts/rollup.ts | 34 +++++++++++- .../src/l1_tx_utils/l1_tx_utils.test.ts | 55 +++++++++++-------- .../src/l1_tx_utils/readonly_l1_tx_utils.ts | 3 +- yarn-project/ethereum/src/utils.ts | 18 ++++-- .../foundation/src/types/index.test.ts | 32 ++++++++++- yarn-project/foundation/src/types/index.ts | 19 +++++++ 11 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 yarn-project/ethereum/src/client.test.ts diff --git a/yarn-project/archiver/src/l1/calldata_retriever.test.ts b/yarn-project/archiver/src/l1/calldata_retriever.test.ts index 36f1db400daf..3f6e43fba6f4 100644 --- a/yarn-project/archiver/src/l1/calldata_retriever.test.ts +++ b/yarn-project/archiver/src/l1/calldata_retriever.test.ts @@ -1,3 +1,4 @@ +import { L1RpcError } from '@aztec/ethereum/client'; import { MULTI_CALL_3_ADDRESS, type ViemCommitteeAttestations, type ViemHeader } from '@aztec/ethereum/contracts'; import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types'; import { CheckpointNumber } from '@aztec/foundation/branded-types'; @@ -1002,7 +1003,9 @@ describe('CalldataRetriever', () => { const proposeCalldata = makeProposeCalldata(); // First call (trace_transaction) fails - debugClient.request.mockRejectedValueOnce(new Error('trace_transaction not supported')); + debugClient.request.mockRejectedValueOnce( + new L1RpcError('L1 RPC request failed', { cause: new Error('trace_transaction not supported') }), + ); // Second call (debug_traceTransaction) succeeds - returns root trace with nested calls debugClient.request.mockResolvedValueOnce({ diff --git a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts index 5a4646542537..b66f73c51a87 100644 --- a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts +++ b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts @@ -4,8 +4,9 @@ import { createExtendedL1Client } from '@aztec/ethereum/client'; import type { Anvil } from '@aztec/ethereum/test'; import type { ExtendedViemWalletClient } from '@aztec/ethereum/types'; import { DateProvider } from '@aztec/foundation/timer'; +import { getErrorCause } from '@aztec/foundation/types'; -import { parseEther } from 'viem'; +import { RpcRequestError, parseEther } from 'viem'; import { mnemonicToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; @@ -129,8 +130,8 @@ describe('e2e_cheat_codes', () => { }); // done with a try-catch because viem errors are noisy and we need to check just a small portion of the error. fail('should not be able to send funds from random address'); - } catch (e: any) { - expect(e.message).toContain('No Signer available'); + } catch (e: unknown) { + expect(getErrorCause(e, RpcRequestError)?.details).toContain('No Signer available'); } }); }); diff --git a/yarn-project/ethereum/src/client.test.ts b/yarn-project/ethereum/src/client.test.ts new file mode 100644 index 000000000000..118c88558ae6 --- /dev/null +++ b/yarn-project/ethereum/src/client.test.ts @@ -0,0 +1,55 @@ +import { startHttpRpcServer } from '@aztec/foundation/json-rpc/server'; + +import { type Server, createServer } from 'node:http'; +import { createPublicClient } from 'viem'; +import { foundry } from 'viem/chains'; + +import { L1RpcError, getL1RpcHttpStatus, isL1RpcHttpStatus, makeL1HttpTransport } from './client.js'; + +async function startRateLimitedL1Server(): Promise<{ server: Server; url: string }> { + const server = createServer((_req, res) => { + res.writeHead(429, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ jsonrpc: '2.0', id: 1, error: { code: -32005, message: 'rate limited' } })); + }); + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error('Expected L1 test server to listen on a TCP port'); + } + return { server, url: `http://127.0.0.1:${address.port}` }; +} + +describe('makeL1HttpTransport', () => { + let l1Server: Server | undefined; + let rpcHttpServer: Awaited> | undefined; + + afterEach(() => { + rpcHttpServer?.close(); + l1Server?.close(); + rpcHttpServer = undefined; + l1Server = undefined; + }); + + it('wraps transport errors while preserving the HTTP status in the cause chain', async () => { + const l1 = await startRateLimitedL1Server(); + l1Server = l1.server; + const l1Client = createPublicClient({ + chain: foundry, + transport: makeL1HttpTransport([l1.url]), + }); + + let error: unknown; + try { + await l1Client.getChainId(); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(L1RpcError); + expect(error).toMatchObject({ message: 'L1 RPC request failed' }); + expect(String(error)).toEqual('L1RpcError: L1 RPC request failed'); + expect(getL1RpcHttpStatus(error)).toBe(429); + expect(isL1RpcHttpStatus(error, 429)).toBe(true); + }); +}); diff --git a/yarn-project/ethereum/src/client.ts b/yarn-project/ethereum/src/client.ts index 010097d50894..43221b81f624 100644 --- a/yarn-project/ethereum/src/client.ts +++ b/yarn-project/ethereum/src/client.ts @@ -1,9 +1,13 @@ import type { Logger } from '@aztec/foundation/log'; import { retryUntil } from '@aztec/foundation/retry'; +import { getErrorCause } from '@aztec/foundation/types'; import { type Chain, + type FallbackTransport, type HDAccount, + HttpRequestError, + type HttpTransport, type LocalAccount, type PrivateKeyAccount, createPublicClient, @@ -31,9 +35,43 @@ type Config = { export type { Config as EthereumClientConfig }; +/** Error exposed by L1 RPC transports without including provider URLs in its message. */ +export class L1RpcError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'L1RpcError'; + } +} + /** Creates a viem fallback HTTP transport for the given L1 RPC URLs. */ export function makeL1HttpTransport(rpcUrls: string[], opts?: { timeout?: number }) { - return fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout }))); + return wrapL1RpcTransport(fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout })))); +} + +/** Returns the HTTP status from an L1 RPC error's cause chain, if one is available. */ +export function getL1RpcHttpStatus(err: unknown): number | undefined { + return getErrorCause(err, HttpRequestError)?.status; +} + +/** Returns true when an L1 RPC error's cause chain contains the given HTTP status. */ +export function isL1RpcHttpStatus(err: unknown, status: number): boolean { + return getL1RpcHttpStatus(err) === status; +} + +function wrapL1RpcTransport(transport: FallbackTransport): FallbackTransport { + const wrappedTransport: FallbackTransport = parameters => { + const fallbackTransport = transport(parameters); + const request: typeof fallbackTransport.request = async args => { + try { + return await fallbackTransport.request(args); + } catch (err) { + throw err instanceof L1RpcError ? err : new L1RpcError('L1 RPC request failed', { cause: err }); + } + }; + return { ...fallbackTransport, request }; + }; + + return wrappedTransport; } /** diff --git a/yarn-project/ethereum/src/contracts/rollup.test.ts b/yarn-project/ethereum/src/contracts/rollup.test.ts index 3c258eefc7ae..0e32dc63be7a 100644 --- a/yarn-project/ethereum/src/contracts/rollup.test.ts +++ b/yarn-project/ethereum/src/contracts/rollup.test.ts @@ -1,4 +1,4 @@ -import { getPublicClient } from '@aztec/ethereum/client'; +import { L1RpcError, getPublicClient } from '@aztec/ethereum/client'; import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types'; import { Buffer32 } from '@aztec/foundation/buffer'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -7,7 +7,8 @@ import { createLogger } from '@aztec/foundation/log'; import { DateProvider } from '@aztec/foundation/timer'; import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi'; -import type { Abi } from 'viem'; +import { jest } from '@jest/globals'; +import { type Abi, RpcRequestError, encodeErrorResult } from 'viem'; import { foundry } from 'viem/chains'; import { DefaultL1ContractsConfig } from '../config.js'; @@ -372,6 +373,27 @@ describe('Rollup', () => { }); }); + describe('committee helpers', () => { + it('handles wrapped insufficient validator set errors', async () => { + const data = encodeErrorResult({ + abi: RollupAbi, + errorName: 'ValidatorSelection__InsufficientValidatorSetSize', + args: [0n, 1n], + }); + using _simulateContractSpy = jest.spyOn(publicClient, 'simulateContract').mockRejectedValueOnce( + new L1RpcError('L1 RPC request failed', { + cause: new RpcRequestError({ + body: { method: 'eth_call', params: [] }, + error: { code: 3, data, message: 'execution reverted' }, + url: 'https://example.com/rpc', + }), + }), + ); + + await expect(rollup.getCurrentEpochCommittee()).resolves.toBeUndefined(); + }); + }); + describe('makeArchiveOverride', () => { it('creates state override that correctly sets archive for a checkpoint number', async () => { const checkpointNumber = CheckpointNumber(5); diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 75ba04c42ec2..60f3fcc1a1b7 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -6,6 +6,7 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import type { ViemSignature } from '@aztec/foundation/eth-signature'; import { createLogger } from '@aztec/foundation/log'; import { makeBackoff, retry } from '@aztec/foundation/retry'; +import { getErrorCause } from '@aztec/foundation/types'; import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi'; import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi'; import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage'; @@ -13,11 +14,14 @@ import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage'; import chunk from 'lodash.chunk'; import { type Account, + ContractFunctionRevertedError, type GetContractReturnType, type Hex, type Log, + RpcRequestError, type StateOverride, type WatchContractEventReturnType, + decodeErrorResult, encodeAbiParameters, encodeFunctionData, getContract, @@ -230,6 +234,32 @@ export type CheckpointProposedArgs = { /** Log type for CheckpointProposed events. */ export type CheckpointProposedLog = L1EventLog; +const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize'; + +function isValidatorSelectionError(err: unknown, errorName: string): boolean { + return ( + getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName || + decodeRpcRequestErrorName(err) === errorName + ); +} + +function decodeRpcRequestErrorName(err: unknown): string | undefined { + const data = getErrorCause(err, RpcRequestError)?.data; + if (!isHexString(data)) { + return undefined; + } + + try { + return decodeErrorResult({ abi: RollupAbi, data }).errorName; + } catch { + return undefined; + } +} + +function isHexString(value: unknown): value is Hex { + return typeof value === 'string' && value.startsWith('0x'); +} + export class RollupContract { private readonly rollup: GetContractReturnType; private readonly logger = createLogger('ethereum:rollup'); @@ -588,7 +618,7 @@ export class RollupContract { args: [timestamp], }) .catch(e => { - if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) { + if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) { return { result: undefined }; } throw e; @@ -618,7 +648,7 @@ export class RollupContract { args: [], }) .catch(e => { - if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) { + if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) { return { result: undefined }; } throw e; diff --git a/yarn-project/ethereum/src/l1_tx_utils/l1_tx_utils.test.ts b/yarn-project/ethereum/src/l1_tx_utils/l1_tx_utils.test.ts index bb88af483530..1936be416b94 100644 --- a/yarn-project/ethereum/src/l1_tx_utils/l1_tx_utils.test.ts +++ b/yarn-project/ethereum/src/l1_tx_utils/l1_tx_utils.test.ts @@ -7,6 +7,7 @@ import { createLogger } from '@aztec/foundation/log'; import { retryFastUntil, retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; import { DateProvider, TestDateProvider } from '@aztec/foundation/timer'; +import { getErrorCause } from '@aztec/foundation/types'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -16,6 +17,8 @@ import { type BlockTag, type GetTransactionParameters, type Hex, + MethodNotFoundRpcError, + RpcRequestError, TransactionNotFoundError, type TransactionSerializable, createPublicClient, @@ -25,7 +28,7 @@ import { import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; -import { createExtendedL1Client, getPublicClient } from '../client.js'; +import { L1RpcError, createExtendedL1Client, getPublicClient } from '../client.js'; import { EthCheatCodes } from '../test/eth_cheat_codes.js'; import type { Anvil } from '../test/start_anvil.js'; import { startAnvil } from '../test/start_anvil.js'; @@ -904,28 +907,15 @@ describe('L1TxUtils', () => { value: 0n, }); fail('Should have thrown'); - } catch (err: any) { - const res = err; - const { message } = res; - // Verify the error contains actual newlines, not escaped \n - expect(message).not.toContain('\\n'); - expect(message.split('\n').length).toBeGreaterThan(1); - - // Check that we have the key error information - expect(message).toContain('max fee per gas less than block base fee'); - - // Check request body formatting if present - if (message.includes('Request body:')) { - const bodyStart = message.indexOf('Request body:'); - const body = message.slice(bodyStart); - expect(body).toContain('eth_sendRawTransaction'); - - // TODO: Fix this test. We no longer generate an error that gets truncated - // Check params are truncated if too long - // if (body.includes('0x')) { - // expect(body).toContain('...'); - // } - } + } catch (err: unknown) { + expect(err).toBeInstanceOf(Error); + expect((err as Error).message).toContain('L1 RPC request failed'); + + const rpcError = getErrorCause(err, RpcRequestError); + expect(rpcError?.details).toContain('max fee per gas less than block base fee'); + + const metaMessages = rpcError?.metaMessages?.join('\n') ?? ''; + expect(metaMessages).toContain('eth_sendRawTransaction'); } }, 10_000); @@ -1904,6 +1894,25 @@ describe('L1TxUtils', () => { expect(readOnlyUtils).not.toHaveProperty('sendAndMonitorTransaction'); }); + it('uses fallback gas estimate when wrapped simulateBlocks error reports unsupported method', async () => { + const readOnlyUtils = new ReadOnlyL1TxUtils(publicClient, logger, dateProvider); + using _simulateBlocksSpy = jest.spyOn(publicClient, 'simulateBlocks').mockRejectedValue( + new L1RpcError('L1 RPC request failed', { + cause: new MethodNotFoundRpcError(new Error('method not found'), { method: 'eth_simulateV1' }), + }), + ); + + await expect( + readOnlyUtils.simulate( + { to: '0x1234567890123456789012345678901234567890', data: '0xabcdef', value: 0n }, + undefined, + undefined, + undefined, + { fallbackGasEstimate: 123n }, + ), + ).resolves.toEqual({ gasUsed: 123n, result: '0x' }); + }); + it('L1TxUtils can be instantiated with wallet client and has write methods', () => { const l1TxUtils = createL1TxUtils(walletClient, { logger }); expect(l1TxUtils).toBeDefined(); diff --git a/yarn-project/ethereum/src/l1_tx_utils/readonly_l1_tx_utils.ts b/yarn-project/ethereum/src/l1_tx_utils/readonly_l1_tx_utils.ts index b54b6d027b64..cea21480f4fd 100644 --- a/yarn-project/ethereum/src/l1_tx_utils/readonly_l1_tx_utils.ts +++ b/yarn-project/ethereum/src/l1_tx_utils/readonly_l1_tx_utils.ts @@ -3,6 +3,7 @@ import type { EthAddress } from '@aztec/foundation/eth-address'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { makeBackoff, retry } from '@aztec/foundation/retry'; import { DateProvider } from '@aztec/foundation/timer'; +import { getErrorCause } from '@aztec/foundation/types'; import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi'; import pickBy from 'lodash.pickby'; @@ -395,7 +396,7 @@ export class ReadOnlyL1TxUtils { this.logger?.debug(`L1 transaction simulation succeeded`, { ...result[0].calls[0] }); return { gasUsed: result[0].gasUsed, result: result[0].calls[0].data as `0x${string}` }; } catch (err) { - if (err instanceof MethodNotFoundRpcError || err instanceof MethodNotSupportedRpcError) { + if (getErrorCause(err, MethodNotFoundRpcError) || getErrorCause(err, MethodNotSupportedRpcError)) { if (gasConfig.fallbackGasEstimate) { this.logger?.warn( `Node does not support eth_simulateV1 API. Using fallback gas estimate: ${gasConfig.fallbackGasEstimate}`, diff --git a/yarn-project/ethereum/src/utils.ts b/yarn-project/ethereum/src/utils.ts index 3079fbaf457a..543df2182bc9 100644 --- a/yarn-project/ethereum/src/utils.ts +++ b/yarn-project/ethereum/src/utils.ts @@ -27,8 +27,8 @@ export interface L2Claim { export class FormattedViemError extends Error { metaMessages?: any[]; - constructor(message: string, metaMessages?: any[]) { - super(message); + constructor(message: string, metaMessages?: any[], options?: ErrorOptions) { + super(message, options); this.name = 'FormattedViemError'; this.metaMessages = metaMessages; } @@ -197,6 +197,8 @@ export function formatViemError(error: any, abi: Abi = ErrorsAbi): FormattedViem return error; } + const originalError = error; + // First try to decode as a custom error using the ABI try { const data = getNestedErrorData(error); @@ -207,7 +209,9 @@ export function formatViemError(error: any, abi: Abi = ErrorsAbi): FormattedViem data: data as Hex, }); if (decoded) { - return new FormattedViemError(`${decoded.errorName}(${decoded.args?.join(', ') ?? ''})`, error?.metaMessages); + return new FormattedViemError(`${decoded.errorName}(${decoded.args?.join(', ') ?? ''})`, error?.metaMessages, { + cause: originalError, + }); } } @@ -224,7 +228,7 @@ export function formatViemError(error: any, abi: Abi = ErrorsAbi): FormattedViem revertError.metaMessages && revertError.metaMessages?.length > 1 ? revertError.metaMessages[1].trimStart() : ''; - return new FormattedViemError(`${errorName}${args}`, error?.metaMessages); + return new FormattedViemError(`${errorName}${args}`, error?.metaMessages, { cause: originalError }); } } } catch { @@ -247,10 +251,12 @@ export function formatViemError(error: any, abi: Abi = ErrorsAbi): FormattedViem // If it's a regular Error instance, return it with its message if (error instanceof Error) { - return new FormattedViemError(truncateErrorMessage(error.message), (error as any)?.metaMessages); + return new FormattedViemError(truncateErrorMessage(error.message), (error as any)?.metaMessages, { + cause: originalError, + }); } - return new FormattedViemError(truncateErrorMessage(String(error))); + return new FormattedViemError(truncateErrorMessage(String(error)), undefined, { cause: originalError }); } function stripAbis(obj: any) { diff --git a/yarn-project/foundation/src/types/index.test.ts b/yarn-project/foundation/src/types/index.test.ts index 53ffe8bda5c5..f9c7ffcb6d1b 100644 --- a/yarn-project/foundation/src/types/index.test.ts +++ b/yarn-project/foundation/src/types/index.test.ts @@ -1,4 +1,4 @@ -import { isErrorClass } from './index.js'; +import { getErrorCause, isErrorClass } from './index.js'; describe('isErrorClass', () => { class CustomError extends Error { @@ -34,3 +34,33 @@ describe('isErrorClass', () => { expect(isErrorClass(error, CustomError)).toBe(false); }); }); + +describe('getErrorCause', () => { + class RootError extends Error { + constructor(cause?: unknown) { + super('root error', { cause }); + this.name = 'RootError'; + } + } + + class CauseError extends Error { + constructor() { + super('cause error'); + this.name = 'CauseError'; + } + } + + it('returns typed errors from the cause chain', () => { + const cause = new CauseError(); + const middle = new Error('middle', { cause }); + const error = new RootError(middle); + + expect(getErrorCause(error, RootError)).toBe(error); + expect(getErrorCause(error, CauseError)).toBe(cause); + }); + + it('returns undefined when the cause chain does not contain the error class', () => { + expect(getErrorCause(new Error('plain'), CauseError)).toBeUndefined(); + expect(getErrorCause('plain', CauseError)).toBeUndefined(); + }); +}); diff --git a/yarn-project/foundation/src/types/index.ts b/yarn-project/foundation/src/types/index.ts index 7d76aac4c64c..6a65c7aeed0b 100644 --- a/yarn-project/foundation/src/types/index.ts +++ b/yarn-project/foundation/src/types/index.ts @@ -29,6 +29,25 @@ export function isErrorClass(value: unknown, errorClass: new (. return value instanceof errorClass || (value instanceof Error && value.name === errorClass.name); } +const MAX_ERR_DEPTH = 10; + +/** Returns the first error in the cause chain matching the given error class. */ +export function getErrorCause(err: unknown, errorClass: new (...args: any[]) => T): T | undefined { + let current = err; + for (let i = 0; current !== undefined && current !== null && i < MAX_ERR_DEPTH; i++) { + if (isErrorClass(current, errorClass)) { + return current; + } + + if (typeof current === 'object' && Object.hasOwn(current, 'cause')) { + current = (current as { cause: unknown }).cause; + } else { + return undefined; + } + } + return undefined; +} + /** Resolves a record-like type. Lifted from viem. */ export type Prettify = { [K in keyof T]: T[K]; From 9adeda6833783964eae56021603f90ec02f5003d Mon Sep 17 00:00:00 2001 From: Facundo Date: Wed, 3 Jun 2026 10:32:23 +0100 Subject: [PATCH 2/7] chore(p2p): BlockTxsRequest comment (#23818) . --- .../services/reqresp/protocols/block_txs/block_txs_reqresp.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts index aaa7bb795a0f..1ba3395452ae 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts @@ -30,7 +30,8 @@ export class BlockTxsRequest { // block (proposal) with the same archive root but different txs. We include a commitment to the tx hashes // so that the responder can verify this (for its own sake, and not be penalized). readonly blockTxHashesCommitment: Buffer32, - // Hashes of txs we are requesting (optional). Used as a fallback if the peer doesn't have the block. + // Explicit hashes of txs we are requesting (optional). The peer will try to serve these txs even if + // it doesn't have the block. If the requester lists txs from the block here, this serves as a fallback. // Note: if a hash here is NOT part of the block (proposal), the peer can decide to return it or not. readonly txHashes: TxHashArray, ) {} From 3aca31125b02d0371aa8e376a8c9ec56360d4b7d Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 3 Jun 2026 13:32:54 +0100 Subject: [PATCH 3/7] chore: improve pod scheduling (#23820) - replace hard requirement to run on 2 core machines with affinity - replace hard requirement to run on spots with affinity - run full-nodes on spots - p2p bootstrap smaller CPU requests. Fix A-1140 and A-1141 --- .../values/bot-resources-prod.yaml | 20 ++++++++++--- .../values/full-node-resources-prod.yaml | 30 ++++++++++++++++++- .../values/p2p-bootstrap-resources-dev.yaml | 6 +++- .../values/p2p-bootstrap-resources-prod.yaml | 14 ++------- .../values/prover-resources-prod.yaml | 24 +++++++++++++-- .../values/rpc-resources-prod.yaml | 12 +++++++- .../values/validator-resources-prod.yaml | 1 - .../values/validator-resources-spot.yaml | 20 ++++++++++--- 8 files changed, 102 insertions(+), 25 deletions(-) diff --git a/spartan/terraform/deploy-aztec-infra/values/bot-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/bot-resources-prod.yaml index 66da7f06194a..e939bd8fdd46 100644 --- a/spartan/terraform/deploy-aztec-infra/values/bot-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/bot-resources-prod.yaml @@ -2,16 +2,28 @@ bot: nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" - pool: "spot" affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - - key: cloud.google.com/gke-spot - operator: Exists + - key: cores + operator: NotIn + values: + - "32" + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: cores + operator: In + values: + - "2" + - key: pool + operator: In + values: + - spot tolerations: - key: "cloud.google.com/gke-spot" diff --git a/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml index 841620706871..8304ff84be3b 100644 --- a/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml @@ -1,7 +1,35 @@ nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" + +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cores + operator: NotIn + values: + - "32" + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: cores + operator: In + values: + - "2" + - key: pool + operator: In + values: + - spot + +tolerations: + - key: "cloud.google.com/gke-spot" + operator: "Equal" + value: "true" + effect: "NoSchedule" + replicaCount: 1 node: diff --git a/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-dev.yaml b/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-dev.yaml index 818c2c7d3b98..4e81468c7975 100644 --- a/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-dev.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-dev.yaml @@ -1,7 +1,11 @@ +nodeSelector: + local-ssd: "false" + node-type: "network" + node: resources: requests: - cpu: "0.25" + cpu: "0.1" memory: "0.5Gi" limits: cpu: "0.5" diff --git a/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-prod.yaml index 51ee11e621f7..7342d3d7b2ff 100644 --- a/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/p2p-bootstrap-resources-prod.yaml @@ -5,22 +5,14 @@ nodeSelector: node: resources: requests: - cpu: "0.5" + cpu: "0.1" memory: "0.5Gi" limits: cpu: "1" memory: "1Gi" persistence: - enabled: true + enabled: false statefulSet: - enabled: true - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 4Gi + enabled: false diff --git a/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml index 4082073c9f98..a021502eacf6 100644 --- a/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml @@ -11,7 +11,17 @@ node: nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" + + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: cores + operator: In + values: + - "2" persistence: enabled: true @@ -31,7 +41,17 @@ broker: nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" + + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: cores + operator: In + values: + - "2" persistence: enabled: true diff --git a/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml index ea2ea6d68309..7da41db45281 100644 --- a/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml @@ -1,7 +1,17 @@ nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" + +affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: cores + operator: In + values: + - "2" replicaCount: 1 diff --git a/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml index fa1f7db6b30d..22e549df1eba 100644 --- a/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml @@ -2,7 +2,6 @@ validator: nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" node: resources: requests: diff --git a/spartan/terraform/deploy-aztec-infra/values/validator-resources-spot.yaml b/spartan/terraform/deploy-aztec-infra/values/validator-resources-spot.yaml index 2699f98765f9..7abf21258365 100644 --- a/spartan/terraform/deploy-aztec-infra/values/validator-resources-spot.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/validator-resources-spot.yaml @@ -2,16 +2,28 @@ validator: nodeSelector: local-ssd: "false" node-type: "network" - cores: "2" - pool: "spot" affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - - key: cloud.google.com/gke-spot - operator: Exists + - key: cores + operator: NotIn + values: + - "32" + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: cores + operator: In + values: + - "2" + - key: pool + operator: In + values: + - spot tolerations: - key: "cloud.google.com/gke-spot" From 780b3638015ccaa331709203a4161a3c56940513 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 3 Jun 2026 15:52:48 +0100 Subject: [PATCH 4/7] chore: setup custom testnet coinbase (#23824) . --- .../scripts/setup-attester-keystore.sh | 4 +- spartan/environments/testnet.env | 1 + spartan/scripts/deploy_network.sh | 1 + spartan/terraform/deploy-aztec-infra/main.tf | 119 +++++++++--------- .../terraform/deploy-aztec-infra/variables.tf | 7 ++ 5 files changed, 72 insertions(+), 60 deletions(-) diff --git a/spartan/aztec-node/scripts/setup-attester-keystore.sh b/spartan/aztec-node/scripts/setup-attester-keystore.sh index a9cc44ed4c6b..74b72160b2f3 100644 --- a/spartan/aztec-node/scripts/setup-attester-keystore.sh +++ b/spartan/aztec-node/scripts/setup-attester-keystore.sh @@ -95,9 +95,11 @@ for ((v = 0; v < VALIDATORS_PER_NODE; v++)); do attester="${private_keys[$v]}" fi + coinbase="${COINBASE:-$attester}" + validators_json+="{ \"attester\": \"$attester\", - \"coinbase\": \"$attester\", + \"coinbase\": \"$coinbase\", \"feeRecipient\": \"0x0000000000000000000000000000000000000000000000000000000000000000\" }" done diff --git a/spartan/environments/testnet.env b/spartan/environments/testnet.env index b6170953e3d2..c25b3c552fe6 100644 --- a/spartan/environments/testnet.env +++ b/spartan/environments/testnet.env @@ -80,6 +80,7 @@ VALIDATOR_PUBLISHERS_PER_REPLICA=8 VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX=5000 VALIDATOR_HA_REPLICAS=1 VALIDATOR_RESOURCE_PROFILE="prod" +VALIDATOR_COINBASE="0x36502A83735ED62671B55858809703898cDE4f95" PUBLISHERS_PER_PROVER=2 PROVER_PUBLISHER_MNEMONIC_START_INDEX=8000 diff --git a/spartan/scripts/deploy_network.sh b/spartan/scripts/deploy_network.sh index 7d7fdcb7b43b..e81d90a35964 100755 --- a/spartan/scripts/deploy_network.sh +++ b/spartan/scripts/deploy_network.sh @@ -569,6 +569,7 @@ VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX = ${VALIDATOR_PUBLISHER_MNEMONIC_START_ VALIDATORS_PER_NODE = ${VALIDATORS_PER_NODE} VALIDATOR_REPLICAS = ${VALIDATOR_REPLICAS} VALIDATOR_PUBLISHERS_PER_REPLICA = ${VALIDATOR_PUBLISHERS_PER_REPLICA} +VALIDATOR_COINBASE = $(tf_str "${VALIDATOR_COINBASE:-}") VALIDATOR_HA_REPLICAS = ${VALIDATOR_HA_REPLICAS} VALIDATOR_HA_REPLICA_COUNT = ${VALIDATOR_HA_REPLICA_COUNT:-null} VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H = ${VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H} diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index a4fce4e3783f..ab7f209e8ce0 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -179,65 +179,66 @@ locals { } validator_common_settings = { - "validator.service.p2p.nodePortEnabled" = var.P2P_NODEPORT_ENABLED - "validator.web3signerUrl" = "http://${var.RELEASE_PREFIX}-signer-web3signer.${var.NAMESPACE}.svc.cluster.local:9000/" - "validator.mnemonic" = var.VALIDATOR_MNEMONIC - "validator.mnemonicStartIndex" = var.VALIDATOR_MNEMONIC_START_INDEX - "validator.validatorsPerNode" = var.VALIDATORS_PER_NODE - "validator.publishersPerReplica" = var.VALIDATOR_PUBLISHERS_PER_REPLICA - "validator.publisherMnemonicStartIndex" = var.VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX - "validator.sentinel.enabled" = var.SENTINEL_ENABLED - "validator.slash.inactivityTargetPercentage" = var.SLASH_INACTIVITY_TARGET_PERCENTAGE - "validator.slash.inactivityPenalty" = var.SLASH_INACTIVITY_PENALTY - "validator.slash.dataWithholdingPenalty" = var.SLASH_DATA_WITHHOLDING_PENALTY - "validator.slash.dataWithholdingToleranceSlots" = var.SLASH_DATA_WITHHOLDING_TOLERANCE_SLOTS - "validator.slash.proposeInvalidAttestationsPenalty" = var.SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY - "validator.slash.duplicateProposalPenalty" = var.SLASH_DUPLICATE_PROPOSAL_PENALTY - "validator.slash.duplicateAttestationPenalty" = var.SLASH_DUPLICATE_ATTESTATION_PENALTY - "validator.slash.proposeDescendantOfCheckpointWithInvalidAttestationsPenalty" = var.SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY - "validator.slash.attestInvalidCheckpointProposalPenalty" = var.SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY - "validator.slash.unknownPenalty" = var.SLASH_UNKNOWN_PENALTY - "validator.slash.invalidBlockPenalty" = var.SLASH_INVALID_BLOCK_PENALTY - "validator.slash.invalidCheckpointProposalPenalty" = var.SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY - "validator.slash.offenseExpirationRounds" = var.SLASH_OFFENSE_EXPIRATION_ROUNDS - "validator.slash.maxPayloadSize" = var.SLASH_MAX_PAYLOAD_SIZE - "validator.node.env.TRANSACTIONS_DISABLED" = var.TRANSACTIONS_DISABLED - "validator.node.env.DEBUG_FORCE_TX_PROOF_VERIFICATION" = var.DEBUG_FORCE_TX_PROOF_VERIFICATION - "validator.node.env.KEY_INDEX_START" = var.VALIDATOR_MNEMONIC_START_INDEX - "validator.node.env.PUBLISHER_KEY_INDEX_START" = var.VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX - "validator.node.env.VALIDATORS_PER_NODE" = var.VALIDATORS_PER_NODE - "validator.node.env.VALIDATOR_PUBLISHERS_PER_REPLICA" = var.VALIDATOR_PUBLISHERS_PER_REPLICA - "validator.node.proverRealProofs" = var.PROVER_REAL_PROOFS - "validator.node.env.SEQ_MIN_TX_PER_BLOCK" = var.SEQ_MIN_TX_PER_BLOCK - "validator.node.env.SEQ_MAX_TX_PER_BLOCK" = var.SEQ_MAX_TX_PER_BLOCK - "validator.node.env.SEQ_MAX_TX_PER_CHECKPOINT" = var.SEQ_MAX_TX_PER_CHECKPOINT - "validator.node.env.P2P_MAX_PENDING_TX_COUNT" = var.P2P_MAX_PENDING_TX_COUNT - "validator.node.env.SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER" = var.SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER - "validator.node.env.SEQ_BLOCK_DURATION_MS" = var.SEQ_BLOCK_DURATION_MS - "validator.node.env.SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT" = var.SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT - "validator.node.env.SEQ_BUILD_CHECKPOINT_IF_EMPTY" = var.SEQ_BUILD_CHECKPOINT_IF_EMPTY - "validator.node.env.AZTEC_EPOCHS_LAG" = var.AZTEC_EPOCHS_LAG - "validator.node.env.SEQ_ENFORCE_TIME_TABLE" = var.SEQ_ENFORCE_TIME_TABLE - "validator.node.env.P2P_TX_POOL_DELETE_TXS_AFTER_REORG" = var.P2P_TX_POOL_DELETE_TXS_AFTER_REORG - "validator.node.env.L1_PRIORITY_FEE_BUMP_PERCENTAGE" = var.VALIDATOR_L1_PRIORITY_FEE_BUMP_PERCENTAGE - "validator.node.env.L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE" = var.VALIDATOR_L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE - "validator.node.env.BLOB_ALLOW_EMPTY_SOURCES" = var.BLOB_ALLOW_EMPTY_SOURCES - "validator.node.env.PROVER_TEST_VERIFICATION_DELAY_MS" = var.PROVER_TEST_VERIFICATION_DELAY_MS - "validator.node.env.BB_CHONK_VERIFY_MAX_BATCH" = var.BB_CHONK_VERIFY_MAX_BATCH - "validator.node.env.BB_CHONK_VERIFY_BATCH_CONCURRENCY" = var.BB_CHONK_VERIFY_BATCH_CONCURRENCY - "validator.node.env.DEBUG_P2P_INSTRUMENT_MESSAGES" = var.DEBUG_P2P_INSTRUMENT_MESSAGES - "validator.node.secret.envEnabled" = true - "validator.node.secret.mnemonic" = var.VALIDATOR_MNEMONIC - "validator.node.secret.mnemonicIndex" = var.VALIDATOR_MNEMONIC_START_INDEX - "validator.node.env.P2P_GOSSIPSUB_D" = var.P2P_GOSSIPSUB_D - "validator.node.env.P2P_GOSSIPSUB_DLO" = var.P2P_GOSSIPSUB_DLO - "validator.node.env.P2P_GOSSIPSUB_DHI" = var.P2P_GOSSIPSUB_DHI - "validator.node.env.P2P_DROP_TX_CHANCE" = var.P2P_DROP_TX_CHANCE - "validator.node.env.WS_NUM_HISTORIC_CHECKPOINTS" = var.WS_NUM_HISTORIC_CHECKPOINTS - "validator.node.env.TX_COLLECTION_FILE_STORE_URLS" = var.TX_COLLECTION_FILE_STORE_URLS - "validator.node.env.SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT" = var.SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT - "validator.node.env.L1_TX_FAILED_STORE" = var.L1_TX_FAILED_STORE - "validator.node.adminApiKeyHash" = var.ADMIN_API_KEY_HASH + "validator.service.p2p.nodePortEnabled" = var.P2P_NODEPORT_ENABLED + "validator.web3signerUrl" = "http://${var.RELEASE_PREFIX}-signer-web3signer.${var.NAMESPACE}.svc.cluster.local:9000/" + "validator.mnemonic" = var.VALIDATOR_MNEMONIC + "validator.mnemonicStartIndex" = var.VALIDATOR_MNEMONIC_START_INDEX + "validator.validatorsPerNode" = var.VALIDATORS_PER_NODE + "validator.publishersPerReplica" = var.VALIDATOR_PUBLISHERS_PER_REPLICA + "validator.publisherMnemonicStartIndex" = var.VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX + "validator.node.env.COINBASE" = var.VALIDATOR_COINBASE + "validator.sentinel.enabled" = var.SENTINEL_ENABLED + "validator.slash.inactivityTargetPercentage" = var.SLASH_INACTIVITY_TARGET_PERCENTAGE + "validator.slash.inactivityPenalty" = var.SLASH_INACTIVITY_PENALTY + "validator.slash.dataWithholdingPenalty" = var.SLASH_DATA_WITHHOLDING_PENALTY + "validator.slash.dataWithholdingToleranceSlots" = var.SLASH_DATA_WITHHOLDING_TOLERANCE_SLOTS + "validator.slash.proposeInvalidAttestationsPenalty" = var.SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY + "validator.slash.duplicateProposalPenalty" = var.SLASH_DUPLICATE_PROPOSAL_PENALTY + "validator.slash.duplicateAttestationPenalty" = var.SLASH_DUPLICATE_ATTESTATION_PENALTY + "validator.slash.proposeDescendantOfCheckpointWithInvalidAttestationsPenalty" = var.SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY + "validator.slash.attestInvalidCheckpointProposalPenalty" = var.SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY + "validator.slash.unknownPenalty" = var.SLASH_UNKNOWN_PENALTY + "validator.slash.invalidBlockPenalty" = var.SLASH_INVALID_BLOCK_PENALTY + "validator.slash.invalidCheckpointProposalPenalty" = var.SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY + "validator.slash.offenseExpirationRounds" = var.SLASH_OFFENSE_EXPIRATION_ROUNDS + "validator.slash.maxPayloadSize" = var.SLASH_MAX_PAYLOAD_SIZE + "validator.node.env.TRANSACTIONS_DISABLED" = var.TRANSACTIONS_DISABLED + "validator.node.env.DEBUG_FORCE_TX_PROOF_VERIFICATION" = var.DEBUG_FORCE_TX_PROOF_VERIFICATION + "validator.node.env.KEY_INDEX_START" = var.VALIDATOR_MNEMONIC_START_INDEX + "validator.node.env.PUBLISHER_KEY_INDEX_START" = var.VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX + "validator.node.env.VALIDATORS_PER_NODE" = var.VALIDATORS_PER_NODE + "validator.node.env.VALIDATOR_PUBLISHERS_PER_REPLICA" = var.VALIDATOR_PUBLISHERS_PER_REPLICA + "validator.node.proverRealProofs" = var.PROVER_REAL_PROOFS + "validator.node.env.SEQ_MIN_TX_PER_BLOCK" = var.SEQ_MIN_TX_PER_BLOCK + "validator.node.env.SEQ_MAX_TX_PER_BLOCK" = var.SEQ_MAX_TX_PER_BLOCK + "validator.node.env.SEQ_MAX_TX_PER_CHECKPOINT" = var.SEQ_MAX_TX_PER_CHECKPOINT + "validator.node.env.P2P_MAX_PENDING_TX_COUNT" = var.P2P_MAX_PENDING_TX_COUNT + "validator.node.env.SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER" = var.SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER + "validator.node.env.SEQ_BLOCK_DURATION_MS" = var.SEQ_BLOCK_DURATION_MS + "validator.node.env.SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT" = var.SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT + "validator.node.env.SEQ_BUILD_CHECKPOINT_IF_EMPTY" = var.SEQ_BUILD_CHECKPOINT_IF_EMPTY + "validator.node.env.AZTEC_EPOCHS_LAG" = var.AZTEC_EPOCHS_LAG + "validator.node.env.SEQ_ENFORCE_TIME_TABLE" = var.SEQ_ENFORCE_TIME_TABLE + "validator.node.env.P2P_TX_POOL_DELETE_TXS_AFTER_REORG" = var.P2P_TX_POOL_DELETE_TXS_AFTER_REORG + "validator.node.env.L1_PRIORITY_FEE_BUMP_PERCENTAGE" = var.VALIDATOR_L1_PRIORITY_FEE_BUMP_PERCENTAGE + "validator.node.env.L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE" = var.VALIDATOR_L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE + "validator.node.env.BLOB_ALLOW_EMPTY_SOURCES" = var.BLOB_ALLOW_EMPTY_SOURCES + "validator.node.env.PROVER_TEST_VERIFICATION_DELAY_MS" = var.PROVER_TEST_VERIFICATION_DELAY_MS + "validator.node.env.BB_CHONK_VERIFY_MAX_BATCH" = var.BB_CHONK_VERIFY_MAX_BATCH + "validator.node.env.BB_CHONK_VERIFY_BATCH_CONCURRENCY" = var.BB_CHONK_VERIFY_BATCH_CONCURRENCY + "validator.node.env.DEBUG_P2P_INSTRUMENT_MESSAGES" = var.DEBUG_P2P_INSTRUMENT_MESSAGES + "validator.node.secret.envEnabled" = true + "validator.node.secret.mnemonic" = var.VALIDATOR_MNEMONIC + "validator.node.secret.mnemonicIndex" = var.VALIDATOR_MNEMONIC_START_INDEX + "validator.node.env.P2P_GOSSIPSUB_D" = var.P2P_GOSSIPSUB_D + "validator.node.env.P2P_GOSSIPSUB_DLO" = var.P2P_GOSSIPSUB_DLO + "validator.node.env.P2P_GOSSIPSUB_DHI" = var.P2P_GOSSIPSUB_DHI + "validator.node.env.P2P_DROP_TX_CHANCE" = var.P2P_DROP_TX_CHANCE + "validator.node.env.WS_NUM_HISTORIC_CHECKPOINTS" = var.WS_NUM_HISTORIC_CHECKPOINTS + "validator.node.env.TX_COLLECTION_FILE_STORE_URLS" = var.TX_COLLECTION_FILE_STORE_URLS + "validator.node.env.SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT" = var.SEQ_SKIP_CHECKPOINT_PUBLISH_PERCENT + "validator.node.env.L1_TX_FAILED_STORE" = var.L1_TX_FAILED_STORE + "validator.node.adminApiKeyHash" = var.ADMIN_API_KEY_HASH } # Note: nonsensitive() is required here because helm_releases is used in for_each, diff --git a/spartan/terraform/deploy-aztec-infra/variables.tf b/spartan/terraform/deploy-aztec-infra/variables.tf index f1f715c9a535..b0069565e797 100644 --- a/spartan/terraform/deploy-aztec-infra/variables.tf +++ b/spartan/terraform/deploy-aztec-infra/variables.tf @@ -198,6 +198,13 @@ variable "VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX" { default = 5000 } +variable "VALIDATOR_COINBASE" { + description = "Optional coinbase address for validator sequencers. Defaults to each validator attester address when unset." + type = string + nullable = true + default = null +} + variable "VALIDATOR_L1_PRIORITY_FEE_BUMP_PERCENTAGE" { description = "Override for validator L1 priority fee bump percentage" type = string From 5a46609bb45bbf83314e55dec15137c3f33921bc Mon Sep 17 00:00:00 2001 From: spypsy Date: Wed, 3 Jun 2026 19:33:52 +0300 Subject: [PATCH 5/7] fix(bench): upload benchmarks to correct gh repo (#23825) upload proving benchmarks to benchmarks-result repo --- .github/workflows/nightly-spartan-bench.yml | 1 + .github/workflows/weekly-proving-bench.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/nightly-spartan-bench.yml b/.github/workflows/nightly-spartan-bench.yml index 14c604cce386..7a7aa5b499ea 100644 --- a/.github/workflows/nightly-spartan-bench.yml +++ b/.github/workflows/nightly-spartan-bench.yml @@ -306,6 +306,7 @@ jobs: tool: "customSmallerIsBetter" output-file-path: ./bench-out/bench.json github-token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + gh-repository: github.com/AztecProtocol/benchmark-page-data auto-push: true ref: ${{ github.event.workflow_run.head_sha || github.sha }} alert-threshold: "120%" diff --git a/.github/workflows/weekly-proving-bench.yml b/.github/workflows/weekly-proving-bench.yml index bdb56a2f5a43..fde19f0b5770 100644 --- a/.github/workflows/weekly-proving-bench.yml +++ b/.github/workflows/weekly-proving-bench.yml @@ -151,6 +151,7 @@ jobs: tool: "customSmallerIsBetter" output-file-path: ./bench-out/bench.json github-token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + gh-repository: github.com/AztecProtocol/benchmark-page-data auto-push: true ref: ${{ github.event.workflow_run.head_sha || github.sha }} alert-threshold: "120%" From 4ae495662603b348cf154a9107997a01ee1e8c5b Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 3 Jun 2026 21:22:37 +0100 Subject: [PATCH 6/7] chore: add hi-mem taint (#23828) . --- .github/workflows/deploy-network.yml | 64 ++++++++++++++----- .github/workflows/deploy-staging.yml | 5 +- .../values/prover-resources-dev-hi-tps.yaml | 12 ++++ .../values/prover-resources-prod-hi-tps.yaml | 12 ++++ spartan/terraform/gke-cluster/cluster/main.tf | 6 ++ .../terraform/gke-cluster/docker-registry.tf | 32 ++++++++++ spartan/terraform/gke-cluster/iam.tf | 7 ++ spartan/terraform/gke-cluster/outputs.tf | 19 ++++++ spartan/terraform/gke-cluster/variables.tf | 12 ++++ 9 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 spartan/terraform/gke-cluster/docker-registry.tf diff --git a/.github/workflows/deploy-network.yml b/.github/workflows/deploy-network.yml index f06cf98cc907..c1d0130b0256 100644 --- a/.github/workflows/deploy-network.yml +++ b/.github/workflows/deploy-network.yml @@ -17,6 +17,15 @@ on: description: "Full Aztec docker image (e.g., aztecprotocol/aztec:2.3.4). If not set, constructed from semver." required: false type: string + prover_docker_image: + description: "Full Prover docker image URL. If not set defaults to aztec_docker_image." + required: false + type: string + use_internal_docker_registry: + description: "Construct Aztec docker images from INTERNAL_DOCKER_REGISTRY and semver." + required: false + type: boolean + default: false ref: description: "Git ref to checkout" required: false @@ -62,6 +71,15 @@ on: description: "Full Aztec docker image (e.g., aztecprotocol/aztec:2.3.4). If not set, constructed from semver." required: false type: string + prover_docker_image: + description: "Full Prover docker image URL. If not set defaults to aztec_docker_image." + required: false + type: string + use_internal_docker_registry: + description: "Construct Aztec docker images from INTERNAL_DOCKER_REGISTRY and semver." + required: false + type: boolean + default: false namespace: description: "Kubernetes namespace override (optional, defaults to env file value)" required: false @@ -123,6 +141,8 @@ jobs: node-version: 22 - name: Validate inputs + env: + INTERNAL_DOCKER_REGISTRY: ${{ secrets.INTERNAL_DOCKER_REGISTRY }} run: | # Validate network if [[ ! -f "spartan/environments/${{ inputs.network }}.env" ]]; then @@ -146,28 +166,42 @@ jobs: fi fi - # Resolve the docker image - if [[ -n "${{ inputs.aztec_docker_image }}" ]]; then - AZTEC_DOCKER_IMAGE="${{ inputs.aztec_docker_image }}" - else - AZTEC_DOCKER_IMAGE="aztecprotocol/aztec:${{ inputs.semver }}" + if [[ "${{ inputs.use_internal_docker_registry }}" == "true" && -z "${{ inputs.semver }}" ]]; then + echo "Error: semver must be provided when use_internal_docker_registry is true" + exit 1 fi - echo "AZTEC_DOCKER_IMAGE=$AZTEC_DOCKER_IMAGE" >> $GITHUB_ENV - # Use the CRS-baked prover-agent image when it exists; otherwise let the - # deploy script fall back to AZTEC_DOCKER_IMAGE and download CRS on startup. - if [[ -n "${{ inputs.semver }}" ]]; then - PROVER_AGENT_DOCKER_IMAGE="aztecprotocol/aztec-prover-agent:${{ inputs.semver }}" + # Resolve the docker image + AZTEC_DOCKER_IMAGE="${{ inputs.aztec_docker_image }}" + PROVER_AGENT_DOCKER_IMAGE="${{ inputs.prover_docker_image }}" + INTERNAL_REGISTRY_BASE_URL="" + + if [[ "${{ inputs.use_internal_docker_registry }}" == "true" ]]; then + INTERNAL_REGISTRY_BASE_URL="${INTERNAL_DOCKER_REGISTRY%/}" + echo "::add-mask::$INTERNAL_REGISTRY_BASE_URL" + fi - echo "Checking if prover agent image exists: $PROVER_AGENT_DOCKER_IMAGE" - if docker manifest inspect "$PROVER_AGENT_DOCKER_IMAGE" > /dev/null 2>&1; then - echo "PROVER_AGENT_DOCKER_IMAGE=$PROVER_AGENT_DOCKER_IMAGE" >> $GITHUB_ENV + if [[ -z "$AZTEC_DOCKER_IMAGE" ]]; then + if [[ -n "$INTERNAL_REGISTRY_BASE_URL" ]]; then + AZTEC_DOCKER_IMAGE="$INTERNAL_REGISTRY_BASE_URL/aztec:${{ inputs.semver }}" + echo "::add-mask::$AZTEC_DOCKER_IMAGE" else - echo "Prover agent image does not exist: $PROVER_AGENT_DOCKER_IMAGE" - echo "Falling back to AZTEC_DOCKER_IMAGE for prover agents." + AZTEC_DOCKER_IMAGE="aztecprotocol/aztec:${{ inputs.semver }}" + fi + fi + + if [[ -z "$PROVER_AGENT_DOCKER_IMAGE" ]]; then + if [[ -n "$INTERNAL_REGISTRY_BASE_URL" ]]; then + PROVER_AGENT_DOCKER_IMAGE="$INTERNAL_REGISTRY_BASE_URL/aztec-prover-agent:${{ inputs.semver }}" + echo "::add-mask::$PROVER_AGENT_DOCKER_IMAGE" + elif [[ -z "${{ inputs.aztec_docker_image }}" && -n "${{ inputs.semver }}" ]]; then + PROVER_AGENT_DOCKER_IMAGE="aztecprotocol/aztec-prover-agent:${{ inputs.semver }}" fi fi + echo "AZTEC_DOCKER_IMAGE=$AZTEC_DOCKER_IMAGE" >> $GITHUB_ENV + echo "PROVER_AGENT_DOCKER_IMAGE=$PROVER_AGENT_DOCKER_IMAGE" >> $GITHUB_ENV + - name: Store the GCP key in a file env: GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index d7a875ff5f7b..f4b3091dd4cb 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -32,10 +32,10 @@ jobs: tag: ${{ steps.resolve.outputs.tag }} semver: ${{ steps.resolve.outputs.semver }} steps: - - name: Checkout v4-next + - name: Checkout v5-next uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: - ref: v4-next + ref: v5-next token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} fetch-depth: 0 @@ -82,6 +82,7 @@ jobs: with: network: staging semver: ${{ needs.determine-tag.outputs.semver }} + use_internal_docker_registry: true source_tag: ${{ needs.determine-tag.outputs.tag }} deploy_contracts: ${{ inputs.deploy_contracts == true }} secrets: inherit diff --git a/spartan/terraform/deploy-aztec-infra/values/prover-resources-dev-hi-tps.yaml b/spartan/terraform/deploy-aztec-infra/values/prover-resources-dev-hi-tps.yaml index dfb893b64230..45aa18f91a79 100644 --- a/spartan/terraform/deploy-aztec-infra/values/prover-resources-dev-hi-tps.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/prover-resources-dev-hi-tps.yaml @@ -14,6 +14,12 @@ node: cores: "8" hi-mem: "true" + tolerations: + - key: "hi-mem" + operator: "Equal" + value: "true" + effect: "NoSchedule" + persistence: enabled: true statefulSet: @@ -35,6 +41,12 @@ broker: cores: "8" hi-mem: "true" + tolerations: + - key: "hi-mem" + operator: "Equal" + value: "true" + effect: "NoSchedule" + persistence: enabled: true statefulSet: diff --git a/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod-hi-tps.yaml b/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod-hi-tps.yaml index e55eb40e9c66..55b52265ef2b 100644 --- a/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod-hi-tps.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod-hi-tps.yaml @@ -14,6 +14,12 @@ node: cores: "8" hi-mem: "true" + tolerations: + - key: "hi-mem" + operator: "Equal" + value: "true" + effect: "NoSchedule" + persistence: enabled: true statefulSet: @@ -35,6 +41,12 @@ broker: cores: "8" hi-mem: "true" + tolerations: + - key: "hi-mem" + operator: "Equal" + value: "true" + effect: "NoSchedule" + persistence: enabled: true statefulSet: diff --git a/spartan/terraform/gke-cluster/cluster/main.tf b/spartan/terraform/gke-cluster/cluster/main.tf index 231883327214..5d18eda4ecb4 100644 --- a/spartan/terraform/gke-cluster/cluster/main.tf +++ b/spartan/terraform/gke-cluster/cluster/main.tf @@ -191,6 +191,12 @@ resource "google_container_node_pool" "aztec_nodes-8core-hi-mem" { hi-mem = "true" } tags = ["aztec-gke-node", "aztec"] + + taint { + key = "hi-mem" + value = "true" + effect = "NO_SCHEDULE" + } } # Management configuration diff --git a/spartan/terraform/gke-cluster/docker-registry.tf b/spartan/terraform/gke-cluster/docker-registry.tf new file mode 100644 index 000000000000..fa1f15a4d3de --- /dev/null +++ b/spartan/terraform/gke-cluster/docker-registry.tf @@ -0,0 +1,32 @@ +resource "google_project_service" "artifact_registry" { + project = var.project + service = "artifactregistry.googleapis.com" + + disable_on_destroy = false +} + +resource "google_artifact_registry_repository" "docker_registry" { + project = var.project + location = var.region + repository_id = var.docker_registry_repository_id + description = "Docker repository for Spartan GKE images" + format = "DOCKER" + + depends_on = [google_project_service.artifact_registry] +} + +resource "google_artifact_registry_repository_iam_member" "gke_sa_docker_registry_reader" { + project = google_artifact_registry_repository.docker_registry.project + location = google_artifact_registry_repository.docker_registry.location + repository = google_artifact_registry_repository.docker_registry.name + role = "roles/artifactregistry.reader" + member = "serviceAccount:${google_service_account.gke_sa.email}" +} + +resource "google_artifact_registry_repository_iam_member" "ci_docker_registry_writer" { + project = google_artifact_registry_repository.docker_registry.project + location = google_artifact_registry_repository.docker_registry.location + repository = google_artifact_registry_repository.docker_registry.name + role = "roles/artifactregistry.writer" + member = "serviceAccount:${google_service_account.ci.email}" +} diff --git a/spartan/terraform/gke-cluster/iam.tf b/spartan/terraform/gke-cluster/iam.tf index 4506b1135a6a..635903fd6aad 100644 --- a/spartan/terraform/gke-cluster/iam.tf +++ b/spartan/terraform/gke-cluster/iam.tf @@ -41,6 +41,13 @@ resource "google_project_iam_member" "helm_sa_roles" { member = "serviceAccount:${google_service_account.helm_sa.email}" } +# Create a service account for CI +resource "google_service_account" "ci" { + account_id = var.ci_service_account_id + display_name = "CI Service Account" + description = "Service account for CI jobs that publish Docker images" +} + # Service account for External Secrets Operator resource "google_service_account" "eso" { account_id = "external-secrets-operator" diff --git a/spartan/terraform/gke-cluster/outputs.tf b/spartan/terraform/gke-cluster/outputs.tf index 4e192c9c203c..4ea9dd72a454 100644 --- a/spartan/terraform/gke-cluster/outputs.tf +++ b/spartan/terraform/gke-cluster/outputs.tf @@ -6,11 +6,30 @@ output "eso_service_account_email" { value = google_service_account.eso.email } +output "ci_service_account_email" { + value = google_service_account.ci.email +} + output "region" { description = "Google cloud region" value = var.region } +output "docker_registry_hostname" { + description = "Artifact Registry Docker hostname" + value = "${var.region}-docker.pkg.dev" +} + +output "docker_registry_repository" { + description = "Artifact Registry Docker repository resource name" + value = google_artifact_registry_repository.docker_registry.name +} + +output "docker_registry_repository_url" { + description = "Artifact Registry Docker repository URL prefix for image names" + value = "${var.region}-docker.pkg.dev/${var.project}/${google_artifact_registry_repository.docker_registry.repository_id}" +} + output "devnet_network_rpc_ips" { description = "Static IPs and hostnames for v4 devnet networks" value = { diff --git a/spartan/terraform/gke-cluster/variables.tf b/spartan/terraform/gke-cluster/variables.tf index fae5061e332b..105c85d12e25 100644 --- a/spartan/terraform/gke-cluster/variables.tf +++ b/spartan/terraform/gke-cluster/variables.tf @@ -9,3 +9,15 @@ variable "region" { variable "zone" { default = "us-west1-a" } + +variable "docker_registry_repository_id" { + description = "Artifact Registry Docker repository ID for Spartan images." + type = string + default = "aztec" +} + +variable "ci_service_account_id" { + description = "Service account ID for CI jobs that push images to the Docker registry." + type = string + default = "aztec-ci" +} From c70de5aeda8b29898759ff7e9f783611b527674b Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 3 Jun 2026 22:41:38 +0100 Subject: [PATCH 7/7] chore: update artifact registry (#23845) . --- spartan/terraform/gke-cluster/iam.tf | 7 +++++- spartan/terraform/gke-cluster/npm-registry.tf | 25 +++++++++++++++++++ spartan/terraform/gke-cluster/outputs.tf | 20 ++++++++++++++- spartan/terraform/gke-cluster/variables.tf | 14 ++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 spartan/terraform/gke-cluster/npm-registry.tf diff --git a/spartan/terraform/gke-cluster/iam.tf b/spartan/terraform/gke-cluster/iam.tf index 635903fd6aad..10663f891ed2 100644 --- a/spartan/terraform/gke-cluster/iam.tf +++ b/spartan/terraform/gke-cluster/iam.tf @@ -48,6 +48,12 @@ resource "google_service_account" "ci" { description = "Service account for CI jobs that publish Docker images" } +resource "google_service_account" "npm_registry_reader" { + account_id = var.npm_registry_reader_service_account_id + display_name = "npm Registry Reader Service Account" + description = "Service account for CI jobs that install internal npm packages" +} + # Service account for External Secrets Operator resource "google_service_account" "eso" { account_id = "external-secrets-operator" @@ -79,4 +85,3 @@ data "google_iam_policy" "all_users_storage_read" { ] } } - diff --git a/spartan/terraform/gke-cluster/npm-registry.tf b/spartan/terraform/gke-cluster/npm-registry.tf new file mode 100644 index 000000000000..4ad27c5816de --- /dev/null +++ b/spartan/terraform/gke-cluster/npm-registry.tf @@ -0,0 +1,25 @@ +resource "google_artifact_registry_repository" "npm_registry" { + project = var.project + location = var.region + repository_id = var.npm_registry_repository_id + description = "npm repository" + format = "NPM" + + depends_on = [google_project_service.artifact_registry] +} + +resource "google_artifact_registry_repository_iam_member" "ci_npm_registry_reader" { + project = google_artifact_registry_repository.npm_registry.project + location = google_artifact_registry_repository.npm_registry.location + repository = google_artifact_registry_repository.npm_registry.name + role = "roles/artifactregistry.reader" + member = "serviceAccount:${google_service_account.npm_registry_reader.email}" +} + +resource "google_artifact_registry_repository_iam_member" "ci_npm_registry_publisher" { + project = google_artifact_registry_repository.npm_registry.project + location = google_artifact_registry_repository.npm_registry.location + repository = google_artifact_registry_repository.npm_registry.name + role = "roles/artifactregistry.writer" + member = "serviceAccount:${google_service_account.ci.email}" +} diff --git a/spartan/terraform/gke-cluster/outputs.tf b/spartan/terraform/gke-cluster/outputs.tf index 4ea9dd72a454..c38798aa005b 100644 --- a/spartan/terraform/gke-cluster/outputs.tf +++ b/spartan/terraform/gke-cluster/outputs.tf @@ -10,6 +10,10 @@ output "ci_service_account_email" { value = google_service_account.ci.email } +output "npm_registry_reader_service_account_email" { + value = google_service_account.npm_registry_reader.email +} + output "region" { description = "Google cloud region" value = var.region @@ -30,6 +34,21 @@ output "docker_registry_repository_url" { value = "${var.region}-docker.pkg.dev/${var.project}/${google_artifact_registry_repository.docker_registry.repository_id}" } +output "npm_registry_hostname" { + description = "Artifact Registry npm hostname" + value = "${var.region}-npm.pkg.dev" +} + +output "npm_registry_repository" { + description = "Artifact Registry npm repository resource name" + value = google_artifact_registry_repository.npm_registry.name +} + +output "npm_registry_repository_url" { + description = "Artifact Registry npm repository URL for npm config" + value = "https://${var.region}-npm.pkg.dev/${var.project}/${google_artifact_registry_repository.npm_registry.repository_id}/" +} + output "devnet_network_rpc_ips" { description = "Static IPs and hostnames for v4 devnet networks" value = { @@ -40,4 +59,3 @@ output "devnet_network_rpc_ips" { } } } - diff --git a/spartan/terraform/gke-cluster/variables.tf b/spartan/terraform/gke-cluster/variables.tf index 105c85d12e25..a5b256eb3e89 100644 --- a/spartan/terraform/gke-cluster/variables.tf +++ b/spartan/terraform/gke-cluster/variables.tf @@ -16,8 +16,20 @@ variable "docker_registry_repository_id" { default = "aztec" } +variable "npm_registry_repository_id" { + description = "Artifact Registry npm repository ID for internal Aztec packages." + type = string + default = "aztec-npm" +} + variable "ci_service_account_id" { - description = "Service account ID for CI jobs that push images to the Docker registry." + description = "Service account ID for CI jobs that publish internal artifacts." type = string default = "aztec-ci" } + +variable "npm_registry_reader_service_account_id" { + description = "Service account ID for CI jobs that install internal npm packages." + type = string + default = "aztec-npm-reader" +}