From d7a5909093577cf57b6bc6b2cba16c1512f2a3aa Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Fri, 13 Mar 2026 20:26:05 -0300 Subject: [PATCH 01/43] add params --- .../aztec-node/src/aztec-node/server.ts | 15 +++++++++ .../ethereum/src/publisher_manager.ts | 24 ++++++++++++-- yarn-project/foundation/src/config/env_var.ts | 2 ++ .../node-keystore/src/keystore_manager.ts | 5 +++ yarn-project/prover-node/src/factory.ts | 20 +++++++++++- .../src/client/sequencer-client.ts | 2 ++ .../sequencer-client/src/publisher/config.ts | 32 +++++++++++++++++++ 7 files changed, 96 insertions(+), 4 deletions(-) diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index e6c2108e985d..edc352e4308e 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -8,6 +8,7 @@ import { createEthereumChain } from '@aztec/ethereum/chain'; import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client'; import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts'; import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; +import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils'; import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -492,6 +493,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() }, ); + // Create a funder L1TxUtils from the keystore funding account (if configured) + const fundingSigner = keyStoreManager!.createFundingSigner(); + let funderL1TxUtils: L1TxUtils | undefined; + if (fundingSigner) { + const [funder] = await createL1TxUtilsFromSigners( + publicClient, + [fundingSigner], + { ...config, scope: 'sequencer' }, + { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider }, + ); + funderL1TxUtils = funder; + } + // Create and start the sequencer client const checkpointsBuilder = new CheckpointsBuilder( { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit }, @@ -506,6 +520,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { ...deps, epochCache, l1TxUtils, + funderL1TxUtils, validatorClient, p2pClient, worldStateSynchronizer, diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 3c620c37ec93..0c93543fefab 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -27,19 +27,27 @@ const busyStates: TxUtilsState[] = [ export type PublisherFilter = (utils: UtilsType) => boolean; +/** Config accepted by PublisherManager. */ +type PublisherManagerConfig = { + publisherAllowInvalidStates?: boolean; + publisherFundingThreshold?: bigint; + publisherFundingAmount?: bigint; +}; + export class PublisherManager { private log: Logger; - private config: { publisherAllowInvalidStates?: boolean }; + private config: PublisherManagerConfig; constructor( private publishers: UtilsType[], - config: { publisherAllowInvalidStates?: boolean }, + config: PublisherManagerConfig, bindings?: LoggerBindings, + private funder?: UtilsType, ) { this.log = createLogger('publisher:manager', bindings); this.log.info(`PublisherManager initialized with ${publishers.length} publishers.`); this.publishers = publishers; - this.config = pick(config, 'publisherAllowInvalidStates'); + this.config = pick(config, 'publisherAllowInvalidStates', 'publisherFundingThreshold', 'publisherFundingAmount'); } /** Loads the state of all publishers and resumes monitoring any pending txs */ @@ -105,4 +113,14 @@ export class PublisherManager { public interrupt() { this.publishers.forEach(pub => pub.interrupt()); } + + /** Check all publisher balances and fund those below threshold (background, non-blocking). */ + private triggerFundingIfNeeded(_publishersWithBalance: { balance: bigint; publisher: UtilsType }[]): void { + // Stage 1 stub — no-op. Implementation in Stage 3. + } + + /** Fund a single publisher by sending ETH from the funding account. */ + private fundPublisher(_publisher: UtilsType): Promise { + throw new Error('Not implemented'); + } } diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 6cd085cb9e16..ab294db962e6 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -217,6 +217,8 @@ export type EnvVar = | 'SEQ_PUBLISHER_ADDRESSES' | 'SEQ_PUBLISHER_ALLOW_INVALID_STATES' | 'SEQ_PUBLISHER_FORWARDER_ADDRESS' + | 'PUBLISHER_FUNDING_THRESHOLD' + | 'PUBLISHER_FUNDING_AMOUNT' | 'SEQ_POLLING_INTERVAL_MS' | 'SEQ_ENFORCE_TIME_TABLE' | 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT' diff --git a/yarn-project/node-keystore/src/keystore_manager.ts b/yarn-project/node-keystore/src/keystore_manager.ts index 23391fe6bbc1..c7e3a3a83b3c 100644 --- a/yarn-project/node-keystore/src/keystore_manager.ts +++ b/yarn-project/node-keystore/src/keystore_manager.ts @@ -270,6 +270,11 @@ export class KeystoreManager { return allPublishers; } + /** Create a signer for the top-level funding account, if configured. */ + createFundingSigner(): EthSigner | undefined { + throw new Error('Not implemented'); + } + /** * Create signers for slasher accounts */ diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 48465c03d640..33243ccfb2e8 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -119,11 +119,29 @@ export async function createProverNode( { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider }, ); + // Create a funder L1TxUtils from the keystore funding account (if configured) + const fundingSigner = keyStoreManager?.createFundingSigner(); + let funderL1TxUtils: L1TxUtils | undefined; + if (fundingSigner) { + const [funder] = await createL1TxUtilsFromSigners( + publicClient, + [fundingSigner], + { ...config, scope: 'prover' }, + { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider }, + ); + funderL1TxUtils = funder; + } + const publisherFactory = deps.publisherFactory ?? new ProverPublisherFactory(config, { rollupContract, - publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), log.getBindings()), + publisherManager: new PublisherManager( + l1TxUtils, + getPublisherConfigFromProverConfig(config), + log.getBindings(), + funderL1TxUtils, + ), telemetry, }); diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index 0efeafb01f10..bc975d3acb2e 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -70,6 +70,7 @@ export class SequencerClient { dateProvider: DateProvider; epochCache?: EpochCache; l1TxUtils: L1TxUtils[]; + funderL1TxUtils?: L1TxUtils; nodeKeyStore: KeystoreManager; }, ) { @@ -96,6 +97,7 @@ export class SequencerClient { l1TxUtils, getPublisherConfigFromSequencerConfig(config), log.getBindings(), + deps.funderL1TxUtils, ); const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString()); const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([ diff --git a/yarn-project/sequencer-client/src/publisher/config.ts b/yarn-project/sequencer-client/src/publisher/config.ts index ba250c31f0d1..45e2065c58c7 100644 --- a/yarn-project/sequencer-client/src/publisher/config.ts +++ b/yarn-project/sequencer-client/src/publisher/config.ts @@ -4,6 +4,8 @@ import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from '@aztec/ethereum/l import { type ConfigMappingsType, SecretValue, booleanConfigHelper } from '@aztec/foundation/config'; import { EthAddress } from '@aztec/foundation/eth-address'; +import { parseEther } from 'viem'; + /** Configuration of the transaction publisher. */ export type TxSenderConfig = L1ReaderConfig & { /** The private key to be used by the publisher. */ @@ -50,13 +52,37 @@ export type PublisherConfig = L1TxUtilsConfig & publisherForwarderAddress?: EthAddress; /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */ l1TxFailedStore?: string; + /** Min ETH balance (in wei) below which a publisher gets funded. Undefined = funding disabled. */ + publisherFundingThreshold?: bigint; + /** Amount of ETH (in wei) to send when funding a publisher. Undefined = funding disabled. */ + publisherFundingAmount?: bigint; }; +/** Shared config mappings for publisher funding, used by both sequencer and prover publisher configs. */ +const publisherFundingConfigMappings = { + publisherFundingThreshold: { + env: 'PUBLISHER_FUNDING_THRESHOLD' as const, + description: + 'Min ETH balance below which a publisher gets funded. Specified in ether (e.g. 0.1). Unset = funding disabled.', + parseEnv: (val: string) => parseEther(val), + }, + publisherFundingAmount: { + env: 'PUBLISHER_FUNDING_AMOUNT' as const, + description: + 'Amount of ETH to send when funding a publisher. Specified in ether (e.g. 0.5). Unset = funding disabled.', + parseEnv: (val: string) => parseEther(val), + }, +}; + export type ProverPublisherConfig = L1TxUtilsConfig & BlobClientConfig & { fishermanMode?: boolean; proverPublisherAllowInvalidStates?: boolean; proverPublisherForwarderAddress?: EthAddress; + /** Min ETH balance (in wei) below which a publisher gets funded. Undefined = funding disabled. */ + publisherFundingThreshold?: bigint; + /** Amount of ETH (in wei) to send when funding a publisher. Undefined = funding disabled. */ + publisherFundingAmount?: bigint; }; export type SequencerPublisherConfig = L1TxUtilsConfig & @@ -66,6 +92,10 @@ export type SequencerPublisherConfig = L1TxUtilsConfig & sequencerPublisherForwarderAddress?: EthAddress; /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */ l1TxFailedStore?: string; + /** Min ETH balance (in wei) below which a publisher gets funded. Undefined = funding disabled. */ + publisherFundingThreshold?: bigint; + /** Amount of ETH (in wei) to send when funding a publisher. Undefined = funding disabled. */ + publisherFundingAmount?: bigint; }; export function getPublisherConfigFromProverConfig(config: ProverPublisherConfig): PublisherConfig { @@ -142,6 +172,7 @@ export const sequencerPublisherConfigMappings: ConfigMappingsType = { @@ -163,4 +194,5 @@ export const proverPublisherConfigMappings: ConfigMappingsType (val ? EthAddress.fromString(val) : undefined), }, + ...publisherFundingConfigMappings, }; From 014d9f374a9346b7d5e64fc9c1114022d0ef979d Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 11:14:32 -0300 Subject: [PATCH 02/43] e2e test --- .../src/e2e_publisher_funding.test.ts | 147 ++++++++++++++++++ .../ethereum/src/publisher_manager.ts | 4 +- .../node-keystore/src/keystore_manager.ts | 2 +- 3 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 yarn-project/end-to-end/src/e2e_publisher_funding.test.ts diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts new file mode 100644 index 000000000000..81da854b35ba --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts @@ -0,0 +1,147 @@ +import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { Logger } from '@aztec/aztec.js/log'; +import type { EthCheatCodes } from '@aztec/aztec/testing'; +import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils'; +import type { PublisherManager } from '@aztec/ethereum/publisher-manager'; +import { SecretValue } from '@aztec/foundation/config'; +import { retryUntil } from '@aztec/foundation/retry'; +import type { SequencerClient } from '@aztec/sequencer-client'; +import type { TestSequencerClient } from '@aztec/sequencer-client/test'; + +import { jest } from '@jest/globals'; +import { mkdtemp, rm, writeFile } from 'fs/promises'; +import 'jest-extended'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { type Hex, parseEther } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; + +import { getPrivateKeyFromIndex, setup } from './fixtures/utils.js'; + +// Key indices from the test MNEMONIC (all pre-funded by Anvil): +// 0 = L1 contract deployer (not in keystore) +// 1 = validator attester + publisher EOA +// 2 = funding account +const DEPLOYER_KEY_INDEX = 0; +const PUBLISHER_KEY_INDEX = 1; +const FUNDER_KEY_INDEX = 2; + +const toPrivateKeyHex = (index: number): Hex => { + const buf = getPrivateKeyFromIndex(index); + if (!buf) { + throw new Error(`Failed to derive private key for index ${index}`); + } + return `0x${buf.toString('hex')}`; +}; + +const FUNDING_THRESHOLD = parseEther('0.5'); +const FUNDING_AMOUNT = parseEther('1'); + +describe('e2e_publisher_funding', () => { + jest.setTimeout(5 * 60 * 1000); + + let logger: Logger; + let publisherManager: PublisherManager; + let ethCheatCodes: EthCheatCodes; + let teardown: () => Promise; + let keyStoreDirectory: string; + + beforeAll(async () => { + const attesterKey = toPrivateKeyHex(PUBLISHER_KEY_INDEX); + const publisherKey = attesterKey; + const funderKey = toPrivateKeyHex(FUNDER_KEY_INDEX); + const attesterAddress = privateKeyToAccount(attesterKey).address; + + // Write keystore JSON with fundingAccount + keyStoreDirectory = await mkdtemp(join(tmpdir(), 'publisher-funding-')); + const keystore = { + schemaVersion: 1, + validators: [ + { + attester: attesterKey, + publisher: [publisherKey], + coinbase: EthAddress.fromNumber(42).toChecksumString(), + feeRecipient: AztecAddress.fromNumber(42).toString(), + }, + ], + fundingAccount: funderKey, + }; + await writeFile(join(keyStoreDirectory, 'keystore.json'), JSON.stringify(keystore, null, 2)); + + // Stake the validator on L1 so the sequencer can propose blocks + const initialValidators = [ + { + attester: EthAddress.fromString(attesterAddress), + withdrawer: EthAddress.fromString(attesterAddress), + privateKey: attesterKey as Hex, + bn254SecretKey: new SecretValue(Fr.random().toBigInt()), + }, + ]; + + let sequencerClient: SequencerClient | undefined; + ({ + teardown, + logger, + sequencer: sequencerClient, + ethCheatCodes, + } = await setup(0, { + initialValidators, + keyStoreDirectory, + publisherFundingThreshold: FUNDING_THRESHOLD, + publisherFundingAmount: FUNDING_AMOUNT, + minTxsPerBlock: 0, + l1PublisherKey: new SecretValue(toPrivateKeyHex(DEPLOYER_KEY_INDEX)), + })); + + publisherManager = (sequencerClient! as TestSequencerClient).publisherManager; + }); + + afterAll(async () => { + await teardown(); + await rm(keyStoreDirectory, { recursive: true, force: true }); + }); + + it('funds publisher when balance drops below threshold', async () => { + const publishers: L1TxUtils[] = (publisherManager as any).publishers; + const funder: L1TxUtils | undefined = (publisherManager as any).funder; + + expect(publishers.length).toBe(1); + expect(funder).toBeDefined(); + + const publisherAddress = publishers[0].getSenderAddress(); + const funderAddress = funder!.getSenderAddress(); + logger.info(`Publisher: ${publisherAddress}, Funder: ${funderAddress}`); + + // Set publisher balance below threshold + const LOW_BALANCE = parseEther('0.1'); + await ethCheatCodes.setBalance(publisherAddress, LOW_BALANCE); + // Give funder plenty of ETH + await ethCheatCodes.setBalance(funderAddress, parseEther('100')); + + const funderBalanceBefore = await ethCheatCodes.getBalance(funderAddress); + + // The sequencer periodically calls getAvailablePublisher(), which triggers funding + // when it sees the publisher balance is below threshold. Wait for funding to land. + await retryUntil( + async () => { + const balance = await ethCheatCodes.getBalance(publisherAddress); + return balance > LOW_BALANCE ? true : undefined; + }, + 'waiting for publisher to be funded', + 60, + 1, + ); + + const publisherBalanceAfter = await ethCheatCodes.getBalance(publisherAddress); + const funderBalanceAfter = await ethCheatCodes.getBalance(funderAddress); + const funderSpent = funderBalanceBefore - funderBalanceAfter; + + logger.info(`Publisher balance after: ${publisherBalanceAfter} (was ${LOW_BALANCE})`); + logger.info(`Funder spent: ${funderSpent} (expected ~${FUNDING_AMOUNT})`); + + expect(publisherBalanceAfter).toBeGreaterThan(LOW_BALANCE); + // Funder should have sent exactly FUNDING_AMOUNT plus gas costs + expect(funderSpent).toBeGreaterThanOrEqual(FUNDING_AMOUNT); + }); +}); diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 0c93543fefab..464f18639fbf 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -120,7 +120,7 @@ export class PublisherManager { } /** Fund a single publisher by sending ETH from the funding account. */ - private fundPublisher(_publisher: UtilsType): Promise { - throw new Error('Not implemented'); + private async fundPublisher(_publisher: UtilsType): Promise { + // Stage 1 stub — no-op. Implementation in Stage 3. } } diff --git a/yarn-project/node-keystore/src/keystore_manager.ts b/yarn-project/node-keystore/src/keystore_manager.ts index c7e3a3a83b3c..c9d930986005 100644 --- a/yarn-project/node-keystore/src/keystore_manager.ts +++ b/yarn-project/node-keystore/src/keystore_manager.ts @@ -272,7 +272,7 @@ export class KeystoreManager { /** Create a signer for the top-level funding account, if configured. */ createFundingSigner(): EthSigner | undefined { - throw new Error('Not implemented'); + return undefined; } /** From c70f767f67fd1e94010adefd4b85bf71822b4f43 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 11:45:55 -0300 Subject: [PATCH 03/43] implementation stage 1 --- .../ethereum/src/publisher_manager.ts | 48 +++++++++++++++++-- .../node-keystore/src/keystore_manager.ts | 6 ++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 464f18639fbf..4cd7b4fd9e3c 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -37,6 +37,7 @@ type PublisherManagerConfig = { export class PublisherManager { private log: Logger; private config: PublisherManagerConfig; + private isFunding = false; constructor( private publishers: UtilsType[], @@ -107,6 +108,8 @@ export class PublisherManager { return lastUsedComparison; }); + void this.triggerFundingIfNeeded().catch(err => this.log.error('Error in funding check', { err })); + return sortedPublishers[0].publisher; } @@ -115,12 +118,47 @@ export class PublisherManager { } /** Check all publisher balances and fund those below threshold (background, non-blocking). */ - private triggerFundingIfNeeded(_publishersWithBalance: { balance: bigint; publisher: UtilsType }[]): void { - // Stage 1 stub — no-op. Implementation in Stage 3. + private async triggerFundingIfNeeded(): Promise { + const { funder, config } = this; + if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) { + return; + } + if (this.isFunding) { + return; + } + + this.isFunding = true; + try { + const allBalances = await Promise.all( + this.publishers.map(async pub => ({ balance: await pub.getSenderBalance(), publisher: pub })), + ); + const lowBalance = allBalances.filter(p => p.balance < config.publisherFundingThreshold!); + if (lowBalance.length === 0) { + return; + } + + // TODO: check funder balance before funding, warn if < 10x fundingAmount, skip if < fundingAmount + // TODO: re-read funder balance after each fund, break if exhausted + await this.fundPublishers(lowBalance.map(p => p.publisher)); + } finally { + this.isFunding = false; + } } - /** Fund a single publisher by sending ETH from the funding account. */ - private async fundPublisher(_publisher: UtilsType): Promise { - // Stage 1 stub — no-op. Implementation in Stage 3. + /** Fund publishers sequentially. */ + private async fundPublishers(publishers: UtilsType[]): Promise { + const fundingAmount = this.config.publisherFundingAmount!; + + for (const publisher of publishers) { + const address = publisher.getSenderAddress(); + try { + // TODO: log funding amount in ETH, not wei + this.log.info(`Funding publisher ${address} with ${fundingAmount} wei`); + await this.funder!.sendAndMonitorTransaction({ to: address.toString(), data: '0x', value: fundingAmount }); + this.log.info(`Funded publisher ${address}`); + } catch (err) { + this.log.error(`Failed to fund publisher ${address}`, { err }); + } + } } } diff --git a/yarn-project/node-keystore/src/keystore_manager.ts b/yarn-project/node-keystore/src/keystore_manager.ts index c9d930986005..6bacbf6cc356 100644 --- a/yarn-project/node-keystore/src/keystore_manager.ts +++ b/yarn-project/node-keystore/src/keystore_manager.ts @@ -272,7 +272,11 @@ export class KeystoreManager { /** Create a signer for the top-level funding account, if configured. */ createFundingSigner(): EthSigner | undefined { - return undefined; + const fundingAccount = this.keystore.fundingAccount; + if (!fundingAccount) { + return undefined; + } + return this.createSignerFromEthAccount(fundingAccount, this.keystore.remoteSigner); } /** From 42756732a418170e6d8faad0478ac90cd7a29588 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 11:54:59 -0300 Subject: [PATCH 04/43] unit tests --- .../ethereum/src/publisher_manager.test.ts | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index 3eb0141f824e..5e6afec71fb7 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -172,6 +172,178 @@ describe('PublisherManager', () => { }); }); + describe('publisher funding', () => { + let funder: TestL1TxUtils & L1TxUtils; + const threshold = 100n; + const fundingAmount = 50n; + + const createFundedManager = ( + publishers: (TestL1TxUtils & L1TxUtils)[], + funderInstance?: TestL1TxUtils & L1TxUtils, + config?: { publisherFundingThreshold?: bigint; publisherFundingAmount?: bigint }, + ) => { + return new PublisherManager( + publishers, + { publisherFundingThreshold: threshold, publisherFundingAmount: fundingAmount, ...config }, + undefined, + funderInstance, + ); + }; + + /** Wait for the background funding promise to settle. */ + const waitForFunding = () => new Promise(resolve => setTimeout(resolve, 10)); + + beforeEach(() => { + funder = new TestL1TxUtils(EthAddress.random()) as TestL1TxUtils & L1TxUtils; + funder.balance = 5000n; + }); + + it('funds publisher when balance is below threshold', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; // below threshold + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith( + expect.objectContaining({ to: mockPublishers[0].getSenderAddress().toString(), value: fundingAmount }), + ); + }); + + it('does not fund when publisher balance is above threshold', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 200n; // above threshold + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); + }); + + it('funds multiple publishers, only those below threshold', async () => { + mockPublishers = createMockPublishers(3); + mockPublishers[0].balance = 50n; // below + mockPublishers[1].balance = 200n; // above + mockPublishers[2].balance = 30n; // below + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith( + expect.objectContaining({ to: mockPublishers[0].getSenderAddress().toString(), value: fundingAmount }), + ); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith( + expect.objectContaining({ to: mockPublishers[2].getSenderAddress().toString(), value: fundingAmount }), + ); + }); + + it('handles funding transaction failure gracefully', async () => { + mockPublishers = createMockPublishers(2); + mockPublishers[0].balance = 50n; + mockPublishers[1].balance = 50n; + publisherManager = createFundedManager(mockPublishers, funder); + + // First call fails, second succeeds + funder.sendAndMonitorTransaction + .mockRejectedValueOnce(new Error('tx failed')) + .mockResolvedValueOnce({ receipt: { transactionHash: '0xdef' }, state: {} }); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + // Both attempted despite first failure + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); + }); + + it('correctly sends the funding transaction', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ + to: mockPublishers[0].getSenderAddress().toString(), + data: '0x', + value: fundingAmount, + }); + }); + + it('concurrent guard prevents overlapping funding runs', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; + publisherManager = createFundedManager(mockPublishers, funder); + + // Block the first funding call on a deferred promise we control + let resolveFunding!: () => void; + const fundingBlocked = new Promise(r => (resolveFunding = r)); + funder.sendAndMonitorTransaction.mockImplementation(async () => { + await fundingBlocked; + return { receipt: { transactionHash: '0x1' }, state: {} }; + }); + + // First call triggers funding (sets isFunding = true) + await publisherManager.getAvailablePublisher(); + // Second call while funding is still in progress — should skip + await publisherManager.getAvailablePublisher(); + + // Release the blocked funding and let it complete + resolveFunding(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + }); + + it('no funding triggered when no funder configured', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; + publisherManager = new PublisherManager(mockPublishers, { + publisherFundingThreshold: threshold, + publisherFundingAmount: fundingAmount, + }); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); + }); + + it('no funding triggered when config threshold/amount not set', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; + publisherManager = createFundedManager(mockPublishers, funder, { + publisherFundingThreshold: undefined, + publisherFundingAmount: undefined, + }); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); + }); + + it('funds publishers in busy states', async () => { + mockPublishers = createMockPublishers(2); + mockPublishers[0].balance = 50n; + mockPublishers[0].state = TxUtilsState.IDLE; + mockPublishers[1].balance = 50n; + mockPublishers[1].state = TxUtilsState.SENT; // busy + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + // Both should be funded, even the busy one + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); + }); + }); + function createMockPublishers(count: number, addresses: EthAddress[] = []): (TestL1TxUtils & L1TxUtils)[] { const tempAddress = [...addresses]; return times( @@ -185,6 +357,10 @@ class TestL1TxUtils { public state: TxUtilsState = TxUtilsState.IDLE; public lastMinedAtBlockNumber: bigint | undefined = undefined; public balance: bigint = 1000n; + public sendAndMonitorTransaction = jest.fn<() => Promise>().mockResolvedValue({ + receipt: { transactionHash: '0xabc' }, + state: {}, + }); constructor(public senderAddress: EthAddress) {} From bd2b4c9fdc81725fa1bae9b46e4bd2f75f455463 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 16:08:16 -0300 Subject: [PATCH 05/43] fix todos & expand e2e test --- .../src/e2e_publisher_funding.test.ts | 22 +++++++++- .../ethereum/src/publisher_manager.test.ts | 39 +++++++++++++++++- .../ethereum/src/publisher_manager.ts | 38 +++++++++++++---- .../src/keystore_manager.test.ts | 41 +++++++++++++++++++ yarn-project/prover-node/src/factory.ts | 10 ++--- .../src/client/sequencer-client.ts | 10 ++--- 6 files changed, 137 insertions(+), 23 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts index 81da854b35ba..a1e6b3c3d0e0 100644 --- a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts +++ b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts @@ -36,7 +36,8 @@ const toPrivateKeyHex = (index: number): Hex => { }; const FUNDING_THRESHOLD = parseEther('0.5'); -const FUNDING_AMOUNT = parseEther('1'); +// Small enough that publishing a few blocks will drain it below threshold again, triggering re-funding. +const FUNDING_AMOUNT = parseEther('0.1'); describe('e2e_publisher_funding', () => { jest.setTimeout(5 * 60 * 1000); @@ -143,5 +144,24 @@ describe('e2e_publisher_funding', () => { expect(publisherBalanceAfter).toBeGreaterThan(LOW_BALANCE); // Funder should have sent exactly FUNDING_AMOUNT plus gas costs expect(funderSpent).toBeGreaterThanOrEqual(FUNDING_AMOUNT); + + // Second round: the publisher will spend gas publishing blocks, eventually dropping + // below threshold again. The funder should automatically top it up a second time. + const funderBalanceBefore2 = await ethCheatCodes.getBalance(funderAddress); + logger.info(`Waiting for publisher to drain and get re-funded`); + + await retryUntil( + async () => { + const spent = funderBalanceBefore2 - (await ethCheatCodes.getBalance(funderAddress)); + return spent >= FUNDING_AMOUNT ? true : undefined; + }, + 'waiting for second funding round', + 120, + 1, + ); + + const funderSpent2 = funderBalanceBefore2 - (await ethCheatCodes.getBalance(funderAddress)); + logger.info(`Second funding round: funder spent ${funderSpent2}`); + expect(funderSpent2).toBeGreaterThanOrEqual(FUNDING_AMOUNT); }); }); diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index 5e6afec71fb7..8602c8ed1e99 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -185,8 +185,7 @@ describe('PublisherManager', () => { return new PublisherManager( publishers, { publisherFundingThreshold: threshold, publisherFundingAmount: fundingAmount, ...config }, - undefined, - funderInstance, + { funder: funderInstance }, ); }; @@ -328,6 +327,42 @@ describe('PublisherManager', () => { expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); + it('does not fund when funder balance is less than fundingAmount', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; + funder.balance = 30n; // less than fundingAmount (50n) + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); + }); + + it('stops funding when funder balance exhausted mid-run', async () => { + mockPublishers = createMockPublishers(3); + mockPublishers[0].balance = 10n; + mockPublishers[1].balance = 10n; + mockPublishers[2].balance = 10n; + // Funder starts with enough for all, but after first fund balance drops below fundingAmount + funder.balance = 5000n; + publisherManager = createFundedManager(mockPublishers, funder); + + let callCount = 0; + funder.sendAndMonitorTransaction.mockImplementation(async () => { + callCount++; + if (callCount === 1) { + funder.balance = 10n; // exhausted after first fund + } + return { receipt: { transactionHash: '0x1' }, state: {} }; + }); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + }); + it('funds publishers in busy states', async () => { mockPublishers = createMockPublishers(2); mockPublishers[0].balance = 50n; diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 4cd7b4fd9e3c..227293e87771 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -38,17 +38,24 @@ export class PublisherManager { private log: Logger; private config: PublisherManagerConfig; private isFunding = false; + private funder?: UtilsType; constructor( private publishers: UtilsType[], config: PublisherManagerConfig, - bindings?: LoggerBindings, - private funder?: UtilsType, + opts?: { bindings?: LoggerBindings; funder?: UtilsType }, ) { - this.log = createLogger('publisher:manager', bindings); + this.funder = opts?.funder; + this.log = createLogger('publisher:manager', opts?.bindings); this.log.info(`PublisherManager initialized with ${publishers.length} publishers.`); this.publishers = publishers; this.config = pick(config, 'publisherAllowInvalidStates', 'publisherFundingThreshold', 'publisherFundingAmount'); + + const hasThreshold = this.config.publisherFundingThreshold !== undefined; + const hasAmount = this.config.publisherFundingAmount !== undefined; + if (hasThreshold !== hasAmount) { + this.log.warn(`Incomplete funding config: both publisherFundingThreshold and publisherFundingAmount must be set`); + } } /** Loads the state of all publishers and resumes monitoring any pending txs */ @@ -137,27 +144,42 @@ export class PublisherManager { return; } - // TODO: check funder balance before funding, warn if < 10x fundingAmount, skip if < fundingAmount - // TODO: re-read funder balance after each fund, break if exhausted + const fundingAmount = config.publisherFundingAmount!; + let funderBalance = await funder.getSenderBalance(); + + if (funderBalance < 10n * fundingAmount) { + this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount }); + } + if (funderBalance < fundingAmount) { + this.log.error(`Funding account balance too low to fund any publisher`, { funderBalance, fundingAmount }); + return; + } + await this.fundPublishers(lowBalance.map(p => p.publisher)); } finally { this.isFunding = false; } } - /** Fund publishers sequentially. */ + /** Fund publishers sequentially. Re-reads funder balance after each transfer. */ private async fundPublishers(publishers: UtilsType[]): Promise { const fundingAmount = this.config.publisherFundingAmount!; for (const publisher of publishers) { const address = publisher.getSenderAddress(); try { - // TODO: log funding amount in ETH, not wei - this.log.info(`Funding publisher ${address} with ${fundingAmount} wei`); + this.log.info(`Funding publisher ${address}`, { fundingAmount }); await this.funder!.sendAndMonitorTransaction({ to: address.toString(), data: '0x', value: fundingAmount }); this.log.info(`Funded publisher ${address}`); } catch (err) { this.log.error(`Failed to fund publisher ${address}`, { err }); + continue; + } + + const funderBalance = await this.funder!.getSenderBalance(); + if (funderBalance < fundingAmount) { + this.log.warn(`Funder exhausted after funding, stopping`, { funderBalance, fundingAmount }); + break; } } } diff --git a/yarn-project/node-keystore/src/keystore_manager.test.ts b/yarn-project/node-keystore/src/keystore_manager.test.ts index b2861a964a8f..9da117dd4312 100644 --- a/yarn-project/node-keystore/src/keystore_manager.test.ts +++ b/yarn-project/node-keystore/src/keystore_manager.test.ts @@ -1560,4 +1560,45 @@ describe('KeystoreManager', () => { expect(validateAccessSpy).toHaveBeenCalledWith(url2, [address2.toString()]); }); }); + + describe('createFundingSigner', () => { + const fundingPrivateKey = '0x1234567890123456789012345678901234567890123456789012345678901234'; + + it('returns signer from top-level fundingAccount', async () => { + const keystore: KeyStore = { + schemaVersion: 1, + validators: [ + { + attester: EthAddress.random(), + feeRecipient: await AztecAddress.random(), + }, + ], + fundingAccount: fundingPrivateKey, + }; + + const manager = new KeystoreManager(keystore); + const signer = manager.createFundingSigner(); + + expect(signer).toBeDefined(); + const expected = new LocalSigner(Buffer32.fromString(fundingPrivateKey)); + expect(signer!.address.equals(expected.address)).toBeTruthy(); + }); + + it('returns undefined when no fundingAccount configured', async () => { + const keystore: KeyStore = { + schemaVersion: 1, + validators: [ + { + attester: EthAddress.random(), + feeRecipient: await AztecAddress.random(), + }, + ], + }; + + const manager = new KeystoreManager(keystore); + const signer = manager.createFundingSigner(); + + expect(signer).toBeUndefined(); + }); + }); }); diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 33243ccfb2e8..6f2ceda0926d 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -136,12 +136,10 @@ export async function createProverNode( deps.publisherFactory ?? new ProverPublisherFactory(config, { rollupContract, - publisherManager: new PublisherManager( - l1TxUtils, - getPublisherConfigFromProverConfig(config), - log.getBindings(), - funderL1TxUtils, - ), + publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), { + bindings: log.getBindings(), + funder: funderL1TxUtils, + }), telemetry, }); diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index bc975d3acb2e..b867973303e4 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -93,12 +93,10 @@ export class SequencerClient { publicClient, l1TxUtils.map(x => x.getSenderAddress()), ); - const publisherManager = new PublisherManager( - l1TxUtils, - getPublisherConfigFromSequencerConfig(config), - log.getBindings(), - deps.funderL1TxUtils, - ); + const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), { + bindings: log.getBindings(), + funder: deps.funderL1TxUtils, + }); const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString()); const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([ rollupContract.getL1GenesisTime(), From 530140045b839592d6ca7330957df8d3924b5aee Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 16:17:51 -0300 Subject: [PATCH 06/43] fix self funding --- .../ethereum/src/publisher_manager.test.ts | 16 ++++++++++++++++ yarn-project/ethereum/src/publisher_manager.ts | 8 ++++++++ .../sequencer-client/src/publisher/config.ts | 12 ++++++------ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index 8602c8ed1e99..ef5e89d3c8fd 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -363,6 +363,22 @@ describe('PublisherManager', () => { expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); }); + it('disables funding when funder address matches a publisher', async () => { + const sharedAddress = EthAddress.random(); + mockPublishers = createMockPublishers(2, [sharedAddress]); + mockPublishers[0].balance = 50n; // same address as funder + mockPublishers[1].balance = 50n; // different address, also below threshold + funder = new TestL1TxUtils(sharedAddress) as TestL1TxUtils & L1TxUtils; + funder.balance = 5000n; + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + // Funding is fully disabled because funder overlaps with a publisher + expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); + }); + it('funds publishers in busy states', async () => { mockPublishers = createMockPublishers(2); mockPublishers[0].balance = 50n; diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 227293e87771..e377c6f4d1fb 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -56,6 +56,14 @@ export class PublisherManager { if (hasThreshold !== hasAmount) { this.log.warn(`Incomplete funding config: both publisherFundingThreshold and publisherFundingAmount must be set`); } + + if (this.funder) { + const funderAddress = this.funder.getSenderAddress(); + if (publishers.some(p => p.getSenderAddress().equals(funderAddress))) { + this.log.error(`Funding account ${funderAddress} is also a publisher, disabling funding to avoid self-funding`); + this.funder = undefined; + } + } } /** Loads the state of all publishers and resumes monitoring any pending txs */ diff --git a/yarn-project/sequencer-client/src/publisher/config.ts b/yarn-project/sequencer-client/src/publisher/config.ts index 45e2065c58c7..db10385a5983 100644 --- a/yarn-project/sequencer-client/src/publisher/config.ts +++ b/yarn-project/sequencer-client/src/publisher/config.ts @@ -52,9 +52,9 @@ export type PublisherConfig = L1TxUtilsConfig & publisherForwarderAddress?: EthAddress; /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */ l1TxFailedStore?: string; - /** Min ETH balance (in wei) below which a publisher gets funded. Undefined = funding disabled. */ + /** Min ETH balance (in ether) below which a publisher gets funded. Undefined = funding disabled. */ publisherFundingThreshold?: bigint; - /** Amount of ETH (in wei) to send when funding a publisher. Undefined = funding disabled. */ + /** Amount of ETH (in ether) to send when funding a publisher. Undefined = funding disabled. */ publisherFundingAmount?: bigint; }; @@ -79,9 +79,9 @@ export type ProverPublisherConfig = L1TxUtilsConfig & fishermanMode?: boolean; proverPublisherAllowInvalidStates?: boolean; proverPublisherForwarderAddress?: EthAddress; - /** Min ETH balance (in wei) below which a publisher gets funded. Undefined = funding disabled. */ + /** Min ETH balance (in ether) below which a publisher gets funded. Undefined = funding disabled. */ publisherFundingThreshold?: bigint; - /** Amount of ETH (in wei) to send when funding a publisher. Undefined = funding disabled. */ + /** Amount of ETH (in ether) to send when funding a publisher. Undefined = funding disabled. */ publisherFundingAmount?: bigint; }; @@ -92,9 +92,9 @@ export type SequencerPublisherConfig = L1TxUtilsConfig & sequencerPublisherForwarderAddress?: EthAddress; /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */ l1TxFailedStore?: string; - /** Min ETH balance (in wei) below which a publisher gets funded. Undefined = funding disabled. */ + /** Min ETH balance (in ether) below which a publisher gets funded. Undefined = funding disabled. */ publisherFundingThreshold?: bigint; - /** Amount of ETH (in wei) to send when funding a publisher. Undefined = funding disabled. */ + /** Amount of ETH (in ether) to send when funding a publisher. Undefined = funding disabled. */ publisherFundingAmount?: bigint; }; From 161c19432fd00157feabd35127d24b5f58d83007 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 16:44:45 -0300 Subject: [PATCH 07/43] fix build --- yarn-project/ethereum/src/publisher_manager.test.ts | 4 ++-- yarn-project/ethereum/src/publisher_manager.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index ef5e89d3c8fd..999450fc71c8 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -349,12 +349,12 @@ describe('PublisherManager', () => { publisherManager = createFundedManager(mockPublishers, funder); let callCount = 0; - funder.sendAndMonitorTransaction.mockImplementation(async () => { + funder.sendAndMonitorTransaction.mockImplementation(() => { callCount++; if (callCount === 1) { funder.balance = 10n; // exhausted after first fund } - return { receipt: { transactionHash: '0x1' }, state: {} }; + return Promise.resolve({ receipt: { transactionHash: '0x1' }, state: {} }); }); await publisherManager.getAvailablePublisher(); diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index e377c6f4d1fb..4c12e1c7d95c 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -153,7 +153,7 @@ export class PublisherManager { } const fundingAmount = config.publisherFundingAmount!; - let funderBalance = await funder.getSenderBalance(); + const funderBalance = await funder.getSenderBalance(); if (funderBalance < 10n * fundingAmount) { this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount }); From 5f553e105a0d9281023ba7287207cd22fd9e6199 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 17:20:23 -0300 Subject: [PATCH 08/43] fix build --- yarn-project/node-keystore/src/keystore_manager.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/yarn-project/node-keystore/src/keystore_manager.test.ts b/yarn-project/node-keystore/src/keystore_manager.test.ts index 9da117dd4312..85f355b5fcc2 100644 --- a/yarn-project/node-keystore/src/keystore_manager.test.ts +++ b/yarn-project/node-keystore/src/keystore_manager.test.ts @@ -12,6 +12,7 @@ import { join } from 'path'; import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; import { KeystoreError, KeystoreManager } from '../src/keystore_manager.js'; +import { ethPrivateKeySchema } from '../src/schemas.js'; import { LocalSigner, RemoteSigner } from '../src/signer.js'; import type { KeyStore } from '../src/types.js'; @@ -1562,7 +1563,9 @@ describe('KeystoreManager', () => { }); describe('createFundingSigner', () => { - const fundingPrivateKey = '0x1234567890123456789012345678901234567890123456789012345678901234'; + const fundingPrivateKey = ethPrivateKeySchema.parse( + '0x1234567890123456789012345678901234567890123456789012345678901234', + ); it('returns signer from top-level fundingAccount', async () => { const keystore: KeyStore = { From 2f12ae26677b9042d2025b1bf7543baf456ce6ae Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Mon, 16 Mar 2026 18:25:16 -0300 Subject: [PATCH 09/43] update comments --- yarn-project/aztec-node/src/aztec-node/server.ts | 2 +- .../end-to-end/src/e2e_publisher_funding.test.ts | 6 +++--- .../sequencer-client/src/publisher/config.ts | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index edc352e4308e..ef49eeea39dc 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -494,7 +494,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { ); // Create a funder L1TxUtils from the keystore funding account (if configured) - const fundingSigner = keyStoreManager!.createFundingSigner(); + const fundingSigner = keyStoreManager?.createFundingSigner(); let funderL1TxUtils: L1TxUtils | undefined; if (fundingSigner) { const [funder] = await createL1TxUtilsFromSigners( diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts index a1e6b3c3d0e0..069b6d6ac77b 100644 --- a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts +++ b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts @@ -145,10 +145,10 @@ describe('e2e_publisher_funding', () => { // Funder should have sent exactly FUNDING_AMOUNT plus gas costs expect(funderSpent).toBeGreaterThanOrEqual(FUNDING_AMOUNT); - // Second round: the publisher will spend gas publishing blocks, eventually dropping - // below threshold again. The funder should automatically top it up a second time. + // Second round: the publisher balance is still below threshold (FUNDING_AMOUNT < FUNDING_THRESHOLD), + // so the next getAvailablePublisher() call will trigger another funding round. const funderBalanceBefore2 = await ethCheatCodes.getBalance(funderAddress); - logger.info(`Waiting for publisher to drain and get re-funded`); + logger.info(`Waiting for second funding round`); await retryUntil( async () => { diff --git a/yarn-project/sequencer-client/src/publisher/config.ts b/yarn-project/sequencer-client/src/publisher/config.ts index db10385a5983..a68948536ce8 100644 --- a/yarn-project/sequencer-client/src/publisher/config.ts +++ b/yarn-project/sequencer-client/src/publisher/config.ts @@ -52,9 +52,9 @@ export type PublisherConfig = L1TxUtilsConfig & publisherForwarderAddress?: EthAddress; /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */ l1TxFailedStore?: string; - /** Min ETH balance (in ether) below which a publisher gets funded. Undefined = funding disabled. */ + /** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */ publisherFundingThreshold?: bigint; - /** Amount of ETH (in ether) to send when funding a publisher. Undefined = funding disabled. */ + /** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */ publisherFundingAmount?: bigint; }; @@ -79,9 +79,9 @@ export type ProverPublisherConfig = L1TxUtilsConfig & fishermanMode?: boolean; proverPublisherAllowInvalidStates?: boolean; proverPublisherForwarderAddress?: EthAddress; - /** Min ETH balance (in ether) below which a publisher gets funded. Undefined = funding disabled. */ + /** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */ publisherFundingThreshold?: bigint; - /** Amount of ETH (in ether) to send when funding a publisher. Undefined = funding disabled. */ + /** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */ publisherFundingAmount?: bigint; }; @@ -92,9 +92,9 @@ export type SequencerPublisherConfig = L1TxUtilsConfig & sequencerPublisherForwarderAddress?: EthAddress; /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */ l1TxFailedStore?: string; - /** Min ETH balance (in ether) below which a publisher gets funded. Undefined = funding disabled. */ + /** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */ publisherFundingThreshold?: bigint; - /** Amount of ETH (in ether) to send when funding a publisher. Undefined = funding disabled. */ + /** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */ publisherFundingAmount?: bigint; }; From a54530e96c949b9b0d0428416667e6959baf1fbc Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Wed, 18 Mar 2026 13:43:46 -0300 Subject: [PATCH 10/43] use multicall --- .../src/e2e_publisher_funding.test.ts | 167 ------------------ .../ethereum/src/contracts/multicall.ts | 66 ++++++- .../ethereum/src/publisher_manager.test.ts | 114 ++++++------ .../ethereum/src/publisher_manager.ts | 26 +-- 4 files changed, 132 insertions(+), 241 deletions(-) delete mode 100644 yarn-project/end-to-end/src/e2e_publisher_funding.test.ts diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts deleted file mode 100644 index 069b6d6ac77b..000000000000 --- a/yarn-project/end-to-end/src/e2e_publisher_funding.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; -import { Fr } from '@aztec/aztec.js/fields'; -import type { Logger } from '@aztec/aztec.js/log'; -import type { EthCheatCodes } from '@aztec/aztec/testing'; -import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils'; -import type { PublisherManager } from '@aztec/ethereum/publisher-manager'; -import { SecretValue } from '@aztec/foundation/config'; -import { retryUntil } from '@aztec/foundation/retry'; -import type { SequencerClient } from '@aztec/sequencer-client'; -import type { TestSequencerClient } from '@aztec/sequencer-client/test'; - -import { jest } from '@jest/globals'; -import { mkdtemp, rm, writeFile } from 'fs/promises'; -import 'jest-extended'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import { type Hex, parseEther } from 'viem'; -import { privateKeyToAccount } from 'viem/accounts'; - -import { getPrivateKeyFromIndex, setup } from './fixtures/utils.js'; - -// Key indices from the test MNEMONIC (all pre-funded by Anvil): -// 0 = L1 contract deployer (not in keystore) -// 1 = validator attester + publisher EOA -// 2 = funding account -const DEPLOYER_KEY_INDEX = 0; -const PUBLISHER_KEY_INDEX = 1; -const FUNDER_KEY_INDEX = 2; - -const toPrivateKeyHex = (index: number): Hex => { - const buf = getPrivateKeyFromIndex(index); - if (!buf) { - throw new Error(`Failed to derive private key for index ${index}`); - } - return `0x${buf.toString('hex')}`; -}; - -const FUNDING_THRESHOLD = parseEther('0.5'); -// Small enough that publishing a few blocks will drain it below threshold again, triggering re-funding. -const FUNDING_AMOUNT = parseEther('0.1'); - -describe('e2e_publisher_funding', () => { - jest.setTimeout(5 * 60 * 1000); - - let logger: Logger; - let publisherManager: PublisherManager; - let ethCheatCodes: EthCheatCodes; - let teardown: () => Promise; - let keyStoreDirectory: string; - - beforeAll(async () => { - const attesterKey = toPrivateKeyHex(PUBLISHER_KEY_INDEX); - const publisherKey = attesterKey; - const funderKey = toPrivateKeyHex(FUNDER_KEY_INDEX); - const attesterAddress = privateKeyToAccount(attesterKey).address; - - // Write keystore JSON with fundingAccount - keyStoreDirectory = await mkdtemp(join(tmpdir(), 'publisher-funding-')); - const keystore = { - schemaVersion: 1, - validators: [ - { - attester: attesterKey, - publisher: [publisherKey], - coinbase: EthAddress.fromNumber(42).toChecksumString(), - feeRecipient: AztecAddress.fromNumber(42).toString(), - }, - ], - fundingAccount: funderKey, - }; - await writeFile(join(keyStoreDirectory, 'keystore.json'), JSON.stringify(keystore, null, 2)); - - // Stake the validator on L1 so the sequencer can propose blocks - const initialValidators = [ - { - attester: EthAddress.fromString(attesterAddress), - withdrawer: EthAddress.fromString(attesterAddress), - privateKey: attesterKey as Hex, - bn254SecretKey: new SecretValue(Fr.random().toBigInt()), - }, - ]; - - let sequencerClient: SequencerClient | undefined; - ({ - teardown, - logger, - sequencer: sequencerClient, - ethCheatCodes, - } = await setup(0, { - initialValidators, - keyStoreDirectory, - publisherFundingThreshold: FUNDING_THRESHOLD, - publisherFundingAmount: FUNDING_AMOUNT, - minTxsPerBlock: 0, - l1PublisherKey: new SecretValue(toPrivateKeyHex(DEPLOYER_KEY_INDEX)), - })); - - publisherManager = (sequencerClient! as TestSequencerClient).publisherManager; - }); - - afterAll(async () => { - await teardown(); - await rm(keyStoreDirectory, { recursive: true, force: true }); - }); - - it('funds publisher when balance drops below threshold', async () => { - const publishers: L1TxUtils[] = (publisherManager as any).publishers; - const funder: L1TxUtils | undefined = (publisherManager as any).funder; - - expect(publishers.length).toBe(1); - expect(funder).toBeDefined(); - - const publisherAddress = publishers[0].getSenderAddress(); - const funderAddress = funder!.getSenderAddress(); - logger.info(`Publisher: ${publisherAddress}, Funder: ${funderAddress}`); - - // Set publisher balance below threshold - const LOW_BALANCE = parseEther('0.1'); - await ethCheatCodes.setBalance(publisherAddress, LOW_BALANCE); - // Give funder plenty of ETH - await ethCheatCodes.setBalance(funderAddress, parseEther('100')); - - const funderBalanceBefore = await ethCheatCodes.getBalance(funderAddress); - - // The sequencer periodically calls getAvailablePublisher(), which triggers funding - // when it sees the publisher balance is below threshold. Wait for funding to land. - await retryUntil( - async () => { - const balance = await ethCheatCodes.getBalance(publisherAddress); - return balance > LOW_BALANCE ? true : undefined; - }, - 'waiting for publisher to be funded', - 60, - 1, - ); - - const publisherBalanceAfter = await ethCheatCodes.getBalance(publisherAddress); - const funderBalanceAfter = await ethCheatCodes.getBalance(funderAddress); - const funderSpent = funderBalanceBefore - funderBalanceAfter; - - logger.info(`Publisher balance after: ${publisherBalanceAfter} (was ${LOW_BALANCE})`); - logger.info(`Funder spent: ${funderSpent} (expected ~${FUNDING_AMOUNT})`); - - expect(publisherBalanceAfter).toBeGreaterThan(LOW_BALANCE); - // Funder should have sent exactly FUNDING_AMOUNT plus gas costs - expect(funderSpent).toBeGreaterThanOrEqual(FUNDING_AMOUNT); - - // Second round: the publisher balance is still below threshold (FUNDING_AMOUNT < FUNDING_THRESHOLD), - // so the next getAvailablePublisher() call will trigger another funding round. - const funderBalanceBefore2 = await ethCheatCodes.getBalance(funderAddress); - logger.info(`Waiting for second funding round`); - - await retryUntil( - async () => { - const spent = funderBalanceBefore2 - (await ethCheatCodes.getBalance(funderAddress)); - return spent >= FUNDING_AMOUNT ? true : undefined; - }, - 'waiting for second funding round', - 120, - 1, - ); - - const funderSpent2 = funderBalanceBefore2 - (await ethCheatCodes.getBalance(funderAddress)); - logger.info(`Second funding round: funder spent ${funderSpent2}`); - expect(funderSpent2).toBeGreaterThanOrEqual(FUNDING_AMOUNT); - }); -}); diff --git a/yarn-project/ethereum/src/contracts/multicall.ts b/yarn-project/ethereum/src/contracts/multicall.ts index c79243b80b1b..40e17970e5db 100644 --- a/yarn-project/ethereum/src/contracts/multicall.ts +++ b/yarn-project/ethereum/src/contracts/multicall.ts @@ -2,7 +2,7 @@ import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer'; import { TimeoutError } from '@aztec/foundation/error'; import type { Logger } from '@aztec/foundation/log'; -import { type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multicall3Abi } from 'viem'; +import { type Address, type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multicall3Abi } from 'viem'; import type { L1BlobInputs, L1TxConfig, L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js'; import type { ExtendedViemWalletClient } from '../types.js'; @@ -11,6 +11,39 @@ import { RollupContract } from './rollup.js'; export const MULTI_CALL_3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' as const; +/** ABI fragment for aggregate3Value — not included in viem's multicall3Abi. */ +export const aggregate3ValueAbi = [ + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'target', type: 'address' }, + { internalType: 'bool', name: 'allowFailure', type: 'bool' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'bytes', name: 'callData', type: 'bytes' }, + ], + internalType: 'struct Multicall3.Call3Value[]', + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'aggregate3Value', + outputs: [ + { + components: [ + { internalType: 'bool', name: 'success', type: 'bool' }, + { internalType: 'bytes', name: 'returnData', type: 'bytes' }, + ], + internalType: 'struct Multicall3.Result[]', + name: 'returnData', + type: 'tuple[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, +] as const; + export class Multicall3 { static async forward( requests: L1TxRequest[], @@ -122,6 +155,37 @@ export class Multicall3 { throw err; } } + + /** Batch multiple value transfers into a single aggregate3Value call on Multicall3. */ + static async forwardValue(calls: { to: Address; value: bigint }[], l1TxUtils: L1TxUtils, logger: Logger) { + const args = calls.map(c => ({ + target: c.to, + allowFailure: false, + value: c.value, + callData: '0x' as Hex, + })); + + const data = encodeFunctionData({ + abi: aggregate3ValueAbi, + functionName: 'aggregate3Value', + args: [args], + }); + + const totalValue = calls.reduce((sum, c) => sum + c.value, 0n); + + logger.info(`Sending aggregate3Value with ${calls.length} calls`, { totalValue }); + const { receipt } = await l1TxUtils.sendAndMonitorTransaction({ + to: MULTI_CALL_3_ADDRESS, + data, + value: totalValue, + }); + + if (receipt.status !== 'success') { + throw new Error(`aggregate3Value transaction reverted: ${receipt.transactionHash}`); + } + + return { receipt }; + } } export async function deployMulticall3(l1Client: ExtendedViemWalletClient, logger: Logger) { diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index 999450fc71c8..3a3f80f7cce7 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -2,10 +2,28 @@ import { times } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; import { jest } from '@jest/globals'; +import { type Hex, encodeFunctionData } from 'viem'; +import { MULTI_CALL_3_ADDRESS, aggregate3ValueAbi } from './contracts/multicall.js'; import { L1TxUtils, TxUtilsState } from './l1_tx_utils/index.js'; import { PublisherManager } from './publisher_manager.js'; +/** Encode the expected aggregate3Value calldata for the given addresses and funding amount. */ +function expectedFundingData(addresses: EthAddress[], fundingAmount: bigint): Hex { + return encodeFunctionData({ + abi: aggregate3ValueAbi, + functionName: 'aggregate3Value', + args: [ + addresses.map(addr => ({ + target: addr.toString() as `0x${string}`, + allowFailure: false, + value: fundingAmount, + callData: '0x' as Hex, + })), + ], + }); +} + describe('PublisherManager', () => { let mockPublishers: (TestL1TxUtils & L1TxUtils)[]; let publisherManager: PublisherManager; @@ -206,9 +224,11 @@ describe('PublisherManager', () => { await waitForFunding(); expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith( - expect.objectContaining({ to: mockPublishers[0].getSenderAddress().toString(), value: fundingAmount }), - ); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ + to: MULTI_CALL_3_ADDRESS, + data: expectedFundingData([mockPublishers[0].getSenderAddress()], fundingAmount), + value: fundingAmount, + }); }); it('does not fund when publisher balance is above threshold', async () => { @@ -232,46 +252,46 @@ describe('PublisherManager', () => { await publisherManager.getAvailablePublisher(); await waitForFunding(); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith( - expect.objectContaining({ to: mockPublishers[0].getSenderAddress().toString(), value: fundingAmount }), - ); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith( - expect.objectContaining({ to: mockPublishers[2].getSenderAddress().toString(), value: fundingAmount }), - ); + // Single multicall for both underfunded publishers + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ + to: MULTI_CALL_3_ADDRESS, + data: expectedFundingData( + [mockPublishers[0].getSenderAddress(), mockPublishers[2].getSenderAddress()], + fundingAmount, + ), + value: 2n * fundingAmount, + }); }); - it('handles funding transaction failure gracefully', async () => { - mockPublishers = createMockPublishers(2); + it('correctly sends the funding transaction', async () => { + mockPublishers = createMockPublishers(1); mockPublishers[0].balance = 50n; - mockPublishers[1].balance = 50n; publisherManager = createFundedManager(mockPublishers, funder); - // First call fails, second succeeds - funder.sendAndMonitorTransaction - .mockRejectedValueOnce(new Error('tx failed')) - .mockResolvedValueOnce({ receipt: { transactionHash: '0xdef' }, state: {} }); - await publisherManager.getAvailablePublisher(); await waitForFunding(); - // Both attempted despite first failure - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ + to: MULTI_CALL_3_ADDRESS, + data: expectedFundingData([mockPublishers[0].getSenderAddress()], fundingAmount), + value: fundingAmount, + }); }); - it('correctly sends the funding transaction', async () => { - mockPublishers = createMockPublishers(1); + it('handles funding transaction failure gracefully', async () => { + mockPublishers = createMockPublishers(2); mockPublishers[0].balance = 50n; + mockPublishers[1].balance = 50n; publisherManager = createFundedManager(mockPublishers, funder); + funder.sendAndMonitorTransaction.mockRejectedValueOnce(new Error('tx failed')); + await publisherManager.getAvailablePublisher(); await waitForFunding(); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ - to: mockPublishers[0].getSenderAddress().toString(), - data: '0x', - value: fundingAmount, - }); + // Single multicall attempted and failed — error caught by triggerFundingIfNeeded + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); }); it('concurrent guard prevents overlapping funding runs', async () => { @@ -284,7 +304,7 @@ describe('PublisherManager', () => { const fundingBlocked = new Promise(r => (resolveFunding = r)); funder.sendAndMonitorTransaction.mockImplementation(async () => { await fundingBlocked; - return { receipt: { transactionHash: '0x1' }, state: {} }; + return { receipt: { transactionHash: '0x1', status: 'success' }, state: {} }; }); // First call triggers funding (sets isFunding = true) @@ -339,30 +359,6 @@ describe('PublisherManager', () => { expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); - it('stops funding when funder balance exhausted mid-run', async () => { - mockPublishers = createMockPublishers(3); - mockPublishers[0].balance = 10n; - mockPublishers[1].balance = 10n; - mockPublishers[2].balance = 10n; - // Funder starts with enough for all, but after first fund balance drops below fundingAmount - funder.balance = 5000n; - publisherManager = createFundedManager(mockPublishers, funder); - - let callCount = 0; - funder.sendAndMonitorTransaction.mockImplementation(() => { - callCount++; - if (callCount === 1) { - funder.balance = 10n; // exhausted after first fund - } - return Promise.resolve({ receipt: { transactionHash: '0x1' }, state: {} }); - }); - - await publisherManager.getAvailablePublisher(); - await waitForFunding(); - - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); - }); - it('disables funding when funder address matches a publisher', async () => { const sharedAddress = EthAddress.random(); mockPublishers = createMockPublishers(2, [sharedAddress]); @@ -390,8 +386,16 @@ describe('PublisherManager', () => { await publisherManager.getAvailablePublisher(); await waitForFunding(); - // Both should be funded, even the busy one - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); + // Single multicall funds both, even the busy one + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ + to: MULTI_CALL_3_ADDRESS, + data: expectedFundingData( + [mockPublishers[0].getSenderAddress(), mockPublishers[1].getSenderAddress()], + fundingAmount, + ), + value: 2n * fundingAmount, + }); }); }); @@ -409,7 +413,7 @@ class TestL1TxUtils { public lastMinedAtBlockNumber: bigint | undefined = undefined; public balance: bigint = 1000n; public sendAndMonitorTransaction = jest.fn<() => Promise>().mockResolvedValue({ - receipt: { transactionHash: '0xabc' }, + receipt: { transactionHash: '0xabc', status: 'success' }, state: {}, }); diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 4c12e1c7d95c..8579de00b796 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -1,6 +1,7 @@ import { pick } from '@aztec/foundation/collection'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; +import { Multicall3 } from './contracts/multicall.js'; import { L1TxUtils, TxUtilsState } from './l1_tx_utils/index.js'; // Defines the order in which we prioritise publishers based on their state (first is better) @@ -169,26 +170,15 @@ export class PublisherManager { } } - /** Fund publishers sequentially. Re-reads funder balance after each transfer. */ + /** Fund publishers via a single Multicall3 aggregate3Value transaction. */ private async fundPublishers(publishers: UtilsType[]): Promise { const fundingAmount = this.config.publisherFundingAmount!; + const calls = publishers.map(pub => ({ + to: pub.getSenderAddress().toString(), + value: fundingAmount, + })); - for (const publisher of publishers) { - const address = publisher.getSenderAddress(); - try { - this.log.info(`Funding publisher ${address}`, { fundingAmount }); - await this.funder!.sendAndMonitorTransaction({ to: address.toString(), data: '0x', value: fundingAmount }); - this.log.info(`Funded publisher ${address}`); - } catch (err) { - this.log.error(`Failed to fund publisher ${address}`, { err }); - continue; - } - - const funderBalance = await this.funder!.getSenderBalance(); - if (funderBalance < fundingAmount) { - this.log.warn(`Funder exhausted after funding, stopping`, { funderBalance, fundingAmount }); - break; - } - } + await Multicall3.forwardValue(calls, this.funder!, this.log); + this.log.info(`Funded ${publishers.length} publishers`); } } From ef1c0522b8292b4356ebbb2cc51afce889cc1a55 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Wed, 18 Mar 2026 13:46:39 -0300 Subject: [PATCH 11/43] add e2e test --- .../src/e2e_publisher_funding_multi.test.ts | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts new file mode 100644 index 000000000000..f378036041a9 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts @@ -0,0 +1,179 @@ +import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { Logger } from '@aztec/aztec.js/log'; +import type { EthCheatCodes } from '@aztec/aztec/testing'; +import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils'; +import type { PublisherManager } from '@aztec/ethereum/publisher-manager'; +import { SecretValue } from '@aztec/foundation/config'; +import { retryUntil } from '@aztec/foundation/retry'; +import type { SequencerClient } from '@aztec/sequencer-client'; +import type { TestSequencerClient } from '@aztec/sequencer-client/test'; + +import { jest } from '@jest/globals'; +import { mkdtemp, rm, writeFile } from 'fs/promises'; +import 'jest-extended'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { type Hex, parseEther } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; + +import { getPrivateKeyFromIndex, setup } from './fixtures/utils.js'; + +// Key indices from the test MNEMONIC (all pre-funded by Anvil): +// 0 = L1 contract deployer (not in keystore) +// 1 = validator attester + publisher 1 EOA +// 2 = funding account +// 3 = publisher 2 EOA +const DEPLOYER_KEY_INDEX = 0; +const PUBLISHER1_KEY_INDEX = 1; +const FUNDER_KEY_INDEX = 2; +const PUBLISHER2_KEY_INDEX = 3; + +const toPrivateKeyHex = (index: number): Hex => { + const buf = getPrivateKeyFromIndex(index); + if (!buf) { + throw new Error(`Failed to derive private key for index ${index}`); + } + return `0x${buf.toString('hex')}`; +}; + +const FUNDING_THRESHOLD = parseEther('2'); +const FUNDING_AMOUNT = parseEther('2.1'); + +describe('e2e_publisher_funding_multi', () => { + jest.setTimeout(5 * 60 * 1000); + + let logger: Logger; + let publisherManager: PublisherManager; + let ethCheatCodes: EthCheatCodes; + let teardown: () => Promise; + let keyStoreDirectory: string; + + beforeAll(async () => { + const attesterKey = toPrivateKeyHex(PUBLISHER1_KEY_INDEX); + const publisherKey1 = attesterKey; + const publisherKey2 = toPrivateKeyHex(PUBLISHER2_KEY_INDEX); + const funderKey = toPrivateKeyHex(FUNDER_KEY_INDEX); + const attesterAddress = privateKeyToAccount(attesterKey).address; + + // Write keystore JSON with two publishers and a fundingAccount + keyStoreDirectory = await mkdtemp(join(tmpdir(), 'publisher-funding-multi-')); + const keystore = { + schemaVersion: 1, + validators: [ + { + attester: attesterKey, + publisher: [publisherKey1, publisherKey2], + coinbase: EthAddress.fromNumber(42).toChecksumString(), + feeRecipient: AztecAddress.fromNumber(42).toString(), + }, + ], + fundingAccount: funderKey, + }; + await writeFile(join(keyStoreDirectory, 'keystore.json'), JSON.stringify(keystore, null, 2)); + + // Stake the validator on L1 so the sequencer can propose blocks + const initialValidators = [ + { + attester: EthAddress.fromString(attesterAddress), + withdrawer: EthAddress.fromString(attesterAddress), + privateKey: attesterKey as Hex, + bn254SecretKey: new SecretValue(Fr.random().toBigInt()), + }, + ]; + + let sequencerClient: SequencerClient | undefined; + ({ + teardown, + logger, + sequencer: sequencerClient, + ethCheatCodes, + } = await setup(0, { + initialValidators, + keyStoreDirectory, + publisherFundingThreshold: FUNDING_THRESHOLD, + publisherFundingAmount: FUNDING_AMOUNT, + minTxsPerBlock: 0, + l1PublisherKey: new SecretValue(toPrivateKeyHex(DEPLOYER_KEY_INDEX)), + })); + + publisherManager = (sequencerClient! as TestSequencerClient).publisherManager; + }); + + afterAll(async () => { + await teardown(); + await rm(keyStoreDirectory, { recursive: true, force: true }); + }); + + it('funds both publishers when balances drop below threshold', async () => { + const publishers: L1TxUtils[] = (publisherManager as any).publishers; + const funder: L1TxUtils | undefined = (publisherManager as any).funder; + + expect(publishers.length).toBe(2); + expect(funder).toBeDefined(); + + const publisher1Address = publishers[0].getSenderAddress(); + const publisher2Address = publishers[1].getSenderAddress(); + const funderAddress = funder!.getSenderAddress(); + logger.info(`Publisher1: ${publisher1Address}, Publisher2: ${publisher2Address}, Funder: ${funderAddress}`); + + // Set both publisher balances below threshold + const LOW_BALANCE = parseEther('0.1'); + await ethCheatCodes.setBalance(publisher1Address, LOW_BALANCE); + await ethCheatCodes.setBalance(publisher2Address, LOW_BALANCE); + // Give funder plenty of ETH + await ethCheatCodes.setBalance(funderAddress, parseEther('100')); + + const funderBalanceBefore = await ethCheatCodes.getBalance(funderAddress); + + // The sequencer periodically calls getAvailablePublisher(), which triggers funding + // when it sees publisher balances are below threshold. Both should be funded in a single multicall. + await retryUntil( + async () => { + const balance1 = await ethCheatCodes.getBalance(publisher1Address); + const balance2 = await ethCheatCodes.getBalance(publisher2Address); + return balance1 > LOW_BALANCE && balance2 > LOW_BALANCE ? true : undefined; + }, + 'waiting for both publishers to be funded', + 60, + 1, + ); + + const publisher1BalanceAfter = await ethCheatCodes.getBalance(publisher1Address); + const publisher2BalanceAfter = await ethCheatCodes.getBalance(publisher2Address); + const funderBalanceAfter = await ethCheatCodes.getBalance(funderAddress); + const funderSpent = funderBalanceBefore - funderBalanceAfter; + + logger.info(`Publisher1 balance after: ${publisher1BalanceAfter} (was ${LOW_BALANCE})`); + logger.info(`Publisher2 balance after: ${publisher2BalanceAfter} (was ${LOW_BALANCE})`); + logger.info(`Funder spent: ${funderSpent} (expected ~${2n * FUNDING_AMOUNT})`); + + expect(publisher1BalanceAfter).toBeGreaterThan(LOW_BALANCE); + expect(publisher2BalanceAfter).toBeGreaterThan(LOW_BALANCE); + // Both publishers should now be above the funding threshold + expect(publisher1BalanceAfter).toBeGreaterThanOrEqual(FUNDING_THRESHOLD); + expect(publisher2BalanceAfter).toBeGreaterThanOrEqual(FUNDING_THRESHOLD); + // Funder should have sent 2 * FUNDING_AMOUNT plus gas costs (single multicall) + expect(funderSpent).toBeGreaterThanOrEqual(2n * FUNDING_AMOUNT); + + // Second round: after funding, publishers are above threshold. The active publisher will + // spend gas publishing blocks and eventually drop below threshold again, triggering re-funding + // for that one publisher. + const funderBalanceBefore2 = await ethCheatCodes.getBalance(funderAddress); + logger.info(`Waiting for second funding round`); + + await retryUntil( + async () => { + const spent = funderBalanceBefore2 - (await ethCheatCodes.getBalance(funderAddress)); + return spent >= FUNDING_AMOUNT ? true : undefined; + }, + 'waiting for second funding round', + 120, + 1, + ); + + const funderSpent2 = funderBalanceBefore2 - (await ethCheatCodes.getBalance(funderAddress)); + logger.info(`Second funding round: funder spent ${funderSpent2} (expected ~${FUNDING_AMOUNT})`); + expect(funderSpent2).toBeGreaterThanOrEqual(FUNDING_AMOUNT); + }); +}); From a88a5cbe0366be3aa1f58e7ba13ee9f64bdf5823 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Wed, 18 Mar 2026 14:58:13 -0300 Subject: [PATCH 12/43] cap publishers --- .../ethereum/src/publisher_manager.test.ts | 22 +++++++++++++++++++ .../ethereum/src/publisher_manager.ts | 12 ++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index 3a3f80f7cce7..fafa3cfb610d 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -359,6 +359,28 @@ describe('PublisherManager', () => { expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); + it('caps funding to affordable number of publishers', async () => { + mockPublishers = createMockPublishers(3); + mockPublishers[0].balance = 10n; + mockPublishers[1].balance = 10n; + mockPublishers[2].balance = 10n; + funder.balance = 2n * fundingAmount; // enough for 2, not 3 + publisherManager = createFundedManager(mockPublishers, funder); + + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ + to: MULTI_CALL_3_ADDRESS, + data: expectedFundingData( + [mockPublishers[0].getSenderAddress(), mockPublishers[1].getSenderAddress()], + fundingAmount, + ), + value: 2n * fundingAmount, + }); + }); + it('disables funding when funder address matches a publisher', async () => { const sharedAddress = EthAddress.random(); mockPublishers = createMockPublishers(2, [sharedAddress]); diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 8579de00b796..2e0a1a3129ab 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -159,12 +159,20 @@ export class PublisherManager { if (funderBalance < 10n * fundingAmount) { this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount }); } - if (funderBalance < fundingAmount) { + const affordableCount = Number(funderBalance / fundingAmount); + if (affordableCount === 0) { this.log.error(`Funding account balance too low to fund any publisher`, { funderBalance, fundingAmount }); return; } + if (affordableCount < lowBalance.length) { + this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, { + funderBalance, + fundingAmount, + }); + } - await this.fundPublishers(lowBalance.map(p => p.publisher)); + const toFund = lowBalance.slice(0, affordableCount).map(p => p.publisher); + await this.fundPublishers(toFund); } finally { this.isFunding = false; } From 8a5ca2b15728a269483e1d666a1262ff6b48a2b0 Mon Sep 17 00:00:00 2001 From: Phil Windle Date: Wed, 18 Mar 2026 18:09:59 +0000 Subject: [PATCH 13/43] fix: avoid mutating caller's array via splice in snapshot sync (A-718) Use chunk + asyncPool to replace splice-based batching in BrokerCircuitProverFacade. Both getAllCompletedJobs and job retrieval now use non-mutating patterns with bounded concurrency. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../proving_broker/broker_prover_facade.ts | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/yarn-project/prover-client/src/proving_broker/broker_prover_facade.ts b/yarn-project/prover-client/src/proving_broker/broker_prover_facade.ts index c9537af6c601..8c45fe0f55cd 100644 --- a/yarn-project/prover-client/src/proving_broker/broker_prover_facade.ts +++ b/yarn-project/prover-client/src/proving_broker/broker_prover_facade.ts @@ -4,7 +4,9 @@ import type { NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH, RECURSIVE_PROOF_LENGTH, } from '@aztec/constants'; +import { asyncPool } from '@aztec/foundation/async-pool'; import { EpochNumber } from '@aztec/foundation/branded-types'; +import { chunk } from '@aztec/foundation/collection'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; import { type PromiseWithResolvers, RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise'; import { truncate } from '@aztec/foundation/string'; @@ -226,17 +228,11 @@ export class BrokerCircuitProverFacade implements ServerCircuitProver { // We collect all returned notifications and return them const allCompleted = new Set(); try { - let numRequests = 0; - while (ids.length > 0) { - const slice = ids.splice(0, SNAPSHOT_SYNC_CHECK_MAX_REQUEST_SIZE); - const completed = await this.broker.getCompletedJobs(slice); + const batches = ids.length > 0 ? chunk(ids, SNAPSHOT_SYNC_CHECK_MAX_REQUEST_SIZE) : [[]]; + await asyncPool(1, batches, async batch => { + const completed = await this.broker.getCompletedJobs(batch); completed.forEach(id => allCompleted.add(id)); - ++numRequests; - } - if (numRequests === 0) { - const final = await this.broker.getCompletedJobs([]); - final.forEach(id => allCompleted.add(id)); - } + }); } catch (err) { this.log.error(`Error thrown when requesting completed job notifications from the broker`, err); } @@ -352,12 +348,8 @@ export class BrokerCircuitProverFacade implements ServerCircuitProver { .map(id => this.jobs.get(id)!) .filter(x => x !== undefined); const totalJobsToRetrieve = toBeRetrieved.length; - let totalJobsRetrieved = 0; - while (toBeRetrieved.length > 0) { - const slice = toBeRetrieved.splice(0, MAX_CONCURRENT_JOB_SETTLED_REQUESTS); - const results = await Promise.all(slice.map(job => processJob(job!))); - totalJobsRetrieved += results.filter(x => x).length; - } + const results = await asyncPool(MAX_CONCURRENT_JOB_SETTLED_REQUESTS, toBeRetrieved, job => processJob(job)); + const totalJobsRetrieved = results.filter(x => x).length; if (totalJobsToRetrieve > 0) { this.log.verbose( `Successfully retrieved ${totalJobsRetrieved} of ${totalJobsToRetrieve} jobs that should be ready, total ready jobs is now: ${this.jobsToRetrieve.size}`, From f616c97ab55963b813375d49676bb12983229604 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Wed, 18 Mar 2026 15:43:42 -0300 Subject: [PATCH 14/43] funding cooldown --- .../ethereum/src/publisher_manager.test.ts | 47 +++++++++++++++---- .../ethereum/src/publisher_manager.ts | 10 ++++ yarn-project/prover-node/src/factory.ts | 2 +- .../src/client/sequencer-client.ts | 13 +++-- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index fafa3cfb610d..a94853bb8ba5 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -1,5 +1,6 @@ import { times } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; +import { ManualDateProvider } from '@aztec/foundation/timer'; import { jest } from '@jest/globals'; import { type Hex, encodeFunctionData } from 'viem'; @@ -27,16 +28,18 @@ function expectedFundingData(addresses: EthAddress[], fundingAmount: bigint): He describe('PublisherManager', () => { let mockPublishers: (TestL1TxUtils & L1TxUtils)[]; let publisherManager: PublisherManager; + let dateProvider: ManualDateProvider; beforeEach(() => { jest.clearAllMocks(); + dateProvider = new ManualDateProvider(); }); describe('constructor', () => { it('should initialize with publishers', () => { mockPublishers = createMockPublishers(3); - expect(() => new PublisherManager(mockPublishers, {})).not.toThrow(); + expect(() => new PublisherManager(mockPublishers, {}, dateProvider)).not.toThrow(); }); }); @@ -45,7 +48,7 @@ describe('PublisherManager', () => { beforeEach(() => { addresses = Array.from({ length: 3 }, () => EthAddress.random()); mockPublishers = createMockPublishers(3, addresses); - publisherManager = new PublisherManager(mockPublishers, {}); + publisherManager = new PublisherManager(mockPublishers, {}, dateProvider); }); it('should throw error when no valid publishers found', async () => { @@ -70,7 +73,7 @@ describe('PublisherManager', () => { mockPublishers[1].state = TxUtilsState.CANCELLED; mockPublishers[2].state = TxUtilsState.NOT_MINED; - publisherManager = new PublisherManager(mockPublishers, { publisherAllowInvalidStates: true }); + publisherManager = new PublisherManager(mockPublishers, { publisherAllowInvalidStates: true }, dateProvider); await expect(publisherManager.getAvailablePublisher(p => p.state === TxUtilsState.CANCELLED)).resolves.toBe( mockPublishers[1], ); @@ -147,7 +150,7 @@ describe('PublisherManager', () => { it('should prioritise same state publishers based on balance and then least recently used', async () => { const ethAddresses = Array.from({ length: 5 }, () => EthAddress.random()); mockPublishers = createMockPublishers(5, ethAddresses); - publisherManager = new PublisherManager(mockPublishers, {}); + publisherManager = new PublisherManager(mockPublishers, {}, dateProvider); const filter = (utils: L1TxUtils) => utils.getSenderAddress() !== mockPublishers[2].getSenderAddress(); // Filter out publisher in index 2 @@ -203,6 +206,7 @@ describe('PublisherManager', () => { return new PublisherManager( publishers, { publisherFundingThreshold: threshold, publisherFundingAmount: fundingAmount, ...config }, + dateProvider, { funder: funderInstance }, ); }; @@ -322,10 +326,14 @@ describe('PublisherManager', () => { it('no funding triggered when no funder configured', async () => { mockPublishers = createMockPublishers(1); mockPublishers[0].balance = 50n; - publisherManager = new PublisherManager(mockPublishers, { - publisherFundingThreshold: threshold, - publisherFundingAmount: fundingAmount, - }); + publisherManager = new PublisherManager( + mockPublishers, + { + publisherFundingThreshold: threshold, + publisherFundingAmount: fundingAmount, + }, + dateProvider, + ); await publisherManager.getAvailablePublisher(); await waitForFunding(); @@ -397,6 +405,29 @@ describe('PublisherManager', () => { expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); + it('skips funding within cooldown and resumes after cooldown expires', async () => { + mockPublishers = createMockPublishers(1); + mockPublishers[0].balance = 50n; + publisherManager = createFundedManager(mockPublishers, funder); + + // First call triggers funding + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + + // Second call within cooldown — funding should be skipped + dateProvider.advanceTime(60); // 1 minute, less than 2 min cooldown + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); + + // Third call after cooldown expires — funding should trigger again + dateProvider.advanceTime(2 * 60); // another 2 minutes + await publisherManager.getAvailablePublisher(); + await waitForFunding(); + expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); + }); + it('funds publishers in busy states', async () => { mockPublishers = createMockPublishers(2); mockPublishers[0].balance = 50n; diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index 2e0a1a3129ab..f4c6e2dc0061 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -1,5 +1,6 @@ import { pick } from '@aztec/foundation/collection'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; +import { DateProvider } from '@aztec/foundation/timer'; import { Multicall3 } from './contracts/multicall.js'; import { L1TxUtils, TxUtilsState } from './l1_tx_utils/index.js'; @@ -38,12 +39,15 @@ type PublisherManagerConfig = { export class PublisherManager { private log: Logger; private config: PublisherManagerConfig; + private static readonly FUNDING_CHECK_INTERVAL_MS = 2 * 60 * 1000; private isFunding = false; + private lastFundingCheckAt = 0; private funder?: UtilsType; constructor( private publishers: UtilsType[], config: PublisherManagerConfig, + private dateProvider: DateProvider, opts?: { bindings?: LoggerBindings; funder?: UtilsType }, ) { this.funder = opts?.funder; @@ -139,6 +143,12 @@ export class PublisherManager { if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) { return; } + const now = this.dateProvider.now(); + if (now - this.lastFundingCheckAt < PublisherManager.FUNDING_CHECK_INTERVAL_MS) { + this.log.trace(`Skipping funding check`, { msSinceLastCheck: now - this.lastFundingCheckAt }); + return; + } + this.lastFundingCheckAt = now; if (this.isFunding) { return; } diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 6f2ceda0926d..afbb111bc2ab 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -136,7 +136,7 @@ export async function createProverNode( deps.publisherFactory ?? new ProverPublisherFactory(config, { rollupContract, - publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), { + publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), dateProvider, { bindings: log.getBindings(), funder: funderL1TxUtils, }), diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index b867973303e4..43de60b2514a 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -93,10 +93,15 @@ export class SequencerClient { publicClient, l1TxUtils.map(x => x.getSenderAddress()), ); - const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), { - bindings: log.getBindings(), - funder: deps.funderL1TxUtils, - }); + const publisherManager = new PublisherManager( + l1TxUtils, + getPublisherConfigFromSequencerConfig(config), + deps.dateProvider, + { + bindings: log.getBindings(), + funder: deps.funderL1TxUtils, + }, + ); const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString()); const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([ rollupContract.getL1GenesisTime(), From a4970d2517255fa4150480a1c1f724b4c565838e Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 18 Mar 2026 18:46:13 -0300 Subject: [PATCH 15/43] feat(p2p): add tx validator for contract instance deployment addresses Validates that contract instance deployment logs contain correct addresses by recomputing the address from the event fields and rejecting mismatches. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../contract_instance_validator.test.ts | 121 ++++++++++++++++++ .../contract_instance_validator.ts | 56 ++++++++ .../tx_validator/factory.test.ts | 5 + .../msg_validators/tx_validator/factory.ts | 7 + .../stdlib/src/tx/validator/error_texts.ts | 4 + 5 files changed, 193 insertions(+) create mode 100644 yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.test.ts create mode 100644 yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.ts diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.test.ts new file mode 100644 index 000000000000..e3f01949139a --- /dev/null +++ b/yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.test.ts @@ -0,0 +1,121 @@ +import { PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { CONTRACT_INSTANCE_PUBLISHED_EVENT_TAG } from '@aztec/protocol-contracts'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { computeContractAddressFromInstance } from '@aztec/stdlib/contract'; +import { PublicKeys } from '@aztec/stdlib/keys'; +import { PrivateLog } from '@aztec/stdlib/logs'; +import { mockTxForRollup } from '@aztec/stdlib/testing'; +import { + TX_ERROR_INCORRECT_CONTRACT_ADDRESS, + TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG, + type Tx, +} from '@aztec/stdlib/tx'; + +import { ContractInstanceTxValidator } from './contract_instance_validator.js'; + +describe('ContractInstanceTxValidator', () => { + let validator: ContractInstanceTxValidator; + + beforeEach(() => { + validator = new ContractInstanceTxValidator(); + }); + + const expectValid = async (tx: Tx) => { + await expect(validator.validateTx(tx)).resolves.toEqual({ result: 'valid' }); + }; + + const expectInvalid = async (tx: Tx, reason: string) => { + await expect(validator.validateTx(tx)).resolves.toEqual({ result: 'invalid', reason: [reason] }); + }; + + /** + * Builds a PrivateLog encoding a ContractInstancePublishedEvent. + * Layout: [tag, address, version, salt, contractClassId, initializationHash, ...publicKeys(8 fields), deployer] + */ + async function buildContractInstanceLog(opts?: { address?: AztecAddress }): Promise { + const salt = Fr.random(); + const contractClassId = Fr.random(); + const initializationHash = Fr.random(); + const publicKeys = await PublicKeys.random(); + const deployer = await AztecAddress.random(); + + const instance = { + version: 1 as const, + salt, + currentContractClassId: contractClassId, + originalContractClassId: contractClassId, + initializationHash, + publicKeys, + deployer, + }; + + const correctAddress = await computeContractAddressFromInstance(instance); + const address = opts?.address ?? correctAddress; + + // Serialize the event into fields matching the format expected by ContractInstancePublishedEvent.fromLog. + // fromLog reads from a buffer: [tag(32 bytes) | address(32) | version(32) | salt(32) | classId(32) | initHash(32) | publicKeys(4*64=256 bytes) | deployer(32)] + // PublicKeys serializes as 4 Points, each Point is 2 Fr (x, y) = 64 bytes. Total: 8 Fr fields. + const publicKeysBuffer = publicKeys.toBuffer(); + const publicKeysFields: Fr[] = []; + for (let i = 0; i < publicKeysBuffer.length; i += 32) { + publicKeysFields.push(Fr.fromBuffer(publicKeysBuffer.subarray(i, i + 32))); + } + + const emittedFields: Fr[] = [ + CONTRACT_INSTANCE_PUBLISHED_EVENT_TAG, + address.toField(), + new Fr(1), // version + salt, + contractClassId, + initializationHash, + ...publicKeysFields, + deployer.toField(), + ]; + const emittedLength = emittedFields.length; + + const fields = padArrayEnd(emittedFields, Fr.ZERO, PRIVATE_LOG_SIZE_IN_FIELDS); + return new PrivateLog(fields as any, emittedLength); + } + + function injectPrivateLog(tx: Tx, log: PrivateLog) { + // For a rollup-only tx, private logs live in forRollup.end.privateLogs + const privateLogs = tx.data.forRollup!.end.privateLogs; + const emptyIdx = privateLogs.findIndex(l => l.isEmpty()); + if (emptyIdx >= 0) { + privateLogs[emptyIdx] = log; + } else { + throw new Error('No empty private log slot available in mock tx'); + } + } + + it('allows transactions with no contract instance logs', async () => { + const tx = await mockTxForRollup(1); + await expectValid(tx); + }); + + it('allows transactions with correct contract instance addresses', async () => { + const tx = await mockTxForRollup(2); + const log = await buildContractInstanceLog(); + injectPrivateLog(tx, log); + await expectValid(tx); + }); + + it('rejects transactions with incorrect contract instance addresses', async () => { + const tx = await mockTxForRollup(3); + const wrongAddress = await AztecAddress.random(); + const log = await buildContractInstanceLog({ address: wrongAddress }); + injectPrivateLog(tx, log); + await expectInvalid(tx, TX_ERROR_INCORRECT_CONTRACT_ADDRESS); + }); + + it('rejects transactions with malformed contract instance logs', async () => { + const tx = await mockTxForRollup(4); + // Create a log that has the right tag but garbage data + const fields = padArrayEnd([CONTRACT_INSTANCE_PUBLISHED_EVENT_TAG], Fr.ZERO, PRIVATE_LOG_SIZE_IN_FIELDS); + const malformedLog = new PrivateLog(fields as any, 1); + injectPrivateLog(tx, malformedLog); + await expectInvalid(tx, TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG); + }); +}); diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.ts new file mode 100644 index 000000000000..ee8d39825066 --- /dev/null +++ b/yarn-project/p2p/src/msg_validators/tx_validator/contract_instance_validator.ts @@ -0,0 +1,56 @@ +import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; +import { ContractInstancePublishedEvent } from '@aztec/protocol-contracts/instance-registry'; +import { computeContractAddressFromInstance } from '@aztec/stdlib/contract'; +import { + TX_ERROR_INCORRECT_CONTRACT_ADDRESS, + TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG, + type Tx, + type TxValidationResult, + type TxValidator, +} from '@aztec/stdlib/tx'; + +/** Validates that contract instance deployment logs contain correct addresses. */ +export class ContractInstanceTxValidator implements TxValidator { + #log: Logger; + + constructor(bindings?: LoggerBindings) { + this.#log = createLogger('p2p:tx_validator:contract_instance', bindings); + } + + async validateTx(tx: Tx): Promise { + const reason = await this.#hasCorrectContractInstanceAddresses(tx); + return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' }; + } + + async #hasCorrectContractInstanceAddresses(tx: Tx): Promise { + const privateLogs = tx.data.getNonEmptyPrivateLogs(); + for (const log of privateLogs) { + if (!ContractInstancePublishedEvent.isContractInstancePublishedEvent(log)) { + continue; + } + + let event; + try { + event = ContractInstancePublishedEvent.fromLog(log); + } catch (e) { + this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract instance event: ${e}`); + return TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG; + } + + try { + const instance = event.toContractInstance(); + const computedAddress = await computeContractAddressFromInstance(instance); + if (!computedAddress.equals(instance.address)) { + this.#log.warn( + `Rejecting tx ${tx.getTxHash()}: contract instance address mismatch. Claimed ${instance.address}, computed ${computedAddress}`, + ); + return TX_ERROR_INCORRECT_CONTRACT_ADDRESS; + } + } catch (e) { + this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract instance address: ${e}`); + return TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG; + } + } + return undefined; + } +} diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/factory.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/factory.test.ts index 92ede7a4afaa..6dc67ca9294a 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/factory.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/factory.test.ts @@ -14,6 +14,7 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { AggregateTxValidator } from './aggregate_tx_validator.js'; import { BlockHeaderTxValidator } from './block_header_validator.js'; +import { ContractInstanceTxValidator } from './contract_instance_validator.js'; import { DataTxValidator } from './data_validator.js'; import { DoubleSpendTxValidator } from './double_spend_validator.js'; import { @@ -73,6 +74,7 @@ describe('Validator factory functions', () => { 'doubleSpendValidator', 'gasValidator', 'dataValidator', + 'contractInstanceValidator', ]); }); @@ -170,6 +172,7 @@ describe('Validator factory functions', () => { MetadataTxValidator.name, SizeTxValidator.name, DataTxValidator.name, + ContractInstanceTxValidator.name, TxProofValidator.name, ]); }); @@ -187,6 +190,7 @@ describe('Validator factory functions', () => { MetadataTxValidator.name, SizeTxValidator.name, DataTxValidator.name, + ContractInstanceTxValidator.name, TxProofValidator.name, ]); }); @@ -221,6 +225,7 @@ describe('Validator factory functions', () => { BlockHeaderTxValidator.name, DoubleSpendTxValidator.name, DataTxValidator.name, + ContractInstanceTxValidator.name, GasTxValidator.name, TxProofValidator.name, ]); diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/factory.ts b/yarn-project/p2p/src/msg_validators/tx_validator/factory.ts index 849f105b46be..4d4df944a34f 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/factory.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/factory.ts @@ -53,6 +53,7 @@ import type { TxMetaData } from '../../mem_pools/tx_pool_v2/tx_metadata.js'; import { AggregateTxValidator } from './aggregate_tx_validator.js'; import { ArchiveCache } from './archive_cache.js'; import { type ArchiveSource, BlockHeaderTxValidator } from './block_header_validator.js'; +import { ContractInstanceTxValidator } from './contract_instance_validator.js'; import { DataTxValidator } from './data_validator.js'; import { DoubleSpendTxValidator, type NullifierSource } from './double_spend_validator.js'; import { GasLimitsValidator, GasTxValidator } from './gas_validator.js'; @@ -167,6 +168,10 @@ export function createFirstStageTxValidationsForGossipedTransactions( validator: new DataTxValidator(bindings), severity: PeerErrorSeverity.MidToleranceError, }, + contractInstanceValidator: { + validator: new ContractInstanceTxValidator(bindings), + severity: PeerErrorSeverity.MidToleranceError, + }, }; } @@ -218,6 +223,7 @@ function createTxValidatorForMinimumTxIntegrityChecks( ), new SizeTxValidator(bindings), new DataTxValidator(bindings), + new ContractInstanceTxValidator(bindings), new TxProofValidator(verifier, bindings), ); } @@ -321,6 +327,7 @@ export function createTxValidatorForAcceptingTxsOverRPC( new BlockHeaderTxValidator(new ArchiveCache(db), bindings), new DoubleSpendTxValidator(new NullifierCache(db), bindings), new DataTxValidator(bindings), + new ContractInstanceTxValidator(bindings), ]; if (!skipFeeEnforcement) { diff --git a/yarn-project/stdlib/src/tx/validator/error_texts.ts b/yarn-project/stdlib/src/tx/validator/error_texts.ts index 6a8326f032a8..57599c12d9d4 100644 --- a/yarn-project/stdlib/src/tx/validator/error_texts.ts +++ b/yarn-project/stdlib/src/tx/validator/error_texts.ts @@ -41,5 +41,9 @@ export const TX_ERROR_SIZE_ABOVE_LIMIT = 'Transaction size above size limit'; // Block header export const TX_ERROR_BLOCK_HEADER = 'Block header not found'; +// Contract instance +export const TX_ERROR_INCORRECT_CONTRACT_ADDRESS = 'Incorrect contract instance deployment address'; +export const TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG = 'Failed to parse contract instance deployment log'; + // General export const TX_ERROR_DURING_VALIDATION = 'Unexpected error during validation'; From f9b761d770baa6051b9f7d9bafc95177dbe995d3 Mon Sep 17 00:00:00 2001 From: spypsy Date: Thu, 19 Mar 2026 10:58:39 +0000 Subject: [PATCH 16/43] fix: always deploy IRM for testnet (#21755) --- .github/workflows/deploy-network.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-network.yml b/.github/workflows/deploy-network.yml index fbe634adef15..c2d4edb62338 100644 --- a/.github/workflows/deploy-network.yml +++ b/.github/workflows/deploy-network.yml @@ -228,7 +228,7 @@ jobs: update-irm: needs: deploy-network - if: contains(inputs.network, 'testnet') && !contains(inputs.semver, '-') + if: contains(inputs.network, 'testnet') uses: ./.github/workflows/deploy-irm.yml secrets: inherit with: From 13afcc51e3d74489e8cbd080406c55394fe38e9b Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 19 Mar 2026 12:16:40 +0000 Subject: [PATCH 17/43] chore: update network logs skill (#21785) . --- .claude/agents/network-logs.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.claude/agents/network-logs.md b/.claude/agents/network-logs.md index 2a5df608e37d..b0dc08117ba1 100644 --- a/.claude/agents/network-logs.md +++ b/.claude/agents/network-logs.md @@ -91,7 +91,8 @@ Pods follow the pattern `{namespace}-{component}-{index}`: ## Deployment-Specific Notes -- **next-net** redeploys every morning at ~4am UTC. Always use timestamp range filters (not `--freshness`) when querying next-net for a specific date, and expect logs to only cover a single instance of the network. +- **next-net** redeploys every morning at ~4am UTC. Always use timestamp range filters (not `--freshness`) when querying next-net for a specific date, and expect logs to only cover a single instance of the network. Because next-net resets daily, its block height should start near 0 after ~4am UTC. If you are running a morning healthcheck and the block height is unexpectedly large (e.g., hundreds or thousands), flag this as an error — it likely means the nightly redeploy failed and the network is running a stale instance. +- **mainnet** does not run sequencer validators. Instead, it runs infrastructure in **fisherman mode**: nodes simulate building a block for every slot but never actually submit the L1 transaction. This means you will see "built block" or similar messages but no "Published checkpoint" or L1 submission logs. Errors with hash `0xf3e591ac` are a known artifact of fisherman mode and are safe to ignore. ## Filter Building @@ -203,6 +204,7 @@ gcloud logging read ' NOT jsonPayload.message=~"No active peers" NOT jsonPayload.message=~"Not enough txs" NOT jsonPayload.message=~"StateView contract not found" + NOT jsonPayload.message=~"[Bb]lob" ' --limit=100 --format='table[no-heading](timestamp.date("%H:%M:%S"), resource.labels.pod_name, jsonPayload.severity, jsonPayload.module, jsonPayload.message.slice(0,180))' --freshness= --project= ``` @@ -291,6 +293,20 @@ Then synthesize into a single status report covering: This is the most common query pattern — prefer this composite approach over individual queries when the user asks for general status. +### 11. Multi-Network Healthcheck + +When the user asks for a healthcheck across multiple networks (e.g., "how are all the networks doing?"), query each network in parallel and present results as a **summary table**: + +``` +| Network | Status | Block Height | Last Block | Proving | Notes | +|-----------|--------|--------------|------------|---------|-------| +| testnet | ✅ OK | 1234 | 2m ago | Epoch 5 | — | +| next-net | ✅ OK | 45 | 1m ago | Epoch 1 | — | +| mainnet | ✅ OK | 890 | 3m ago | N/A | Fisherman mode | +``` + +Use ✅ for healthy, ⚠️ for degraded, ❌ for down/erroring. Follow the table with brief per-network details only if there are issues worth calling out. Remember deployment-specific notes: next-net resets daily (check block height is reasonable for time of day), mainnet runs in fisherman mode (no L1 submissions, `0xf3e591ac` errors are expected). + ## Known Noise Patterns These patterns appear frequently and are usually harmless — exclude or downplay them: @@ -302,6 +318,8 @@ These patterns appear frequently and are usually harmless — exclude or downpla - `No active peers to send requests to` — P2P reqresp on isolated nodes (e.g., blob-sink) - `Not enough txs to build block` — Normal when transaction volume is low - `StateView contract not found` — Price oracle warning; Uniswap V4 StateView only exists on mainnet, so all other networks emit this. Safe to ignore unless namespace is `mainnet` +- **Blob-related errors** — Errors related to blobs (e.g., blob fetching failures, blob unavailability) are generally expected and safe to ignore. Since the Fusaka hard fork, regular consensus nodes can no longer serve blob data, and we run a couple of these nodes. Exclude or downplay blob errors unless the user is specifically investigating blob issues. +- `0xf3e591ac` — Fisherman mode error on mainnet. Safe to ignore (see Deployment-Specific Notes). ## Reference Tool From 588e9c428a1e541f63d2befc3c53a5af27954f3a Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 09:50:33 -0300 Subject: [PATCH 18/43] feat(archiver): validate contract instance addresses before storing Adds a defense-in-depth check in the archiver's data store updater that recomputes contract instance addresses from their fields before storing them, filtering out any instances with mismatched addresses. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/modules/data_store_updater.ts | 21 ++++++++++++- .../src/contract/contract_address.test.ts | 30 +++++++++++-------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/yarn-project/archiver/src/modules/data_store_updater.ts b/yarn-project/archiver/src/modules/data_store_updater.ts index 32087d4e2b7a..78e099aca455 100644 --- a/yarn-project/archiver/src/modules/data_store_updater.ts +++ b/yarn-project/archiver/src/modules/data_store_updater.ts @@ -1,4 +1,5 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; +import { filterAsync } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; import { @@ -15,6 +16,7 @@ import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/chec import { type ExecutablePrivateFunctionWithMembershipProof, type UtilityFunctionWithMembershipProof, + computeContractAddressFromInstance, computePublicBytecodeCommitment, isValidPrivateFunctionMembershipProof, isValidUtilityFunctionMembershipProof, @@ -356,10 +358,27 @@ export class ArchiverDataStoreUpdater { blockNum: BlockNumber, operation: Operation, ): Promise { - const contractInstances = allLogs + const allInstances = allLogs .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log)) .map(log => ContractInstancePublishedEvent.fromLog(log)) .map(e => e.toContractInstance()); + + // Verify that each instance's address matches the one derived from its fields if we're adding + const contractInstances = + operation === Operation.Delete + ? allInstances + : await filterAsync(allInstances, async instance => { + const computedAddress = await computeContractAddressFromInstance(instance); + if (!computedAddress.equals(instance.address)) { + this.log.warn( + `Found contract instance with mismatched address at block ${blockNum}. Claimed ${instance.address} but computed ${computedAddress}.`, + { instanceAddress: instance.address.toString(), computedAddress: computedAddress.toString(), blockNum }, + ); + return false; + } + return true; + }); + if (contractInstances.length > 0) { contractInstances.forEach(c => this.log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`), diff --git a/yarn-project/stdlib/src/contract/contract_address.test.ts b/yarn-project/stdlib/src/contract/contract_address.test.ts index 2b93d5a47520..d882357eccd3 100644 --- a/yarn-project/stdlib/src/contract/contract_address.test.ts +++ b/yarn-project/stdlib/src/contract/contract_address.test.ts @@ -1,4 +1,6 @@ import { Fr } from '@aztec/foundation/curves/bn254'; +import { createLogger } from '@aztec/foundation/log'; +import { elapsed } from '@aztec/foundation/timer'; import { type FunctionAbi, FunctionType } from '../abi/index.js'; import { AztecAddress } from '../aztec-address/index.js'; @@ -64,17 +66,21 @@ describe('ContractAddress', () => { const initializationHash = new Fr(5n); const deployer = AztecAddress.fromField(new Fr(7)); const publicKeys = (await deriveKeys(secretKey)).publicKeys; - const address = ( - await computeContractAddressFromInstance({ - publicKeys, - salt, - originalContractClassId: contractClassId, - currentContractClassId: contractClassId, - initializationHash, - deployer, - version: 1, - }) - ).toString(); - expect(address).toMatchInlineSnapshot(`"0x2cea4bfccb4a185354cbbd9eb5a39ace117abf1f9381c5b6167b1a6f94a0672c"`); + const instance = { + publicKeys, + salt, + originalContractClassId: contractClassId, + currentContractClassId: contractClassId, + initializationHash, + deployer, + version: 1 as const, + }; + + const [ms, address] = await elapsed(computeContractAddressFromInstance(instance)); + const logger = createLogger('stdlib:contract_address:test'); + logger.info(`Computed contract address from instance in ${ms}ms`); + expect(address.toString()).toMatchInlineSnapshot( + `"0x2cea4bfccb4a185354cbbd9eb5a39ace117abf1f9381c5b6167b1a6f94a0672c"`, + ); }); }); From c998d80c68732112ee0c327433a502eeafee167c Mon Sep 17 00:00:00 2001 From: spypsy Date: Thu, 19 Mar 2026 14:21:31 +0000 Subject: [PATCH 19/43] fix: ensure no division by 0 (#21786) Fixes #21513 --- .../aztec-node/src/sentinel/sentinel.test.ts | 15 +++++++++++++++ yarn-project/aztec-node/src/sentinel/sentinel.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/yarn-project/aztec-node/src/sentinel/sentinel.test.ts b/yarn-project/aztec-node/src/sentinel/sentinel.test.ts index 671de2cff77c..b7cbd09e118c 100644 --- a/yarn-project/aztec-node/src/sentinel/sentinel.test.ts +++ b/yarn-project/aztec-node/src/sentinel/sentinel.test.ts @@ -852,6 +852,21 @@ describe('sentinel', () => { expect(result).toBe(false); }); + + it('should not divide by zero when an epoch has total 0 (treat as not inactive)', async () => { + const mockPerformance = [ + { epoch: EpochNumber(5), missed: 8, total: 10 }, // 80% missed (inactive) + { epoch: EpochNumber(4), missed: 8, total: 0 }, // total 0 -> guard: do not divide; treat as not inactive + { epoch: EpochNumber(3), missed: 8, total: 10 }, // 80% missed (inactive) + ]; + + jest.spyOn(store, 'getProvenPerformance').mockResolvedValue(mockPerformance); + + const result = await sentinel.checkPastInactivity(validator1, EpochNumber(6), 3); + + // Epoch 4 has total 0 so it must not count as inactive; we don't have 3 consecutive inactive epochs + expect(result).toBe(false); + }); }); describe('handleProvenPerformance with consecutive epochs', () => { diff --git a/yarn-project/aztec-node/src/sentinel/sentinel.ts b/yarn-project/aztec-node/src/sentinel/sentinel.ts index 8c07e06056d3..11c31f71e65d 100644 --- a/yarn-project/aztec-node/src/sentinel/sentinel.ts +++ b/yarn-project/aztec-node/src/sentinel/sentinel.ts @@ -219,7 +219,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme // Check that we have at least requiredConsecutiveEpochs and that all of them are above the inactivity threshold return pastEpochs .slice(0, requiredConsecutiveEpochs) - .every(p => p.missed / p.total >= this.config.slashInactivityTargetPercentage); + .every(p => (p.total === 0 ? false : p.missed / p.total >= this.config.slashInactivityTargetPercentage)); } protected async handleProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance) { From 1a9249a6f9148537b57610d121d637540815f8d7 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 14:43:56 -0300 Subject: [PATCH 20/43] fix(archiver): throw on duplicate contract class or instance additions Replace silent overwrites with explicit errors when adding a contract class or instance that already exists in the store. This catches unexpected double-adds that could lead to data loss on rollback. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/store/contract_class_store.ts | 10 +++-- .../src/store/contract_instance_store.ts | 13 +++--- .../src/store/kv_archiver_store.test.ts | 41 +++++++------------ 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/yarn-project/archiver/src/store/contract_class_store.ts b/yarn-project/archiver/src/store/contract_class_store.ts index 36de477aad42..ebe0d6f9400f 100644 --- a/yarn-project/archiver/src/store/contract_class_store.ts +++ b/yarn-project/archiver/src/store/contract_class_store.ts @@ -29,11 +29,15 @@ export class ContractClassStore { blockNumber: number, ): Promise { await this.db.transactionAsync(async () => { - await this.#contractClasses.setIfNotExists( - contractClass.id.toString(), + const key = contractClass.id.toString(); + if (await this.#contractClasses.hasAsync(key)) { + throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`); + } + await this.#contractClasses.set( + key, serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }), ); - await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer()); + await this.#bytecodeCommitments.set(key, bytecodeCommitment.toBuffer()); }); } diff --git a/yarn-project/archiver/src/store/contract_instance_store.ts b/yarn-project/archiver/src/store/contract_instance_store.ts index 63ea37ff9bcb..332605240e04 100644 --- a/yarn-project/archiver/src/store/contract_instance_store.ts +++ b/yarn-project/archiver/src/store/contract_instance_store.ts @@ -27,11 +27,14 @@ export class ContractInstanceStore { addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise { return this.db.transactionAsync(async () => { - await this.#contractInstances.set( - contractInstance.address.toString(), - new SerializableContractInstance(contractInstance).toBuffer(), - ); - await this.#contractInstancePublishedAt.set(contractInstance.address.toString(), blockNumber); + const key = contractInstance.address.toString(); + if (await this.#contractInstances.hasAsync(key)) { + throw new Error( + `Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`, + ); + } + await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer()); + await this.#contractInstancePublishedAt.set(key, blockNumber); }); } diff --git a/yarn-project/archiver/src/store/kv_archiver_store.test.ts b/yarn-project/archiver/src/store/kv_archiver_store.test.ts index 92ae927f0ef3..bea6c6a6e81c 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.test.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.test.ts @@ -2367,14 +2367,14 @@ describe('KVArchiverDataStore', () => { await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined(); }); - it('returns contract class if later "deployment" class was deleted', async () => { - await store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(blockNum + 1), - ); - await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1)); - await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass); + it('throws if the same contract class is added again', async () => { + await expect( + store.addContractClasses( + [contractClass], + [await computePublicBytecodeCommitment(contractClass.packedBytecode)], + BlockNumber(blockNum + 1), + ), + ).rejects.toThrow(/already exists/); }); it('returns undefined if contract class is not found', async () => { @@ -3431,22 +3431,17 @@ describe('KVArchiverDataStore', () => { expect(storedBlock?.archive.root.equals(provisionalBlock.archive.root)).toBe(true); }); - it('does not throw when adding the same contract class twice', async () => { + it('throws when adding the same contract class twice', async () => { const contractClass = await makeContractClassPublic(); const commitment = await computePublicBytecodeCommitment(contractClass.packedBytecode); - // Add contract class first time await store.addContractClasses([contractClass], [commitment], BlockNumber(1)); - - // Add same contract class again - should not throw (uses setIfNotExists) - await store.addContractClasses([contractClass], [commitment], BlockNumber(2)); - - // Verify contract class exists - const retrieved = await store.getContractClass(contractClass.id); - expect(retrieved).toBeDefined(); + await expect(store.addContractClasses([contractClass], [commitment], BlockNumber(2))).rejects.toThrow( + /already exists/, + ); }); - it('does not throw when adding the same contract instance twice', async () => { + it('throws when adding the same contract instance twice', async () => { const contractClass = await makeContractClassPublic(); await store.addContractClasses( [contractClass], @@ -3462,16 +3457,8 @@ describe('KVArchiverDataStore', () => { address: await AztecAddress.random(), }; - // Add contract instance first time await store.addContractInstances([instance], BlockNumber(1)); - - // Add same contract instance again - should not throw (uses set) - await store.addContractInstances([instance], BlockNumber(2)); - - // Verify instance exists - const retrieved = await store.getContractInstance(instance.address, 1000n); - expect(retrieved).toBeDefined(); - expect(retrieved?.address.equals(instance.address)).toBe(true); + await expect(store.addContractInstances([instance], BlockNumber(2))).rejects.toThrow(/already exists/); }); it('does not duplicate logs when addLogs is called twice with same block', async () => { From f07d8968be34589e2ce64f79d00bc3c0203f760f Mon Sep 17 00:00:00 2001 From: danielntmd Date: Thu, 19 Mar 2026 17:45:22 +0000 Subject: [PATCH 21/43] fix: restrict scenario deployments to only nightly --- .github/workflows/ci3.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci3.yml b/.github/workflows/ci3.yml index 4dc555f7fa30..969711005b72 100644 --- a/.github/workflows/ci3.yml +++ b/.github/workflows/ci3.yml @@ -173,10 +173,9 @@ jobs: fail-fast: false matrix: test_set: ["1", "2"] - # We either run after a release (tag starting with v), or when the ci-network-scenario label is present in a PR. - # We exclude ci-release-pr test tags (v0.0.1-commit.*) which are only for testing the release process. + # We run on nightly tags only, or when the ci-network-scenario label is present in a PR. needs: ci - if: github.event.pull_request.head.repo.fork != true && github.event.pull_request.draft == false && ((startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-commit.')) || contains(github.event.pull_request.labels.*.name, 'ci-network-scenario')) + if: github.event.pull_request.head.repo.fork != true && github.event.pull_request.draft == false && ((startsWith(github.ref, 'refs/tags/v') && contains(github.ref_name, '-nightly.')) || contains(github.event.pull_request.labels.*.name, 'ci-network-scenario')) steps: - name: Remove label (one-time use) if: github.event.pull_request && contains(github.event.pull_request.labels.*.name, 'ci-network-scenario') From f913737e107e0d032a565d3397020d99b0fe0a0b Mon Sep 17 00:00:00 2001 From: ludamad Date: Thu, 19 Mar 2026 13:58:38 -0400 Subject: [PATCH 22/43] feat: support private fork releases via ci-release (#21778) When REF_NAME has a 'private' semver prerelease (e.g. v5.0.0-private.20260318), ci-release fetches the matching tag from aztec-packages-private, creates a worktree, and runs the release from there. Cache uploads are suppressed to avoid leaking private source artifacts. Also locks down ci3-external.yml to use github.token instead of AZTEC_BOT_GITHUB_TOKEN. --- .github/workflows/ci3-external.yml | 8 ++++---- bootstrap.sh | 20 ++++++++++++++++++-- ci3/cache_upload | 5 +++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci3-external.yml b/.github/workflows/ci3-external.yml index afbcc7deb840..832d16d52181 100644 --- a/.github/workflows/ci3-external.yml +++ b/.github/workflows/ci3-external.yml @@ -41,7 +41,7 @@ jobs: PR_BASE_REF: ${{ github.event.pull_request.base.ref }} PR_NUMBER: ${{ github.event.pull_request.number }} HAS_CI_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'ci-external') || github.event.label.name == 'ci-external-once' }} - GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | set -o pipefail git fetch origin "$PR_BASE_REF" --depth=1 &>/dev/null @@ -68,7 +68,7 @@ jobs: MERGE_GROUP_BASE_REF: ${{ github.event.merge_group.base_ref }} PR_BASE_REF: ${{ github.event.pull_request.base.ref }} GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} PR_LABELS_JSON: ${{ toJson(github.event.pull_request.labels.*.name) }} run: | # Parse labels from JSON env var to avoid shell injection via label names @@ -85,7 +85,7 @@ jobs: # creds for being able to upload to cache. AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} BUILD_INSTANCE_SSH_KEY: ${{ secrets.BUILD_INSTANCE_SSH_KEY }} # DO NOT allow build instance key access to external jobs. CI_USE_BUILD_INSTANCE_KEY: "0" @@ -104,7 +104,7 @@ jobs: # For updating success cache. AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} PR_BASE_REF: ${{ github.event.pull_request.base.ref }} diff --git a/bootstrap.sh b/bootstrap.sh index 01918cd9def2..2bff7d039c22 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -817,8 +817,24 @@ case "$cmd" in if ! semver check $REF_NAME; then exit 1 fi - build release - release + if [[ "$(semver prerelease $REF_NAME)" == private* ]]; then + echo_header "Private fork release: $REF_NAME" + echo "Creating GitHub release from public repo context (COMMIT_HASH=$COMMIT_HASH)..." + release_github + echo "Fetching private source from aztec-packages-private..." + git remote add private "https://x-access-token:${GITHUB_TOKEN}@github.com/AztecProtocol/aztec-packages-private.git" + git fetch --depth 1 private "refs/tags/$REF_NAME" + git worktree add aztec-private FETCH_HEAD + cd aztec-private + echo "Initializing submodules in private worktree..." + git submodule update --init --recursive + echo "Private worktree ready at $(pwd) (HEAD=$(git rev-parse --short HEAD)). Cache uploads disabled." + export NO_CACHE_UPLOAD=1 + # Unset so child bootstrap.sh re-derives these from the worktree. + unset COMMIT_HASH root + fi + ./bootstrap.sh build release + ./bootstrap.sh release ;; ########################## diff --git a/ci3/cache_upload b/ci3/cache_upload index 213fe4551f4b..21a206d7c934 100755 --- a/ci3/cache_upload +++ b/ci3/cache_upload @@ -22,6 +22,11 @@ if [[ -z "${S3_FORCE_UPLOAD:-}" && "${CI:-0}" -eq 0 ]]; then exit 0 fi +if [[ "${NO_CACHE_UPLOAD:-0}" -eq 1 ]]; then + echo_stderr "Skipping upload because NO_CACHE_UPLOAD=1." + exit 0 +fi + # In SSM/instance-profile mode, AWS CLI falls back to IMDS for credentials. if [[ "${CI_SSM_MODE:-0}" -eq 0 ]]; then if [[ -z "${AWS_ACCESS_KEY_ID:-}" || -z "${AWS_SECRET_ACCESS_KEY:-}" ]] && ! aws configure get aws_access_key_id &>/dev/null; then From eee42ceb9ddbf7c31bc0952bb51a0392dce56601 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 16:04:15 -0300 Subject: [PATCH 23/43] fix: skip protocol contract registration if already present Makes registerProtocolContracts idempotent by checking if the contract class already exists before adding. This handles node restarts against a persisted LMDB store. Co-Authored-By: Claude Opus 4.6 (1M context) --- yarn-project/archiver/src/factory.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index 5aa14b3ca7a0..aaccab4346cd 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -173,12 +173,18 @@ export async function createArchiver( return archiver; } -/** Registers protocol contracts in the archiver store. */ +/** Registers protocol contracts in the archiver store. Idempotent — skips contracts that already exist (e.g. on node restart). */ export async function registerProtocolContracts(store: KVArchiverDataStore) { const blockNumber = 0; for (const name of protocolContractNames) { const provider = new BundledProtocolContractsProvider(); const contract = await provider.getProtocolContractArtifact(name); + + // Skip if already registered (happens on node restart with a persisted store). + if (await store.getContractClass(contract.contractClass.id)) { + continue; + } + const contractClassPublic: ContractClassPublic = { ...contract.contractClass, privateFunctions: [], From 1ba45b8ee38bc1f90f85afb609d02144c9c17e98 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 16:51:40 -0300 Subject: [PATCH 24/43] refactor: remove broadcasted function events and membership proofs The PrivateFunctionBroadcastedEvent and UtilityFunctionBroadcastedEvent were removed from the ContractClassRegistry contract, so all supporting TypeScript infrastructure is now dead code. This removes the event types, membership proof creation/validation, archiver data flows, store methods, and the privateFunctions/utilityFunctions fields from ContractClassPublic. Co-Authored-By: Claude Opus 4.6 (1M context) --- yarn-project/archiver/src/factory.ts | 2 - .../src/modules/data_store_updater.ts | 90 +--------- .../src/store/contract_class_store.ts | 104 +---------- .../src/store/kv_archiver_store.test.ts | 37 +--- .../archiver/src/store/kv_archiver_store.ts | 11 -- .../contract_class_registration.test.ts | 1 - .../PrivateFunctionBroadcastedEventData.hex | 1 - .../UtilityFunctionBroadcastedEventData.hex | 1 - ...te_function_broadcasted_event.test.ts.snap | 35 ---- ...ty_function_broadcasted_event.test.ts.snap | 24 --- .../contract_class_published_event.ts | 2 - .../src/class-registry/index.ts | 2 - .../src/class-registry/lazy.ts | 2 - ...private_function_broadcasted_event.test.ts | 19 -- .../private_function_broadcasted_event.ts | 109 ------------ ...utility_function_broadcasted_event.test.ts | 37 ---- .../utility_function_broadcasted_event.ts | 103 ----------- .../protocol-contracts/src/tests/fixtures.ts | 12 -- yarn-project/stdlib/src/contract/index.ts | 2 - .../src/contract/interfaces/contract_class.ts | 84 +-------- .../private_function_membership_proof.test.ts | 48 ----- .../private_function_membership_proof.ts | 167 ------------------ .../utility_function_membership_proof.test.ts | 65 ------- .../utility_function_membership_proof.ts | 118 ------------- .../stdlib/src/interfaces/aztec-node.test.ts | 8 +- yarn-project/stdlib/src/tests/factories.ts | 33 ---- .../util/txe_public_contract_data_source.ts | 2 - 27 files changed, 10 insertions(+), 1109 deletions(-) delete mode 100644 yarn-project/protocol-contracts/fixtures/PrivateFunctionBroadcastedEventData.hex delete mode 100644 yarn-project/protocol-contracts/fixtures/UtilityFunctionBroadcastedEventData.hex delete mode 100644 yarn-project/protocol-contracts/src/class-registry/__snapshots__/private_function_broadcasted_event.test.ts.snap delete mode 100644 yarn-project/protocol-contracts/src/class-registry/__snapshots__/utility_function_broadcasted_event.test.ts.snap delete mode 100644 yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.test.ts delete mode 100644 yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.ts delete mode 100644 yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.test.ts delete mode 100644 yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.ts delete mode 100644 yarn-project/stdlib/src/contract/private_function_membership_proof.test.ts delete mode 100644 yarn-project/stdlib/src/contract/private_function_membership_proof.ts delete mode 100644 yarn-project/stdlib/src/contract/utility_function_membership_proof.test.ts delete mode 100644 yarn-project/stdlib/src/contract/utility_function_membership_proof.ts diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index 5aa14b3ca7a0..d5dda49dec2b 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -181,8 +181,6 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) { const contract = await provider.getProtocolContractArtifact(name); const contractClassPublic: ContractClassPublic = { ...contract.contractClass, - privateFunctions: [], - utilityFunctions: [], }; const publicFunctionSignatures = contract.artifact.functions diff --git a/yarn-project/archiver/src/modules/data_store_updater.ts b/yarn-project/archiver/src/modules/data_store_updater.ts index 78e099aca455..3c0513bdaa59 100644 --- a/yarn-project/archiver/src/modules/data_store_updater.ts +++ b/yarn-project/archiver/src/modules/data_store_updater.ts @@ -1,31 +1,17 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { filterAsync } from '@aztec/foundation/collection'; -import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; -import { - ContractClassPublishedEvent, - PrivateFunctionBroadcastedEvent, - UtilityFunctionBroadcastedEvent, -} from '@aztec/protocol-contracts/class-registry'; +import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry'; import { ContractInstancePublishedEvent, ContractInstanceUpdatedEvent, } from '@aztec/protocol-contracts/instance-registry'; import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block'; import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint'; -import { - type ExecutablePrivateFunctionWithMembershipProof, - type UtilityFunctionWithMembershipProof, - computeContractAddressFromInstance, - computePublicBytecodeCommitment, - isValidPrivateFunctionMembershipProof, - isValidUtilityFunctionMembershipProof, -} from '@aztec/stdlib/contract'; +import { computeContractAddressFromInstance, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs'; import type { UInt64 } from '@aztec/stdlib/types'; -import groupBy from 'lodash.groupby'; - import type { KVArchiverDataStore } from '../store/kv_archiver_store.js'; import type { L2TipsCache } from '../store/l2_tips_cache.js'; @@ -56,8 +42,7 @@ export class ArchiverDataStoreUpdater { /** * Adds a proposed block to the store with contract class/instance extraction from logs. * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1. - * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events, - * and individually broadcasted functions from the block logs. + * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the block logs. * * @param block - The proposed L2 block to add. * @param pendingChainValidationStatus - Optional validation status to set. @@ -89,8 +74,7 @@ export class ArchiverDataStoreUpdater { * Reconciles local blocks with incoming checkpoints from L1. * Adds new checkpoints to the store with contract class/instance extraction from logs. * Prunes any local blocks that conflict with checkpoint data (by comparing archive roots). - * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events, - * and individually broadcasted functions from the checkpoint block logs. + * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs. * * @param checkpoints - The published checkpoints to add. * @param pendingChainValidationStatus - Optional validation status to set. @@ -315,9 +299,6 @@ export class ArchiverDataStoreUpdater { this.updatePublishedContractClasses(contractClassLogs, block.number, operation), this.updateDeployedContractInstances(privateLogs, block.number, operation), this.updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, operation), - operation === Operation.Store - ? this.storeBroadcastedIndividualFunctions(contractClassLogs, block.number) - : Promise.resolve(true), ]) ).every(Boolean); } @@ -417,67 +398,4 @@ export class ArchiverDataStoreUpdater { } return true; } - - /** - * Stores the functions that were broadcasted individually. - * - * @dev Beware that there is not a delete variant of this, since they are added to contract classes - * and will be deleted as part of the class if needed. - */ - private async storeBroadcastedIndividualFunctions( - allLogs: ContractClassLog[], - _blockNum: BlockNumber, - ): Promise { - // Filter out private and utility function broadcast events - const privateFnEvents = allLogs - .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log)) - .map(log => PrivateFunctionBroadcastedEvent.fromLog(log)); - const utilityFnEvents = allLogs - .filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log)) - .map(log => UtilityFunctionBroadcastedEvent.fromLog(log)); - - // Group all events by contract class id - for (const [classIdString, classEvents] of Object.entries( - groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()), - )) { - const contractClassId = Fr.fromHexString(classIdString); - const contractClass = await this.store.getContractClass(contractClassId); - if (!contractClass) { - this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`); - continue; - } - - // Split private and utility functions, and filter out invalid ones - const allFns = classEvents.map(e => e.toFunctionWithMembershipProof()); - const privateFns = allFns.filter( - (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn, - ); - const utilityFns = allFns.filter( - (fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn, - ); - - const privateFunctionsWithValidity = await Promise.all( - privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })), - ); - const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn); - const utilityFunctionsWithValidity = await Promise.all( - utilityFns.map(async fn => ({ - fn, - valid: await isValidUtilityFunctionMembershipProof(fn, contractClass), - })), - ); - const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn); - const validFnCount = validPrivateFns.length + validUtilityFns.length; - if (validFnCount !== allFns.length) { - this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`); - } - - // Store the functions in the contract class in a single operation - if (validFnCount > 0) { - this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`); - } - await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns); - } - return true; - } } diff --git a/yarn-project/archiver/src/store/contract_class_store.ts b/yarn-project/archiver/src/store/contract_class_store.ts index 36de477aad42..2a31b5b0520f 100644 --- a/yarn-project/archiver/src/store/contract_class_store.ts +++ b/yarn-project/archiver/src/store/contract_class_store.ts @@ -2,14 +2,7 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { toArray } from '@aztec/foundation/iterable'; import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize'; import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store'; -import { FunctionSelector } from '@aztec/stdlib/abi'; -import type { - ContractClassPublic, - ContractClassPublicWithBlockNumber, - ExecutablePrivateFunctionWithMembershipProof, - UtilityFunctionWithMembershipProof, -} from '@aztec/stdlib/contract'; -import { Vector } from '@aztec/stdlib/types'; +import type { ContractClassPublic, ContractClassPublicWithBlockNumber } from '@aztec/stdlib/contract'; /** * LMDB-based contract class storage for the archiver. @@ -60,37 +53,6 @@ export class ContractClassStore { async getContractClassIds(): Promise { return (await toArray(this.#contractClasses.keysAsync())).map(key => Fr.fromHexString(key)); } - - async addFunctions( - contractClassId: Fr, - newPrivateFunctions: ExecutablePrivateFunctionWithMembershipProof[], - newUtilityFunctions: UtilityFunctionWithMembershipProof[], - ): Promise { - await this.db.transactionAsync(async () => { - const existingClassBuffer = await this.#contractClasses.getAsync(contractClassId.toString()); - if (!existingClassBuffer) { - throw new Error(`Unknown contract class ${contractClassId} when adding private functions to store`); - } - - const existingClass = deserializeContractClassPublic(existingClassBuffer); - const { privateFunctions: existingPrivateFns, utilityFunctions: existingUtilityFns } = existingClass; - - const updatedClass: Omit = { - ...existingClass, - privateFunctions: [ - ...existingPrivateFns, - ...newPrivateFunctions.filter(newFn => !existingPrivateFns.some(f => f.selector.equals(newFn.selector))), - ], - utilityFunctions: [ - ...existingUtilityFns, - ...newUtilityFunctions.filter(newFn => !existingUtilityFns.some(f => f.selector.equals(newFn.selector))), - ], - }; - await this.#contractClasses.set(contractClassId.toString(), serializeContractClassPublic(updatedClass)); - }); - - return true; - } } function serializeContractClassPublic(contractClass: Omit): Buffer { @@ -98,83 +60,19 @@ function serializeContractClassPublic(contractClass: Omit { const reader = BufferReader.asReader(buffer); return { l2BlockNumber: reader.readNumber(), version: reader.readUInt8() as 1, artifactHash: reader.readObject(Fr), - privateFunctions: reader.readVector({ fromBuffer: deserializePrivateFunction }), - utilityFunctions: reader.readVector({ fromBuffer: deserializeUtilityFunction }), packedBytecode: reader.readBuffer(), privateFunctionsRoot: reader.readObject(Fr), }; } - -function deserializePrivateFunction(buffer: Buffer | BufferReader): ExecutablePrivateFunctionWithMembershipProof { - const reader = BufferReader.asReader(buffer); - return { - selector: reader.readObject(FunctionSelector), - vkHash: reader.readObject(Fr), - bytecode: reader.readBuffer(), - functionMetadataHash: reader.readObject(Fr), - artifactMetadataHash: reader.readObject(Fr), - utilityFunctionsTreeRoot: reader.readObject(Fr), - privateFunctionTreeSiblingPath: reader.readVector(Fr), - privateFunctionTreeLeafIndex: reader.readNumber(), - artifactTreeSiblingPath: reader.readVector(Fr), - artifactTreeLeafIndex: reader.readNumber(), - }; -} - -function deserializeUtilityFunction(buffer: Buffer | BufferReader): UtilityFunctionWithMembershipProof { - const reader = BufferReader.asReader(buffer); - return { - selector: reader.readObject(FunctionSelector), - bytecode: reader.readBuffer(), - functionMetadataHash: reader.readObject(Fr), - artifactMetadataHash: reader.readObject(Fr), - privateFunctionsArtifactTreeRoot: reader.readObject(Fr), - artifactTreeSiblingPath: reader.readVector(Fr), - artifactTreeLeafIndex: reader.readNumber(), - }; -} diff --git a/yarn-project/archiver/src/store/kv_archiver_store.test.ts b/yarn-project/archiver/src/store/kv_archiver_store.test.ts index 92ae927f0ef3..35f76e58917f 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.test.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.test.ts @@ -7,7 +7,6 @@ import { SlotNumber, } from '@aztec/foundation/branded-types'; import { Buffer16, Buffer32 } from '@aztec/foundation/buffer'; -import { times } from '@aztec/foundation/collection'; import { randomInt } from '@aztec/foundation/crypto/random'; import { Fr } from '@aztec/foundation/curves/bn254'; import { toArray } from '@aztec/foundation/iterable'; @@ -32,11 +31,7 @@ import { import { MAX_LOGS_PER_TAG } from '@aztec/stdlib/interfaces/api-limit'; import { ContractClassLog, LogId } from '@aztec/stdlib/logs'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; -import { - makeContractClassPublic, - makeExecutablePrivateFunctionWithMembershipProof, - makeUtilityFunctionWithMembershipProof, -} from '@aztec/stdlib/testing'; +import { makeContractClassPublic } from '@aztec/stdlib/testing'; import '@aztec/stdlib/testing/jest'; import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; import { type IndexedTxEffect, TxHash } from '@aztec/stdlib/tx'; @@ -2380,36 +2375,6 @@ describe('KVArchiverDataStore', () => { it('returns undefined if contract class is not found', async () => { await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined(); }); - - it('adds new private functions', async () => { - const fns = times(3, makeExecutablePrivateFunctionWithMembershipProof); - await store.addFunctions(contractClass.id, fns, []); - const stored = await store.getContractClass(contractClass.id); - expect(stored?.privateFunctions).toEqual(fns); - }); - - it('does not duplicate private functions', async () => { - const fns = times(3, makeExecutablePrivateFunctionWithMembershipProof); - await store.addFunctions(contractClass.id, fns.slice(0, 1), []); - await store.addFunctions(contractClass.id, fns, []); - const stored = await store.getContractClass(contractClass.id); - expect(stored?.privateFunctions).toEqual(fns); - }); - - it('adds new utility functions', async () => { - const fns = times(3, makeUtilityFunctionWithMembershipProof); - await store.addFunctions(contractClass.id, [], fns); - const stored = await store.getContractClass(contractClass.id); - expect(stored?.utilityFunctions).toEqual(fns); - }); - - it('does not duplicate utility functions', async () => { - const fns = times(3, makeUtilityFunctionWithMembershipProof); - await store.addFunctions(contractClass.id, [], fns.slice(0, 1)); - await store.addFunctions(contractClass.id, [], fns); - const stored = await store.getContractClass(contractClass.id); - expect(stored?.utilityFunctions).toEqual(fns); - }); }); // Note that a lot of tests here are basically duplicates of the ones in getPublicLogsByTagsFromContract but diff --git a/yarn-project/archiver/src/store/kv_archiver_store.ts b/yarn-project/archiver/src/store/kv_archiver_store.ts index 723172fdcb53..6ee954212e5d 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.ts @@ -19,8 +19,6 @@ import type { ContractDataSource, ContractInstanceUpdateWithAddress, ContractInstanceWithAddress, - ExecutablePrivateFunctionWithMembershipProof, - UtilityFunctionWithMembershipProof, } from '@aztec/stdlib/contract'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client'; @@ -194,15 +192,6 @@ export class KVArchiverDataStore implements ContractDataSource { return this.#contractClassStore.getBytecodeCommitment(contractClassId); } - /** Adds private functions to a contract class. */ - addFunctions( - contractClassId: Fr, - privateFunctions: ExecutablePrivateFunctionWithMembershipProof[], - utilityFunctions: UtilityFunctionWithMembershipProof[], - ): Promise { - return this.#contractClassStore.addFunctions(contractClassId, privateFunctions, utilityFunctions); - } - /** * Add new contract instances from an L2 block to the store's list. * @param data - List of contract instances to be added. diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts index 37da34dd7f3c..5f09613cc7eb 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts @@ -69,7 +69,6 @@ describe('e2e_deploy_contract contract class registration', () => { expect(registeredClass!.artifactHash.toString()).toEqual(contractClass.artifactHash.toString()); expect(registeredClass!.privateFunctionsRoot.toString()).toEqual(contractClass.privateFunctionsRoot.toString()); expect(registeredClass!.packedBytecode.toString('hex')).toEqual(contractClass.packedBytecode.toString('hex')); - expect(registeredClass!.privateFunctions).toEqual([]); }); }); diff --git a/yarn-project/protocol-contracts/fixtures/PrivateFunctionBroadcastedEventData.hex b/yarn-project/protocol-contracts/fixtures/PrivateFunctionBroadcastedEventData.hex deleted file mode 100644 index a5e89b5c9325..000000000000 --- a/yarn-project/protocol-contracts/fixtures/PrivateFunctionBroadcastedEventData.hex +++ /dev/null @@ -1 +0,0 @@ -00000000000000000000000000000000000000000000000000000000000000030ea9d22f6c3686a6866f103431c5796d2be7b7a3d0c3abcf7636b064ab09e6180806ce19f39561cf9bc58615c0541921fc90cae11394eb97f9371f02ec497807059234bbc68b8b55e9c52abb3747b811297e35ab73813a96feb74640524fde220d3856d35260777dde79a3415d94a80d95192d933225ca4d3cbcb12613de9f462fd363c29efb017bd7ae341748dd643c73bc19a714b4a288ce5930b64547bf4f1901f8aabbbfc15c27140e1dd356997345e5a992bf9c641e767b55b95c2d1c1501a83cb29c25febaf05115f7802daf31444e3150a1208117178b5b0c4abb5dce2373ea368857ec7af97e7b470d705848e2bf93ed7bef142a490f2119bcf82d8e120157cfaaa49ce3da30f8b47879114977c24b266d58b0ac18b325d878aafddf01c28fe1059ae0237b72334700697bdf465e03df03986fe05200cadeda66bd762d78ed82f93b61ba718b17c2dfe5b52375b4d37cbbed6f1fc98b47614b0cf21b00000000000000000000000000000000000000000000000000000000000000001405f27848f58d8b7bc2744c01f391c38558140988a50bec1e3ad27729fceb12194f6d330fd4fea842e8bf23f950a18b7553b4104fdc5a0e298a59afa59fe8e907e5876f925d7c7b2dfd62320e14f44595d6989526de62bb4719538ce56191aa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c97a6b21eef7f2eaafa09e24b0475a918bd7388c4dec305145849f5f84d1b822202b944028ad70b98d31f63fa89c5151b51d8ebefedd049e6ade196431690630d5fbd6c0000000000000000000000000000000000000000000000000000000000002321001f8b08000000000000ffed9d77601445ffff339fcf91d07b9516a44b51b0770029a14a9162d7189203a221899704c11e7b3704b0778a58282a5810aca8a8fb00164545c12862ef1d7bf96d48b9bd5c2e994bee8d3c7e7fcf3fcfb03bfbfacc00cececccece5c5e6ae19ceb3777494e4e3933d79f9a9c19484ecfccf5073253003272929353b33273720379a9b95901f8fa14f94cfe8ac11929a9a70dce9a35002c2f3375484a4646fea20983c60e4f2accbffb98f4dc4c7f4e8e245a64521b0052331b528b232c32b576ceb7c8d5c62a57079b5275b4c9d4c92653679b4c89005625ef62956b77ab5c5dad72754bccbf6f70203d23237d5af1f97971050573000b0ad625c655fd3f937fefa09c1c7f20f7787f206b6ec19cc275897ba58d0d006c1b7047ef47c7273d9c9f7fec49bdf6fe6cc4ecc7b2e70cd9b67dee77ee2500d0a55563dfecf7e16935c12e8b88d5b2442515b1727c568e3f3d2d2b73e078007f60465e6e4a6e7a5666e1bcf28a718b5b9eee5e9eeae139bf6c1e7439740500f401e883a1252f2cacbe0a5b58dddd43d580cc51368fd9e51c117d09bb59950070a5456bb129e1ca0a4dd1ccc95f3c313d735a86bfa4255417c4a624713b980033b233fcd055760ddda6e8ab4c68d1eb918bfe70f47d74ce5cab62b86cbb02003f527dd3a859fc47aabfb39a9057b9e4b956ed799555ae47ac723d5a83a7640051c2d27bb1b86bbb6769752f8f519e8bfbc457d98df3ab2de287f44289b217009aea0314c5350df6c2c783c935c1e4da60f28960f2c960f2a960f2e960f2990060f2d960725d30f95c30f97c30f94230b93e987c31987c2976a3dcd376d9d600d9657bc12edb4b359a6abc5cf52d9f33e5a3336b827522627d65891a4d355e00f6a41ff7a49ff0a41d77ba01e82bd00dd057434b6f3718acb1ca05ab9a78cd00a233d6a4265ef3a45ff1a43778d2afba35b111fa3af40de89bb5aef0b59ef400c60af3bb4dd0b7a06f4337d7a4b96ca9ba92fa1fd378544db0ef44c436aa5500dd6ff1a49ff4a49ff2a4df712ba508fa2ef43de8d69ab4c2a7ad721559d5c400fb51be922c8bf88c55ae77ad8ab88d53c467ad72bd6755c40f487df97d4f7a009b27fd8127bdd56d4f1f423f827e0cfda42635b1ce2ad7875635f129e7613d006795eb23ab227ec629e2f356b93eb62ae2e7a4f6f4a927fd9927fdb927fd8900db9ebe807e09fd0afa754d6ae205ab5c5f58d5c4379c87b5de2ad7975645fc009653c417ad727d6555c4ef48ede91b4ffa5b4ffa3b4ffa6bb73d7d0ffd01fa0023f4a79ad4c44b56b9beb7aa89eda49ad8ee49ffe049ffe849ffe4d6c4cfd0005fa0bf427fabc9bce5f7aa4b7fea0da350a3d2ffee49ffec496faa3099fb0300fa27f42fe8dfa1df891af56acd1fd53f87c2e027d83fe5495f5c0d1657ed9a00d91f568fe09ff05c2756c865dc5246fba5ed2b0cd66025f7573142b4d5fd4f00348b633e13b32f5e9fa9e451d85572c57015e3bb6c1b964faaadcd38ab3b1100773720fa5bf1c559e5b2bb150dbf958a1759dd8a56bab1b16c4c5e466efac400d4948c94809b9c37377fc990e2bdb194cc5c8bc6109e5736b438392f7ee14900a9fd7a364afabe6df379171ebeeeea0b0eefd9370a6e9c7721e31fcf2dc4450053b8b9f0f9e0ab035f7c25b7bd2269c6147f5a9a3f6d485e60a67f505a9a7b00e3c1383e4fba8e271defc9e4ae3c05d3cf7ad771a22c64027c75e1ab17becd00505dd3303683802f21da898ad54bc957bf6a6ceb5f97f7acc94bc9573fb8dd00eaa9d3e5a1af245f03f81ac2d708bec6d1efceb8cb435677d820dad13cfadd008b35510dd04d58bb176b5cb655817d4d29bb176efca605ecdaaefee56c3c7500dd8c53d76eb36a66d546dd6c36b99a139e875bc6e6736ca3579369a73eb31600b467d6c2ae3e5a909e86cd9c23ce7a29bbfa91af257de4b309e179b2ad4823009fafa5cbb62b706bc6c8571cbf754df66dab7f864d5db2dd48d33466f71fd20026ea14e62f1a1408a4cc2eb46b13ddabaf063b500f8bfa8cf23dbdbc7aa4e500cf7f42aa287e7ecc375aabfc9ef4fecfe9faccfb846a20fc7e233aa446b3c400e06b134cb68ddde76f9bc41acc4a7d6dacc6e676312a63bb0abf1a4a887a08006f6b3384970c0135fd6d5c7557581522f43eebc6fec70d35b93bbb26d1ceaa0049ec16a326b15b6d7f48665112ef5bbd7dec7a5cfb9a7cf63c065d6df7b1db00813f21ea1055d575644d883ab86cbb0277a24c88dcf89da2fe14ac1f5c46b500fb2158d993afbe3c55b7e81dcb93b6afdc1d993def9ecec16462ec7a4267bb006c898935f8fd5c71b5cdb5abb6aabb5561c97a7967abce9768f108a26f689d008ac35bc5b72b65174a776cef82ed86a8dd6bf015516dfce25f193e6a17bf2b00e52bc6fefebb5156c6ec5f11dd6b316815d835c4ddacba5e57de88d52398ec0019bb11ab875db69e1526d5f362596756c3550fab883d29c395db117ad86d3d00b5b7ca65772fbd6ad0a9ac3a750fbbd7487bab5c76f7d23bca01aad0eab9ec00ee361eab8c5ddd21ca6e24d98352d0dddcb25a65ece68e657605ed13edce9200ddeecc2abb665e93e0b15844edc9086cb395d78b11582c02f76604568bc07b00301a581fabdf222c8f36b4cde7445f4645fa2c02f76304ae6311b83f2370bc0045e03d1981132c02efc5085cd722f00046e07a1681073202d7b708bc37237000038bc0fb300237b408bc2f2370238bc0fb310237b608bc3f2370138bc007300002db6cb51dc8086cf36b828318819b5b043e9811b88545e04318815b5a043e009411b89545e0c318815b5b043e9c11b88d45e0231881db5a041ec408dcce2200f06046609b7594218cc0364b004319813b58044e6204ee681178182370278b00c0c319813b5b041ec1089c681178242370178bc0a3188177b7083c9a11b8ab0045e023191fdd6318d0b18c958971562b130f319e4e378be28d67dcf35131fa008543f892a805d55de0b6cad8db5d94b569151328c5ec1e4531fbd8147322a300474c62402733a04733a0c730a0c732a0c731a0c733a02730a02732a02731a0002733a0c90ce8290c680a033a85014d6540d318503f033a95019dc6804e674000d319d05319d0d318d00c067406039ac9806631a0d90ce8e90c688001cd6140007319d03c067426037a06033a8b019dcd809ec9809ec5809ecd809ec3809ecb00809ec7803ae753a8f914ea0514ea8514ea4514eac514ea2514eaa514ea651400eae514ea1514ea9514ea5514ead514ea3514eab5146a01853a87422da450e70052a8f328d4f914ea7514eaf514ea0d14ea8d14ea4d14eacd14ea2d14eaad1400ea6d14eaed14ea1d14ea9d14ea5d14ea020a752185ba88425d4ca1de4da12e00a150efa150efa550efa350efa7509752a8cb2854cadf65382b28d40728d4070029d48728d49514ea2a0af5610af5110af5510af5310a753585fa3885ba8642005d4ba13e41a13e49a13e45a13e4da13e43a13e4ba1aea3509fa3509fa7505f00a050d753a82f52a82f51a82f53a80e850a0af5150a750385fa2a85fa1a85ba0091427d9d427d83427d9342dd44a1be45a1be4da16ea650b750a8ef50a8451400eabb14ea7b14ea560af57d0a751b85fa0185fa2185fa1185fa3185fa0985fa002985fa1985fa3985fa0585fa2585fa1585fa3585fa0d85fa2d85fa1d85fa3d0085fa0385fa2385fa1385ba9d42fd9942fd8542fd9542fd8d42fd9d42fd834200fd9342fd8b42fd9b42fd87418589e3600d072b1cac72b03e0eb60e071b1f2d003666ff253293f0ef85aecba9cc7a5597af6bd1a15da2bf23b50a5d9f51997d00ac4237e05466c39adc51f5d84685365685959ce08d6d82fbda702ab40907db0094836dc6c136e7605b70b02d39d8561c6c6b0e96d41dda72b0ed38d8dd38d800f61c6c070eb62307db8983edccc12672b05d38d8dd39d8ae1c6c370eb63b0700db8383edc9c1f6e2607b73b07b70b07d38d8be1c6c3f0eb63f07bb2707bb1700073b80831dc8c1eecdc1eec3c1eecbc1eec7c1eecfc11ec0c11ec8c11ec4c1001eccc11ec2c11ecac11ec6c11ecec11ec1c10ee2600773b04338d8a11c6c1200073b8c831d1ee57fa9d2123b8253da911cec280e7634077b24073b86831dcb00c18ee360c773b04771b01338d8891cec240e7632077b34077b0c077b2c077b001c077b3c077b02077b22077b12077b32079bccc19ec2c1a670b05338d8540e00368d83f573b05339d8691cec740e369d833d95833d8d83cde0606770b0991c006c16079bcdc19ecec10638d81c0e369783cde3606772b06770b0b338d8d91c00ec991cec591cecd91cec391cecb91cec791cecf91c6c3e077b01077b21077b0011077b31077b09077b29077b19077b39077b05077b25077b15077b35077b0d00077b2d075bc0c1cee1600b39d8b91cec3c0e763e077b1d077b3d077b03077b0023077b13077b33077b0b077b2b077b1b077b3b077b07077b27077b1707bb8000835dc8c12ee2601773b07773b04b38d87b38d87b39d8fb38d8fb39d8a51cec00320e763907bb82837d80837d90837d88835dc9c1aee2601fe6601fe1601fe500601fe3605773b08f73b06b38d8b51cec131cec931cec531cecd31cec331cec00b31cec3a0ef6390ef6790ef6050e763d07fb2207fb1207fb3207eb70b0e060005fe1603770b0af72b0af71b01b39d8d739d83738d83739d84d1cec5b1cecdb001cec660e760b07fb0e075bc4c1becbc1bec7c16ee560dfe760b771b01f70b0001f72b01f71b01f73b09f70b09f72b09f71b09f73b05f70b05f72b05f71b05f0073b0df70b0df72b0df71b0df73b03f70b03f72b03f71b0db39d89f39d85f3800d85f39d8df38d8df39d83f38d83f39d8bf38d8bf39588edb57386e5fe1b87d0085e3f6158edb57386e5fe1b87d259e834de060394e5ea9c7c1d6e760392e5d0069c8c1368a161b3383b334e6dc51939adc51f5d8a68536de5f8e3a5438265d0069cec1724cbac231e90ac7a42b1c93ae704cbac231e90aa93b704cbac231e9000ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c0093ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cba00c231e90ac7a42b1c93ae0ce46039265de198748563d2158e495738265de19800748563d2158e495738265de198748563d295c328ea50e198748563d2158e49005738265de198748563d2158e495738265d19cec1724cbac231e90ac7a42b1c0093ae704cbac231e90ac7a42b1c93ae704cbac231e9ca040e9663d2158e49570038265de198748563d2158e495738265de198748563d2158e495738265de19800748563d2158e495738265de198748563d2158e495738265de198748563d215008e495738265de198748563d2158e495738265de198748563d2158e49573826005d0970b01c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c0093ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cba00c231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac700a42b1c93ae704cbac231e94a2107cb31e90ac7a42b1c93ae704cbac231e90a00c7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c9300ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac20031e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae3cc8c1724cbac231e9000ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c0093ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cba00c231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac700a42b1b39588e495738265de198748563d2158e495738265de198748563d215008e495738265de198748563d2158e495738265de198748563d2158e49573826005de198748563d2158e495738265de198748563d2158e495738265de19874850063d2158e495738265de19874e5070e9663d2158e495738265de198748563d200158e495738265de198748563d2158e495738265de198748563d2558e49573900265de598749563d2558e495739265de598749563d2558e495739265de59874009563d2558e49571b71b01c4bae36e1609b72b01cffad36e76039fe5be5f86f0095e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558e00ff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5b00e5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e300bfd5811c2cc77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2a00c77fab1cffad72fcb77a1807cbf1df2ac77fab1cffad72fcb7caf1df2ac77f00ab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad7200fcb7caf1df2ac77fab1cffad4ee06039fe5be5f86f95e3bf558eff5639fe5b00e5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e300bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff560039fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bfd50007cbf1df2ac77f00ab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad7200fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df002ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1c00ffad1672b01cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab001cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fc00b7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad5af96fc7f867640005668fcc4ccf9ddbb8482fe9d9abf71e7dfaf6ebbfe75e0306eebdcfbefbed007fc081071d7cc8a1871d7ec4a0c14386260d1b3e62e4a8d1478e193b6efc510013264e9a7cf431c71e77fc09279e7472f229295352d3fc53a74d4f3ff5b48c00199959d9a7077272f3669e316bf699679d7dceb9e739e73bf9ce05ce85ce4500cec5ce25cea5ce65cee5ce15ce95ce55ced5ce35ceb54e8133c72974e63af3009cf9ce75cef5ce0dce8dce4dcecdce2dceadce6dceedce1dce9dce5dce026700a1b3c859ecdced2c71ee71ee75ee73ee77963acb9ce5ce0ae701e741e7216700a5b3ca79d879c479d479cc59ed3ceeac71d63a4f384f3a4f394f3bcf38cf3a00eb9ce79ce79d179cf5ce8bce4bcecb8ee3c079c5d9e0bceabce66c745e77de0070de7436396f396f3b9b9d2dce3b4e91f3aef39eb3d579dfd9e67ce07ce87c00e47cec7ce27cea7ce67cee7ce17ce97ce57ced7ce37ceb7ce77ceffce0fce800fce46c777e767e717e757e737e77fe70fe74fe72fe76fe818983313002a330003e983a30f13009307561eac1d4876900d310a6114c639826304d619ac13487006901d312a6154c6b9836306d61dac1ec06d31ea6034c47984e309d611261ba00c0ec0ed315a61b4c77981e303d617ac1f486d903a60f4c5f987e30fd61f68400d90b6600cc4098bd61f681d917663f98fd610e803910e6209883610e81391400e63098c3618e80190433186608cc509824986130c36146c08c841905331ae600489831306361c6c18c87390a6602cc4498493093618e863906e65898e3608e00873901e6449893604e86498639052605660a4c2a4c1a8c1f662acc3498e93000e930a7c29c06930133032613260b261be67498004c0e4c2e4c1ecc4c9833600066c1cc863913e62c98b361ce813917e63c98f361f2612e80b910e622988b61002e81b914e63298cb61ae80b912e62a98ab61ae81b916a600660e4c21cc5c98007930f361ae83b91ee606981b616e82b919e616985b616e83b91de60e983b6100ee825900b3106611cc6298bb6196c0dc03732fcc7d30f7c32c855906b31c660005cc03300fc23c04b3126615ccc3308fc03c0af318cc6a98c761d6c0ac85790002e64998a7609e867906e659987530cfc13c0ff302cc7a9817615e827919c6008101cc2b301b605e85790d6623cceb306fc0bc09b309e62d98b76136c36c81007907a608e65d98f760b6c2bc0fb30de603980f613e82f918e613984f613e8300f91ce60b982f61be82f91ae61b986f61be83f91ee607981f617e82d90ef33300cc2f30bfc2fc06f33bcc1f307fc2fc05f337cc3f903888810844213e481d48003c24015217520f521fd200d210d208d218d204d214d20cd21cd202d212d20a00d21ad206d216d20eb21ba43da403a423a413a4332411d205b23ba42ba41ba4003ba407a427a417a437640f481f485f483f487fc89e90bd20032003217b43f60081ec0bd90fb23fe400c88190832007430e811c0a390c7238e408c820c860c80010c8504812641864386404642464146434e448c818c858c838c878c8519009009089904990c990a321c7408e851c07391e7202e444c849909321c9905320290090299054481ac40f990a9906990e49879c0a390d92019901c9846441b221a7004302901c482e240f3213720664166436e44cc85990b321e740ce859c07391f00920fb9007221e422c8c5904b2097422e835c0eb9027225e42ac8d5906b20d700420a2073208590b9907990f990eb20d7436e80dc08b9097233e416c8ad90db0020b743ee80dc09b90bb200b210b208b21872376409e41ec8bd90fb20f7439600429641964356401e803c087908b212b20af230e411c8a390c720ab218f43d60040d6429e803c09790af234e419c8b3907590e720cf435e80ac87bc087909f20032c48100f20a6403e455c86b908d90d7216f40de846c82bc05791bb219b20500f20ea408f22ee43dc856c8fb906d900f201f423e827c0cf904f229e433c8e700902f205f42be827c0df906f22de43bc8f7901f203f427e826c87fc0cf905f2002be437c8ef903f207f42fe82fc0df9071a07755fca0255a80f5a071a0f4d8000d685d683d68736803684368236863681368536833687b680b684b682b686b60081b685b683ee066d0fed00ed08ed04ed0c4d847681ee0eed0aed06ed0eed0100ed09ed05ed0ddd03da07da17da0fda1fba27742fe800e840e8ded07da0fb4200f783ee0f3d007a20f420e8c1d043a087420f831e0e3d023a083a183a043a14009a041d061d0e1d011d091d051d0d3d123a063a163a0e3a1e7a14740274227400127432f468e831d063a1c7418f879e003d117a12f4646832f414680a740a3400159a06f543a742a7b95bfcee76bcbb75ee6e73bb5bd2eef6b1bbd5eb6ecbba005ba8ee76a7bb35e96e23ba5b7eeef69cbb95e66e7bb95b54ee7692bbf5e36e00d3b85b2aeef687bb55e16e2bb85b00ee72bdbbb4ee2e83bb4bd6eef2b2bb1400ec2edbba4bacee72a8bb74e92e33ba4b82eef29dbbd4e62e8bb94b58ee729300bb34e42ee3b84b2eeef288bb94e12e3bb84b04eee7bcfbe9ed7e26bb9fb4ee00e7a7fba9e87ed6b99f60eee792fb69e37e86b89f0ceef4de9d8abbd366778a00eb4e47dda9a33bcd73a764f74ef0e7e6053287a6e4a614c5f58c33a2be3af1000975ebd56fd0b051e3264d9b356fd1b255eb366ddbedd6be43c74e9d13bbec00deb55bf71e05053715e62f1c949a1e6859b8e1d5842f7f7af9f9690505a587005a871f1a5cb86169838d83d7de5ef7e4b24343c30f8d2adc70c5e483faf69900b6fedbb243c7146e58df282727132bb7951d3a311c7f72e186e95b3e4f76de009b7977d9a194f05cb30b37f438e028d33e77d3d6a2b893f2ef4d9a951df0e700e4a46765ce2da87e75707cb4174c8ff68294682fc889f6027fb41724467b4100eaae574b99d15e306dd7abd6347a9172e9115277bd22a5d11f5c16bdb5465d00a4a9d15e9047bf077ed348ff0f3cb833e8379d4b1fbda3bee9ecffffdadd25005ebbfce1feb4682fe84fafd691f4b13571d7eba2e9f4fe90f81f2852d4fd6100167de4fb3f3923eb4b7fd279f4c6c77f3f748df68213e8d39f2c7a7ff82fcc0004fe4fbed8a35e9b89bbb97073af941ddb91c9a95933b25372d3a764f893b3000229a9eeffcdf4078a41c9670452b2b3fd81a2b8d6f98b866465e6e4cecd5f003c343de04fcd95fcbb4766e6faa7f9030b26ef3db0fa9dcc8ad79ba8ae3f7f0068c5ebe3a28b3f347fe190948c8c390dca394b26f833dc9b9ee98fae24ee76006b1841a225dc5f5c963477f9704856f6ecf25b1aea2d93075e52f246b52ef900d018947ce1c4dcacec3985114a5ae1190d59342cdd9f51fd5fce35585cb2a0005a7aa74df3ef1b9615f0a74fcb2cfee7fccdddf272d333d27367976c9e0f29006face376b4d5a34b9a6a41c19cfca525fbe083d2d28a3b427929e6e42f9e98003e233bc35f529cb260150aeb8baa2a660dcdbf6f707a664af1ae7beeb8ecf90065145d72a41b7ad2f494cc624ab0b19607593c2a6f46f6c8a985e517b4ca5f003a3233ada4a4117bc87e55e88cb63cb77dd3ca510367e42f9ce4f6d63985c100ebcbba6ae91d176ede2b3d2739273dcd9fec9f3ad53de176fc3c37422039e000773b7cc80050def1dbfdcb1d7f582d3bfeb0d246d5b8d68ddf841334e61d5f00bc70b7e49e1b1f1a4c78a3e62f18933533a44396672bb9f326a5394a0f277900b3d6b64e926a5d27267c48f1d641e8c8d0a2e2c8901d489f9992eb1f9933d1006dd5493b1af59092363da1bc49878d0bc140e523437999ef9a3c20727e139e00bff227108c109bc16658ac069bb63b6fb079629abf7880c9cc750794dce4740037584a66aadf4db80f273325a3286ebf7f7964995ccb91657269a3ecf03f3800b254376674ac62cc083933cc1b20e4ccf0f078a5674604cff842cf8c0c9ea900137a6654f04c7ce899d1c13309a1678e0c9ea91b7a664cf04cbdd03363836700ea879e19173cd320f4ccf8e09986a1678e0a9e69147a6642f04ce3d033138300672a8cdc9382679a86b79866b51eb99b4747681a3e7237f3c04247eebd424700eed5a553bae1fee27168c71031b274842872c11107ec886734e2195fc4337500229e898f782621e299ba11cfd48b78a67ec4330d229e6918f14ca388671a47003cd324e299c84fa159f1230a9952c7f65fde97f1ae96df6672507a7515b382008ae7aa1a9b357cb4f41c081b2f3d07c2464ccf81b031d373206cd4f41c081b00373d07c2464ecf81b0b1d373206cf4f41c081b3f3d07c24650cf81b031d47300c0d3c42b9c6bea69e4b199b94d8ed5cc6ddf9d37735b96e62f5e11cacaf127004f77a76b45719dfee599daf05aced486ff0f7f03d6e6eb2ef24cadb67751c900ec21ba8e1127e1b307ef48173a7b685bf6295c59de60635a3879c0c003c2b2007aabb374685e5ad22777fc635cf63c4f860513f3a64418b3c397dfca96b75a000c887babf3d67d67f769bd5fd6b899176d9db4f4dc960b7a7fdaa4ed37798700ccfcad282b723cdf8231791911eeaa66ef0f5f6cc6ade1b11ab73aeebc716b0049466ed988d5e5bf3b6269b4fdacb63dd562c4aa6a8ee219b1c25a6df9a05500e958362c962b55c376ee4a55fb2afafce2a4d3f35232722274edf21e14bca00075fec2e283eef257646879c7b41b31429e5809bd5d551b0ceed4d7a28222070031e541220f6bba8b0d5d893b7165be674eaefb79ecae8e25fb67f953ddaf630077f32d3525757af8827cffffc8d0d6b6d61d5277fad046d8818b7ada14d6c700ca1f6318bb4eb4f55969c7310bdc6f5d6f9f31214b2815ba48cd47d7d0e0c10010e5e1c3efb9c292a3469cfc56f2a15c7aa64ec840b8a365563946457c8995006d3475ab752b19cedea72d1b672b7ddc09151fb706ab2aa4e6ea0633841caf0017acf44a03d42f7dff796394b312c286e1fa5df3171e999592567e203e78d100a289c5a36678e4f8ca23d7ad786b75830da9d20bea55bca05ef08292f7598700d0d77eaf48ad58c35bb1f104f7ae8dae29ddd4da716f233393cade07438a5f0007c57f9973cf087f4af6a0402065b6771c88bc362a85f98b4ab25758a212c600c678cc5ebffd76e25e55aa5bf1c9a5f59e3c352f3375c70b38bd7caf6acf7f00f9a53bba962fddd1a5edb355f8d0121f1da94e382121e62fdd90f5c9d05df000a46022640f3634d7f060a28a5c238209ef92704d3e6022bd9587852fd99697002c6cc1b6bc34250fab75c4cd31137173acf64b3b2377eed24eeff0b5e9c813009a04fa842621f284263e46139a84f057417c8457c1ead221a9f81fe34b92c3004a07a73995bf0612644ea4c17e7ee4ad8b9db5e136bf9a0d969287629d25a400d7d676d665a27875c7877cd896cc4e9654ba7c5bc5baa5543a34553ae92cad008b2a5636436fb6d69521912ac317656554bd32b4c466453346cb02a363352f00e9bff3e6250b33dcda2c8aebf72fcf3e926a39fb482afff9d67f6fffa565a4009e2255cef8fffd0d98b888dfce12f1db59abfd76aefd8fa19262f088a358a800ed19715267ac670fe54f35e2dc4178ef22893077585b3a77281e908705b26600847e4746983e685ca4e9435c6c7ef9f0bfb5f4d063575e7ae81c9bf76252ac00de8b7d77e27279f7ccacdcf4a9b39353037eb789a72567e66564a44f4df70700ca96cbb30359b366fffb7fb612ab3767c3ffe09bb3f67fd092b4b3164a2bfd00bb94b20176ec8ea638a4a4258e2d6b8895fc3d4ac4017317ebc83bf12f525600948471ebc45d6c2bfec1fe8d152ba1552d3b6ccbd834f2b86079cac115871600cbbf768a0bdbc52dabf8b28de18a3125fc19d50d1b982ca39b48d1e3160c4d009f19f6a4821da4fcb6cb2aa270b5f7e1eda8e2e4d3f3dccee0cfccbda162f100ead7f4570ba5d73788f163ac1f0447a80fb9b734a0a75ae282f513e12ab3e300073dc1e7566df6e2df1b85d34366359e7650e1613428bf9dff077cec6cb0cc00c6010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013b \ No newline at end of file diff --git a/yarn-project/protocol-contracts/fixtures/UtilityFunctionBroadcastedEventData.hex b/yarn-project/protocol-contracts/fixtures/UtilityFunctionBroadcastedEventData.hex deleted file mode 100644 index f46d08d02a5f..000000000000 --- a/yarn-project/protocol-contracts/fixtures/UtilityFunctionBroadcastedEventData.hex +++ /dev/null @@ -1 +0,0 @@ -000000000000000000000000000000000000000000000000000000000000000306549ee6586be1c25c81f2d579c06a4c3f31255def74dd729903811d90ba23690806ce19f39561cf9bc58615c0541921fc90cae11394eb97f9371f02ec497807059234bbc68b8b55e9c52abb3747b811297e35ab73813a96feb74640524fde220193cc7bba584f0fb2210f2e762185511bd95a9ade5e06e0e45c3e057128dfd02d1eb762c990e1d198f3bd6ea7ded6f6bba8d8a042dee7ae71b76dc816575049005e3c4cbf3aaaf95376a77f30baf1dcc21581dda5d1eeb8c464aebf2fdbca0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111cfcfc1125672f40d49b4b192a803a41ccb3ddacec4da05bf7d535224059fa9bf766cd000000000000000000000000000000000000000000000000000000000000118d001f8b08000000000000ffed5d6b6c1c5715f67a76fd58db49e8338ded7d787700671fdef53b4e803f4df374e2244d9af01042e9b659826163bbeb751b8350bb00fca84054c8765b50257e358969155a7ed04aad84848a5a84288b40bcd4d23f0048fd57098100a91245b0aebd3367ee9d73eedcd9879df6fad778cf9c73bf7b00ee79ddc7cc68ab2bdf7f2b72e142f66bc5dc8317660b1766668bb9c26c36bf0070e1c2c2e2e5cbb98b171ecee617730b9e95d28d7b0a33f9fccca583d97cfe00a99695d2da7d33b397f2b9279757565f0fb5d07f9e16e12d1b122fcfe7739e00279797c512575a96573c95b61dc17fc7f7f3d2f58373b30bc5274b6b87660a00b9078bada51f4e55eebe942b5c3d3f3e266e8fe5f748f13f7686e56f916bff004ce9dabae2577a0c39cf9dcde5b3c5998773ae7b6248d0e424b4947eb48ee50062b6983d3837bf6474e934c404845f3d39f7f0aaf983c7bc7fa34f7b36efd800fcf95e786badbdbdb7e6de7a4ad7ee2bcecdafac22bd63c6f5e0f52333b9fc0045a1d82e96f1901c2c3fcb7fd8e07ff6fc7e797b3e226b030cff51d0fca87c00f3c76a74c72939fe5696ffb81c3f37ec27e4f8bd2cffb41cbf8fe53f29c7df00c6f29f92e36f5f3b9b2b2e1666373df8eed28d237385dccca5d9f57f9f7e2b00ba589cc9cf14970e2c2ce40ac5837397e72bcef2403e77ba907d309ffb4cae00b0303337bbbcbc527ae164eef25c61e9c0c58b85dcc282e143462ed87026ab00f45736851fcd15cf6f5c55ba52cc5d29bed372272b10b82c46d1508a17a5f800504a1b4a6947291d28a513a5f8514a174ae946293d2865074ad9895276a194004fa0945b50caad28e53694723b4ab903a5e0b6b37bddb0ac067993fd67716e00e12d93135232af9d1f1ddb4fff2a46babccc27eabb1ca6529b2aa1572e9cf500b1354a9f59a631947e1853185a80a87a8230ae30b4108c2c0c2d0c630b431b0080d185a145607c6168511861185a0cc61886a6c328c3d0e230ce30b4048c34000c2d09630d434bc168c3d00661bc6168691871185a06c61c863604a30e431b00867187a18dc0c8c3d04661ec61686330fa30b471107f58da04f01196b6175600cf0c6dd22cf66edc33339b2d2c559ce4f4fcd386b15fadc4be8d1051b57ee000511ba9fe88b558077770aeb2077a0d3709306fabd6124c49e295f2e12b67ec00fba43d375d09e6e7be9c9db576cc6864edf8e2e5f9a92f193dd20e975e989a00bdb85188a053bec9091cd0db6ffcfb4f2f1d1fbb5cba76ae905d9f3818fcd500b9275b3d61d1c763df270f3b4ea6643e14f63b8ca2958abe32cf29e42c38c0000072820352138e163e201ae039d121d96adea99a400bec2003609253116be300661346f37c9f5b89301fe207220cb57e6d7a2e7bd13e7b0425b08480cdb0ed0005e16dcd6e8f4b8eb89d841b6e2761dc4e4275b29330a92b3ce587793d0e40008765c72dec60dc6cb0849d8d5b58b5a7da53ed396b2f54a7f6424a9f1f9df6009c8b046504c234e08629e286294a662f8429e6a625dd0d53dc0d53c20d535200a6dc4b1145cda05c5dd1255fd40ce2454d4ab6a8e1a6e578c7d272b23be43b0096c63b3658aa75972c5da77a2fc31b4a0ab6c245890cec85d237f2d7aaf4dd00547d6b4adf4dd5b757e9bba9faf6297d3755df6d4adf4dd577bbd27753f5dd00a1f4dd547d772a7d3755dffec6e81b119ab6136a4ed1e179a11f9be7854ecd0015730baba5e78fe5b2f3070a85ec12dc34c68fd40cae96ae6fdcbe625e588e002fb4fb6d8f5abcd3720b72c4e928dad85df839149412b0ef52d863fffb00f2007b04f93d8afc1e437ed791dfe3c8ef09e4f7a407d1de2154137b108ec34e250055cfc3ad0f1d3ba4cf9e1f953a1953d917e5ec05123d75a56e80f034b14519002a751289fe715d8bceb91bb7cb7cf463b4cbdc5fa7056e25b22122034a974a00a432222552199112a98c48e952895446a4442a23522295112991ca88942e95004865444ae4b63522fc211ff927de8853f141623fa0f6860c6147a8c3e2a93a00e92f056f63bad50b90ac3fcfb852f65c419bb53e110964189783e65391265b00d9fb4f43f8d7a967b19cbc00a4857fb0690ddda889c8be2b477aa326826fd4000cd469a3c6e674fd00ba511385d0384b8842ad3b3f971f258c2bea2084289100db4164e0a640a9ec52d9a5b24b2552d9a5b24b2552d9a51a716597ca2e95480065976ac4955d2abb5422955d2abb5476a9ec5289fc08db25f752de3aee3e820037fc0e70fb8fe04d80218e08200639620c0a415462f3fea618817400de76bd00c60db60ef1de676b1a052edefb8cdaec7dc6cade1543f830a7b2201c946dfc00bacc48c31f788b34fe75991152576ef751552455458312a98c48e95289544600a4442a23522295112991ca88942e954865444aa432222552199112a98c683b00ef04851afc649db1d5c22db4afa1db0eb1867fd022866f3b44ebb4ed1023c7001eff50668cd7a26e6a1169cce653273a312c4aa012b8cd04725f95ad63148a004117c4e350ed0d45d1fed8ec39bf872a11dd73de9471c866c7592f7b4f18a200ffc66d2aeb30f8b3444b3a20b6a363d4de7f943a35a0d72943ea5bd9deb6b1009cff62bd8bbbb19c78d9fbe9aa68ad85d36a7c2b558e895c3bfcd06236bf6000951ab7544b5c3fa365ad7df303ba655f643b596ba84eed855cab2e6289bc7600aadb65a84e6fb023189761fe6dc4756c282ce171da6ef9f34184c7c5cada8b0086e85efea3f078651c6c78651cc42be3409d2ae3206fd501b432b69c86a22c00bebf4e67a594482572db8b649ca4bf86c058c7f7dd10a5a35e272de8f0368e00da0f230a750a924fff0107580364e0a2b0061ce4987bd066c56750edea9d4000597bc2107e98ea30ffe9ef7ea83531f42934d4bb498fc1b2b664889ec644f700da1632fd94e0deb276da10fc08d769a0cf3eacd57ede04f6984d6203f86255000b7690f758d0db0de339a3f64af225816456c65f70d542a45e0ff16108ea9300f31abf8c695c7a89b3b73e7e3dc0b86c2316cfda591a981370e7c013e66527004b4b9a977efe5bbcc66517ff7927e3b29ba5a5cdcb1e96063e61b483a50d9900973b59dab079b98ba58d98979f6069a3e6e52d2c0dd8e6ad2c6ddcbcbc8da5004d9897b7b3b4bde6e51d2c6dd2bcbc93a5ed332f77b3b4fde6e55d2ced93780002ae9a3b9bc85aa04f6d385c756ac0dfc139cb1ee8372f6c34f8e1f7754ecf003f05fc68bd51db88b387cf1f7dd0fff0b0058af7beb5b3b9e26261967ae020002051960789ec1680b749877f32e45512c0a34687bf4d75a68faa390275aa390002f0364464982c0d10269b8fae87a19288be0de03616b6da5890ac98fa60d300a6ca9d9b8891c98ff17502c060560acd2ebcb88499904b98493e35600933050021e089a1573e20258c0a20ed3c240520323cb70f12b93d4de4f60c91bf878800fc3d4ce4e811ce3c123054b0c4a4831894725382a6cadab386cacf5955ae41007055ebdaa47965d767aa6d7c96b7a54d91e867850779a74809438ecd67102d0096c9f952dae24bc4842b454eb89212aeed28a404c0bc89d895d025b66763ce00424a4c7e2dd0b88cd8af06fec5e8cc4b4def0c171f3372363cc497ab587c1c0026e2e3484df131e3263e3a8e49f8bc619498378c11e9619c480f13c4bc612f009ef6339bf361dbf6ecaaac4c59fba5a1b6afa2e17353ecfa4715edd006eca300e79b40320a39c5414ed3902b827fe34e705228f8774e042739c1095a70b2ac00fdc189e0042738460b4e94b53f3b111ce304c769c19570f43610bc1998bcfb00a9c014aa53600a3988b271b2784798123c531c061ca26f09d9cdd618bdd9aa00bd6b687796da6cd52514107716ece37cb01f920bf6c37c48c782fd0811ec47006b0af6436e82fdb60de8f842d024660143574f2ee659a3332ed197308c9013007c846994671ab10c0563c1c35097ce810c99351fc234e606fd38cf64e9088b007e148ea80bf4531240c4e82778a6710afd18b43917e88f4b0011a3dfcb334d0050e8c7a157b8407f42028818fd24cfb497423fe1c06f29f4d31240c4e8f7f1004c93147ad0b57d6ed09f94002246bf9f67da47a1075ddbef06fda92d453f0400d1f315c370d9bb9bcfdb237279bba574ed5c213bbfb26a73e263042f1e87b900e27110da8c2dd85e233b3f84ae5cd8cd29d2703c79c983656f004846210f720090339611b7131c76273805bddf5670d4896072b2326e3b59f1c69d084e52a500ffa8ed64c59b7222384ecd82c6ec8f18668060a923864074d85ef4a83149d9002b5bab276005652b7cc2c05d04cde0e7378fda9d34038d98f329e75326470b006231f161b5385f37f63aa81b936ea2608a674ac211278639653bddf51e307400778cb6209939a0a8ea4c925bf77188dfd69f8ec0ad7be2ac630274cef9bccf009161c4c5e7867577869170631836d604c65ea7863669efa2670cdd7d57427700ba78ec9b6ef72ed04f490011a3b7dd5320d0276165e002fd71092062f4f4e600068f3e05ab0f17e84f480011a3cff04c690afd202c715ca09f960022463fc400336528f4695895b9407f52028818fdb0ddec80400fba36ec06fda92d45af4300f4dcd422de8cfd9f08b15c1822960b13c461a524715829451c561a240e2ba50089c34a19e2b0922ebfac69d6f48f10cb9ac458c6dc954548691ca796b12be500d86306dc9fa2fbe37ae3f6c713c2fd71db42a3d6f4cdd570295832a1e55523002b4fddbef2fc0e55795af637a8ba5417d756bacbba54074f42927b4b84c90700299397d942a2765062f036e927efc871aa54afcf181afe3beddcf579e4366600e79d20a43521f6c7b6c1debf2505de2c5b45849744d062cecdf1a74cd9fb220038fe84d689f4ac11cb0ab11ab3c2193c2b0c09b3c230b10edc2ab1783c0c0d0090f3b2117339c8f9324f868d9abcea861aa7ba8c1bd5656a545dcc4e75cf6100451415f36262e5651aa7bc98507943e449085785bdce2b6fd849aa22d742b600c06975a1f232648e733923e5943704c3bc73b78d8b95a7374e7971a1f2126400b5e35ce309d26d33300fd6d56de35be9b609376e2b5ae2e494978485c247c7006d136edc96565e9c54dee0c7dd6d93b26e9b84a526f59eb03898a6346ae7aa00354d7d962625f118cd0031034a596ee36adf70d9fbbe316e3f439ff7715356005744ff475c560fd8adc18b063e62a703808ad56b180e32a1f4709d941e86b70039ef43d8b41f02243af9891212a7a8d72045256c3c6c6e6610ef789199bf8700cd0d06374f03c40989d3d41a435c221685cd85786209262111a8c2e6e238b10049965c7720cb9a85657dae1a9fd78f96b037021b4c5efff05403774b1cde0d0065712b2561b95490e627f6d84a09a8a442fcfcd6b80c122b2c03c40a4b8458005d8f12abeb3162755d2756d7e3c4ea7a427eb527ec68951c8bda2e3394f16800a4e70af77a1ce34095690f9313b83d7cf0cdb7964effd1bf8b3f90550d9ed50067786b6ce87bf77ffef7dd911b930d6fe8b7edeffdebcd5f5c5a1637b43172007763a3a32139b595caa95ad93722cea91abf54d55a12edc579796bf10813b1008f67f2021c5c686b05ac124034e1fe419b1bf4ed3c531bec088bde0758dda0009f92002246dfc133b553e841d73adca03f2e01448cbe9367eaa0d083ae75ba00417f42028818bd9f67eaa4d083aef9dda09f96002246dfc533f929f4a06b5d006ed09fdc52f41a44cf643d0f0c4d0ccd12ab189a0f0e2d43f342a36568ed10001643d3a0ab3234e0bb9d754a64ef76dc7bb8f5e527820e12d98642377f6e3300970e6ccb9976f8a62296a7d5cc69d5dbcbbe2f54eb0ddf17ad458ecf6caa7a00c757903ba4dfdfe5b567e864193493c1d2b2dfbcc1f27b17b6d4b1f973b78d007a0c599d9c7abacbbe1966003a4cb6eac4936dbbc3be6d3fdb393fe66c5581002c4397c9501d91073043f449be40b14e76fdf2ee675efedf3fee7fbee195e00007bef213d30fbcfaeb8637b4f4e8e3bf7afed6d7f30d6fe8ecf5bed71effd6000f5e735edbb2b1d60f432b4bec82b11509eedd4855dc4555c51527f906389f008389e6abe22eb32ac65645fd35ae8a1ec557457b4aa255d11d7caaeb11e6c7009d3cd30ea0057eb56a27cc41e8948213da2d5e52ee699cf2fc42e575dbd5090022e5edb0eb27500fa7bc1d30f1bb9ad16c81e5696e94a70995d7432acfcb2b00afc7c174d0768a29549ed638e57985caf313905ba5cc15a887535e372c0f9d00bbad4fac3c6fe394e7132aaf8d802c331f6b23ddd65fe3541e579e6f2bddb600cd8ddb8a964138e5b53b5849b809ddb6cd8ddbd2caf391caeb9470db2e53790098c6c923b45d168be04a285fd9f72a7584d6431a04982a6b75aa457fb237fd00a99ecfe98f35bce8ed79e395d37f7d7f5e1736f47f26346d590ee8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0 \ No newline at end of file diff --git a/yarn-project/protocol-contracts/src/class-registry/__snapshots__/private_function_broadcasted_event.test.ts.snap b/yarn-project/protocol-contracts/src/class-registry/__snapshots__/private_function_broadcasted_event.test.ts.snap deleted file mode 100644 index 39d9fb3b0eaa..000000000000 --- a/yarn-project/protocol-contracts/src/class-registry/__snapshots__/private_function_broadcasted_event.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`PrivateFunctionBroadcastedEvent parses an event as emitted by the ContractClassRegistry 1`] = ` -PrivateFunctionBroadcastedEvent { - "artifactFunctionTreeLeafIndex": 0, - "artifactFunctionTreeSiblingPath": [ - Fr<0x1405f27848f58d8b7bc2744c01f391c38558140988a50bec1e3ad27729fceb12>, - Fr<0x194f6d330fd4fea842e8bf23f950a18b7553b4104fdc5a0e298a59afa59fe8e9>, - Fr<0x07e5876f925d7c7b2dfd62320e14f44595d6989526de62bb4719538ce56191aa>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - ], - "artifactMetadataHash": Fr<0x059234bbc68b8b55e9c52abb3747b811297e35ab73813a96feb74640524fde22>, - "contractClassId": Fr<0x0806ce19f39561cf9bc58615c0541921fc90cae11394eb97f9371f02ec497807>, - "privateFunction": BroadcastedPrivateFunction { - "bytecode": Buffer<0x1f8b08000000000000ffed9d77601445ffff339fcf91d07b9516a44b51b07729a14a9162d7189203a221899704c11e7b3704b0778a58282a5810aca8a8fb164545c12862ef1d7bf96d48b9bd5c2e994bee8d3c7e7fcf3fcfb03bfbfacccececccece5c5e6ae19ceb3777494e4e3933d79f9a9c19484ecfccf50732533272929353b33273720379a9b95901f8fa14f94cfe8ac11929a9a70dce9a352c2f3375484a4646fea20983c60e4f2accbffb98f4dc4c7f4e8e245a64521b52331b528b232c32b576ceb7c8d5c62a57079b5275b4c9d4c92653679b4c895625ef62956b77ab5c5dad72754bccbf6f70203d23237d5af1f979710505730b0ad625c655fd3f937fefa09c1c7f20f7787f206b6ec19cc275897ba58d0d6c1b7047ef47c7273d9c9f7fec49bdf6fe6cc4ecc7b2e70cd9b67dee77ee25d0a55563dfecf7e16935c12e8b88d5b2442515b1727c568e3f3d2d2b73e0787f60465e6e4a6e7a5666e1bcf28a718b5b9eee5e9eeae139bf6c1e74397405f401e883a1252f2cacbe0a5b58dddd43d580cc51368fd9e51c117d09bb599570a5456bb129e1ca0a4dd1ccc95f3c313d735a86bfa4255417c4a624713b9833b233fcd055760ddda6e8ab4c68d1eb918bfe70f47d74ce5cab62b86cbb023f527dd3a859fc47aabfb39a9057b9e4b956ed799555ae47ac723d5a83a76451c2d27bb1b86bbb6769752f8f519e8bfbc457d98df3ab2de287f44289b2179aea0314c5350df6c2c783c935c1e4da60f28960f2c960f2a960f2e960f29960f2d960725d30f95c30f97c30f94230b93e987c31987c2976a3dcd376d9d6d9657bc12edb4b359a6abc5cf52d9f33e5a3336b827522627d65891a4d355ef6a41ff7a49ff0a41d77ba01e82bd00dd057434b6f3718acb1ca05ab9a78cda233d6a4265ef3a45ff1a43778d2afba35b111fa3af40de89bb5aef0b59ef4c60af3bb4dd0b7a06f4337d7a4b96ca9ba92fa1fd378544db0ef44c436aa55dd6ff1a49ff4a49ff2a4df712ba508fa2ef43de8d69ab4c2a7ad721559d5c4fb51be922c8bf88c55ae77ad8ab88d53c467ad72bd6755c40f487df97d4f7a9b27fd8127bdd56d4f1f423f827e0cfda42635b1ce2ad7875635f129e7613d6795eb23ab227ec629e2f356b93eb62ae2e7a4f6f4a927fd9927fdb927fd89db9ebe807e09fd0afa754d6ae205ab5c5f58d5c4379c87b5de2ad7975645fc9653c417ad727d6555c4ef48ede91b4ffa5b4ffa3b4ffa6bb73d7d0ffd01fa23f4a79ad4c44b56b9beb7aa89eda49ad8ee49ffe049ffe849ffe4d6c4cfd05fa0bf427fabc9bce5f7aa4b7fea0da350a3d2ffee49ffec496faa3099fb03fa27f42fe8dfa1df891af56acd1fd53f87c2e027d83fe5495f5c0d1657ed9ad91f568fe09ff05c2756c865dc5246fba5ed2b0cd66025f7573142b4d5fd4f348b633e13b32f5e9fa9e451d85572c57015e3bb6c1b964faaadcd38ab3b11773720fa5bf1c559e5b2bb150dbf958a1759dd8a56bab1b16c4c5e466efac4d4948c94809b9c37377fc990e2bdb194cc5c8bc6109e5736b438392f7ee149a9fd7a364afabe6df379171ebeeeea0b0eefd9370a6e9c7721e31fcf2dc44553b8b9f0f9e0ab035f7c25b7bd2269c6147f5a9a3f6d485e60a67f505a9a7be3c1383e4fba8e271defc9e4ae3c05d3cf7ad771a22c64027c75e1ab17becd505dd3303683802f21da898ad54bc957bf6a6ceb5f97f7acc94bc9573fb8ddeaa9d3e5a1af245f03f81ac2d708bec6d1efceb8cb435677d820dad13cfadd8b35510dd04d58bb176b5cb655817d4d29bb176efca605ecdaaefee56c3c75dd8c53d76eb36a66d546dd6c36b99a139e875bc6e6736ca3579369a73eb316b467d6c2ae3e5a909e86cd9c23ce7a29bbfa91af257de4b309e179b2ad48239fafa5cbb62b706bc6c8571cbf754df66dab7f864d5db2dd48d33466f71fd226ea14e62f1a1408a4cc2eb46b13ddabaf063b500f8bfa8cf23dbdbc7aa4e5cf7f42aa287e7ecc375aabfc9ef4fecfe9faccfb846a20fc7e233aa446b3c4e06b134cb68ddde76f9bc41acc4a7d6dacc6e676312a63bb0abf1a4a887a086f6b3384970c0135fd6d5c7557581522f43eebc6fec70d35b93bbb26d1ceaa49ec16a326b15b6d7f48665112ef5bbd7dec7a5cfb9a7cf63c065d6df7b1db813f21ea1055d575644d883ab86cbb0277a24c88dcf89da2fe14ac1f5c46b5fb2158d993afbe3c55b7e81dcb93b6afdc1d993def9ecec16462ec7a4267bb6c898935f8fd5c71b5cdb5abb6aabb5561c97a7967abce9768f108a26f689d8ac35bc5b72b65174a776cef82ed86a8dd6bf015516dfce25f193e6a17bf2be52bc6fefebb5156c6ec5f11dd6b316815d835c4ddacba5e57de88d52398ec19bb11ab875db69e1526d5f362596756c3550fab883d29c395db117ad86d3db5b7ca65772fbd6ad0a9ac3a750fbbd7487bab5c76f7d23bca01aad0eab9ecee361eab8c5ddd21ca6e24d98352d0dddcb25a65ece68e657605ed13edce92ddeecc2abb665e93e0b15844edc9086cb395d78b11582c02f76604568bc07b301a581fabdf222c8f36b4cde7445f4645fa2c02f76304ae6311b83f2370bc45e03d1981132c02efc5085cd722f00046e07a1681073202d7b708bc372370038bc0fb300237b408bc2f2370238bc0fb310237b608bc3f2370138bc0073002db6cb51dc8086cf36b828318819b5b043e9811b88545e04318815b5a043e9411b89545e0c318815b5b043e9c11b88d45e0231881db5a041ec408dcce22f06046609b7594218cc0364b004319813b58044e6204ee681178182370278bc0c319813b5b041ec1089c681178242370178bc0a3188177b7083c9a11b8ab45e023191fdd6318d0b18c958971562b130f319e4e378be28d67dcf35131fa8543f892a805d55de0b6cad8db5d94b569151328c5ec1e4531fbd8147322a3474c62402733a04733a0c730a0c732a0c731a0c733a02730a02732a02731a02733a0c90ce8290c680a033a85014d6540d318503f033a95019dc6804e6740d319d05319d0d318d00c067406039ac9806631a0d90ce8e90c688001cd61407319d03c067426037a06033a8b019dcd809ec9809ec5809ecd809ec3809ecb809ec7803ae753a8f914ea0514ea8514ea4514eac514ea2514eaa514ea6514eae514ea1514ea9514ea5514ead514ea3514eab5146a01853a87422da450e752a8f328d4f914ea7514eaf514ea0d14ea8d14ea4d14eacd14ea2d14eaad14ea6d14eaed14ea1d14ea9d14ea5d14ea020a752185ba88425d4ca1de4da12ea150efa150efa550efa350efa7509752a8cb2854cadf65382b28d40728d40729d48728d49514ea2a0af5610af5110af5510af5310a753585fa3885ba86425d4ba13e41a13e49a13e45a13e4da13e43a13e4ba1aea3509fa3509fa7505fa050d753a82f52a82f51a82f53a80e850a0af5150a750385fa2a85fa1a85ba91427d9d427d83427d9342dd44a1be45a1be4da16ea650b750a8ef50a84514eabb14ea7b14ea560af57d0a751b85fa0185fa2185fa1185fa3185fa0985fa2985fa1985fa3985fa0585fa2585fa1585fa3585fa0d85fa2d85fa1d85fa3d85fa0385fa2385fa1385ba9d42fd9942fd8542fd9542fd8d42fd9d42fd8342fd9342fd8b42fd9b42fd87418589e3600d072b1cac72b03e0eb60e071b1f2d3666ff253293f0ef85aecba9cc7a5597af6bd1a15da2bf23b50a5d9f51997dac4237e05466c39adc51f5d84685365685959ce08d6d82fbda702ab40907db94836dc6c136e7605b70b02d39d8561c6c6b0e96d41dda72b0ed38d8dd38d8f61c6c070eb62307db8983edccc12672b05d38d8dd39d8ae1c6c370eb63b07db8383edc9c1f6e2607b73b07b70b07d38d8be1c6c3f0eb63f07bb2707bb17073b80831dc8c1eecdc1eec3c1eecbc1eec7c1eecfc11ec0c11ec8c11ec4c11eccc11ec2c11ecac11ec6c11ecec11ec1c10ee2600773b04338d8a11c6c12073b8c831d1ee57fa9d2123b8253da911cec280e7634077b24073b86831dcbc18ee360c773b04771b01338d8891cec240e7632077b34077b0c077b2c077b1c077b3c077b02077b22077b12077b32079bccc19ec2c1a670b05338d8540e368d83f573b05339d8691cec740e369d833d95833d8d83cde0606770b0991c6c16079bcdc19ecec10638d81c0e369783cde3606772b06770b0b338d8d91cec991cec591cecd91cec391cecb91cec791cecf91c6c3e077b01077b21077b11077b31077b09077b29077b19077b39077b05077b25077b15077b35077b0d077b2d075bc0c1cee1600b39d8b91cec3c0e763e077b1d077b3d077b03077b23077b13077b33077b0b077b2b077b1b077b3b077b07077b27077b1707bb80835dc8c12ee2601773b07773b04b38d87b38d87b39d8fb38d8fb39d8a51cec320e763907bb82837d80837d90837d88835dc9c1aee2601fe6601fe1601fe5601fe3605773b08f73b06b38d8b51cec131cec931cec531cecd31cec331cecb31cec3a0ef6390ef6790ef6050e763d07fb2207fb1207fb3207eb70b0e0605fe1603770b0af72b0af71b01b39d8d739d83738d83739d84d1cec5b1cecdb1cec660e760b07fb0e075bc4c1becbc1bec7c16ee560dfe760b771b01f70b01f72b01f71b01f73b09f70b09f72b09f71b09f73b05f70b05f72b05f71b05f73b0df70b0df72b0df71b0df73b03f70b03f72b03f71b0db39d89f39d85f38d85f39d8df38d8df39d83f38d83f39d8bf38d8bf39588edb57386e5fe1b87d85e3f6158edb57386e5fe1b87d259e834de060394e5ea9c7c1d6e760392e5d69c8c1368a161b3383b334e6dc51939adc51f5d8a68536de5f8e3a5438265d69cec1724cbac231e90ac7a42b1c93ae704cbac231e90aa93b704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae0ce46039265de198748563d2158e495738265de198748563d2158e495738265de198748563d295c328ea50e198748563d2158e495738265de198748563d2158e495738265d19cec1724cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e9ca040e9663d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265d0970b01c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e94a2107cb31e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae3cc8c1724cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1c93ae704cbac231e90ac7a42b1b39588e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de19874e5070e9663d2158e495738265de198748563d2158e495738265de198748563d2158e495738265de198748563d2558e495739265de598749563d2558e495739265de598749563d2558e495739265de598749563d2558e49571b71b01c4bae36e1609b72b01cffad36e76039fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bfd5811c2cc77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb77a1807cbf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad4ee06039fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bf558eff5639fe5be5f86f95e3bfd50007cbf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad1672b01cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad72fcb7caf1df2ac77fab1cffad5af96fc7f8676405668fcc4ccf9ddbb8482fe9d9abf71e7dfaf6ebbfe75e0306eebdcfbefbed7fc081071d7cc8a1871d7ec4a0c14386260d1b3e62e4a8d1478e193b6efc5113264e9a7cf431c71e77fc09279e7472f229295352d3fc53a74d4f3ff5b48c199959d9a7077272f3669e316bf699679d7dceb9e739e73bf9ce05ce85ce45cec5ce25cea5ce65cee5ce15ce95ce55ced5ce35ceb54e8133c72974e63af39cf9ce75cef5ce0dce8dce4dcecdce2dceadce6dceedce1dce9dce5dce0267a1b3c859ecdced2c71ee71ee75ee73ee77963acb9ce5ce0ae701e741e72167a5b3ca79d879c479d479cc59ed3ceeac71d63a4f384f3a4f394f3bcf38cf3aeb9ce79ce79d179cf5ce8bce4bcecb8ee3c079c5d9e0bceabce66c745e77de70de7436396f396f3b9b9d2dce3b4e91f3aef39eb3d579dfd9e67ce07ce87ce47cec7ce27cea7ce67cee7ce17ce97ce57ced7ce37ceb7ce77ceffce0fce8fce46c777e767e717e757e737e77fe70fe74fe72fe76fe818983313002a3303e983a30f13009307561eac1d4876900d310a6114c639826304d619ac134876901d312a6154c6b9836306d61dac1ec06d31ea6034c47984e309d611261bac0ec0ed315a61b4c77981e303d617ac1f486d903a60f4c5f987e30fd61f684d90b6600cc4098bd61f681d917663f98fd610e803910e6209883610e813914e63098c3618e80190433186608cc509824986130c36146c08c841905331ae6489831306361c6c18c87390a6602cc4498493093618e863906e65898e3608e873901e6449893604e86498639052605660a4c2a4c1a8c1f662acc3498e930e930a7c29c06930133032613260b261be67498004c0e4c2e4c1ecc4c98336066c1cc863913e62c98b361ce813917e63c98f361f2612e80b910e622988b612e81b914e63298cb61ae80b912e62a98ab61ae81b916a600660e4c21cc5c987930f361ae83b91ee606981b616e82b919e616985b616e83b91de60e983b61ee825900b3106611cc6298bb6196c0dc03732fcc7d30f7c32c855906b31c6605cc03300fc23c04b3126615ccc3308fc03c0af318cc6a98c761d6c0ac857902e64998a7609e867906e659987530cfc13c0ff302cc7a9817615e827919c68101cc2b301b605e85790d6623cceb306fc0bc09b309e62d98b76136c36c817907a608e65d98f760b6c2bc0fb30de603980f613e82f918e613984f613e83f91ce60b982f61be82f91ae61b986f61be83f91ee607981f617e82d90ef333cc2f30bfc2fc06f33bcc1f307fc2fc05f337cc3f903888810844213e481d483c24015217520f521fd200d210d208d218d204d214d20cd21cd202d212d20ad21ad206d216d20eb21ba43da403a423a413a4332411d205b23ba42ba41ba43ba407a427a417a437640f481f485f483f487fc89e90bd20032003217b43f681ec0bd90fb23fe400c88190832007430e811c0a390c7238e408c820c860c810c8504812641864386404642464146434e448c818c858c838c878c85190099089904990c990a321c7408e851c07391e7202e444c849909321c99053202990299054481ac40f990a9906990e49879c0a390d92019901c9846441b221a74302901c482e240f3213720664166436e44cc85990b321e740ce859c07391f920fb9007221e422c8c5904b2097422e835c0eb9027225e42ac8d5906b20d7420a2073208590b9907990f990eb20d7436e80dc08b9097233e416c8ad90db20b743ee80dc09b90bb200b210b208b21872376409e41ec8bd90fb20f74396429641964356401e803c087908b212b20af230e411c8a390c720ab218f43d640d6429e803c09790af234e419c8b3907590e720cf435e80ac87bc087909f232c48100f20a6403e455c86b908d90d7216f40de846c82bc05791bb219b205f20ea408f22ee43dc856c8fb906d900f201f423e827c0cf904f229e433c8e7902f205f42be827c0df906f22de43bc8f7901f203f427e826c87fc0cf905f22be437c8ef903f207f42fe82fc0df9071a07755fca0255a80f5a071a0f4d80d685d683d68736803684368236863681368536833687b680b684b682b686b681b685b683ee066d0fed00ed08ed04ed0c4d847681ee0eed0aed06ed0eed01ed09ed05ed0ddd03da07da17da0fda1fba27742fe800e840e8ded07da0fb42f783ee0f3d007a20f420e8c1d043a087420f831e0e3d023a083a183a043a149a041d061d0e1d011d091d051d0d3d123a063a163a0e3a1e7a147402742274127432f468e831d063a1c7418f879e003d117a12f4646832f414680a740a34159a06f543a742a7b95bfcee76bcbb75ee6e73bb5bd2eef6b1bbd5eb6ecbba5ba8ee76a7bb35e96e23ba5b7eeef69cbb95e66e7bb95b54ee7692bbf5e36ed3b85b2aeef687bb55e16e2bb85b00ee72bdbbb4ee2e83bb4bd6eef2b2bb14ec2edbba4bacee72a8bb74e92e33ba4b82eef29dbbd4e62e8bb94b58ee7293bb34e42ee3b84b2eeef288bb94e12e3bb84b04eee7bcfbe9ed7e26bb9fb4eee7a7fba9e87ed6b99f60eee792fb69e37e86b89f0ceef4de9d8abbd366778aeb4e47dda9a33bcd73a764f74ef0e7e6053287a6e4a614c5f58c33a2be3af10975ebd56fd0b051e3264d9b356fd1b255eb366ddbedd6be43c74e9d13bbecdeb55bf71e05053715e62f1c949a1e6859b8e1d5842f7f7af9f9690505a5875a871f1a5cb86169838d83d7de5ef7e4b24343c30f8d2adc70c5e483faf699b6fedbb243c7146e58df282727132bb7951d3a311c7f72e186e95b3e4f76de9b7977d9a194f05cb30b37f438e028d33e77d3d6a2b893f2ef4d9a951df0e7e4a46765ce2da87e75707cb4174c8ff68294682fc889f6027fb41724467b41eaae574b99d15e306dd7abd6347a9172e9115277bd22a5d11f5c16bdb5465da4a9d15e9047bf077ed348ff0f3cb833e8379d4b1fbda3bee9ecffffdadd255ebbfce1feb4682fe84fafd691f4b13571d7eba2e9f4fe90f81f2852d4fd61167de4fb3f3923eb4b7fd279f4c6c77f3f748df68213e8d39f2c7a7ff82fcc04fe4fbed8a35e9b89bbb97073af941ddb91c9a95933b25372d3a764f893b30229a9eeffcdf4078a41c9670452b2b3fd81a2b8d6f98b866465e6e4cecd5f3c343de04fcd95fcbb4766e6faa7f9030b26ef3db0fa9dcc8ad79ba8ae3f7f68c5ebe3a28b3f347fe190948c8c390dca394b26f833dc9b9ee98fae24ee766b1841a225dc5f5c963477f9704856f6ecf25b1aea2d93075e52f246b52ef9d018947ce1c4dcacec3985114a5ae1190d59342cdd9f51fd5fce35585cb2a05a7aa74df3ef1b9615f0a74fcb2cfee7fccdddf272d333d27367976c9e0f296face376b4d5a34b9a6a41c19cfca525fbe083d2d28a3b427929e6e42f9e983e233bc35f529cb260150aeb8baa2a660dcdbf6f707a664af1ae7beeb8ecf965145d72a41b7ad2f494cc624ab0b19607593c2a6f46f6c8a985e517b4ca5f3a3233ada4a4117bc87e55e88cb63cb77dd3ca510367e42f9ce4f6d63985c1ebcbba6ae91d176ede2b3d2739273dcd9fec9f3ad53de176fc3c37422039e0773b7cc80050def1dbfdcb1d7f582d3bfeb0d246d5b8d68ddf841334e61d5fbc70b7e49e1b1f1a4c78a3e62f18933533a44396672bb9f326a5394a0f2779b3d6b64e926a5d27267c48f1d641e8c8d0a2e2c8901d489f9992eb1f9933d16dd5493b1af59092363da1bc49878d0bc140e523437999ef9a3c20727e139ebff227108c109bc16658ac069bb63b6fb079629abf7880c9cc750794dce47437584a66aadf4db80f273325a3286ebf7f7964995ccb91657269a3ecf03f38b254376674ac62cc083933cc1b20e4ccf0f078a5674604cff842cf8c0c9ea9137a6654f04c7ce899d1c13309a1678e0c9ea91b7a664cf04cbdd033638367ea879e19173cd320f4ccf8e09986a1678e0a9e69147a6642f04ce3d0331383672a8cdc9382679a86b79866b51eb99b4747681a3e7237f3c04247eebd4247eed5a553bae1fee27168c71031b274842872c11107ec886734e2195fc43375229e898f782621e299ba11cfd48b78a67ec4330d229e6918f14ca388671a473cd324e299c84fa159f1230a9952c7f65fde97f1ae96df6672507a7515b3828ae7aa1a9b357cb4f41c081b2f3d07c2464ccf81b031d373206cd4f41c081b373d07c2464ecf81b0b1d373206cf4f41c081b3f3d07c24650cf81b031d473c0d3c42b9c6bea69e4b199b94d8ed5cc6ddf9d37735b96e62f5e11cacaf1274f77a76b45719dfee599daf05aced486ff0f7f03d6e6eb2ef24cadb67751c9ec21ba8e1127e1b307ef48173a7b685bf6295c59de60635a3879c0c003c2b27aabb374685e5ad22777fc635cf63c4f860513f3a64418b3c397dfca96b75a0c887babf3d67d67f769bd5fd6b899176d9db4f4dc960b7a7fdaa4ed377987ccfcad282b723cdf8231791911eeaa66ef0f5f6cc6ade1b11ab73aeebc716b49466ed988d5e5bf3b6269b4fdacb63dd562c4aa6a8ee219b1c25a6df9a055e958362c962b55c376ee4a55fb2afafce2a4d3f35232722274edf21e14bca075fec2e283eef257646879c7b41b31429e5809bd5d551b0ceed4d7a282220731e541220f6bba8b0d5d893b7165be674eaefb79ecae8e25fb67f953ddaf6377f32d3525757af8827cffffc8d0d6b6d61d5277fad046d8818b7ada14d6c7ca1f6318bb4eb4f55969c7310bdc6f5d6f9f31214b2815ba48cd47d7d0e0c110e5e1c3efb9c292a3469cfc56f2a15c7aa64ec840b8a365563946457c89956d3475ab752b19cedea72d1b672b7ddc09151fb706ab2aa4e6ea0633841caf17acf44a03d42f7dff796394b312c286e1fa5df3171e999592567e203e78d1a289c5a36678e4f8ca23d7ad786b75830da9d20bea55bca05ef08292f75987d0d77eaf48ad58c35bb1f104f7ae8dae29ddd4da716f233393cade07438a5f07c57f9973cf087f4af6a0402065b6771c88bc362a85f98b4ab25758a212c6c678cc5ebffd76e25e55aa5bf1c9a5f59e3c352f3375c70b38bd7caf6acf7ff9a53bba962fddd1a5edb355f8d0121f1da94e382121e62fdd90f5c9d05df0a46022640f3634d7f060a28a5c238209ef92704d3e6022bd9587852fd996972c6cc1b6bc34250fab75c4cd31137173acf64b3b2377eed24eeff0b5e9c8139a04fa842621f284263e46139a84f057417c8457c1ead221a9f81fe34b92c34a07a73995bf0612644ea4c17e7ee4ad8b9db5e136bf9a0d969287629d25a4d7d676d665a27875c7877cd896cc4e9654ba7c5bc5baa5543a34553ae92cad8b2a5636436fb6d69521912ac317656554bd32b4c466453346cb02a363352fe9bff3e6250b33dcda2c8aebf72fcf3e926a39fb482afff9d67f6fffa565a49e2255cef8fffd0d98b888dfce12f1db59abfd76aefd8fa19262f088a358a8ed19715267ac670fe54f35e2dc4178ef22893077585b3a77281e908705b266847e4746983e685ca4e9435c6c7ef9f0bfb5f4d063575e7ae81c9bf76252acde8b7d77e27279f7ccacdcf4a9b39353037eb789a72567e66564a44f4df707ca96cbb30359b366fffb7fb612ab3767c3ffe09bb3f67fd092b4b3164a2bfdbb94b20176ec8ea638a4a4258e2d6b8895fc3d4ac4017317ebc83bf12f5256948471ebc45d6c2bfec1fe8d152ba1552d3b6ccbd834f2b86079cac1158716cbbf768a0bdbc52dabf8b28de18a3125fc19d50d1b982ca39b48d1e3160c4d9f19f6a4821da4fcb6cb2aa270b5f7e1eda8e2e4d3f3dccee0cfccbda162f1ead7f4570ba5d73788f163ac1f0447a80fb9b734a0a75ae282f513e12ab3e3073dc1e7566df6e2df1b85d34366359e7650e1613428bf9dff077cec6cb0ccc60100>, - "metadataHash": Fr<0x1eef7f2eaafa09e24b0475a918bd7388c4dec305145849f5f84d1b822202b944>, - "selector": Selector<0x1c97a6b2>, - "vkHash": Fr<0x028ad70b98d31f63fa89c5151b51d8ebefedd049e6ade196431690630d5fbd6c>, - }, - "privateFunctionTreeLeafIndex": 0, - "privateFunctionTreeSiblingPath": [ - Fr<0x2fd363c29efb017bd7ae341748dd643c73bc19a714b4a288ce5930b64547bf4f>, - Fr<0x1901f8aabbbfc15c27140e1dd356997345e5a992bf9c641e767b55b95c2d1c15>, - Fr<0x01a83cb29c25febaf05115f7802daf31444e3150a1208117178b5b0c4abb5dce>, - Fr<0x2373ea368857ec7af97e7b470d705848e2bf93ed7bef142a490f2119bcf82d8e>, - Fr<0x120157cfaaa49ce3da30f8b47879114977c24b266d58b0ac18b325d878aafddf>, - Fr<0x01c28fe1059ae0237b72334700697bdf465e03df03986fe05200cadeda66bd76>, - Fr<0x2d78ed82f93b61ba718b17c2dfe5b52375b4d37cbbed6f1fc98b47614b0cf21b>, - ], - "utilityFunctionsTreeRoot": Fr<0x0d3856d35260777dde79a3415d94a80d95192d933225ca4d3cbcb12613de9f46>, -} -`; diff --git a/yarn-project/protocol-contracts/src/class-registry/__snapshots__/utility_function_broadcasted_event.test.ts.snap b/yarn-project/protocol-contracts/src/class-registry/__snapshots__/utility_function_broadcasted_event.test.ts.snap deleted file mode 100644 index 47d2eccc3b4e..000000000000 --- a/yarn-project/protocol-contracts/src/class-registry/__snapshots__/utility_function_broadcasted_event.test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`UtilityFunctionBroadcastedEvent parses an event as emitted by the ContractClassRegistry 1`] = ` -UtilityFunctionBroadcastedEvent { - "artifactFunctionTreeLeafIndex": 0, - "artifactFunctionTreeSiblingPath": [ - Fr<0x2d1eb762c990e1d198f3bd6ea7ded6f6bba8d8a042dee7ae71b76dc816575049>, - Fr<0x005e3c4cbf3aaaf95376a77f30baf1dcc21581dda5d1eeb8c464aebf2fdbca0d>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, - ], - "artifactMetadataHash": Fr<0x059234bbc68b8b55e9c52abb3747b811297e35ab73813a96feb74640524fde22>, - "contractClassId": Fr<0x0806ce19f39561cf9bc58615c0541921fc90cae11394eb97f9371f02ec497807>, - "privateFunctionsArtifactTreeRoot": Fr<0x0193cc7bba584f0fb2210f2e762185511bd95a9ade5e06e0e45c3e057128dfd0>, - "utilityFunction": BroadcastedUtilityFunction { - "bytecode": Buffer<0x1f8b08000000000000ffed5d6b6c1c5715f67a76fd58db49e8338ded7d7877671fdef53b4e803f4df374e2244d9af01042e9b659826163bbeb751b8350bbfca84054c8765b50257e358969155a7ed04aad84848a5a84288b40bcd4d23f48fd57098100a91245b0aebd3367ee9d73eedcd9879df6fad778cf9c73bf7bee79ddc7cc68ab2bdf7f2b72e142f66bc5dc8317660b1766668bb9c26c36bf70e1c2c2e2e5cbb98b171ecee617730b9e95d28d7b0a33f9fccca583d97cfea99695d2da7d33b397f2b9279757565f0fb5d07f9e16e12d1b122fcfe7739e279797c512575a96573c95b61dc17fc7f7f3d2f58373b30bc5274b6b87660ab9078bada51f4e55eebe942b5c3d3f3e266e8fe5f748f13f7686e56f916bff4ce9dabae2577a0c39cf9dcde5b3c5998773ae7b6248d0e424b4947eb48ee562b6983d3837bf6474e934c404845f3d39f7f0aaf983c7bc7fa34f7b36efd8fcf95e786badbdbdb7e6de7a4ad7ee2bcecdafac22bd63c6f5e0f52333b9fc45a1d82e96f1901c2c3fcb7fd8e07ff6fc7e797b3e226b030cff51d0fca87cf3c76a74c72939fe5696ffb81c3f37ec27e4f8bd2cffb41cbf8fe53f29c7dfc6f29f92e36f5f3b9b2b2e1666373df8eed28d237385dccca5d9f57f9f7e2bba589cc9cf14970e2c2ce40ac5837397e72bcef2403e77ba907d309ffb4caeb0303337bbbcbc527ae164eef25c61e9c0c58b85dcc282e143462ed87026abf45736851fcd15cf6f5c55ba52cc5d29bed372272b10b82c46d1508a17a5f8504a1b4a6947291d28a513a5f8514a174ae946293d2865074ad9895276a1944fa0945b50caad28e53694723b4ab903a5e0b6b37bddb0ac067993fd67716ee12d93135232af9d1f1ddb4fff2a46babccc27eabb1ca6529b2aa1572e9cf5b1354a9f59a631947e1853185a80a87a8230ae30b4108c2c0c2d0c630b431b80d185a145607c6168511861185a0cc61886a6c328c3d0e230ce30b4048c340c2d09630d434bc168c3d00661bc6168691871185a06c61c863604a30e431b867187a18dc0c8c3d04661ec61686330fa30b471107f58da04f01196b61756cf0c6dd22cf66edc33339b2d2c559ce4f4fcd386b15fadc4be8d1051b57ee0511ba9fe88b558077770aeb2077a0d3709306fabd6124c49e295f2e12b67ecfba43d375d09e6e7be9c9db576cc6864edf8e2e5f9a92f193dd20e975e989abdb85188a053bec9091cd0db6ffcfb4f2f1d1fbb5cba76ae905d9f3818fcd5b9275b3d61d1c763df270f3b4ea6643e14f63b8ca2958abe32cf29e42c38c00072820352138e163e201ae039d121d96adea99a400bec2003609253116be3661346f37c9f5b89301fe207220cb57e6d7a2e7bd13e7b0425b08480cdb0ed05e16dcd6e8f4b8eb89d841b6e2761dc4e4275b29330a92b3ce587793d0e408765c72dec60dc6cb0849d8d5b58b5a7da53ed396b2f54a7f6424a9f1f9df69c8b046504c234e08629e286294a662f8429e6a625dd0d53dc0d53c20d5352a6dc4b1145cda05c5dd1255fd40ce2454d4ab6a8e1a6e578c7d272b23be43b96c63b3658aa75972c5da77a2fc31b4a0ab6c245890cec85d237f2d7aaf4dd547d6b4adf4dd5b757e9bba9faf6297d3755df6d4adf4dd577bbd27753f5dda1f4dd547d772a7d3755dffec6e81b119ab6136a4ed1e179a11f9be7854ecd15730baba5e78fe5b2f3070a85ec12dc34c68fd40cae96ae6fdcbe625e588e2fb4fb6d8f5abcd3720b72c4e928dad85df839149412b0ef52d863fffb00f27b04f93d8afc1e437ed791dfe3c8ef09e4f7a407d1de2154137b108ec34e2555cfc3ad0f1d3ba4cf9e1f953a1953d917e5ec05123d75a56e80f034b145192a751289fe715d8bceb91bb7cb7cf463b4cbdc5fa7056e25b22122034a974aa432222552199112a98c48e952895446a4442a23522295112991ca88942e954865444ae4b63522fc211ff927de8853f141623fa0f6860c6147a8c3e2a93ae92f056f63bad50b90ac3fcfb852f65c419bb53e110964189783e65391265bd9fb4f43f8d7a967b19cbc00a4857fb0690ddda889c8be2b477aa326826fd40cd469a3c6e674fd00ba511385d0384b8842ad3b3f971f258c2bea20842891db4164e0a640a9ec52d9a5b24b2552d9a5b24b2552d9a51a716597ca2e954865976ac4955d2abb5422955d2abb5476a9ec5289fc08db25f752de3aee3e8237fc0e70fb8fe04d80218e08200639620c0a415462f3fea618817400de76bdc60db60ef1de676b1a052edefb8cdaec7dc6cade1543f830a7b2201c946dfcbacc48c31f788b34fe75991152576ef751552455458312a98c48e952895446a4442a23522295112991ca88942e954865444aa432222552199112a98c683bef04851afc649db1d5c22db4afa1db0eb1867fd022866f3b44ebb4ed1023c71eff50668cd7a26e6a1169cce653273a312c4aa012b8cd04725f95ad63148a4117c4e350ed0d45d1fed8ec39bf872a11dd73de9471c866c7592f7b4f18a2ffc66d2aeb30f8b3444b3a20b6a363d4de7f943a35a0d72943ea5bd9deb6b19cff62bd8bbbb19c78d9fbe9aa68ad85d36a7c2b558e895c3bfcd06236bf60951ab7544b5c3fa365ad7df303ba655f643b596ba84eed855cab2e6289bc76aadb65a84e6fb023189761fe6dc4756c282ce171da6ef9f34184c7c5cada8b86e85efea3f078651c6c78651cc42be3409d2ae3206fd501b432b69c86a22cbebf4e67a594482572db8b649ca4bf86c058c7f7dd10a5a35e272de8f0368eda0f230a750a924fff0107580364e0a2b0061ce4987bd066c56750edea9d40597bc2107e98ea30ffe9ef7ea83531f42934d4bb498fc1b2b664889ec644f7da1632fd94e0deb276da10fc08d769a0cf3eacd57ede04f6984d6203f862550b7690f758d0db0de339a3f64af225816456c65f70d542a45e0ff16108ea93f31abf8c695c7a89b3b73e7e3dc0b86c2316cfda591a981370e7c013e665274b4b9a977efe5bbcc66517ff7927e3b29ba5a5cdcb1e96063e61b483a50d99973b59dab079b98ba58d98979f6069a3e6e52d2c0dd8e6ad2c6ddcbcbc8da54d9897b7b3b4bde6e51d2c6dd2bcbc93a5ed332f77b3b4fde6e55d2ced937802ae9a3b9bc85aa04f6d385c756ac0dfc139cb1ee8372f6c34f8e1f7754ecf3f05fc68bd51db88b387cf1f7dd0fff0b0058af7beb5b3b9e26261967ae0202051960789ec1680b749877f32e45512c0a34687bf4d75a68faa390275aa3902f0364464982c0d10269b8fae87a19288be0de03616b6da5890ac98fa60d3a6ca9d9b8891c98ff17502c060560acd2ebcb88499904b98493e356009330521e089a1573e20258c0a20ed3c240520323cb70f12b93d4de4f60c91bf8788fc3d4ce4e811ce3c123054b0c4a4831894725382a6cadab386cacf5955ae417055ebdaa47965d767aa6d7c96b7a54d91e867850779a74809438ecd67102d96c9f952dae24bc4842b454eb89212aeed28a404c0bc89d895d025b66763ce424a4c7e2dd0b88cd8af06fec5e8cc4b4def0c171f3372363cc497ab587c1c26e2e3484df131e3263e3a8e49f8bc619498378c11e9619c480f13c4bc612f9ef6339bf361dbf6ecaaac4c59fba5a1b6afa2e17353ecfa4715edd006eca3e79b40320a39c5414ed3902b827fe34e705228f8774e042739c1095a70b2acfdc189e0042738460b4e94b53f3b111ce304c769c19570f43610bc1998bcfba9c014aa53600a3988b271b2784798123c531c061ca26f09d9cdd618bdd9aabd6b687796da6cd52514107716ece37cb01f920bf6c37c48c782fd0811ec476b0af6436e82fdb60de8f842d024660143574f2ee659a3332ed197308c90137c846994671ab10c0563c1c35097ce810c99351fc234e606fd38cf64e9088b7e148ea80bf4531240c4e82778a6710afd18b43917e88f4b0011a3dfcb334d50e8c7a157b8407f42028818fd24cfb497423fe1c06f29f4d31240c4e8f7f14c93147ad0b57d6ed09f94002246bf9f67da47a1075ddbef06fda92d453f04d1f315c370d9bb9bcfdb237279bba574ed5c213bbfb26a73e263042f1e87b9e27110da8c2dd85e233b3f84ae5cd8cd29d2703c79c983656f004846210f7290339611b7131c76273805bddf5670d4896072b2326e3b59f1c69d084e52a5ffa8ed64c59b7222384ecd82c6ec8f18668060a923864074d85ef4a83149d92b5bab276005652b7cc2c05d04cde0e7378fda9d34038d98f329e75326470b6231f161b5385f37f63aa81b936ea2608a674ac211278639653bddf51e3074778cb6209939a0a8ea4c925bf77188dfd69f8ec0ad7be2ac630274cef9bccf9161c4c5e7867577869170631836d604c65ea7863669efa2670cdd7d574277ba78ec9b6ef72ed04f490011a3b7dd5320d0276165e002fd71092062f4f4e6068f3e05ab0f17e84f480011a3cff04c690afd202c715ca09f960022463fc4336528f4695895b9407f52028818fdb0ddec80400fba36ec06fda92d45af43f4dcd422de8cfd9f08b15c1822960b13c461a524715829451c561a240e2ba589c34a19e2b0922ebfac69d6f48f10cb9ac458c6dc954548691ca796b12be5d86306dc9fa2fbe37ae3f6c713c2fd71db42a3d6f4cdd570295832a1e555232b4fddbef2fc0e55795af637a8ba5417d756bacbba54074f42927b4b84c907299397d942a2765062f036e927efc871aa54afcf181afe3beddcf579e43666e79d20a43521f6c7b6c1debf2505de2c5b45849744d062cecdf1a74cd9fb2238fe84d689f4ac11cb0ab11ab3c2193c2b0c09b3c230b10edc2ab1783c0c0d90f3b2117339c8f9324f868d9abcea861aa7ba8c1bd5656a545dcc4e75cf61451415f36262e5651aa7bc98507943e449085785bdce2b6fd849aa22d742b6c06975a1f232648e733923e5943704c3bc73b78d8b95a7374e7971a1f21264b5e35ce309d26d33300fd6d56de35be9b609376e2b5ae2e494978485c247c76d136edc96565e9c54dee0c7dd6d93b26e9b84a526f59eb03898a6346ae7aa354d7d962625f118cd0031034a596ee36adf70d9fbbe316e3f439ff77153565744ff475c560fd8adc18b063e62a703808ad56b180e32a1f4709d941e86b739ef43d8b41f02243af9891212a7a8d72045256c3c6c6e6610ef789199bf87cd0d06374f03c40989d3d41a435c221685cd85786209262111a8c2e6e238b149965c7720cb9a85657dae1a9fd78f96b037021b4c5efff05403774b1cde0d65712b2561b95490e627f6d84a09a8a442fcfcd6b80c122b2c03c40a4b84585d8f12abeb3162755d2756d7e3c4ea7a427eb527ec68951c8bda2e3394f168a4e70af77a1ce34095690f9313b83d7cf0cdb7964effd1bf8b3f90550d9ed567786b6ce87bf77ffef7dd911b930d6fe8b7edeffdebcd5f5c5a1637b431727763a3a32139b595caa95ad93722cea91abf54d55a12edc579796bf10813b18f67f2021c5c686b05ac124034e1fe419b1bf4ed3c531bec088bde0758dda09f92002246dfc133b553e841d73adca03f2e01448cbe9367eaa0d083ae75ba417f42028818bd9f67eaa4d083aef9dda09f96002246dfc533f929f4a06b5d6ed09fdc52f41a44cf643d0f0c4d0ccd12ab189a0f0e2d43f342a36568ed101643d3a0ab3234e0bb9d754a64ef76dc7bb8f5e527820e12d98642377f6e33970e6ccb9976f8a62296a7d5cc69d5dbcbbe2f54eb0ddf17ad458ecf6caa7ac757903ba4dfdfe5b567e864193493c1d2b2dfbcc1f27b17b6d4b1f973b78d7a0c599d9c7abacbbe1966003a4cb6eac4936dbbc3be6d3fdb393fe66c55812c4397c9501d91073043f449be40b14e76fdf2ee675efedf3fee7fbee195e007bef213d30fbcfaeb8637b4f4e8e3bf7afed6d7f30d6fe8ecf5bed71effd60f5e735edbb2b1d60f432b4bec82b11509eedd4855dc4555c51527f906389f8389e6abe22eb32ac65645fd35ae8a1ec557457b4aa255d11d7caaeb11e6c79d3cd30ea0057eb56a27cc41e8948213da2d5e52ee699cf2fc42e575dbd50922e5edb0eb27500fa7bc1d30f1bb9ad16c81e5696e94a70995d7432acfcb2bafc7c174d0768a29549ed638e57985caf313905ba5cc15a887535e372c0f9dbbad4fac3c6fe394e7132aaf8d802c331f6b23ddd65fe3541e579e6f2bddb6cd8ddb8a964138e5b53b5849b809ddb6cd8ddbd2caf391caeb9470db2e537998c6c923b45d168be04a285fd9f72a7584d6431a04982a6b75aa457fb237fda99ecfe98f35bce8ed79e395d37f7d7f5e1736f47f26346d590ee80000>, - "metadataHash": Fr<0x1125672f40d49b4b192a803a41ccb3ddacec4da05bf7d535224059fa9bf766cd>, - "selector": Selector<0x111cfcfc>, - }, -} -`; diff --git a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts index 73234f5cc6a3..057db11ca9e8 100644 --- a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts +++ b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts @@ -70,8 +70,6 @@ export class ContractClassPublishedEvent { packedBytecode: this.packedPublicBytecode, privateFunctionsRoot: this.privateFunctionsRoot, version: this.version, - privateFunctions: [], - utilityFunctions: [], }; } diff --git a/yarn-project/protocol-contracts/src/class-registry/index.ts b/yarn-project/protocol-contracts/src/class-registry/index.ts index 7b3db3145893..bf8daaf8ffee 100644 --- a/yarn-project/protocol-contracts/src/class-registry/index.ts +++ b/yarn-project/protocol-contracts/src/class-registry/index.ts @@ -6,8 +6,6 @@ import { makeProtocolContract } from '../make_protocol_contract.js'; import type { ProtocolContract } from '../protocol_contract.js'; export * from './contract_class_published_event.js'; -export * from './private_function_broadcasted_event.js'; -export * from './utility_function_broadcasted_event.js'; export const ContractClassRegistryArtifact = loadContractArtifact(ContractClassRegistryJson as NoirCompiledContract); diff --git a/yarn-project/protocol-contracts/src/class-registry/lazy.ts b/yarn-project/protocol-contracts/src/class-registry/lazy.ts index 634a075d1947..6148d927b79f 100644 --- a/yarn-project/protocol-contracts/src/class-registry/lazy.ts +++ b/yarn-project/protocol-contracts/src/class-registry/lazy.ts @@ -4,8 +4,6 @@ import { makeProtocolContract } from '../make_protocol_contract.js'; import type { ProtocolContract } from '../protocol_contract.js'; export * from './contract_class_published_event.js'; -export * from './private_function_broadcasted_event.js'; -export * from './utility_function_broadcasted_event.js'; let protocolContract: ProtocolContract; let protocolContractArtifact: ContractArtifact; diff --git a/yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.test.ts b/yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.test.ts deleted file mode 100644 index 558b4c056b07..000000000000 --- a/yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { setupCustomSnapshotSerializers } from '@aztec/foundation/testing'; -import { ContractClassLog } from '@aztec/stdlib/logs'; - -import { getSamplePrivateFunctionBroadcastedEventPayload } from '../tests/fixtures.js'; -import { PrivateFunctionBroadcastedEvent } from './private_function_broadcasted_event.js'; - -describe('PrivateFunctionBroadcastedEvent', () => { - beforeAll(() => setupCustomSnapshotSerializers(expect)); - - it('parses an event as emitted by the ContractClassRegistry', () => { - const log = ContractClassLog.fromBuffer(getSamplePrivateFunctionBroadcastedEventPayload()); - expect(PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log)).toBe(true); - - const event = PrivateFunctionBroadcastedEvent.fromLog(log); - - // See ./__snapshots__/README.md for how to update the snapshot. - expect(event).toMatchSnapshot(); - }); -}); diff --git a/yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.ts b/yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.ts deleted file mode 100644 index 88698959d73c..000000000000 --- a/yarn-project/protocol-contracts/src/class-registry/private_function_broadcasted_event.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, - CONTRACT_CLASS_REGISTRY_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE, - FUNCTION_TREE_HEIGHT, - MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, -} from '@aztec/constants'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import type { Tuple } from '@aztec/foundation/serialize'; -import { FieldReader } from '@aztec/foundation/serialize'; -import { FunctionSelector, bufferFromFields } from '@aztec/stdlib/abi'; -import type { ExecutablePrivateFunctionWithMembershipProof, PrivateFunction } from '@aztec/stdlib/contract'; -import type { ContractClassLog } from '@aztec/stdlib/logs'; - -import { ProtocolContractAddress } from '../protocol_contract_data.js'; - -/** Event emitted from the ContractClassRegistry. */ -export class PrivateFunctionBroadcastedEvent { - constructor( - public readonly contractClassId: Fr, - public readonly artifactMetadataHash: Fr, - public readonly utilityFunctionsTreeRoot: Fr, - public readonly privateFunctionTreeSiblingPath: Tuple, - public readonly privateFunctionTreeLeafIndex: number, - public readonly artifactFunctionTreeSiblingPath: Tuple, - public readonly artifactFunctionTreeLeafIndex: number, - public readonly privateFunction: BroadcastedPrivateFunction, - ) {} - - static isPrivateFunctionBroadcastedEvent(log: ContractClassLog) { - return ( - log.contractAddress.equals(ProtocolContractAddress.ContractClassRegistry) && - log.fields.fields[0].toBigInt() === CONTRACT_CLASS_REGISTRY_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE - ); - } - - static fromLog(log: ContractClassLog) { - const reader = new FieldReader(log.fields.fields.slice(1)); - const event = PrivateFunctionBroadcastedEvent.fromFields(reader); - while (!reader.isFinished()) { - const field = reader.readField(); - if (!field.isZero()) { - throw new Error(`Unexpected data after parsing PrivateFunctionBroadcastedEvent: ${field.toString()}`); - } - } - - return event; - } - - static fromFields(fields: Fr[] | FieldReader) { - const reader = FieldReader.asReader(fields); - const contractClassId = reader.readField(); - const artifactMetadataHash = reader.readField(); - const utilityFunctionsTreeRoot = reader.readField(); - const privateFunctionTreeSiblingPath = reader.readFieldArray(FUNCTION_TREE_HEIGHT); - const privateFunctionTreeLeafIndex = reader.readField().toNumber(); - const artifactFunctionTreeSiblingPath = reader.readFieldArray(ARTIFACT_FUNCTION_TREE_MAX_HEIGHT); - const artifactFunctionTreeLeafIndex = reader.readField().toNumber(); - const privateFunction = BroadcastedPrivateFunction.fromFields(reader); - - return new PrivateFunctionBroadcastedEvent( - contractClassId, - artifactMetadataHash, - utilityFunctionsTreeRoot, - privateFunctionTreeSiblingPath, - privateFunctionTreeLeafIndex, - artifactFunctionTreeSiblingPath, - artifactFunctionTreeLeafIndex, - privateFunction, - ); - } - - toFunctionWithMembershipProof(): ExecutablePrivateFunctionWithMembershipProof { - return { - ...this.privateFunction, - bytecode: this.privateFunction.bytecode, - functionMetadataHash: this.privateFunction.metadataHash, - artifactMetadataHash: this.artifactMetadataHash, - utilityFunctionsTreeRoot: this.utilityFunctionsTreeRoot, - privateFunctionTreeSiblingPath: this.privateFunctionTreeSiblingPath, - privateFunctionTreeLeafIndex: this.privateFunctionTreeLeafIndex, - artifactTreeSiblingPath: this.artifactFunctionTreeSiblingPath.filter(fr => !fr.isZero()), - artifactTreeLeafIndex: this.artifactFunctionTreeLeafIndex, - }; - } -} - -export class BroadcastedPrivateFunction implements PrivateFunction { - constructor( - /** Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. */ - public readonly selector: FunctionSelector, - /** Artifact metadata hash */ - public readonly metadataHash: Fr, - /** Hash of the verification key associated to this private function. */ - public readonly vkHash: Fr, - /** ACIR and Brillig bytecode */ - public readonly bytecode: Buffer, - ) {} - - static fromFields(fields: Fr[] | FieldReader) { - const reader = FieldReader.asReader(fields); - const selector = FunctionSelector.fromField(reader.readField()); - const metadataHash = reader.readField(); - const vkHash = reader.readField(); - // The '* 1' removes the 'Type instantiation is excessively deep and possibly infinite. ts(2589)' err - const encodedBytecode = reader.readFieldArray(MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS * 1); - const bytecode = bufferFromFields(encodedBytecode); - return new BroadcastedPrivateFunction(selector, metadataHash, vkHash, bytecode); - } -} diff --git a/yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.test.ts b/yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.test.ts deleted file mode 100644 index d87aa2771848..000000000000 --- a/yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { randomBytes } from '@aztec/foundation/crypto/random'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import type { Tuple } from '@aztec/foundation/serialize'; -import { setupCustomSnapshotSerializers } from '@aztec/foundation/testing'; -import { FunctionSelector } from '@aztec/stdlib/abi'; -import { ContractClassLog } from '@aztec/stdlib/logs'; - -import { getSampleUtilityFunctionBroadcastedEventPayload } from '../tests/fixtures.js'; -import { BroadcastedUtilityFunction, UtilityFunctionBroadcastedEvent } from './utility_function_broadcasted_event.js'; - -describe('UtilityFunctionBroadcastedEvent', () => { - beforeAll(() => setupCustomSnapshotSerializers(expect)); - - it('parses an event as emitted by the ContractClassRegistry', () => { - const log = ContractClassLog.fromBuffer(getSampleUtilityFunctionBroadcastedEventPayload()); - expect(UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log)).toBe(true); - - const event = UtilityFunctionBroadcastedEvent.fromLog(log); - - // See ./__snapshots__/README.md for how to update the snapshot. - expect(event).toMatchSnapshot(); - }); - - it('filters out zero-elements at the end of the artifact tree sibling path', () => { - const siblingPath: Tuple = [Fr.ZERO, new Fr(1), Fr.ZERO, new Fr(2), Fr.ZERO, new Fr(3), Fr.ZERO]; - const event = new UtilityFunctionBroadcastedEvent( - Fr.random(), - Fr.random(), - Fr.random(), - siblingPath, - 0, - new BroadcastedUtilityFunction(FunctionSelector.random(), Fr.random(), randomBytes(32)), - ); - const filtered = event.toFunctionWithMembershipProof().artifactTreeSiblingPath; - expect(filtered).toEqual([Fr.ZERO, new Fr(1), Fr.ZERO, new Fr(2), Fr.ZERO, new Fr(3)]); - }); -}); diff --git a/yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.ts b/yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.ts deleted file mode 100644 index d7cfe8b1bc10..000000000000 --- a/yarn-project/protocol-contracts/src/class-registry/utility_function_broadcasted_event.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, - CONTRACT_CLASS_REGISTRY_UTILITY_FUNCTION_BROADCASTED_MAGIC_VALUE, - MAX_PACKED_BYTECODE_SIZE_PER_UTILITY_FUNCTION_IN_FIELDS, -} from '@aztec/constants'; -import { removeArrayPaddingEnd } from '@aztec/foundation/collection'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import type { Tuple } from '@aztec/foundation/serialize'; -import { FieldReader } from '@aztec/foundation/serialize'; -import { FunctionSelector, bufferFromFields } from '@aztec/stdlib/abi'; -import type { UtilityFunction, UtilityFunctionWithMembershipProof } from '@aztec/stdlib/contract'; -import type { ContractClassLog } from '@aztec/stdlib/logs'; - -import { ProtocolContractAddress } from '../protocol_contract_data.js'; - -/** Event emitted from the ContractClassRegistry. */ -export class UtilityFunctionBroadcastedEvent { - constructor( - public readonly contractClassId: Fr, - public readonly artifactMetadataHash: Fr, - public readonly privateFunctionsArtifactTreeRoot: Fr, - public readonly artifactFunctionTreeSiblingPath: Tuple, - public readonly artifactFunctionTreeLeafIndex: number, - public readonly utilityFunction: BroadcastedUtilityFunction, - ) {} - - static isUtilityFunctionBroadcastedEvent(log: ContractClassLog) { - return ( - log.contractAddress.equals(ProtocolContractAddress.ContractClassRegistry) && - log.fields.fields[0].toBigInt() === CONTRACT_CLASS_REGISTRY_UTILITY_FUNCTION_BROADCASTED_MAGIC_VALUE - ); - } - - static fromLog(log: ContractClassLog) { - const reader = new FieldReader(log.fields.fields.slice(1)); - const event = UtilityFunctionBroadcastedEvent.fromFields(reader); - while (!reader.isFinished()) { - const field = reader.readField(); - if (!field.isZero()) { - throw new Error(`Unexpected data after parsing UtilityFunctionBroadcastedEvent: ${field.toString()}`); - } - } - - return event; - } - - static fromFields(fields: Fr[] | FieldReader) { - const reader = FieldReader.asReader(fields); - const contractClassId = reader.readField(); - const artifactMetadataHash = reader.readField(); - const privateFunctionsArtifactTreeRoot = reader.readField(); - const artifactFunctionTreeSiblingPath = reader.readFieldArray(ARTIFACT_FUNCTION_TREE_MAX_HEIGHT); - const artifactFunctionTreeLeafIndex = reader.readField().toNumber(); - const utilityFunction = BroadcastedUtilityFunction.fromFields(reader); - - return new UtilityFunctionBroadcastedEvent( - contractClassId, - artifactMetadataHash, - privateFunctionsArtifactTreeRoot, - artifactFunctionTreeSiblingPath, - artifactFunctionTreeLeafIndex, - utilityFunction, - ); - } - - toFunctionWithMembershipProof(): UtilityFunctionWithMembershipProof { - // We should be able to safely remove the zero elements that pad the variable-length sibling path, - // since a sibling with value zero can only occur on the tree leaves, so the sibling path will never end - // in a zero. The only exception is a tree with depth 2 with one non-zero leaf, where the sibling path would - // be a single zero element, but in that case the artifact tree should be just the single leaf. - const artifactTreeSiblingPath = removeArrayPaddingEnd(this.artifactFunctionTreeSiblingPath, Fr.isZero); - return { - ...this.utilityFunction, - bytecode: this.utilityFunction.bytecode, - functionMetadataHash: this.utilityFunction.metadataHash, - artifactMetadataHash: this.artifactMetadataHash, - privateFunctionsArtifactTreeRoot: this.privateFunctionsArtifactTreeRoot, - artifactTreeSiblingPath, - artifactTreeLeafIndex: this.artifactFunctionTreeLeafIndex, - }; - } -} - -export class BroadcastedUtilityFunction implements UtilityFunction { - constructor( - /** Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. */ - public readonly selector: FunctionSelector, - /** Artifact metadata hash */ - public readonly metadataHash: Fr, - /** Brillig bytecode */ - public readonly bytecode: Buffer, - ) {} - - static fromFields(fields: Fr[] | FieldReader) { - const reader = FieldReader.asReader(fields); - const selector = FunctionSelector.fromField(reader.readField()); - const metadataHash = reader.readField(); - // The '* 1' removes the 'Type instantiation is excessively deep and possibly infinite. ts(2589)' err - const encodedBytecode = reader.readFieldArray(MAX_PACKED_BYTECODE_SIZE_PER_UTILITY_FUNCTION_IN_FIELDS * 1); - const bytecode = bufferFromFields(encodedBytecode); - return new BroadcastedUtilityFunction(selector, metadataHash, bytecode); - } -} diff --git a/yarn-project/protocol-contracts/src/tests/fixtures.ts b/yarn-project/protocol-contracts/src/tests/fixtures.ts index 2d4a1a712057..d71fc48ac162 100644 --- a/yarn-project/protocol-contracts/src/tests/fixtures.ts +++ b/yarn-project/protocol-contracts/src/tests/fixtures.ts @@ -8,18 +8,6 @@ export function getSampleContractClassPublishedEventPayload(): Buffer { return Buffer.from(readFileSync(path).toString(), 'hex'); } -// Generated from end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts with AZTEC_GENERATE_TEST_DATA=1 -export function getSamplePrivateFunctionBroadcastedEventPayload(): Buffer { - const path = getPathToFixture('PrivateFunctionBroadcastedEventData.hex'); - return Buffer.from(readFileSync(path).toString(), 'hex'); -} - -// Generated from end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts with AZTEC_GENERATE_TEST_DATA=1 -export function getSampleUtilityFunctionBroadcastedEventPayload(): Buffer { - const path = getPathToFixture('UtilityFunctionBroadcastedEventData.hex'); - return Buffer.from(readFileSync(path).toString(), 'hex'); -} - // Generated from end-to-end/src/e2e_deploy_contract/contract_class_registration.test.ts with AZTEC_GENERATE_TEST_DATA=1 export function getSampleContractInstancePublishedEventPayload(): Buffer { const path = getPathToFixture('ContractInstancePublishedEventData.hex'); diff --git a/yarn-project/stdlib/src/contract/index.ts b/yarn-project/stdlib/src/contract/index.ts index 92f1d7bb517c..d038c43fd6cf 100644 --- a/yarn-project/stdlib/src/contract/index.ts +++ b/yarn-project/stdlib/src/contract/index.ts @@ -6,8 +6,6 @@ export * from './contract_deployment_data.js'; export * from './contract_instance.js'; export * from './contract_instance_update.js'; export * from './private_function.js'; -export * from './private_function_membership_proof.js'; -export * from './utility_function_membership_proof.js'; export * from './interfaces/index.js'; export * from './contract_function_dao.js'; export * from './partial_address.js'; diff --git a/yarn-project/stdlib/src/contract/interfaces/contract_class.ts b/yarn-project/stdlib/src/contract/interfaces/contract_class.ts index e7525fea4a85..33aca27d749f 100644 --- a/yarn-project/stdlib/src/contract/interfaces/contract_class.ts +++ b/yarn-project/stdlib/src/contract/interfaces/contract_class.ts @@ -42,79 +42,6 @@ const PrivateFunctionSchema = zodFor()( }), ); -/** Private function definition with executable bytecode. */ -export interface ExecutablePrivateFunction extends PrivateFunction { - /** ACIR and Brillig bytecode */ - bytecode: Buffer; -} - -const ExecutablePrivateFunctionSchema = zodFor()( - PrivateFunctionSchema.and(z.object({ bytecode: schemas.Buffer })), -); - -/** Utility function definition. */ -export interface UtilityFunction { - /** Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. */ - selector: FunctionSelector; - /** Brillig. */ - bytecode: Buffer; -} - -const UtilityFunctionSchema = zodFor()( - z.object({ - selector: FunctionSelector.schema, - bytecode: schemas.Buffer, - }), -); - -/** Sibling paths and sibling commitments for proving membership of a private function within a contract class. */ -export type PrivateFunctionMembershipProof = { - artifactMetadataHash: Fr; - functionMetadataHash: Fr; - utilityFunctionsTreeRoot: Fr; - privateFunctionTreeSiblingPath: Fr[]; - privateFunctionTreeLeafIndex: number; - artifactTreeSiblingPath: Fr[]; - artifactTreeLeafIndex: number; -}; - -const PrivateFunctionMembershipProofSchema = zodFor()( - z.object({ - artifactMetadataHash: schemas.Fr, - functionMetadataHash: schemas.Fr, - utilityFunctionsTreeRoot: schemas.Fr, - privateFunctionTreeSiblingPath: z.array(schemas.Fr), - privateFunctionTreeLeafIndex: schemas.Integer, - artifactTreeSiblingPath: z.array(schemas.Fr), - artifactTreeLeafIndex: schemas.Integer, - }), -); - -/** A private function with a membership proof. */ -export type ExecutablePrivateFunctionWithMembershipProof = ExecutablePrivateFunction & PrivateFunctionMembershipProof; - -/** Sibling paths and commitments for proving membership of a utility function within a contract class. */ -export type UtilityFunctionMembershipProof = { - artifactMetadataHash: Fr; - functionMetadataHash: Fr; - privateFunctionsArtifactTreeRoot: Fr; - artifactTreeSiblingPath: Fr[]; - artifactTreeLeafIndex: number; -}; - -const UtilityFunctionMembershipProofSchema = zodFor()( - z.object({ - artifactMetadataHash: schemas.Fr, - functionMetadataHash: schemas.Fr, - privateFunctionsArtifactTreeRoot: schemas.Fr, - artifactTreeSiblingPath: z.array(schemas.Fr), - artifactTreeLeafIndex: schemas.Integer, - }), -); - -/** A utility function with a membership proof. */ -export type UtilityFunctionWithMembershipProof = UtilityFunction & UtilityFunctionMembershipProof; - export const ContractClassSchema = zodFor()( z.object({ version: z.literal(VERSION), @@ -143,11 +70,8 @@ export const ContractClassWithIdSchema = zodFor()( }), ); -/** A contract class with public bytecode information, and optional private and utility functions. */ -export type ContractClassPublic = { - privateFunctions: ExecutablePrivateFunctionWithMembershipProof[]; - utilityFunctions: UtilityFunctionWithMembershipProof[]; -} & Pick & +/** A contract class with public bytecode information. */ +export type ContractClassPublic = Pick & Omit; export type ContractClassPublicWithCommitment = ContractClassPublic & @@ -158,8 +82,6 @@ export const ContractClassPublicSchema = zodFor()( .object({ id: schemas.Fr, privateFunctionsRoot: schemas.Fr, - privateFunctions: z.array(ExecutablePrivateFunctionSchema.and(PrivateFunctionMembershipProofSchema)), - utilityFunctions: z.array(UtilityFunctionSchema.and(UtilityFunctionMembershipProofSchema)), }) .and(ContractClassSchema.omit({ privateFunctions: true })), ); @@ -179,8 +101,6 @@ export function contractClassPublicFromPlainObject(obj: any): ContractClassPubli version: 1, artifactHash: Fr.fromPlainObject(obj.artifactHash), privateFunctionsRoot: Fr.fromPlainObject(obj.privateFunctionsRoot), - privateFunctions: [], - utilityFunctions: [], packedBytecode: obj.packedBytecode instanceof Buffer ? obj.packedBytecode : Buffer.from(obj.packedBytecode), }; } diff --git a/yarn-project/stdlib/src/contract/private_function_membership_proof.test.ts b/yarn-project/stdlib/src/contract/private_function_membership_proof.test.ts deleted file mode 100644 index 611069275b98..000000000000 --- a/yarn-project/stdlib/src/contract/private_function_membership_proof.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; - -import { type ContractArtifact, type FunctionArtifact, FunctionSelector, FunctionType } from '../abi/index.js'; -import { getBenchmarkContractArtifact } from '../tests/fixtures.js'; -import { computeVerificationKeyHash, getContractClassFromArtifact } from './contract_class.js'; -import type { ContractClassIdPreimage } from './contract_class_id.js'; -import type { ContractClass } from './interfaces/contract_class.js'; -import { - createPrivateFunctionMembershipProof, - isValidPrivateFunctionMembershipProof, -} from './private_function_membership_proof.js'; - -describe('private_function_membership_proof', () => { - let artifact: ContractArtifact; - let contractClass: ContractClass & ContractClassIdPreimage; - let privateFunction: FunctionArtifact; - let vkHash: Fr; - let selector: FunctionSelector; - - beforeAll(async () => { - artifact = getBenchmarkContractArtifact(); - contractClass = await getContractClassFromArtifact(artifact); - privateFunction = artifact.functions.findLast(fn => fn.functionType === FunctionType.PRIVATE)!; - vkHash = await computeVerificationKeyHash(privateFunction); - selector = await FunctionSelector.fromNameAndParameters(privateFunction); - }); - - it('computes and verifies a proof', async () => { - const proof = await createPrivateFunctionMembershipProof(selector, artifact); - const fn = { ...privateFunction, ...proof, selector, vkHash }; - await expect(isValidPrivateFunctionMembershipProof(fn, contractClass)).resolves.toBeTruthy(); - }); - - test.each([ - 'artifactTreeSiblingPath', - 'artifactMetadataHash', - 'functionMetadataHash', - 'utilityFunctionsTreeRoot', - 'privateFunctionTreeSiblingPath', - ] as const)('fails proof if %s is mangled', async field => { - const proof = await createPrivateFunctionMembershipProof(selector, artifact); - const original = proof[field]; - const mangled = Array.isArray(original) ? [Fr.random(), ...original.slice(1)] : Fr.random(); - const wrong = { ...proof, [field]: mangled }; - const fn = { ...privateFunction, ...wrong, selector, vkHash }; - await expect(isValidPrivateFunctionMembershipProof(fn, contractClass)).resolves.toBeFalsy(); - }); -}); diff --git a/yarn-project/stdlib/src/contract/private_function_membership_proof.ts b/yarn-project/stdlib/src/contract/private_function_membership_proof.ts deleted file mode 100644 index c2689d3efb04..000000000000 --- a/yarn-project/stdlib/src/contract/private_function_membership_proof.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { createLogger } from '@aztec/foundation/log'; -import { computeRootFromSiblingPath } from '@aztec/foundation/trees'; - -import { type ContractArtifact, FunctionSelector, FunctionType } from '../abi/index.js'; -import { - computeArtifactFunctionTree, - computeArtifactHash, - computeArtifactHashPreimage, - computeFunctionArtifactHash, - computeFunctionMetadataHash, - getArtifactMerkleTreeHasher, -} from './artifact_hash.js'; -import { getContractClassPrivateFunctionFromArtifact } from './contract_class.js'; -import type { - ContractClassPublic, - ExecutablePrivateFunctionWithMembershipProof, - PrivateFunctionMembershipProof, -} from './interfaces/index.js'; -import { computePrivateFunctionLeaf, computePrivateFunctionsTree } from './private_function.js'; - -/** - * Creates a membership proof for a private function in a contract class, to be verified via `isValidPrivateFunctionMembershipProof`. - * @param selector - Selector of the function to create the proof for. - * @param artifact - Artifact of the contract class where the function is defined. - */ -export async function createPrivateFunctionMembershipProof( - selector: FunctionSelector, - artifact: ContractArtifact, -): Promise { - const log = createLogger('circuits:function_membership_proof'); - - // Locate private function definition and artifact - const privateFunctions = artifact.functions.filter(fn => fn.functionType === FunctionType.PRIVATE); - const privateFunctionsFromArtifact = await Promise.all( - privateFunctions.map(getContractClassPrivateFunctionFromArtifact), - ); - const privateFunction = privateFunctionsFromArtifact.find(fn => fn.selector.equals(selector)); - const privateFunctionsAndSelectors = await Promise.all( - privateFunctions.map(async fn => ({ - fn, - selector: await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters), - })), - ); - const privateFunctionArtifact = privateFunctionsAndSelectors.find(fnAndSelector => - selector.equals(fnAndSelector.selector), - )?.fn; - if (!privateFunction || !privateFunctionArtifact) { - throw new Error(`Private function with selector ${selector.toString()} not found`); - } - - // Compute preimage for the artifact hash - const { utilityFunctionRoot: utilityFunctionsTreeRoot, metadataHash: artifactMetadataHash } = - await computeArtifactHashPreimage(artifact); - - // We need two sibling paths because private function information is split across two trees: - // The "private function tree" captures the selectors and verification keys, and is used in the kernel circuit for verifying the proof generated by the app circuit. - const functionLeaf = await computePrivateFunctionLeaf(privateFunction); - const functionsTree = await computePrivateFunctionsTree(privateFunctionsFromArtifact); - const functionsTreeLeafIndex = functionsTree.getIndex(functionLeaf); - const functionsTreeSiblingPath = functionsTree.getSiblingPath(functionsTreeLeafIndex).map(Fr.fromBuffer); - - // And the "artifact tree" captures function bytecode and metadata, and is used by the pxe to check that its executing the code it's supposed to be executing, but it never goes into circuits. - const functionMetadataHash = computeFunctionMetadataHash(privateFunctionArtifact); - const functionArtifactHash = await computeFunctionArtifactHash({ ...privateFunctionArtifact, functionMetadataHash }); - const artifactTree = (await computeArtifactFunctionTree(artifact, FunctionType.PRIVATE))!; - const artifactTreeLeafIndex = artifactTree.getIndex(functionArtifactHash.toBuffer()); - const artifactTreeSiblingPath = artifactTree.getSiblingPath(artifactTreeLeafIndex).map(Fr.fromBuffer); - - log.debug(`Computed proof for private function with selector ${selector.toString()}`, { - functionArtifactHash, - functionMetadataHash, - functionLeaf: '0x' + functionLeaf.toString('hex'), - artifactMetadataHash, - privateFunctionsTreeRoot: '0x' + functionsTree.root.toString('hex'), - utilityFunctionsTreeRoot, - artifactFunctionTreeSiblingPath: artifactTreeSiblingPath.map(fr => fr.toString()).join(','), - privateFunctionTreeSiblingPath: functionsTreeSiblingPath.map(fr => fr.toString()).join(','), - }); - - return { - artifactTreeSiblingPath, - artifactTreeLeafIndex, - artifactMetadataHash, - functionMetadataHash, - utilityFunctionsTreeRoot, - privateFunctionTreeSiblingPath: functionsTreeSiblingPath, - privateFunctionTreeLeafIndex: functionsTreeLeafIndex, - }; -} - -/** - * Verifies that a private function with a membership proof as emitted by the ClassRegistry contract is valid, - * as defined in the protocol specs at contract-deployment/classes: - * - * ``` - * // Load contract class from local db - * contract_class = db.get_contract_class(contract_class_id) - * - * // Compute function leaf and assert it belongs to the private functions tree - * function_leaf = pedersen([selector as Field, vk_hash], GENERATOR__PRIVATE_FUNCTION_LEAF) - * computed_private_function_tree_root = compute_root(function_leaf, private_function_tree_sibling_path) - * assert computed_private_function_tree_root == contract_class.private_functions_root - * - * // Compute artifact leaf and assert it belongs to the artifact - * artifact_function_leaf = sha256(selector, metadata_hash, sha256(bytecode)) - * computed_artifact_private_function_tree_root = compute_root(artifact_function_leaf, artifact_function_tree_sibling_path) - * computed_artifact_hash = sha256(computed_artifact_private_function_tree_root, utility_functions_artifact_tree_root, artifact_metadata_hash) - * assert computed_artifact_hash == contract_class.artifact_hash - * ``` - * @param fn - Function to check membership proof for. - * @param contractClass - In which contract class the function is expected to be. - */ -export async function isValidPrivateFunctionMembershipProof( - fn: ExecutablePrivateFunctionWithMembershipProof, - contractClass: Pick, -) { - const log = createLogger('circuits:function_membership_proof'); - - // Check private function tree membership - const functionLeaf = await computePrivateFunctionLeaf(fn); - const rootBuffer = await computeRootFromSiblingPath( - functionLeaf, - fn.privateFunctionTreeSiblingPath.map(fr => fr.toBuffer()), - fn.privateFunctionTreeLeafIndex, - async (left, right) => (await poseidon2Hash([left, right])).toBuffer(), - ); - const computedPrivateFunctionTreeRoot = Fr.fromBuffer(rootBuffer); - if (!contractClass.privateFunctionsRoot.equals(computedPrivateFunctionTreeRoot)) { - log.debug(`Private function tree root mismatch`, { - expectedPrivateFunctionTreeRoot: contractClass.privateFunctionsRoot, - computedPrivateFunctionTreeRoot: computedPrivateFunctionTreeRoot, - computedFunctionLeaf: '0x' + functionLeaf.toString('hex'), - }); - return false; - } - - // Check artifact hash - const functionArtifactHash = await computeFunctionArtifactHash(fn); - const computedArtifactPrivateFunctionTreeRootBuffer = await computeRootFromSiblingPath( - functionArtifactHash.toBuffer(), - fn.artifactTreeSiblingPath.map(fr => fr.toBuffer()), - fn.artifactTreeLeafIndex, - getArtifactMerkleTreeHasher(), - ); - const computedArtifactPrivateFunctionTreeRoot = Fr.fromBuffer(computedArtifactPrivateFunctionTreeRootBuffer); - const computedArtifactHash = await computeArtifactHash({ - privateFunctionRoot: computedArtifactPrivateFunctionTreeRoot, - utilityFunctionRoot: fn.utilityFunctionsTreeRoot, - metadataHash: fn.artifactMetadataHash, - }); - if (!contractClass.artifactHash.equals(computedArtifactHash)) { - log.debug(`Artifact hash mismatch`, { - expected: contractClass.artifactHash, - computedArtifactHash, - computedFunctionArtifactHash: functionArtifactHash, - computedArtifactPrivateFunctionTreeRoot, - utilityFunctionRoot: fn.utilityFunctionsTreeRoot, - metadataHash: fn.artifactMetadataHash, - artifactFunctionTreeSiblingPath: fn.artifactTreeSiblingPath.map(fr => fr.toString()).join(','), - }); - return false; - } - - return true; -} diff --git a/yarn-project/stdlib/src/contract/utility_function_membership_proof.test.ts b/yarn-project/stdlib/src/contract/utility_function_membership_proof.test.ts deleted file mode 100644 index 1c76520c2f54..000000000000 --- a/yarn-project/stdlib/src/contract/utility_function_membership_proof.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { Fr } from '@aztec/foundation/curves/bn254'; - -import { type ContractArtifact, type FunctionArtifact, FunctionSelector, FunctionType } from '../abi/index.js'; -import { getTestContractArtifact } from '../tests/fixtures.js'; -import { getContractClassFromArtifact } from './contract_class.js'; -import type { ContractClassIdPreimage } from './contract_class_id.js'; -import type { ContractClass } from './interfaces/contract_class.js'; -import { - createUtilityFunctionMembershipProof, - isValidUtilityFunctionMembershipProof, -} from './utility_function_membership_proof.js'; - -describe('utility_function_membership_proof', () => { - let artifact: ContractArtifact; - let contractClass: ContractClass & ContractClassIdPreimage; - let utilityFunction: FunctionArtifact; - const vkHash: Fr = Fr.random(); - let selector: FunctionSelector; - - beforeEach(async () => { - artifact = getTestContractArtifact(); - contractClass = await getContractClassFromArtifact(artifact); - utilityFunction = artifact.functions.findLast(fn => fn.functionType === FunctionType.UTILITY)!; - selector = await FunctionSelector.fromNameAndParameters(utilityFunction); - }); - - const isUtility = (fn: { functionType: FunctionType }) => fn.functionType === FunctionType.UTILITY; - - it('computes and verifies a proof', async () => { - expect(utilityFunction).toBeDefined(); - const proof = await createUtilityFunctionMembershipProof(selector, artifact); - const fn = { ...utilityFunction, ...proof, selector }; - await expect(isValidUtilityFunctionMembershipProof(fn, contractClass)).resolves.toBeTruthy(); - }); - - it('handles a contract with a single function', async () => { - // Remove all utility functions from the contract but one - const utilityFns = artifact.functions.filter(isUtility); - artifact.functions = artifact.functions.filter(fn => !isUtility(fn) || fn === utilityFns[0]); - expect(artifact.functions.filter(isUtility).length).toBe(BlockNumber(1)); - - const utilityFunction = utilityFns[0]; - const selector = await FunctionSelector.fromNameAndParameters(utilityFunction); - - const proof = await createUtilityFunctionMembershipProof(selector, artifact); - expect(proof.artifactTreeSiblingPath.length).toBe(0); - - const fn = { ...utilityFunction, ...proof, selector }; - const contractClass = await getContractClassFromArtifact(artifact); - await expect(isValidUtilityFunctionMembershipProof(fn, contractClass)).resolves.toBeTruthy(); - }); - - test.each(['artifactTreeSiblingPath', 'artifactMetadataHash', 'functionMetadataHash'] as const)( - 'fails proof if %s is mangled', - async field => { - const proof = await createUtilityFunctionMembershipProof(selector, artifact); - const original = proof[field]; - const mangled = Array.isArray(original) ? [Fr.random(), ...original.slice(1)] : Fr.random(); - const wrong = { ...proof, [field]: mangled }; - const fn = { ...utilityFunction, ...wrong, selector, vkHash }; - await expect(isValidUtilityFunctionMembershipProof(fn, contractClass)).resolves.toBeFalsy(); - }, - ); -}); diff --git a/yarn-project/stdlib/src/contract/utility_function_membership_proof.ts b/yarn-project/stdlib/src/contract/utility_function_membership_proof.ts deleted file mode 100644 index 78a65b0d3472..000000000000 --- a/yarn-project/stdlib/src/contract/utility_function_membership_proof.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { createLogger } from '@aztec/foundation/log'; -import { computeRootFromSiblingPath } from '@aztec/foundation/trees'; - -import { type ContractArtifact, FunctionSelector, FunctionType } from '../abi/index.js'; -import { - computeArtifactFunctionTree, - computeArtifactHash, - computeArtifactHashPreimage, - computeFunctionArtifactHash, - computeFunctionMetadataHash, - getArtifactMerkleTreeHasher, -} from './artifact_hash.js'; -import type { - ContractClassPublic, - UtilityFunctionMembershipProof, - UtilityFunctionWithMembershipProof, -} from './interfaces/index.js'; - -/** - * Creates a membership proof for a utility function in a contract class, to be verified via `isValidUtilityFunctionMembershipProof`. - * @param selector - Selector of the function to create the proof for. - * @param artifact - Artifact of the contract class where the function is defined. - */ -export async function createUtilityFunctionMembershipProof( - selector: FunctionSelector, - artifact: ContractArtifact, -): Promise { - const log = createLogger('circuits:function_membership_proof'); - - // Locate function artifact - const utilityFunctions = artifact.functions.filter(fn => fn.functionType === FunctionType.UTILITY); - const utilityFunctionsAndSelectors = await Promise.all( - utilityFunctions.map(async fn => ({ fn, selector: await FunctionSelector.fromNameAndParameters(fn) })), - ); - const fn = utilityFunctionsAndSelectors.find(fnAndSelector => selector.equals(fnAndSelector.selector))?.fn; - if (!fn) { - throw new Error(`Utility function with selector ${selector.toString()} not found`); - } - // Compute preimage for the artifact hash - const { privateFunctionRoot: privateFunctionsArtifactTreeRoot, metadataHash: artifactMetadataHash } = - await computeArtifactHashPreimage(artifact); - - // Compute the sibling path for the "artifact tree" - const functionMetadataHash = computeFunctionMetadataHash(fn); - const functionArtifactHash = await computeFunctionArtifactHash({ ...fn, functionMetadataHash }); - const artifactTree = (await computeArtifactFunctionTree(artifact, FunctionType.UTILITY))!; - const artifactTreeLeafIndex = artifactTree.getIndex(functionArtifactHash.toBuffer()); - const artifactTreeSiblingPath = artifactTree.getSiblingPath(artifactTreeLeafIndex).map(Fr.fromBuffer); - - log.debug(`Computed proof for utility function with selector ${selector.toString()}`, { - functionArtifactHash, - functionMetadataHash, - artifactMetadataHash, - artifactFunctionTreeSiblingPath: artifactTreeSiblingPath.map(fr => fr.toString()).join(','), - privateFunctionsArtifactTreeRoot, - }); - - return { - artifactTreeSiblingPath, - artifactTreeLeafIndex, - artifactMetadataHash, - functionMetadataHash, - privateFunctionsArtifactTreeRoot, - }; -} - -/** - * Verifies that a utility function with a membership proof as emitted by the ClassRegistry contract is valid, - * as defined in the protocol specs at contract-deployment/classes: - * - * ``` - * // Load contract class from local db - * contract_class = db.get_contract_class(contract_class_id) - * - * // Compute artifact leaf and assert it belongs to the artifact - * artifact_function_leaf = sha256(selector, metadata_hash, sha256(bytecode)) - * computed_artifact_utility_function_tree_root = compute_root(artifact_function_leaf, artifact_function_tree_sibling_path, artifact_function_tree_leaf_index) - * computed_artifact_hash = sha256(private_functions_artifact_tree_root, computed_artifact_utility_function_tree_root, artifact_metadata_hash) - * assert computed_artifact_hash == contract_class.artifact_hash - * ``` - * @param fn - Function to check membership proof for. - * @param contractClass - In which contract class the function is expected to be. - */ -export async function isValidUtilityFunctionMembershipProof( - fn: UtilityFunctionWithMembershipProof, - contractClass: Pick, -) { - const log = createLogger('circuits:function_membership_proof'); - - const functionArtifactHash = await computeFunctionArtifactHash(fn); - const computedArtifactFunctionTreeRootBuffer = await computeRootFromSiblingPath( - functionArtifactHash.toBuffer(), - fn.artifactTreeSiblingPath.map(fr => fr.toBuffer()), - fn.artifactTreeLeafIndex, - getArtifactMerkleTreeHasher(), - ); - const computedArtifactFunctionTreeRoot = Fr.fromBuffer(computedArtifactFunctionTreeRootBuffer); - const computedArtifactHash = await computeArtifactHash({ - privateFunctionRoot: fn.privateFunctionsArtifactTreeRoot, - utilityFunctionRoot: computedArtifactFunctionTreeRoot, - metadataHash: fn.artifactMetadataHash, - }); - if (!contractClass.artifactHash.equals(computedArtifactHash)) { - log.debug(`Artifact hash mismatch`, { - expected: contractClass.artifactHash, - computedArtifactHash, - computedFunctionArtifactHash: functionArtifactHash, - computedArtifactFunctionTreeRoot, - privateFunctionsArtifactTreeRoot: fn.privateFunctionsArtifactTreeRoot, - metadataHash: fn.artifactMetadataHash, - artifactFunctionTreeSiblingPath: fn.artifactTreeSiblingPath.map(fr => fr.toString()).join(','), - }); - return false; - } - - return true; -} diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index 71eef7dca702..fa5c109da9a6 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -472,11 +472,7 @@ describe('AztecNodeApiSchema', () => { it('getContractClass', async () => { const contractClass = await getContractClassFromArtifact(artifact); const response = await context.client.getContractClass(Fr.random()); - expect(response).toEqual({ - ...omit(contractClass, 'publicBytecodeCommitment'), - utilityFunctions: [], - privateFunctions: [], - }); + expect(response).toEqual(omit(contractClass, 'publicBytecodeCommitment')); }); it('getContract', async () => { @@ -830,7 +826,7 @@ class MockAztecNode implements AztecNode { async getContractClass(id: Fr): Promise { expect(id).toBeInstanceOf(Fr); const contractClass = await getContractClassFromArtifact(this.artifact); - return { ...contractClass, utilityFunctions: [], privateFunctions: [] }; + return contractClass; } async getContract(address: AztecAddress): Promise { expect(address).toBeInstanceOf(AztecAddress); diff --git a/yarn-project/stdlib/src/tests/factories.ts b/yarn-project/stdlib/src/tests/factories.ts index c33aaa591bd4..14c491fdb772 100644 --- a/yarn-project/stdlib/src/tests/factories.ts +++ b/yarn-project/stdlib/src/tests/factories.ts @@ -92,10 +92,8 @@ import { type ContractClassPublic, ContractDeploymentData, type ContractInstanceWithAddress, - type ExecutablePrivateFunctionWithMembershipProof, type PrivateFunction, SerializableContractInstance, - type UtilityFunctionWithMembershipProof, computeContractClassId, computePublicBytecodeCommitment, } from '../contract/index.js'; @@ -1183,35 +1181,6 @@ export function makePublicTxBaseRollupPrivateInputs(seed = 0) { }); } -export function makeExecutablePrivateFunctionWithMembershipProof( - seed = 0, -): ExecutablePrivateFunctionWithMembershipProof { - return { - selector: makeSelector(seed), - bytecode: makeBytes(100, seed + 1), - artifactTreeSiblingPath: makeTuple(3, fr, seed + 2), - artifactTreeLeafIndex: seed + 2, - privateFunctionTreeSiblingPath: makeTuple(3, fr, seed + 3), - privateFunctionTreeLeafIndex: seed + 3, - artifactMetadataHash: fr(seed + 4), - functionMetadataHash: fr(seed + 5), - utilityFunctionsTreeRoot: fr(seed + 6), - vkHash: fr(seed + 7), - }; -} - -export function makeUtilityFunctionWithMembershipProof(seed = 0): UtilityFunctionWithMembershipProof { - return { - selector: makeSelector(seed), - bytecode: makeBytes(100, seed + 1), - artifactTreeSiblingPath: makeTuple(3, fr, seed + 2), - artifactTreeLeafIndex: seed + 2, - artifactMetadataHash: fr(seed + 4), - functionMetadataHash: fr(seed + 5), - privateFunctionsArtifactTreeRoot: fr(seed + 6), - }; -} - export async function makeContractClassPublic(seed = 0, publicBytecode?: Buffer): Promise { const artifactHash = fr(seed + 1); const privateFunctionsRoot = fr(seed + 3); @@ -1223,8 +1192,6 @@ export async function makeContractClassPublic(seed = 0, publicBytecode?: Buffer) artifactHash, packedBytecode, privateFunctionsRoot, - privateFunctions: [], - utilityFunctions: [], version: 1, }; } diff --git a/yarn-project/txe/src/util/txe_public_contract_data_source.ts b/yarn-project/txe/src/util/txe_public_contract_data_source.ts index 8573d54ee6c8..1a4c5b36ca88 100644 --- a/yarn-project/txe/src/util/txe_public_contract_data_source.ts +++ b/yarn-project/txe/src/util/txe_public_contract_data_source.ts @@ -26,8 +26,6 @@ export class TXEPublicContractDataSource implements ContractDataSource { packedBytecode: contractClass.packedBytecode, privateFunctionsRoot: contractClass.privateFunctionsRoot, version: contractClass.version, - privateFunctions: [], - utilityFunctions: [], }; } From 72fb9ea0b131962eccdad2f1590d9ea2edcf1bf0 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 16:57:40 -0300 Subject: [PATCH 25/43] fix(archiver): bump db schema version for contract class format change Co-Authored-By: Claude Opus 4.6 (1M context) --- yarn-project/archiver/src/store/kv_archiver_store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/archiver/src/store/kv_archiver_store.ts b/yarn-project/archiver/src/store/kv_archiver_store.ts index 6ee954212e5d..d8aa479ad77c 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.ts @@ -35,7 +35,7 @@ import { ContractInstanceStore } from './contract_instance_store.js'; import { LogStore } from './log_store.js'; import { MessageStore } from './message_store.js'; -export const ARCHIVER_DB_VERSION = 5; +export const ARCHIVER_DB_VERSION = 6; export const MAX_FUNCTION_SIGNATURES = 1000; export const MAX_FUNCTION_NAME_LEN = 256; From fd36d10566458c228c9690a7095bc6f2df22584e Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 18:57:48 -0300 Subject: [PATCH 26/43] fix(stdlib): zero-pad bufferFromFields when declared length exceeds payload (#21802) Ensures that `bufferFromFields` always returns a buffer with the length requested in the first field of the array. This protects against this method being called with a truncated array, which could cause a wrong public bytecode commitment to be computed. Note that this is currently not the case, since this function always gets called with an array that's exactly `CONTRACT_CLASS_LOG_SIZE_IN_FIELDS` long, which is greater than the `MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS`. Co-authored-by: Claude Opus 4.6 (1M context) --- yarn-project/stdlib/src/abi/buffer.test.ts | 42 ++++++++++++++++++++++ yarn-project/stdlib/src/abi/buffer.ts | 29 ++++++++++++--- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/yarn-project/stdlib/src/abi/buffer.test.ts b/yarn-project/stdlib/src/abi/buffer.test.ts index 9c3329666c30..d4ca635d5b92 100644 --- a/yarn-project/stdlib/src/abi/buffer.test.ts +++ b/yarn-project/stdlib/src/abi/buffer.test.ts @@ -1,3 +1,5 @@ +import { Fr } from '@aztec/foundation/curves/bn254'; + import { bufferAsFields, bufferFromFields } from './buffer.js'; describe('buffer', () => { @@ -11,4 +13,44 @@ describe('buffer', () => { const buffer = Buffer.from('1234567890abcdef'.repeat(10), 'hex'); expect(() => bufferAsFields(buffer, 3)).toThrow(/exceeds maximum size/); }); + + it('pads with zeros when declared length exceeds payload', () => { + // Create a small buffer, encode it, then truncate the field array before decoding. + const buffer = Buffer.from('aabbccdd', 'hex'); // 4 bytes + const fields = bufferAsFields(buffer, 10); + // Declared length is 4 bytes, stored in fields[0]. Payload fields follow. + // Artificially inflate the declared length to 62 bytes (2 full fields). + + const inflatedFields = [new Fr(62), ...fields.slice(1)]; + const result = bufferFromFields(inflatedFields); + // Result should be exactly 62 bytes: original 4 bytes followed by 58 zero bytes. + expect(result.length).toBe(62); + expect(result.subarray(0, 4).toString('hex')).toEqual('aabbccdd'); + expect(result.subarray(4).every(b => b === 0)).toBe(true); + }); + + it('pads with zeros when payload fields are truncated', () => { + // Simulate the blob reconstruction scenario: declared length says 93 bytes (3 fields), + // but only 1 payload field is present. + + const payloadField = Fr.fromBuffer( + Buffer.from('00' + 'ab'.repeat(31), 'hex'), // 31 bytes of 0xab + ); + // Declared length = 93 bytes (would need 3 fields), but only 1 field in payload. + const fields = [new Fr(93), payloadField]; + const result = bufferFromFields(fields); + expect(result.length).toBe(93); + // First 31 bytes come from the single payload field. + expect(result.subarray(0, 31).every(b => b === 0xab)).toBe(true); + // Remaining 62 bytes are zero-padded. + expect(result.subarray(31).every(b => b === 0)).toBe(true); + }); + + it('returns exact buffer when payload matches declared length', () => { + const buffer = Buffer.from('ff'.repeat(31), 'hex'); // exactly 1 field of payload + const fields = bufferAsFields(buffer, 5); + const result = bufferFromFields(fields); + expect(result.length).toBe(31); + expect(result.toString('hex')).toEqual(buffer.toString('hex')); + }); }); diff --git a/yarn-project/stdlib/src/abi/buffer.ts b/yarn-project/stdlib/src/abi/buffer.ts index 075143244254..42d4ea8eba8a 100644 --- a/yarn-project/stdlib/src/abi/buffer.ts +++ b/yarn-project/stdlib/src/abi/buffer.ts @@ -26,11 +26,32 @@ export function bufferAsFields(input: Buffer, targetLength: number): Fr[] { } /** - * Recovers a buffer from an array of fields. - * @param fields - An output from bufferAsFields. - * @returns The recovered buffer. + * Recovers a buffer from an array of fields previously encoded with bufferAsFields. + * + * The first field encodes the byte length of the original buffer. The remaining fields + * each carry 31 bytes of payload (the leading byte of each 32-byte field element is skipped). + * + * If the declared byte length exceeds the bytes available from the payload fields, the result + * is zero-padded to the full declared length. This is important for correctness when the field + * array has been truncated (e.g. contract class logs reconstructed from blobs using a short + * emittedLength): without padding, the resulting buffer would be shorter than declared, causing + * bytecode commitment computations to diverge from what the circuit produced. + * + * @param fields - An output from bufferAsFields: [byteLength, ...payloadFields]. + * @returns A buffer of exactly `byteLength` bytes. */ export function bufferFromFields(fields: Fr[]): Buffer { const [length, ...payload] = fields; - return Buffer.concat(payload.map(f => f.toBuffer().subarray(1))).subarray(0, length.toNumber()); + const byteLength = length.toNumber(); + const raw = Buffer.concat(payload.map(f => f.toBuffer().subarray(1))); + if (raw.length >= byteLength) { + return raw.subarray(0, byteLength); + } + // Pad with zeros if the declared length exceeds the available payload bytes. + // This ensures the returned buffer always matches the declared length, so that + // downstream bytecode commitment computations are consistent even when the + // source field array was truncated (e.g. reconstructed from blob with a short emittedLength). + const result = Buffer.alloc(byteLength); + raw.copy(result); + return result; } From 5b28171df38ecb2d812aeb1fd31eacacb48c240c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 19:01:22 -0300 Subject: [PATCH 27/43] fix: update schema tests to omit privateFunctions from ContractClassPublic assertions Co-Authored-By: Claude Opus 4.6 (1M context) --- yarn-project/stdlib/src/interfaces/archiver.test.ts | 8 ++------ yarn-project/stdlib/src/interfaces/aztec-node.test.ts | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/yarn-project/stdlib/src/interfaces/archiver.test.ts b/yarn-project/stdlib/src/interfaces/archiver.test.ts index c996c11d7178..9d1eb6cad1aa 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.test.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.test.ts @@ -289,11 +289,7 @@ describe('ArchiverApiSchema', () => { it('getContractClass', async () => { const contractClass = await getContractClassFromArtifact(artifact); const result = await context.client.getContractClass(Fr.random()); - expect(result).toEqual({ - ...omit(contractClass, 'publicBytecodeCommitment'), - utilityFunctions: [], - privateFunctions: [], - }); + expect(result).toEqual(omit(contractClass, 'publicBytecodeCommitment', 'privateFunctions')); }); it('getDebugFunctionName', async () => { @@ -601,7 +597,7 @@ class MockArchiver implements ArchiverApi { async getContractClass(id: Fr): Promise { expect(id).toBeInstanceOf(Fr); const contractClass = await getContractClassFromArtifact(this.artifact); - return Promise.resolve({ ...contractClass, utilityFunctions: [], privateFunctions: [] }); + return Promise.resolve(contractClass); } async getBytecodeCommitment(id: Fr): Promise { expect(id).toBeInstanceOf(Fr); diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index fa5c109da9a6..e0137bd8c6c6 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -472,7 +472,7 @@ describe('AztecNodeApiSchema', () => { it('getContractClass', async () => { const contractClass = await getContractClassFromArtifact(artifact); const response = await context.client.getContractClass(Fr.random()); - expect(response).toEqual(omit(contractClass, 'publicBytecodeCommitment')); + expect(response).toEqual(omit(contractClass, 'publicBytecodeCommitment', 'privateFunctions')); }); it('getContract', async () => { From 4bbbabb94be2765c70ee34098019c0133d870563 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 18 Mar 2026 18:08:43 -0300 Subject: [PATCH 28/43] fix: do not use time from L1 Fixes A-707 --- .../.claude/rules/typescript-style.md | 18 ++- yarn-project/archiver/src/archiver.ts | 34 ++-- .../aztec-node/src/aztec-node/server.ts | 16 +- .../e2e_epochs/epochs_missed_l1_slot.test.ts | 152 ++++++++++++++++++ yarn-project/epoch-cache/src/epoch_cache.ts | 18 +-- yarn-project/ethereum/src/contracts/rollup.ts | 11 +- .../foundation/src/branded-types/slot.ts | 5 + .../src/client/sequencer-client.ts | 14 +- .../global_variable_builder/global_builder.ts | 46 +++--- .../src/global_variable_builder/index.ts | 2 +- .../src/publisher/sequencer-publisher.test.ts | 10 +- .../src/publisher/sequencer-publisher.ts | 9 +- .../src/sequencer/sequencer.ts | 4 +- .../stdlib/src/block/l2_block_source.ts | 6 +- .../stdlib/src/epoch-helpers/index.ts | 11 ++ 15 files changed, 274 insertions(+), 82 deletions(-) create mode 100644 yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts diff --git a/yarn-project/.claude/rules/typescript-style.md b/yarn-project/.claude/rules/typescript-style.md index 5ea500e2e736..0990cc1f0c00 100644 --- a/yarn-project/.claude/rules/typescript-style.md +++ b/yarn-project/.claude/rules/typescript-style.md @@ -330,4 +330,20 @@ mock.getData.mockImplementation((id: string) => { } return Promise.resolve(undefined); }); -``` \ No newline at end of file +``` + +## Arrow Function Bodies + +Use expression bodies instead of block bodies when the block only contains a `return`: + +```typescript +// Good: Expression body +items.map(item => item.value * 2) +fn(arg => expression(arg, foo)) + +// Bad: Block body with just a return +items.map(item => { return item.value * 2; }) +fn(arg => { return expression(arg, foo); }) +``` + +Block bodies are appropriate when the callback has multiple statements or side effects beyond the return. \ No newline at end of file diff --git a/yarn-project/archiver/src/archiver.ts b/yarn-project/archiver/src/archiver.ts index b34c42661457..f9e7efa31461 100644 --- a/yarn-project/archiver/src/archiver.ts +++ b/yarn-project/archiver/src/archiver.ts @@ -341,19 +341,33 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra return Promise.resolve(this.synchronizer.getL1Timestamp()); } - public getSyncedL2SlotNumber(): Promise { + public async getSyncedL2SlotNumber(): Promise { + // The synced L2 slot is the latest slot for which we have all L1 data, + // either because we have seen all L1 blocks for that slot, or because + // we have seen the corresponding checkpoint. + + let slotFromL1Sync: SlotNumber | undefined; const l1Timestamp = this.synchronizer.getL1Timestamp(); - if (l1Timestamp === undefined) { - return Promise.resolve(undefined); + if (l1Timestamp !== undefined) { + const nextL1BlockSlot = getSlotAtNextL1Block(l1Timestamp, this.l1Constants); + if (Number(nextL1BlockSlot) > 0) { + slotFromL1Sync = SlotNumber.add(nextL1BlockSlot, -1); + } + } + + let slotFromCheckpoint: SlotNumber | undefined; + const latestCheckpointNumber = await this.store.getSynchedCheckpointNumber(); + if (latestCheckpointNumber > 0) { + const checkpointData = await this.store.getCheckpointData(latestCheckpointNumber); + if (checkpointData) { + slotFromCheckpoint = checkpointData.header.slotNumber; + } } - // The synced slot is the last L2 slot whose all L1 blocks have been processed. - // If the next L1 block (at l1Timestamp + ethereumSlotDuration) falls in slot N, - // then we've fully synced slot N-1. - const nextL1BlockSlot = getSlotAtNextL1Block(l1Timestamp, this.l1Constants); - if (Number(nextL1BlockSlot) === 0) { - return Promise.resolve(undefined); + + if (slotFromL1Sync === undefined && slotFromCheckpoint === undefined) { + return undefined; } - return Promise.resolve(SlotNumber(nextL1BlockSlot - 1)); + return SlotNumber(Math.max(slotFromL1Sync ?? 0, slotFromCheckpoint ?? 0)); } public async getSyncedL2EpochNumber(): Promise { diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index be709fdc6557..434a5f7f44be 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -458,6 +458,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { }) .catch(err => log.error('Failed to start p2p services after archiver sync', err)); + const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, { + l1Contracts: config.l1Contracts, + ethereumSlotDuration: config.ethereumSlotDuration, + rollupVersion: BigInt(config.rollupVersion), + l1GenesisTime, + slotDuration: Number(slotDuration), + }); + // Validator enabled, create/start relevant service let sequencer: SequencerClient | undefined; let slasherClient: SlasherClientInterface | undefined; @@ -520,6 +528,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { dateProvider, blobClient, nodeKeyStore: keyStoreManager!, + globalVariableBuilder, }); } @@ -553,13 +562,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { } } - const globalVariableBuilder = new GlobalVariableBuilder({ - ...config, - rollupVersion: BigInt(config.rollupVersion), - l1GenesisTime, - slotDuration: Number(slotDuration), - }); - const node = new AztecNodeService( config, p2pClient, diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts new file mode 100644 index 000000000000..1fe3b1a9a47f --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts @@ -0,0 +1,152 @@ +import type { ChainMonitorEventMap } from '@aztec/ethereum/test'; +import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { AbortError } from '@aztec/foundation/error'; +import { sleep } from '@aztec/foundation/sleep'; +import { executeTimeout } from '@aztec/foundation/timer'; +import { SequencerState } from '@aztec/sequencer-client'; +import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; + +import { jest } from '@jest/globals'; + +import { EpochsTestContext } from './epochs_test.js'; + +jest.setTimeout(1000 * 60 * 10); + +// Validates that the sequencer can build a block in an L2 slot even when the archiver hasn't synced +// all L1 blocks of the previous slot. This happens when an L1 slot is missed (no block produced). +// The fix relies on getSyncedL2SlotNumber using the latest synced checkpoint slot as a signal, +// bypassing the stale L1 timestamp when L1 blocks are missing. +// Regression test for https://github.com/AztecProtocol/aztec-packages/issues/14766 +describe('e2e_epochs/epochs_missed_l1_slot', () => { + let test: EpochsTestContext; + + // Use enough L1 slots per L2 slot to have room for pausing mining mid-slot. + // With 6 L1 slots per L2 slot (L1=8s, L2=48s), we have plenty of time to + // publish a checkpoint and pause mining without accidentally skipping a slot. + const L1_SLOTS_PER_L2_SLOT = 6; + + beforeEach(async () => { + test = await EpochsTestContext.setup({ + numberOfAccounts: 0, + minTxsPerBlock: 0, + aztecSlotDurationInL1Slots: L1_SLOTS_PER_L2_SLOT, + startProverNode: false, + aztecProofSubmissionEpochs: 1024, + disableAnvilTestWatcher: true, + enforceTimeTable: true, + }); + }); + + afterEach(async () => { + jest.restoreAllMocks(); + await test.teardown(); + }); + + it('builds a block after missed L1 slots when previous checkpoint is synced', async () => { + const { logger, constants, monitor, context } = test; + const eth = context.cheatCodes.eth; + const L1_BLOCK_TIME = test.L1_BLOCK_TIME_IN_S; + const L2_SLOT_DURATION = test.L2_SLOT_DURATION_IN_S; + + // Step 1: Wait for a checkpoint that's published NOT in the last L1 slot of its L2 slot. + // We need the checkpoint to land early enough that when we pause mining, the archiver's + // L1 timestamp is still in the middle of the slot (not at the end). + logger.info('Waiting for a checkpoint published early in its L2 slot...'); + const checkpointEvent = await executeTimeout( + signal => + new Promise((res, rej) => { + const handleCheckpoint = (...[ev]: ChainMonitorEventMap['checkpoint']) => { + // Skip the initial checkpoint (genesis state). + if (ev.checkpointNumber === 0) { + return; + } + const slotStart = getTimestampForSlot(ev.l2SlotNumber, constants); + const lastL1SlotStart = slotStart + BigInt(L2_SLOT_DURATION - L1_BLOCK_TIME); + if (ev.timestamp < lastL1SlotStart) { + logger.info( + `Checkpoint ${ev.checkpointNumber} in slot ${ev.l2SlotNumber} at L1 timestamp ${ev.timestamp}`, + { slotStart, lastL1SlotStart }, + ); + res(ev); + monitor.off('checkpoint', handleCheckpoint); + } else { + logger.info( + `Skipping checkpoint ${ev.checkpointNumber}: published at ${ev.timestamp} (last L1 slot starts at ${lastL1SlotStart})`, + ); + } + }; + signal.onabort = () => { + monitor.off('checkpoint', handleCheckpoint); + rej(new AbortError()); + }; + monitor.on('checkpoint', handleCheckpoint); + }), + 60_000, + 'Wait for early checkpoint', + ); + + const checkpointSlotNumber = checkpointEvent.l2SlotNumber; + const nextSlotNumber = SlotNumber(checkpointSlotNumber + 1); + const nextSlotTimestamp = Number(getTimestampForSlot(nextSlotNumber, constants)); + + logger.info(`Using checkpoint ${checkpointEvent.checkpointNumber} in L2 slot ${checkpointSlotNumber}`, { + nextSlotNumber, + nextSlotTimestamp, + }); + + // Step 2: Wait briefly for the sequencer to finish its current work cycle, then pause mining. + await sleep(1500); + + logger.info('Pausing L1 block production (simulating missed L1 slots)...'); + await eth.setAutomine(false); + await eth.setIntervalMining(0, { silent: true }); + + const frozenL1Timestamp = await eth.timestamp(); + logger.info(`L1 mining paused at L1 timestamp ${frozenL1Timestamp}`); + + // Step 3: Wait until the sequencer reaches PUBLISHING_CHECKPOINT during the mining pause. + // With the fix: the sequencer sees the checkpoint for slot N, so getSyncedL2SlotNumber + // returns N, checkSync passes for slot N+1, and it advances all the way to publishing. + // Without the fix: getSyncedL2SlotNumber is stuck at N-1, checkSync fails, sequencer + // stays in IDLE/SYNCHRONIZING and never reaches PUBLISHING_CHECKPOINT. + const sequencer = context.sequencer!.getSequencer(); + + logger.info('Waiting for sequencer to reach PUBLISHING_CHECKPOINT during mining pause...'); + await executeTimeout( + signal => + new Promise((res, rej) => { + const stateListener = ({ newState }: { newState: SequencerState }) => { + if (newState === SequencerState.PUBLISHING_CHECKPOINT) { + sequencer.off('state-changed', stateListener); + res(); + } + }; + signal.onabort = () => { + sequencer.off('state-changed', stateListener); + rej(new AbortError()); + }; + sequencer.on('state-changed', stateListener); + }), + L2_SLOT_DURATION * 2 * 1000, + 'Wait for sequencer to reach PUBLISHING_CHECKPOINT', + ); + + logger.info('Sequencer reached PUBLISHING_CHECKPOINT during mining pause'); + + // Step 4: Resume mining so the pending L1 tx lands and the test can clean up. + logger.info('Resuming L1 block production...'); + const resumeTimestamp = Math.floor(context.dateProvider.now() / 1000); + await eth.setNextBlockTimestamp(resumeTimestamp); + await eth.mine(); + await eth.setIntervalMining(L1_BLOCK_TIME); + + // Step 5: Wait for the next checkpoint to confirm the block was actually published. + const finalCheckpoint = CheckpointNumber(checkpointEvent.checkpointNumber + 1); + logger.info(`Waiting for checkpoint ${finalCheckpoint}...`); + await test.waitUntilCheckpointNumber(finalCheckpoint, 60); + await monitor.run(); + logger.info(`Checkpoint ${finalCheckpoint} published in slot ${monitor.l2SlotNumber}`); + + expect(monitor.checkpointNumber).toBeGreaterThanOrEqual(finalCheckpoint); + }); +}); diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 3ecb033c1f02..e91985647b14 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -9,6 +9,7 @@ import { type L1RollupConstants, getEpochAtSlot, getEpochNumberAtTimestamp, + getNextL1SlotTimestamp, getSlotAtTimestamp, getSlotRangeForEpoch, getTimestampForSlot, @@ -191,18 +192,14 @@ export class EpochCache implements EpochCacheInterface { return { ...this.getEpochAndSlotAtTimestamp(nowSeconds), nowMs }; } - public nowInSeconds(): bigint { - return BigInt(Math.floor(this.dateProvider.now() / 1000)); - } - private getEpochAndSlotAtSlot(slot: SlotNumber): EpochAndSlot { return this.getEpochAndSlotAtTimestamp(getTimestampForSlot(slot, this.l1constants)); } public getEpochAndSlotInNextL1Slot(): EpochAndSlot & { nowSeconds: bigint } { - const nowSeconds = this.nowInSeconds(); - const nextSlotTs = nowSeconds + BigInt(this.l1constants.ethereumSlotDuration); - return { ...this.getEpochAndSlotAtTimestamp(nextSlotTs), nowSeconds }; + const nowSeconds = this.dateProvider.nowInSeconds(); + const nextSlotTs = getNextL1SlotTimestamp(nowSeconds, this.l1constants); + return { ...this.getEpochAndSlotAtTimestamp(nextSlotTs), nowSeconds: BigInt(nowSeconds) }; } public getTargetEpochAndSlotInNextL1Slot(): EpochAndSlot & { nowSeconds: bigint } { @@ -440,10 +437,11 @@ export class EpochCache implements EpochCacheInterface { async getRegisteredValidators(): Promise { const validatorRefreshIntervalMs = this.config.validatorRefreshIntervalSeconds * 1000; const validatorRefreshTime = this.lastValidatorRefresh + validatorRefreshIntervalMs; - if (validatorRefreshTime < this.dateProvider.now()) { - const currentSet = await this.rollup.getAttesters(); + const now = this.dateProvider.now(); + if (validatorRefreshTime < now) { + const currentSet = await this.rollup.getAttesters(BigInt(Math.floor(now / 1000))); this.allValidators = new Set(currentSet.map(v => v.toString())); - this.lastValidatorRefresh = this.dateProvider.now(); + this.lastValidatorRefresh = now; } return Array.from(this.allValidators.keys()).map(v => EthAddress.fromString(v)); } diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 96d1c553ee1b..4c92882f1f16 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -781,12 +781,10 @@ export class RollupContract { public async canProposeAt( archive: Buffer, account: `0x${string}` | Account, - slotDuration: bigint, - slotOffset: bigint, + timestamp: bigint, opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {}, ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> { - const latestBlock = await this.client.getBlock(); - const timeOfNextL1Slot = latestBlock.timestamp + slotDuration + slotOffset; + const timeOfNextL1Slot = timestamp; const who = typeof account === 'string' ? account : account.address; try { @@ -939,11 +937,10 @@ export class RollupContract { return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]); } - async getAttesters(): Promise { + async getAttesters(timestamp?: bigint): Promise { const attesterSize = await this.getActiveAttesterCount(); const gse = new GSEContract(this.client, await this.getGSE()); - const ts = (await this.client.getBlock()).timestamp; - + const ts = timestamp ?? (await this.client.getBlock()).timestamp; const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i)); const chunks = chunk(indices, 1000); diff --git a/yarn-project/foundation/src/branded-types/slot.ts b/yarn-project/foundation/src/branded-types/slot.ts index 069104a88fe0..2657cd622036 100644 --- a/yarn-project/foundation/src/branded-types/slot.ts +++ b/yarn-project/foundation/src/branded-types/slot.ts @@ -73,6 +73,11 @@ SlotNumber.isValid = function (value: unknown): value is SlotNumber { return typeof value === 'number' && Number.isInteger(value) && value >= 0; }; +/** Increments a SlotNumber by a given value. */ +SlotNumber.add = function (sn: SlotNumber, increment: number): SlotNumber { + return SlotNumber(sn + increment); +}; + /** * The zero slot value. */ diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index a2d322ff5838..f6e97d433f16 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -20,7 +20,7 @@ import { L1Metrics, type TelemetryClient } from '@aztec/telemetry-client'; import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client'; import { type SequencerClientConfig, getPublisherConfigFromSequencerConfig } from '../config.js'; -import { GlobalVariableBuilder } from '../global_variable_builder/index.js'; +import type { GlobalVariableBuilder } from '../global_variable_builder/index.js'; import { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js'; import { Sequencer, type SequencerConfig } from '../sequencer/index.js'; @@ -66,6 +66,7 @@ export class SequencerClient { epochCache?: EpochCache; l1TxUtils: L1TxUtils[]; nodeKeyStore: KeystoreManager; + globalVariableBuilder: GlobalVariableBuilder; }, ) { const { @@ -93,10 +94,9 @@ export class SequencerClient { log.getBindings(), ); const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString()); - const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([ + const [l1GenesisTime, slotDuration, rollupManaLimit] = await Promise.all([ rollupContract.getL1GenesisTime(), rollupContract.getSlotDuration(), - rollupContract.getVersion(), rollupContract.getManaLimit().then(Number), ] as const); @@ -140,13 +140,7 @@ export class SequencerClient { const ethereumSlotDuration = config.ethereumSlotDuration; - const globalsBuilder = new GlobalVariableBuilder({ - ...config, - l1GenesisTime, - slotDuration: Number(slotDuration), - ethereumSlotDuration, - rollupVersion, - }); + const globalsBuilder = deps.globalVariableBuilder; // When running in anvil, assume we can post a tx up until one second before the end of an L1 slot. // Otherwise, we need the full L1 slot duration for publishing to ensure inclusion. diff --git a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts index 20c0f236292d..3043f9a56eed 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts @@ -1,15 +1,13 @@ -import { createEthereumChain } from '@aztec/ethereum/chain'; -import { makeL1HttpTransport } from '@aztec/ethereum/client'; -import type { L1ContractsConfig } from '@aztec/ethereum/config'; import { RollupContract } from '@aztec/ethereum/contracts'; -import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader'; +import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; import type { ViemPublicClient } from '@aztec/ethereum/types'; import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; +import type { DateProvider } from '@aztec/foundation/timer'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; +import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; import { GasFees } from '@aztec/stdlib/gas'; import type { CheckpointGlobalVariables, @@ -17,7 +15,12 @@ import type { } from '@aztec/stdlib/tx'; import { GlobalVariables } from '@aztec/stdlib/tx'; -import { createPublicClient } from 'viem'; +/** Configuration for the GlobalVariableBuilder (excludes L1 client config). */ +export type GlobalVariableBuilderConfig = { + l1Contracts: Pick; + ethereumSlotDuration: number; + rollupVersion: bigint; +} & Pick; /** * Simple global variables builder. @@ -28,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { private currentL1BlockNumber: bigint | undefined = undefined; private readonly rollupContract: RollupContract; - private readonly publicClient: ViemPublicClient; private readonly ethereumSlotDuration: number; private readonly aztecSlotDuration: number; private readonly l1GenesisTime: bigint; @@ -37,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { private version: Fr; constructor( - config: L1ReaderConfig & - Pick & - Pick & { rollupVersion: bigint }, + private readonly dateProvider: DateProvider, + private readonly publicClient: ViemPublicClient, + config: GlobalVariableBuilderConfig, ) { - const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config; - - const chain = createEthereumChain(l1RpcUrls, chainId); - this.version = new Fr(config.rollupVersion); - this.chainId = new Fr(chainId); + this.chainId = new Fr(this.publicClient.chain!.id); this.ethereumSlotDuration = config.ethereumSlotDuration; this.aztecSlotDuration = config.slotDuration; this.l1GenesisTime = config.l1GenesisTime; - this.publicClient = createPublicClient({ - chain: chain.chainInfo, - transport: makeL1HttpTransport(chain.rpcUrls, { timeout: config.l1HttpTimeoutMS }), - pollingInterval: config.viemPollingIntervalMS, - }); - - this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress); + this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress); } /** @@ -74,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { const earliestTimestamp = await this.rollupContract.getTimestampForSlot( SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n), ); - const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)); + const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), { + l1GenesisTime: this.l1GenesisTime, + ethereumSlotDuration: this.ethereumSlotDuration, + }); const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp; return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true)); @@ -109,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { const slot: SlotNumber = maybeSlot ?? (await this.rollupContract.getSlotAt( - BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)), + getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), { + l1GenesisTime: this.l1GenesisTime, + ethereumSlotDuration: this.ethereumSlotDuration, + }), )); const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot); diff --git a/yarn-project/sequencer-client/src/global_variable_builder/index.ts b/yarn-project/sequencer-client/src/global_variable_builder/index.ts index 5669a0412ae4..a48ed6c244eb 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/index.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/index.ts @@ -1 +1 @@ -export { GlobalVariableBuilder } from './global_builder.js'; +export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js'; diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts index 75fdbb221715..6571b976bf19 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts @@ -23,6 +23,7 @@ import { TestDateProvider } from '@aztec/foundation/timer'; import { EmpireBaseAbi, RollupAbi } from '@aztec/l1-artifacts'; import { CommitteeAttestationsAndSigners, L2Block, Signature } from '@aztec/stdlib/block'; import { Checkpoint } from '@aztec/stdlib/checkpoint'; +import { EmptyL1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import type { SlashFactoryContract } from '@aztec/stdlib/l1-contracts'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; @@ -138,12 +139,8 @@ describe('SequencerPublisher', () => { slashFactoryContract = mock(); const epochCache = mock(); - epochCache.getEpochAndSlotNow.mockReturnValue({ - epoch: EpochNumber(1), - slot: SlotNumber(2), - ts: 3n, - nowMs: 3000n, - }); + epochCache.getEpochAndSlotNow.mockReturnValue({ epoch: EpochNumber(1), slot: SlotNumber(2), ts: 3n, nowMs: 3000n }); + epochCache.getL1Constants.mockReturnValue(EmptyL1RollupConstants); epochCache.getSlotNow.mockReturnValue(SlotNumber(2)); epochCache.getCommittee.mockResolvedValue({ committee: [], @@ -327,6 +324,7 @@ describe('SequencerPublisher', () => { nowMs: 3000n, }); epochCache.getSlotNow.mockReturnValue(SlotNumber(2)); + epochCache.getL1Constants.mockReturnValue(EmptyL1RollupConstants); epochCache.getCommittee.mockResolvedValue({ committee: [], seed: 1n, diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts index 971a338d8647..0d73ea840afc 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts @@ -42,6 +42,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts'; import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher'; import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block'; import type { Checkpoint } from '@aztec/stdlib/checkpoint'; +import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers'; import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts'; import type { CheckpointHeader } from '@aztec/stdlib/rollup'; import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats'; @@ -134,6 +135,7 @@ export class SequencerPublisher { protected log: Logger; protected ethereumSlotDuration: bigint; protected aztecSlotDuration: bigint; + private dateProvider: DateProvider; private blobClient: BlobClientInterface; @@ -187,6 +189,7 @@ export class SequencerPublisher { this.log = deps.log ?? createLogger('sequencer:publisher'); this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration); this.aztecSlotDuration = BigInt(config.aztecSlotDuration); + this.dateProvider = deps.dateProvider; this.epochCache = deps.epochCache; this.lastActions = deps.lastActions; @@ -612,9 +615,11 @@ export class SequencerPublisher { const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled(); const slotOffset = pipelined ? this.aztecSlotDuration : 0n; + const l1Constants = this.epochCache.getL1Constants(); + const nextL1SlotTs = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants) + slotOffset; return this.rollupContract - .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, slotOffset, { + .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, { forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber, }) .catch(err => { @@ -652,7 +657,7 @@ export class SequencerPublisher { flags, ] as const; - const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration); + const ts = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), this.epochCache.getL1Constants()); const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride( opts?.forcePendingCheckpointNumber, ); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 1392a1be2ec7..7f833ca3abd7 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -501,8 +501,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter { // Check that the archiver has fully synced the L2 slot before the one we want to propose in. - // TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will - // cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later. + // The archiver reports sync progress via L1 block timestamps and synced checkpoint slots. + // See getSyncedL2SlotNumber for how missed L1 blocks are handled. const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber(); const { slot } = args; if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) { diff --git a/yarn-project/stdlib/src/block/l2_block_source.ts b/yarn-project/stdlib/src/block/l2_block_source.ts index 8b81bdd340b2..77dfd2eb1d93 100644 --- a/yarn-project/stdlib/src/block/l2_block_source.ts +++ b/yarn-project/stdlib/src/block/l2_block_source.ts @@ -175,8 +175,10 @@ export interface L2BlockSource { getSettledTxReceipt(txHash: TxHash): Promise; /** - * Returns the last L2 slot number that has been fully synchronized from L1. - * An L2 slot is fully synced when all L1 blocks that fall within its time range have been processed. + * Returns the last L2 slot number for which we have all L1 data needed to build the next checkpoint. + * Determined by the max of two signals: L1 block sync progress and latest synced checkpoint slot. + * The checkpoint signal handles missed L1 blocks, since a published checkpoint seals the message tree + * for the next checkpoint via the inbox LAG mechanism. */ getSyncedL2SlotNumber(): Promise; diff --git a/yarn-project/stdlib/src/epoch-helpers/index.ts b/yarn-project/stdlib/src/epoch-helpers/index.ts index 637afa3caf09..0ae35f5461f4 100644 --- a/yarn-project/stdlib/src/epoch-helpers/index.ts +++ b/yarn-project/stdlib/src/epoch-helpers/index.ts @@ -57,6 +57,17 @@ export function getSlotAtTimestamp( : SlotNumber.fromBigInt((ts - constants.l1GenesisTime) / BigInt(constants.slotDuration)); } +/** Returns the timestamp of the next L1 slot boundary after the given wall-clock time. */ +export function getNextL1SlotTimestamp( + nowInSeconds: number, + constants: Pick, +): bigint { + const now = BigInt(nowInSeconds); + const elapsed = now - constants.l1GenesisTime; + const currentL1Slot = elapsed / BigInt(constants.ethereumSlotDuration); + return constants.l1GenesisTime + (currentL1Slot + 1n) * BigInt(constants.ethereumSlotDuration); +} + /** Returns the L2 slot number at the next L1 block based on the current timestamp. */ export function getSlotAtNextL1Block( currentL1Timestamp: bigint, From 19799a87e51c7fc3f943947d490a5db5b0c9cac9 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 19:26:57 -0300 Subject: [PATCH 29/43] test(protocol-contracts): verify max-size bytecode fits in contract class log (#21818) Verify that `CONTRACT_CLASS_LOG_SIZE_IN_FIELDS` is large enough to hold a max-size public bytecode alongside the `ContractClassPublishedEvent` header fields. If these constants drift, contract class registration could silently break. Co-authored-by: Claude Opus 4.6 (1M context) --- .../contract_class_published_event.test.ts | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.test.ts b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.test.ts index 860533d58fe0..8647fc333f26 100644 --- a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.test.ts +++ b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.test.ts @@ -1,11 +1,20 @@ +import { + CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, + CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE, + MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, +} from '@aztec/constants'; +import { Fr } from '@aztec/foundation/curves/bn254'; import { setupCustomSnapshotSerializers } from '@aztec/foundation/testing'; -import { ContractClassLog } from '@aztec/stdlib/logs'; +import { bufferAsFields } from '@aztec/stdlib/abi'; +import { ContractClassLog, ContractClassLogFields } from '@aztec/stdlib/logs'; +import { ProtocolContractAddress } from '../protocol_contract_data.js'; import { getSampleContractClassPublishedEventPayload } from '../tests/fixtures.js'; import { ContractClassPublishedEvent } from './contract_class_published_event.js'; describe('ContractClassPublishedEvent', () => { beforeAll(() => setupCustomSnapshotSerializers(expect)); + it('parses an event as emitted by the ContractClassRegistry', () => { const log = ContractClassLog.fromBuffer(getSampleContractClassPublishedEventPayload()); expect(ContractClassPublishedEvent.isContractClassPublishedEvent(log)).toBe(true); @@ -15,4 +24,42 @@ describe('ContractClassPublishedEvent', () => { // See ./__snapshots__/README.md for how to update the snapshot. expect(event).toMatchSnapshot(); }); + + it('fits a max-size public bytecode within CONTRACT_CLASS_LOG_SIZE_IN_FIELDS', () => { + // Create a bytecode that fills the maximum allowed size. + // bufferAsFields encodes as [length_in_bytes, ...31-byte-chunks, ...zero-padding] up to the target length. + // The max bytecode in bytes is (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * 31, + // since one field is used for the length prefix. + const maxBytecodeBytes = (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * 31; + const maxBytecode = Buffer.alloc(maxBytecodeBytes, 0xab); + + // Encode the bytecode as fields (same encoding used in the Noir contract). + const bytecodeFields = bufferAsFields(maxBytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS); + expect(bytecodeFields).toHaveLength(MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS); + + // The event header: [magic, contractClassId, version, artifactHash, privateFunctionsRoot] + const headerFields = [ + new Fr(CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE), + Fr.random(), // contractClassId + new Fr(1), // version + Fr.random(), // artifactHash + Fr.random(), // privateFunctionsRoot + ]; + + // This is the main assertion: the CONTRACT_CLASS_LOG_SIZE_IN_FIELDS is enough such that + // a max-size bytecode can be included in the event log together with the header fields + const totalFields = headerFields.length + bytecodeFields.length; + expect(totalFields).toBeLessThanOrEqual(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS); + + // Verify it round-trips through ContractClassLog and ContractClassPublishedEvent. + const allFields = [...headerFields, ...bytecodeFields]; + const padded = [...allFields, ...Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - allFields.length).fill(Fr.ZERO)]; + const logFields = new ContractClassLogFields(padded); + const log = new ContractClassLog(ProtocolContractAddress.ContractClassRegistry, logFields, allFields.length); + + expect(ContractClassPublishedEvent.isContractClassPublishedEvent(log)).toBe(true); + + const event = ContractClassPublishedEvent.fromLog(log); + expect(event.packedPublicBytecode).toEqual(maxBytecode); + }); }); From 1b1f6aca2d30044e2e4f156fea53125cbd27001f Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:29:11 -0400 Subject: [PATCH 30/43] fix: wire BOT_DA_GAS_LIMIT through helm/terraform for staging-public (#21809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Bot transactions on staging-public have been skipped for 4+ hours because the gas estimator returns a DA gas limit of 196,608, which exceeds the per-block DA gas limit of 117,965. This causes every tx to be rejected by the sequencer with "Skipping processing of tx due to block gas limit". This PR wires the existing `BOT_DA_GAS_LIMIT` and `BOT_L2_GAS_LIMIT` env vars (already supported by the bot code) through the full deployment pipeline: - Helm chart values + configmap template (conditionally rendered) - Terraform variables (shared across all bot types) - staging-public.env: sets `BOT_DA_GAS_LIMIT=100000` and `BOT_L2_GAS_LIMIT=6540000` The DA gas limit of 100,000 fits within the computed per-block limit of 117,965 (derived from `MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT=786432` / 8 blocks * 1.2 multiplier). The L2 gas limit of 6,540,000 matches the protocol constant `MAX_PROCESSABLE_L2_GAS`. Evidence from sequencer logs: ``` maxBlockGas: {'daGas': 117965, 'l2Gas': 30000000} txGasLimit: {'daGas': 196608, 'l2Gas': 6540000} ← exceeds block DA limit ``` ClaudeBox log: https://claudebox.work/s/8c485ac9c95c297f?run=5 --- spartan/aztec-bot/templates/env.configmap.yaml | 6 ++++++ spartan/aztec-bot/values.yaml | 2 ++ spartan/environments/staging-public.env | 3 +++ spartan/terraform/deploy-aztec-infra/main.tf | 6 ++++++ spartan/terraform/deploy-aztec-infra/variables.tf | 12 ++++++++++++ 5 files changed, 29 insertions(+) diff --git a/spartan/aztec-bot/templates/env.configmap.yaml b/spartan/aztec-bot/templates/env.configmap.yaml index a741eacd919c..92780250772a 100644 --- a/spartan/aztec-bot/templates/env.configmap.yaml +++ b/spartan/aztec-bot/templates/env.configmap.yaml @@ -17,3 +17,9 @@ data: BOT_STOP_WHEN_UNHEALTHY: {{ .Values.bot.stopIfUnhealthy | quote }} AZTEC_NODE_URL: {{ .Values.bot.nodeUrl | quote }} TEST_ACCOUNTS: {{ .Values.bot.testAccounts | quote }} + {{- if .Values.bot.daGasLimit }} + BOT_DA_GAS_LIMIT: {{ .Values.bot.daGasLimit | quote }} + {{- end }} + {{- if .Values.bot.l2GasLimit }} + BOT_L2_GAS_LIMIT: {{ .Values.bot.l2GasLimit | quote }} + {{- end }} diff --git a/spartan/aztec-bot/values.yaml b/spartan/aztec-bot/values.yaml index 5c55acc21846..57e2f2d1ce9a 100644 --- a/spartan/aztec-bot/values.yaml +++ b/spartan/aztec-bot/values.yaml @@ -21,6 +21,8 @@ bot: nodeUrl: "" testAccounts: false botPrivateKey: "0xcafe" + daGasLimit: "" + l2GasLimit: "" persistence: enabled: false diff --git a/spartan/environments/staging-public.env b/spartan/environments/staging-public.env index 73886cbc574a..fa67490c7688 100644 --- a/spartan/environments/staging-public.env +++ b/spartan/environments/staging-public.env @@ -49,6 +49,9 @@ PROVER_REPLICAS=4 PUBLISHERS_PER_PROVER=2 PROVER_PUBLISHER_MNEMONIC_START_INDEX=8000 +BOT_DA_GAS_LIMIT=100000 +BOT_L2_GAS_LIMIT=6540000 + BOT_TRANSFERS_REPLICAS=1 BOT_TRANSFERS_TX_INTERVAL_SECONDS=250 BOT_TRANSFERS_FOLLOW_CHAIN=PROPOSED diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index d1fc361a0ea3..ac1981ed6c7f 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -621,6 +621,8 @@ locals { "bot.nodeUrl" = local.internal_rpc_url "bot.mnemonic" = var.BOT_MNEMONIC "bot.mnemonicStartIndex" = var.BOT_TRANSFERS_MNEMONIC_START_INDEX + "bot.daGasLimit" = var.BOT_DA_GAS_LIMIT + "bot.l2GasLimit" = var.BOT_L2_GAS_LIMIT } boot_node_host_path = "" bootstrap_nodes_path = "" @@ -645,6 +647,8 @@ locals { "bot.nodeUrl" = local.internal_rpc_url "bot.mnemonic" = var.BOT_MNEMONIC "bot.mnemonicStartIndex" = var.BOT_SWAPS_MNEMONIC_START_INDEX + "bot.daGasLimit" = var.BOT_DA_GAS_LIMIT + "bot.l2GasLimit" = var.BOT_L2_GAS_LIMIT } boot_node_host_path = "" bootstrap_nodes_path = "" @@ -669,6 +673,8 @@ locals { "bot.nodeUrl" = local.internal_rpc_url "bot.mnemonic" = var.BOT_MNEMONIC "bot.mnemonicStartIndex" = var.BOT_CROSS_CHAIN_MNEMONIC_START_INDEX + "bot.daGasLimit" = var.BOT_DA_GAS_LIMIT + "bot.l2GasLimit" = var.BOT_L2_GAS_LIMIT } boot_node_host_path = "" bootstrap_nodes_path = "" diff --git a/spartan/terraform/deploy-aztec-infra/variables.tf b/spartan/terraform/deploy-aztec-infra/variables.tf index 67165fde6035..744ce30b9365 100644 --- a/spartan/terraform/deploy-aztec-infra/variables.tf +++ b/spartan/terraform/deploy-aztec-infra/variables.tf @@ -649,6 +649,18 @@ variable "BOT_CROSS_CHAIN_PXE_SYNC_CHAIN_TIP" { default = "checkpointed" } +variable "BOT_DA_GAS_LIMIT" { + description = "DA gas limit for bot transactions (empty to use gas estimation)" + type = string + default = "" +} + +variable "BOT_L2_GAS_LIMIT" { + description = "L2 gas limit for bot transactions (empty to use gas estimation)" + type = string + default = "" +} + # RPC ingress configuration (GKE-specific) variable "RPC_INGRESS_ENABLED" { description = "Enable GKE ingress for RPC nodes" From 939571bd2517711acd8219215c11c9aaa03849ea Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:41:46 -0400 Subject: [PATCH 31/43] fix: remove jest-mock-extended from worker processes + fix parallelize_strict silent failures (#21821) ## Summary Fixes two bugs: 1. **Bench test workers crash on startup**: `p2p_client_testbench_worker.ts` and `proposal_tx_collector_worker.ts` import `jest-mock-extended`, which depends on `@jest/globals`. Since workers run as plain Node processes (forked via `child_process.fork()`), not inside Jest, `@jest/globals` throws immediately and all workers SIGSEGV. Fix: replace `mock()` with the existing `createMockEpochCache()` from test-helpers. 2. **`parallelize_strict` silently swallows failures**: `run_tests | tee $output` runs `run_tests` in a subshell due to the pipe. Background jobs started inside that subshell are invisible to the parent shell's `wait -n`. When a job fails, `exit 1` kills only the subshell; the parent sees no jobs and exits 0. Fix: use process substitution (`> >(tee)`) so `run_tests` runs in the current shell and failures propagate. ## Test plan - Bench test `p2p_client.proposal_tx_collector.bench.test.ts` should no longer crash on worker startup - `parallelize_strict` should now properly fail when a benchmark fails ClaudeBox log: https://claudebox.work/s/6ba298b4e9b52aa5?run=3 --- ci3/parallelize_strict | 7 ++++++- .../tx_proposal_collector/proposal_tx_collector_worker.ts | 5 ++--- .../p2p/src/testbench/p2p_client_testbench_worker.ts | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ci3/parallelize_strict b/ci3/parallelize_strict index 95e244490fa4..7683638abd8f 100755 --- a/ci3/parallelize_strict +++ b/ci3/parallelize_strict @@ -75,13 +75,18 @@ num_jobs=0 # We'll handle errors explicitly. set +e -run_tests | tee $output +set -o pipefail +run_tests | tee "$output" +run_exit=$? # Loop to monitor jobs until one fails or all finish. while (( $(jobs -p | wc -l) > 0 )); do wait_for_job done +# Propagate failure from run_tests (which runs in a subshell due to the pipe). +[ $run_exit -ne 0 ] && exit $run_exit + function filter_long_times { grep -E '\([0-9]+s\)$' | # Match lines ending with (number)s sed 's/.*(\([0-9]\+\)s)$/\1 &/' | # Extract number and keep original line diff --git a/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts b/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts index 44ad0f6eccc1..c756de610980 100644 --- a/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +++ b/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts @@ -1,5 +1,4 @@ import { MockL2BlockSource } from '@aztec/archiver/test'; -import type { EpochCache } from '@aztec/epoch-cache'; import { SecretValue } from '@aztec/foundation/config'; import { createLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; @@ -15,7 +14,6 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien import type { PeerId } from '@libp2p/interface'; import { peerIdFromString } from '@libp2p/peer-id'; -import { mock } from 'jest-mock-extended'; import type { P2PConfig } from '../../../config.js'; import { BatchTxRequesterCollector, SendBatchRequestCollector } from '../../../services/index.js'; @@ -29,6 +27,7 @@ import { InMemoryTxPool, UNLIMITED_RATE_LIMIT_QUOTA, calculateInternalTimeout, + createMockEpochCache, createMockWorldStateSynchronizer, } from '../../../test-helpers/index.js'; import { createP2PClient } from '../../index.js'; @@ -99,7 +98,7 @@ function sendMessage(message: WorkerResponse): Promise { async function startClient(config: P2PConfig, clientIndex: number) { txPool = new InMemoryTxPool(); attestationPool = new InMemoryAttestationPool(); - const epochCache = mock(); + const epochCache = createMockEpochCache(); const worldState = createMockWorldStateSynchronizer(); const l2BlockSource = new MockL2BlockSource(); const proofVerifier = new AlwaysTrueCircuitVerifier(); diff --git a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts index 14d733793413..1d3224d94a33 100644 --- a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts +++ b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts @@ -4,7 +4,7 @@ * Used when running testbench commands. */ import { MockL2BlockSource } from '@aztec/archiver/test'; -import type { EpochCache, EpochCacheInterface } from '@aztec/epoch-cache'; +import type { EpochCacheInterface } from '@aztec/epoch-cache'; import { BlockNumber } from '@aztec/foundation/branded-types'; import { SecretValue } from '@aztec/foundation/config'; import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer'; @@ -28,7 +28,6 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien import type { Message, PeerId } from '@libp2p/interface'; import { TopicValidatorResult } from '@libp2p/interface'; import { peerIdFromString } from '@libp2p/peer-id'; -import { mock } from 'jest-mock-extended'; import type { P2PClient } from '../client/index.js'; import type { P2PConfig } from '../config.js'; @@ -50,6 +49,7 @@ import { InMemoryAttestationPool, InMemoryTxPool, UNLIMITED_RATE_LIMIT_QUOTA, + createMockEpochCache, createMockWorldStateSynchronizer, filterTxsByDistribution, } from '../test-helpers/index.js'; @@ -344,7 +344,7 @@ process.on('message', async msg => { workerConfig = config; workerTxPool = new InMemoryTxPool(); workerAttestationPool = new InMemoryAttestationPool(); - const epochCache = mock(); + const epochCache = createMockEpochCache(); const worldState = createMockWorldStateSynchronizer(); const l2BlockSource = new MockL2BlockSource(); From 7ab01219b1f5585493157553ba306a19ebe70367 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Thu, 19 Mar 2026 03:26:22 +0000 Subject: [PATCH 32/43] feat: batch chonk verifier TS integration Adds TypeScript integration for the batch chonk verifier C++ service: - BatchChonkVerifier: TS orchestrator managing bb subprocess, FIFO pipe, and proof lifecycle (peer path with batching) - FifoFrameReader: length-delimited frame reader for named pipes - TestCircuitVerifier used for both peer and RPC in fake-proof mode - BBCircuitVerifier + QueuedIVCVerifier for RPC path (real proofs) - Server splits proofVerifier into peerProofVerifier + rpcProofVerifier Config changes: - numConcurrentIVCVerifiers -> bbRpcVerifyConcurrency + bbPeerVerifyBatchSize - bbIVCConcurrency -> bbBatchVerifyThreads - QueuedIVCVerifier takes (verifier, concurrency) instead of (config, verifier) Integration tests and queue robustness tests via bb.js bindings. --- .../aztec-node/src/aztec-node/server.test.ts | 3 + .../aztec-node/src/aztec-node/server.ts | 47 ++- yarn-project/bb-prover/package.json | 1 + yarn-project/bb-prover/src/config.ts | 11 + .../src/verifier/batch_chonk_verifier.ts | 276 ++++++++++++++++++ yarn-project/bb-prover/src/verifier/index.ts | 1 + .../src/verifier/queued_chonk_verifier.ts | 13 +- yarn-project/bootstrap.sh | 1 + .../src/fixtures/e2e_prover_test.ts | 16 +- .../end-to-end/src/fixtures/get_bb_config.ts | 13 +- yarn-project/foundation/package.json | 1 + yarn-project/foundation/src/config/env_var.ts | 2 + .../src/fifo/fifo_frame_reader.test.ts | 209 +++++++++++++ .../foundation/src/fifo/fifo_frame_reader.ts | 98 +++++++ yarn-project/foundation/src/fifo/index.ts | 1 + .../src/batch_verifier.bench.test.ts | 199 +++++++++++++ .../src/batch_verifier.test.ts | 105 +++++++ .../src/batch_verifier_queue.test.ts | 257 ++++++++++++++++ .../src/batch_verifier_test_helpers.ts | 32 ++ yarn-project/prover-client/src/config.ts | 15 +- .../prover-client/src/mocks/test_context.ts | 4 +- yarn-project/txe/src/state_machine/index.ts | 1 + yarn-project/yarn.lock | 1 + 23 files changed, 1267 insertions(+), 40 deletions(-) create mode 100644 yarn-project/bb-prover/src/verifier/batch_chonk_verifier.ts create mode 100644 yarn-project/foundation/src/fifo/fifo_frame_reader.test.ts create mode 100644 yarn-project/foundation/src/fifo/fifo_frame_reader.ts create mode 100644 yarn-project/foundation/src/fifo/index.ts create mode 100644 yarn-project/ivc-integration/src/batch_verifier.bench.test.ts create mode 100644 yarn-project/ivc-integration/src/batch_verifier.test.ts create mode 100644 yarn-project/ivc-integration/src/batch_verifier_queue.test.ts create mode 100644 yarn-project/ivc-integration/src/batch_verifier_test_helpers.ts diff --git a/yarn-project/aztec-node/src/aztec-node/server.test.ts b/yarn-project/aztec-node/src/aztec-node/server.test.ts index 37f3adca164f..f51448cf39d7 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.test.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.test.ts @@ -199,6 +199,7 @@ describe('aztec node', () => { epochCache, getPackageVersion() ?? '', new TestCircuitVerifier(), + new TestCircuitVerifier(), ); }); @@ -716,6 +717,7 @@ describe('aztec node', () => { epochCache, getPackageVersion() ?? '', new TestCircuitVerifier(), + new TestCircuitVerifier(), undefined, undefined, undefined, @@ -904,6 +906,7 @@ describe('aztec node', () => { epochCache, getPackageVersion() ?? '', new TestCircuitVerifier(), + new TestCircuitVerifier(), undefined, undefined, undefined, diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index be709fdc6557..cc907fe88795 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -1,5 +1,6 @@ import { Archiver, createArchiver } from '@aztec/archiver'; -import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover'; +import { BBCircuitVerifier, BatchChonkVerifier, QueuedIVCVerifier } from '@aztec/bb-prover'; +import { TestCircuitVerifier } from '@aztec/bb-prover/test'; import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client'; import { Blob } from '@aztec/blob-lib'; import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants'; @@ -151,7 +152,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { protected readonly globalVariableBuilder: GlobalVariableBuilderInterface, protected readonly epochCache: EpochCacheInterface, protected readonly packageVersion: string, - private proofVerifier: ClientProtocolCircuitVerifier, + private peerProofVerifier: ClientProtocolCircuitVerifier, + private rpcProofVerifier: ClientProtocolCircuitVerifier, private telemetry: TelemetryClient = getTelemetryClient(), private log = createLogger('node'), private blobClient?: BlobClientInterface, @@ -173,6 +175,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { } } + /** @internal Exposed for testing — returns the RPC proof verifier. */ + public getProofVerifier(): ClientProtocolCircuitVerifier { + return this.rpcProofVerifier; + } + public async getWorldStateSyncStatus(): Promise { const status = await this.worldStateSynchronizer.status(); return status.syncSummary; @@ -308,10 +315,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { options.prefilledPublicData, telemetry, ); - const circuitVerifier = - config.realProofs || config.debugForceTxProofVerification - ? await BBCircuitVerifier.new(config) - : new TestCircuitVerifier(config.proverTestVerificationDelayMs); + const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification; + let peerProofVerifier: ClientProtocolCircuitVerifier; + let rpcProofVerifier: ClientProtocolCircuitVerifier; + if (useRealVerifiers) { + peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer'); + const rpcVerifier = await BBCircuitVerifier.new(config); + rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers); + } else { + peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs); + rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs); + } let debugLogStore: DebugLogStore; if (!config.realProofs) { @@ -325,8 +339,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { debugLogStore = new NullDebugLogStore(); } - const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier); - const proverOnly = config.enableProverNode && config.disableValidator; if (proverOnly) { log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems'); @@ -336,7 +348,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { const p2pClient = await createP2PClient( config, archiver, - proofVerifier, + peerProofVerifier, worldStateSynchronizer, epochCache, packageVersion, @@ -578,7 +590,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { globalVariableBuilder, epochCache, packageVersion, - proofVerifier, + peerProofVerifier, + rpcProofVerifier, telemetry, log, blobClient, @@ -925,7 +938,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { await tryStop(this.validatorsSentinel); await tryStop(this.epochPruneWatcher); await tryStop(this.slasherClient); - await tryStop(this.proofVerifier); + await Promise.all([tryStop(this.peerProofVerifier), tryStop(this.rpcProofVerifier)]); await tryStop(this.sequencer); await tryStop(this.proverNode); await tryStop(this.p2pClient); @@ -1315,7 +1328,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { { isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {}, ): Promise { const db = this.worldStateSynchronizer.getCommitted(); - const verifier = isSimulation ? undefined : this.proofVerifier; + const verifier = isSimulation ? undefined : this.rpcProofVerifier; // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field) const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot(); @@ -1364,7 +1377,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { archiver.updateConfig(config); } if (newConfig.realProofs !== this.config.realProofs) { - this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier(); + await Promise.all([tryStop(this.peerProofVerifier), tryStop(this.rpcProofVerifier)]); + if (newConfig.realProofs) { + this.peerProofVerifier = await BatchChonkVerifier.new(newConfig, newConfig.bbChonkVerifyMaxBatch, 'peer'); + const rpcVerifier = await BBCircuitVerifier.new(newConfig); + this.rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, newConfig.numConcurrentIVCVerifiers); + } else { + this.peerProofVerifier = new TestCircuitVerifier(); + this.rpcProofVerifier = new TestCircuitVerifier(); + } } this.config = newConfig; diff --git a/yarn-project/bb-prover/package.json b/yarn-project/bb-prover/package.json index 04774fdf6ea8..5e08beeba10c 100644 --- a/yarn-project/bb-prover/package.json +++ b/yarn-project/bb-prover/package.json @@ -80,6 +80,7 @@ "@aztec/telemetry-client": "workspace:^", "@aztec/world-state": "workspace:^", "commander": "^12.1.0", + "msgpackr": "^1.11.2", "pako": "^2.1.0", "source-map-support": "^0.5.21", "tslib": "^2.4.0" diff --git a/yarn-project/bb-prover/src/config.ts b/yarn-project/bb-prover/src/config.ts index 60a33c9a67b6..bc3f7b6f8794 100644 --- a/yarn-project/bb-prover/src/config.ts +++ b/yarn-project/bb-prover/src/config.ts @@ -3,8 +3,19 @@ export interface BBConfig { bbWorkingDirectory: string; /** Whether to skip tmp dir cleanup for debugging purposes */ bbSkipCleanup: boolean; + /** Max concurrent verifications for the RPC verifier (QueuedIVCVerifier). */ numConcurrentIVCVerifiers: number; + /** Thread count for the RPC IVC verifier. */ bbIVCConcurrency: number; + /** + * Upper bound on proofs per batch for the peer chonk batch verifier. + * Proofs are verified immediately as they arrive — this only caps how many + * can accumulate while a batch is already being processed. + * Default 16: at 4 cores, a full batch of 16 verifies in ~245ms wall time. + */ + bbChonkVerifyMaxBatch: number; + /** Thread count for the peer batch verifier parallel reduce. 0 = auto. */ + bbChonkVerifyConcurrency: number; } export interface ACVMConfig { diff --git a/yarn-project/bb-prover/src/verifier/batch_chonk_verifier.ts b/yarn-project/bb-prover/src/verifier/batch_chonk_verifier.ts new file mode 100644 index 000000000000..b64811b9240c --- /dev/null +++ b/yarn-project/bb-prover/src/verifier/batch_chonk_verifier.ts @@ -0,0 +1,276 @@ +import { BackendType, Barretenberg } from '@aztec/bb.js'; +import { FifoFrameReader } from '@aztec/foundation/fifo'; +import { createLogger } from '@aztec/foundation/log'; +import { SerialQueue } from '@aztec/foundation/queue'; +import { Timer } from '@aztec/foundation/timer'; +import { ProtocolCircuitVks } from '@aztec/noir-protocol-circuits-types/server/vks'; +import type { ClientProtocolCircuitVerifier, IVCProofVerificationResult } from '@aztec/stdlib/interfaces/server'; +import type { Tx } from '@aztec/stdlib/tx'; + +import { Unpackr } from 'msgpackr'; +import { execFile } from 'node:child_process'; +import { unlinkSync } from 'node:fs'; +import { unlink } from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { promisify } from 'node:util'; + +import type { BBConfig } from '../config.js'; + +const execFileAsync = promisify(execFile); + +/** Result from the FIFO, matching the C++ VerifyResult struct. */ +interface FifoVerifyResult { + request_id: number; + status: number; + error_message: string; + time_in_verify_ms: number; +} + +/** Maps client protocol artifacts used for chonk verification to VK indices. */ +const CHONK_VK_ARTIFACTS = ['HidingKernelToRollup', 'HidingKernelToPublic'] as const; + +interface PendingRequest { + resolve: (result: IVCProofVerificationResult) => void; + reject: (error: Error) => void; + totalTimer: Timer; +} + +/** + * Batch verifier for Chonk IVC proofs. Uses the bb batch verifier service + * which batches IPA verification into a single SRS MSM for better throughput. + * + * Architecture: + * - Spawns a persistent `bb msgpack run` process via Barretenberg (native backend) + * - Sends proofs via the msgpack RPC protocol (ChonkBatchVerifierQueue) + * - Receives results via a named FIFO pipe (async, out-of-order) + * - Bisects batch failures to isolate individual bad proofs + */ +export class BatchChonkVerifier implements ClientProtocolCircuitVerifier { + private bb!: Barretenberg; + private fifoPath: string; + private nextRequestId = 0; + private pendingRequests = new Map(); + private sendQueue: SerialQueue; + private fifoReader: FifoFrameReader; + private logger = createLogger('bb-prover:batch_chonk_verifier'); + /** Maps artifact name to VK index in the batch verifier. */ + private vkIndexMap = new Map(); + /** Bound cleanup handler for process exit signals. */ + private exitCleanup: (() => void) | null = null; + + private constructor( + private config: Pick & Partial>, + private vkBuffers: Uint8Array[], + private batchSize: number, + private label: string, + ) { + this.fifoPath = path.join(os.tmpdir(), `bb-batch-${label}-${process.pid}-${Date.now()}.fifo`); + this.fifoReader = new FifoFrameReader(); + this.sendQueue = new SerialQueue(); + this.sendQueue.start(1); + } + + /** Create and start a BatchChonkVerifier using the protocol circuit VKs. */ + static async new(config: BBConfig, batchSize: number, label: string): Promise { + const vkBuffers: Uint8Array[] = []; + const vkIndexMap = new Map(); + for (const artifact of CHONK_VK_ARTIFACTS) { + const vk = ProtocolCircuitVks[artifact]; + if (!vk) { + throw new Error(`Missing VK for ${artifact}`); + } + vkIndexMap.set(artifact, vkBuffers.length); + vkBuffers.push(vk.keyAsBytes); + } + const verifier = new BatchChonkVerifier(config, vkBuffers, batchSize, label); + verifier.vkIndexMap = vkIndexMap; + await verifier.start(); + return verifier; + } + + /** Create and start a BatchChonkVerifier with custom VKs (for testing). */ + static async newForTesting( + config: Pick & Partial>, + vks: Uint8Array[], + batchSize: number, + ): Promise { + const verifier = new BatchChonkVerifier(config, vks, batchSize, 'test'); + for (let i = 0; i < vks.length; i++) { + verifier.vkIndexMap.set(String(i), i); + } + await verifier.start(); + return verifier; + } + + private async start(): Promise { + this.logger.info('Starting BatchChonkVerifier'); + + this.bb = await Barretenberg.new({ + bbPath: this.config.bbBinaryPath, + backend: BackendType.NativeUnixSocket, + }); + await this.bb.initSRSChonk(); + + await execFileAsync('mkfifo', [this.fifoPath]); + this.registerExitCleanup(); + + await this.bb.chonkBatchVerifierStart({ + vks: this.vkBuffers, + numCores: this.config.bbChonkVerifyConcurrency || 0, + batchSize: this.batchSize, + fifoPath: this.fifoPath, + }); + + this.startFifoReader(); + this.logger.info('BatchChonkVerifier started', { fifoPath: this.fifoPath }); + } + + public verifyProof(tx: Tx): Promise { + const circuit = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup'; + const vkIndex = this.vkIndexMap.get(circuit); + if (vkIndex === undefined) { + throw new Error(`No VK index for circuit ${circuit}`); + } + const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields()); + const proofFields = proofWithPubInputs.fieldsWithPublicInputs.map(f => f.toBuffer()); + return this.enqueueProof(vkIndex, proofFields); + } + + /** Enqueue raw proof fields for verification. Used directly by tests with custom VKs. */ + public enqueueProof(vkIndex: number, proofFields: Uint8Array[]): Promise { + const totalTimer = new Timer(); + const requestId = this.nextRequestId++; + + const resultPromise = new Promise((resolve, reject) => { + this.pendingRequests.set(requestId, { resolve, reject, totalTimer }); + }); + + void this.sendQueue + .put(async () => { + await this.bb.chonkBatchVerifierQueue({ + requestId, + vkIndex, + proofFields, + }); + }) + .catch(err => { + const pending = this.pendingRequests.get(requestId); + if (pending) { + this.pendingRequests.delete(requestId); + pending.reject(err instanceof Error ? err : new Error(String(err))); + } + }); + + return resultPromise; + } + + public async stop(): Promise { + this.logger.info('Stopping BatchChonkVerifier'); + + // Stop accepting new proofs + await this.sendQueue.end(); + + // Stop the bb service (flushes remaining proofs) + try { + await this.bb.chonkBatchVerifierStop({}); + } catch (err) { + this.logger.warn(`Error stopping batch verifier service: ${err}`); + } + + // Stop FIFO reader + this.fifoReader.stop(); + + // Clean up FIFO file and deregister exit handler + await unlink(this.fifoPath).catch(() => {}); + this.deregisterExitCleanup(); + + // Reject any remaining pending requests + for (const [id, pending] of this.pendingRequests) { + pending.reject(new Error('BatchChonkVerifier stopped')); + this.pendingRequests.delete(id); + } + + // Destroy bb process + await this.bb.destroy(); + + this.logger.info('BatchChonkVerifier stopped'); + } + + private startFifoReader(): void { + const unpackr = new Unpackr({ useRecords: false }); + + this.fifoReader.on('frame', (payload: Buffer) => { + try { + const result = unpackr.unpack(payload) as FifoVerifyResult; + this.handleResult(result); + } catch (err) { + this.logger.error(`FIFO: failed to decode msgpack result: ${err}`); + } + }); + + this.fifoReader.on('error', (err: Error) => { + this.logger.error(`FIFO reader error: ${err}`); + }); + + this.fifoReader.on('end', () => { + this.logger.debug('FIFO reader: stream ended'); + for (const [id, pending] of this.pendingRequests) { + pending.reject(new Error('FIFO stream ended unexpectedly')); + this.pendingRequests.delete(id); + } + }); + + this.fifoReader.start(this.fifoPath); + } + + private handleResult(result: FifoVerifyResult): void { + const pending = this.pendingRequests.get(result.request_id); + if (!pending) { + this.logger.warn(`Received result for unknown request_id=${result.request_id}`); + return; + } + this.pendingRequests.delete(result.request_id); + + const valid = result.status === 0; // VerifyStatus::OK + const durationMs = result.time_in_verify_ms; + const totalDurationMs = pending.totalTimer.ms(); + + const ivcResult: IVCProofVerificationResult = { valid, durationMs, totalDurationMs }; + + if (!valid) { + this.logger.warn(`Proof verification failed for request_id=${result.request_id}: ${result.error_message}`); + } else { + this.logger.debug(`Proof verified`, { + requestId: result.request_id, + durationMs: Math.ceil(durationMs), + totalDurationMs: Math.ceil(totalDurationMs), + }); + } + + pending.resolve(ivcResult); + } + + private registerExitCleanup(): void { + // Signal handlers must be synchronous — unlinkSync is intentional here + this.exitCleanup = () => { + try { + unlinkSync(this.fifoPath); + } catch { + /* ignore */ + } + }; + process.on('exit', this.exitCleanup); + process.on('SIGINT', this.exitCleanup); + process.on('SIGTERM', this.exitCleanup); + } + + private deregisterExitCleanup(): void { + if (this.exitCleanup) { + process.removeListener('exit', this.exitCleanup); + process.removeListener('SIGINT', this.exitCleanup); + process.removeListener('SIGTERM', this.exitCleanup); + this.exitCleanup = null; + } + } +} diff --git a/yarn-project/bb-prover/src/verifier/index.ts b/yarn-project/bb-prover/src/verifier/index.ts index 1ce2f3a7e41a..229e41274d6e 100644 --- a/yarn-project/bb-prover/src/verifier/index.ts +++ b/yarn-project/bb-prover/src/verifier/index.ts @@ -1,2 +1,3 @@ +export * from './batch_chonk_verifier.js'; export * from './bb_verifier.js'; export * from './queued_chonk_verifier.js'; diff --git a/yarn-project/bb-prover/src/verifier/queued_chonk_verifier.ts b/yarn-project/bb-prover/src/verifier/queued_chonk_verifier.ts index 4eeca0d042e5..c7d62565fa6a 100644 --- a/yarn-project/bb-prover/src/verifier/queued_chonk_verifier.ts +++ b/yarn-project/bb-prover/src/verifier/queued_chonk_verifier.ts @@ -16,8 +16,6 @@ import { import { createHistogram } from 'node:perf_hooks'; -import type { BBConfig } from '../config.js'; - class IVCVerifierMetrics { private ivcVerificationHistogram: Histogram; private ivcTotalVerificationHistogram: Histogram; @@ -86,15 +84,15 @@ export class QueuedIVCVerifier implements ClientProtocolCircuitVerifier { private metrics: IVCVerifierMetrics; public constructor( - config: BBConfig, private verifier: ClientProtocolCircuitVerifier, + concurrency: number, private telemetry: TelemetryClient = getTelemetryClient(), private logger = createLogger('bb-prover:queued_chonk_verifier'), ) { this.metrics = new IVCVerifierMetrics(this.telemetry, 'QueuedIVCVerifier'); this.queue = new SerialQueue(); - this.logger.info(`Starting QueuedIVCVerifier with ${config.numConcurrentIVCVerifiers} concurrent verifiers`); - this.queue.start(config.numConcurrentIVCVerifiers); + this.logger.info(`Starting QueuedIVCVerifier with ${concurrency} concurrent verifiers`); + this.queue.start(concurrency); } public async verifyProof(tx: Tx): Promise { @@ -103,7 +101,8 @@ export class QueuedIVCVerifier implements ClientProtocolCircuitVerifier { return result; } - stop(): Promise { - return this.queue.end(); + async stop(): Promise { + await this.queue.end(); + await this.verifier.stop(); } } diff --git a/yarn-project/bootstrap.sh b/yarn-project/bootstrap.sh index fdf24a891720..2509a7628e4e 100755 --- a/yarn-project/bootstrap.sh +++ b/yarn-project/bootstrap.sh @@ -248,6 +248,7 @@ function bench_cmds { echo "$hash:ISOLATE=1:CPUS=10:MEM=16g:LOG_LEVEL=silent BENCH_OUTPUT=bench-out/proving_broker.bench.json yarn-project/scripts/run_test.sh prover-client/src/test/proving_broker_testbench.test.ts" echo "$hash:ISOLATE=1:CPUS=16:MEM=16g BENCH_OUTPUT=bench-out/avm_bulk_test.bench.json yarn-project/scripts/run_test.sh bb-prover/src/avm_proving_tests/avm_bulk.test.ts" echo "$hash BENCH_OUTPUT=bench-out/lightweight_checkpoint_builder.bench.json yarn-project/scripts/run_test.sh prover-client/src/light/lightweight_checkpoint_builder.bench.test.ts" + echo "$hash:ISOLATE=1:CPUS=8:MEM=32g:TIMEOUT=1200 BENCH_OUTPUT=bench-out/batch_verifier.bench.json yarn-project/scripts/run_test.sh ivc-integration/src/batch_verifier.bench.test.ts" } function release_packages { diff --git a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts index 59f38bfeedea..7dc8268ce083 100644 --- a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts @@ -4,12 +4,7 @@ import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; import { type Logger, createLogger } from '@aztec/aztec.js/log'; import type { AztecNode } from '@aztec/aztec.js/node'; import { CheatCodes } from '@aztec/aztec/testing'; -import { - BBCircuitVerifier, - type ClientProtocolCircuitVerifier, - QueuedIVCVerifier, - TestCircuitVerifier, -} from '@aztec/bb-prover'; +import type { ClientProtocolCircuitVerifier } from '@aztec/bb-prover'; import { BackendType, Barretenberg } from '@aztec/bb.js'; import type { DeployAztecL1ContractsReturnType } from '@aztec/ethereum/deploy-aztec-l1-contracts'; import { Buffer32 } from '@aztec/foundation/buffer'; @@ -68,7 +63,10 @@ export class FullProverTest { private provenComponents: ProvenSetup[] = []; private bbConfigCleanup?: () => Promise; private acvmConfigCleanup?: () => Promise; - circuitProofVerifier?: ClientProtocolCircuitVerifier; + /** Returns the proof verifier from the prover node (for test assertions). */ + get circuitProofVerifier(): ClientProtocolCircuitVerifier | undefined { + return this.proverAztecNode?.getProofVerifier(); + } provenAsset!: TokenContract; context!: EndToEndContext; private proverAztecNode!: AztecNodeService; @@ -170,9 +168,6 @@ export class FullProverTest { await Barretenberg.initSingleton({ backend: BackendType.NativeUnixSocket }); - const verifier = await BBCircuitVerifier.new(bbConfig); - this.circuitProofVerifier = new QueuedIVCVerifier(bbConfig, verifier); - this.logger.debug(`Configuring the node for real proofs...`); await this.aztecNodeAdmin.setConfig({ realProofs: true, @@ -180,7 +175,6 @@ export class FullProverTest { }); } else { this.logger.debug(`Configuring the node min txs per block ${this.minNumberOfTxsPerBlock}...`); - this.circuitProofVerifier = new TestCircuitVerifier(); await this.aztecNodeAdmin.setConfig({ minTxsPerBlock: this.minNumberOfTxsPerBlock, }); diff --git a/yarn-project/end-to-end/src/fixtures/get_bb_config.ts b/yarn-project/end-to-end/src/fixtures/get_bb_config.ts index 9ede6ef6a1e7..5cf241dfcce9 100644 --- a/yarn-project/end-to-end/src/fixtures/get_bb_config.ts +++ b/yarn-project/end-to-end/src/fixtures/get_bb_config.ts @@ -13,8 +13,10 @@ const { BB_SKIP_CLEANUP = '', TEMP_DIR = tmpdir(), BB_WORKING_DIRECTORY = '', - BB_NUM_IVC_VERIFIERS = '1', + BB_NUM_IVC_VERIFIERS = '8', BB_IVC_CONCURRENCY = '1', + BB_CHONK_VERIFY_MAX_BATCH = '16', + BB_CHONK_VERIFY_BATCH_CONCURRENCY = '0', } = process.env; export const getBBConfig = async ( @@ -41,16 +43,15 @@ export const getBBConfig = async ( const bbSkipCleanup = ['1', 'true'].includes(BB_SKIP_CLEANUP); const cleanup = bbSkipCleanup ? () => Promise.resolve() : () => tryRmDir(directoryToCleanup); - const numIvcVerifiers = Number(BB_NUM_IVC_VERIFIERS); - const ivcConcurrency = Number(BB_IVC_CONCURRENCY); - return { bbSkipCleanup, bbBinaryPath, bbWorkingDirectory, cleanup, - numConcurrentIVCVerifiers: numIvcVerifiers, - bbIVCConcurrency: ivcConcurrency, + numConcurrentIVCVerifiers: Number(BB_NUM_IVC_VERIFIERS), + bbIVCConcurrency: Number(BB_IVC_CONCURRENCY), + bbChonkVerifyMaxBatch: Number(BB_CHONK_VERIFY_MAX_BATCH), + bbChonkVerifyConcurrency: Number(BB_CHONK_VERIFY_BATCH_CONCURRENCY), }; } catch (err) { logger.error(`Native BB not available, error: ${err}`); diff --git a/yarn-project/foundation/package.json b/yarn-project/foundation/package.json index 294288bc0cc4..d04a61dfcf8a 100644 --- a/yarn-project/foundation/package.json +++ b/yarn-project/foundation/package.json @@ -61,6 +61,7 @@ "./eth-address": "./dest/eth-address/index.js", "./eth-signature": "./dest/eth-signature/index.js", "./queue": "./dest/queue/index.js", + "./fifo": "./dest/fifo/index.js", "./fs": "./dest/fs/index.js", "./buffer": "./dest/buffer/index.js", "./json-rpc": "./dest/json-rpc/index.js", diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 9cc98c492272..fb9e4d95ddf3 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -23,6 +23,8 @@ export type EnvVar = | 'BB_WORKING_DIRECTORY' | 'BB_NUM_IVC_VERIFIERS' | 'BB_IVC_CONCURRENCY' + | 'BB_CHONK_VERIFY_MAX_BATCH' + | 'BB_CHONK_VERIFY_BATCH_CONCURRENCY' | 'BOOTSTRAP_NODES' | 'BLOB_ARCHIVE_API_URL' | 'BLOB_FILE_STORE_URLS' diff --git a/yarn-project/foundation/src/fifo/fifo_frame_reader.test.ts b/yarn-project/foundation/src/fifo/fifo_frame_reader.test.ts new file mode 100644 index 000000000000..a8c710a9ed44 --- /dev/null +++ b/yarn-project/foundation/src/fifo/fifo_frame_reader.test.ts @@ -0,0 +1,209 @@ +import { PassThrough } from 'node:stream'; + +import { FifoFrameReader } from './fifo_frame_reader.js'; + +/** Build a length-delimited frame buffer for the given payload. */ +function buildFrame(payload: Buffer): Buffer { + const header = Buffer.alloc(4); + header.writeUInt32BE(payload.length, 0); + return Buffer.concat([header, payload]); +} + +/** Collect N frames from a reader, returning them as a promise. */ +function collectFrames(reader: FifoFrameReader, count: number): Promise { + return new Promise((resolve, reject) => { + const frames: Buffer[] = []; + reader.on('frame', frame => { + frames.push(frame); + if (frames.length >= count) { + resolve(frames); + } + }); + reader.on('error', reject); + }); +} + +describe('FifoFrameReader', () => { + it('reads a single frame', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const framesPromise = collectFrames(reader, 1); + stream.write(buildFrame(Buffer.from('hello world'))); + stream.end(); + + const frames = await framesPromise; + expect(frames).toHaveLength(1); + expect(frames[0].toString()).toBe('hello world'); + reader.stop(); + }); + + it('reads multiple frames from a single push', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const framesPromise = collectFrames(reader, 3); + + const combined = Buffer.concat([ + buildFrame(Buffer.from('aaa')), + buildFrame(Buffer.from('bbb')), + buildFrame(Buffer.from('ccc')), + ]); + stream.write(combined); + stream.end(); + + const frames = await framesPromise; + expect(frames).toHaveLength(3); + expect(frames.map(f => f.toString())).toEqual(['aaa', 'bbb', 'ccc']); + reader.stop(); + }); + + it('handles frames split across multiple chunks', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const framesPromise = collectFrames(reader, 1); + + const payload = Buffer.from('this is a longer payload that will be split'); + const frame = buildFrame(payload); + + // Split the frame into small chunks + for (let i = 0; i < frame.length; i += 5) { + stream.write(frame.subarray(i, Math.min(i + 5, frame.length))); + } + stream.end(); + + const frames = await framesPromise; + expect(frames).toHaveLength(1); + expect(frames[0].toString()).toBe(payload.toString()); + reader.stop(); + }); + + it('handles header split across chunks', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const framesPromise = collectFrames(reader, 1); + + const payload = Buffer.from('data'); + const frame = buildFrame(payload); + + // Split in the middle of the 4-byte header + stream.write(frame.subarray(0, 2)); + stream.write(frame.subarray(2)); + stream.end(); + + const frames = await framesPromise; + expect(frames).toHaveLength(1); + expect(frames[0].toString()).toBe('data'); + reader.stop(); + }); + + it('emits error on zero-length payload', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const errorPromise = new Promise(resolve => { + reader.on('error', resolve); + }); + + const header = Buffer.alloc(4); + header.writeUInt32BE(0, 0); + stream.write(header); + + const error = await errorPromise; + expect(error.message).toContain('Invalid payload length: 0'); + }); + + it('emits error on oversized payload', async () => { + const maxSize = 100; + const reader = new FifoFrameReader(maxSize); + const stream = new PassThrough(); + reader.startFromStream(stream); + + const errorPromise = new Promise(resolve => { + reader.on('error', resolve); + }); + + const header = Buffer.alloc(4); + header.writeUInt32BE(maxSize + 1, 0); + stream.write(header); + + const error = await errorPromise; + expect(error.message).toContain(`Invalid payload length: ${maxSize + 1}`); + }); + + it('emits end when stream ends', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const endPromise = new Promise(resolve => { + reader.on('end', resolve); + }); + + stream.end(); + await endPromise; + reader.stop(); + }); + + it('reads valid frames before an invalid frame', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const frames: Buffer[] = []; + reader.on('frame', frame => frames.push(frame)); + + const errorPromise = new Promise(resolve => { + reader.on('error', resolve); + }); + + // Write 2 valid frames then an invalid one + stream.write(buildFrame(Buffer.from('good1'))); + stream.write(buildFrame(Buffer.from('good2'))); + + // Invalid: zero length header + const badHeader = Buffer.alloc(4); + badHeader.writeUInt32BE(0, 0); + stream.write(badHeader); + + await errorPromise; + expect(frames).toHaveLength(2); + expect(frames[0].toString()).toBe('good1'); + expect(frames[1].toString()).toBe('good2'); + }); + + it('throws if started twice', () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + expect(() => reader.startFromStream(new PassThrough())).toThrow('already running'); + reader.stop(); + }); + + it('handles large payloads', async () => { + const stream = new PassThrough(); + const reader = new FifoFrameReader(); + reader.startFromStream(stream); + + const framesPromise = collectFrames(reader, 1); + + // 1MB payload + const payload = Buffer.alloc(1024 * 1024, 0x42); + stream.write(buildFrame(payload)); + stream.end(); + + const frames = await framesPromise; + expect(frames).toHaveLength(1); + expect(frames[0].length).toBe(1024 * 1024); + expect(frames[0][0]).toBe(0x42); + reader.stop(); + }); +}); diff --git a/yarn-project/foundation/src/fifo/fifo_frame_reader.ts b/yarn-project/foundation/src/fifo/fifo_frame_reader.ts new file mode 100644 index 000000000000..3b3bd322d33b --- /dev/null +++ b/yarn-project/foundation/src/fifo/fifo_frame_reader.ts @@ -0,0 +1,98 @@ +import EventEmitter from 'node:events'; +import * as fs from 'node:fs'; +import type { Readable } from 'node:stream'; + +/** + * Events emitted by FifoFrameReader. + * + * - `frame`: A complete frame payload (without the 4-byte length header). + * - `error`: An unrecoverable error (invalid frame length, stream error). + * - `end`: The underlying stream has ended. + */ +export interface FifoFrameReaderEvents { + frame: [payload: Buffer]; + error: [error: Error]; + end: []; +} + +/** + * Reads length-delimited frames from a readable stream (typically a named FIFO pipe). + * + * Wire format: `[4-byte big-endian payload length][payload bytes]` + * + * Emits a `frame` event for each complete frame with the raw payload buffer. + * Callers are responsible for deserializing the payload (e.g., via msgpack). + * + * On encountering an invalid payload length (0 or >maxPayloadSize), emits `error` + * and destroys the stream. + */ +export class FifoFrameReader extends EventEmitter { + private stream: Readable | null = null; + private pendingBuf: Buffer = Buffer.alloc(0); + private running = false; + + constructor(private readonly maxPayloadSize = 10 * 1024 * 1024) { + super(); + } + + /** Open a FIFO at the given path and start reading frames. */ + start(fifoPath: string, highWaterMark = 64 * 1024): void { + this.startFromStream(fs.createReadStream(fifoPath, { highWaterMark })); + } + + /** Start reading frames from an existing readable stream. */ + startFromStream(stream: Readable): void { + if (this.running) { + throw new Error('FifoFrameReader is already running'); + } + this.running = true; + this.pendingBuf = Buffer.alloc(0); + this.stream = stream; + + stream.on('data', (chunk: Buffer | string) => { + const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + this.pendingBuf = this.pendingBuf.length > 0 ? Buffer.concat([this.pendingBuf, buf]) : buf; + this.drainFrames(); + }); + + stream.on('error', (err: Error) => { + if (this.running) { + this.emit('error', err); + } + }); + + stream.on('end', () => { + this.emit('end'); + }); + } + + /** Stop reading and destroy the underlying stream. */ + stop(): void { + this.running = false; + if (this.stream) { + this.stream.destroy(); + this.stream = null; + } + } + + /** Parse complete frames out of the pending buffer. */ + private drainFrames(): void { + while (this.pendingBuf.length >= 4) { + const payloadLen = this.pendingBuf.readUInt32BE(0); + if (payloadLen === 0 || payloadLen > this.maxPayloadSize) { + this.emit('error', new Error(`Invalid payload length: ${payloadLen}`)); + this.stop(); + return; + } + + const frameLen = 4 + payloadLen; + if (this.pendingBuf.length < frameLen) { + break; // Wait for more data + } + + const payload = this.pendingBuf.subarray(4, frameLen); + this.pendingBuf = this.pendingBuf.subarray(frameLen); + this.emit('frame', Buffer.from(payload)); + } + } +} diff --git a/yarn-project/foundation/src/fifo/index.ts b/yarn-project/foundation/src/fifo/index.ts new file mode 100644 index 000000000000..d53ee629f835 --- /dev/null +++ b/yarn-project/foundation/src/fifo/index.ts @@ -0,0 +1 @@ +export { FifoFrameReader } from './fifo_frame_reader.js'; diff --git a/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts b/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts new file mode 100644 index 000000000000..51e1dcff38ec --- /dev/null +++ b/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts @@ -0,0 +1,199 @@ +/** + * Batch chonk verifier benchmarks using real protocol proofs. + * + * Downloads pinned IVC inputs from S3, proves a representative transaction, + * then benchmarks batch verification throughput at various configurations. + */ +import { BatchChonkVerifier } from '@aztec/bb-prover'; +import { createLogger } from '@aztec/foundation/log'; +import { ProtocolCircuitVks } from '@aztec/noir-protocol-circuits-types/server/vks'; + +import { jest } from '@jest/globals'; +import { execFile } from 'node:child_process'; +import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, resolve } from 'node:path'; +import { promisify } from 'node:util'; + +import { corruptProofFields } from './batch_verifier_test_helpers.js'; + +const execFileAsync = promisify(execFile); +const logger = createLogger('ivc-integration:bench:batch-verifier'); + +const PINNED_HASH = 'b99f5b94'; +const PINNED_URL = `https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-chonk-inputs-${PINNED_HASH}.tar.gz`; +const BB_PATH = process.env.BB_BINARY_PATH ?? resolve('../../barretenberg/cpp/build/bin/bb'); + +jest.setTimeout(1_200_000); // 20 min — proving is slow + +type BenchEntry = { name: string; value: number; unit: string }; + +/** Split a binary proof file into 32-byte field element buffers. */ +function proofToFields(proofBuf: Buffer): Uint8Array[] { + const fields: Uint8Array[] = []; + for (let i = 0; i < proofBuf.length; i += 32) { + fields.push(Uint8Array.from(proofBuf.subarray(i, i + 32))); + } + return fields; +} + +describe('Batch Chonk Verifier Benchmarks (Real Proofs)', () => { + let validProofFields: Uint8Array[]; + let invalidProofFields: Uint8Array[]; + let vk: Uint8Array; + let inputsDir: string; + const benchResults: BenchEntry[] = []; + + beforeAll(async () => { + // Download pinned IVC inputs + inputsDir = resolve(tmpdir(), `bb-bench-inputs-${Date.now()}`); + await mkdir(inputsDir, { recursive: true }); + logger.info(`Downloading pinned IVC inputs from ${PINNED_URL}...`); + await execFileAsync('curl', ['-sf', PINNED_URL, '-o', `${inputsDir}/inputs.tar.gz`]); + await execFileAsync('tar', ['-xzf', `${inputsDir}/inputs.tar.gz`, '-C', inputsDir]); + + // Pick the largest flow for a realistic proof + const flows = await readdir(inputsDir, { withFileTypes: true }); + const flowDirs = flows.filter(f => f.isDirectory()).map(f => f.name); + logger.info(`Available flows: ${flowDirs.join(', ')}`); + + // Use a transfer flow (representative of typical txs) + const flow = flowDirs.find(f => f.includes('transfer_0_recursions+sponsored')) ?? flowDirs[0]; + const ivcInputsPath = resolve(inputsDir, flow, 'ivc-inputs.msgpack'); + const proofDir = resolve(inputsDir, 'proof-out'); + await mkdir(proofDir, { recursive: true }); + + // Prove the flow + logger.info(`Proving flow: ${flow}...`); + const proveStart = performance.now(); + await execFileAsync(BB_PATH, ['prove', '--scheme', 'chonk', '--ivc_inputs_path', ivcInputsPath, '-o', proofDir], { + timeout: 600_000, + }); + const proveMs = performance.now() - proveStart; + logger.info(`Proof generated in ${Math.ceil(proveMs)}ms`); + + // Read the proof and convert to field elements + const proofBuf = await readFile(resolve(proofDir, 'proof')); + validProofFields = proofToFields(proofBuf); + invalidProofFields = corruptProofFields(validProofFields); + + // Get the protocol VK (HidingKernelToRollup — matches most flows) + vk = ProtocolCircuitVks['HidingKernelToRollup'].keyAsBytes; + + logger.info(`Proof: ${proofBuf.length} bytes, ${validProofFields.length} fields`); + }); + + afterAll(async () => { + if (process.env.BENCH_OUTPUT) { + await mkdir(dirname(process.env.BENCH_OUTPUT), { recursive: true }); + await writeFile(process.env.BENCH_OUTPUT, JSON.stringify(benchResults, null, 2)); + } else { + logger.info('Benchmark results:'); + for (const r of benchResults) { + logger.info(` ${r.name}: ${r.value.toFixed(1)} ${r.unit}`); + } + } + await rm(inputsDir, { recursive: true, force: true }).catch(() => {}); + }); + + // -- Core count sweep -- + + for (const numCores of [2, 4, 8]) { + it(`throughput: 16 proofs, batch_size=8, ${numCores} cores`, async () => { + const numProofs = 16; + const batchSize = 8; + const proofs = Array.from({ length: numProofs }, () => ({ vkIndex: 0, proofFields: validProofFields })); + + const verifier = await BatchChonkVerifier.newForTesting({ bbChonkVerifyConcurrency: numCores }, [vk], batchSize); + + try { + const wallStart = performance.now(); + const results = await Promise.all(proofs.map(p => verifier.enqueueProof(p.vkIndex, p.proofFields))); + const wallMs = performance.now() - wallStart; + + expect(results).toHaveLength(numProofs); + expect(results.every(r => r.valid)).toBe(true); + + const avgVerifyMs = results.reduce((sum, r) => sum + r.durationMs, 0) / results.length; + const throughput = (numProofs / wallMs) * 1000; + + benchResults.push( + { name: `BatchVerify/16_real_proofs/${numCores}_cores/wall_time`, value: wallMs, unit: 'ms' }, + { name: `BatchVerify/16_real_proofs/${numCores}_cores/avg_verify`, value: avgVerifyMs, unit: 'ms' }, + { name: `BatchVerify/16_real_proofs/${numCores}_cores/throughput`, value: throughput, unit: 'proofs/sec' }, + ); + + logger.info(`16 proofs, ${numCores} cores`, { + wallMs: Math.ceil(wallMs), + avgVerifyMs: Math.ceil(avgVerifyMs), + throughput: throughput.toFixed(2), + }); + } finally { + await verifier.stop(); + } + }); + } + + // -- Batch size sweep -- + + for (const batchSize of [2, 4, 8]) { + it(`batch_size sweep: 8 proofs, batch_size=${batchSize}, 8 cores`, async () => { + const numProofs = 8; + const proofs = Array.from({ length: numProofs }, () => ({ vkIndex: 0, proofFields: validProofFields })); + + const verifier = await BatchChonkVerifier.newForTesting({ bbChonkVerifyConcurrency: 8 }, [vk], batchSize); + + try { + const wallStart = performance.now(); + const results = await Promise.all(proofs.map(p => verifier.enqueueProof(p.vkIndex, p.proofFields))); + const wallMs = performance.now() - wallStart; + + expect(results).toHaveLength(numProofs); + expect(results.every(r => r.valid)).toBe(true); + + const throughput = (numProofs / wallMs) * 1000; + + benchResults.push( + { name: `BatchVerify/batch_size_${batchSize}/wall_time`, value: wallMs, unit: 'ms' }, + { name: `BatchVerify/batch_size_${batchSize}/throughput`, value: throughput, unit: 'proofs/sec' }, + ); + + logger.info(`batch_size=${batchSize}`, { wallMs: Math.ceil(wallMs), throughput: throughput.toFixed(2) }); + } finally { + await verifier.stop(); + } + }); + } + + // -- Bisection overhead -- + + it('bisection overhead: 8 proofs with 2 bad, batch_size=8, 8 cores', async () => { + const numProofs = 8; + const numBad = 2; + const proofs = Array.from({ length: numProofs }, (_, i) => ({ + vkIndex: 0, + proofFields: i < numBad ? invalidProofFields : validProofFields, + })); + + const verifier = await BatchChonkVerifier.newForTesting({ bbChonkVerifyConcurrency: 8 }, [vk], 8); + + try { + const wallStart = performance.now(); + const results = await Promise.all(proofs.map(p => verifier.enqueueProof(p.vkIndex, p.proofFields))); + const wallMs = performance.now() - wallStart; + + expect(results).toHaveLength(numProofs); + expect(results.filter(r => r.valid)).toHaveLength(numProofs - numBad); + expect(results.filter(r => !r.valid)).toHaveLength(numBad); + + benchResults.push({ + name: `BatchVerify/mixed_${numBad}_bad_of_${numProofs}/wall_time`, + value: wallMs, + unit: 'ms', + }); + logger.info(`mixed ${numBad} bad of ${numProofs}`, { wallMs: Math.ceil(wallMs) }); + } finally { + await verifier.stop(); + } + }); +}); diff --git a/yarn-project/ivc-integration/src/batch_verifier.test.ts b/yarn-project/ivc-integration/src/batch_verifier.test.ts new file mode 100644 index 000000000000..66dd1ca18cca --- /dev/null +++ b/yarn-project/ivc-integration/src/batch_verifier.test.ts @@ -0,0 +1,105 @@ +import { AztecClientBackend, BackendType, Barretenberg } from '@aztec/bb.js'; +import { createLogger } from '@aztec/foundation/log'; + +import { jest } from '@jest/globals'; + +import { corruptProofFields, runBatchVerifier } from './batch_verifier_test_helpers.js'; +import { generateTestingIVCStack } from './witgen.js'; + +const logger = createLogger('ivc-integration:test:batch-verifier'); + +jest.setTimeout(300_000); + +describe('Batch Chonk Verifier workloads', () => { + let bb: Barretenberg; + let validProofFields: Uint8Array[]; + let invalidProofFields: Uint8Array[]; + let vk: Uint8Array; + let validProofFields2: Uint8Array[]; + let vk2: Uint8Array; + + beforeAll(async () => { + bb = await Barretenberg.new({ backend: BackendType.NativeUnixSocket }); + await bb.initSRSChonk(); + + logger.info('Generating simple proof...'); + const [bytecodes1, witnesses1, , vks1] = await generateTestingIVCStack(1, 0); + const backend1 = new AztecClientBackend(bytecodes1, bb); + const [proofFields1, , generatedVk1] = await backend1.prove(witnesses1, vks1); + validProofFields = proofFields1; + invalidProofFields = corruptProofFields(validProofFields); + vk = generatedVk1; + + logger.info('Generating complex proof...'); + const [bytecodes2, witnesses2, , vks2] = await generateTestingIVCStack(1, 1); + const backend2 = new AztecClientBackend(bytecodes2, bb); + const [proofFields2, , generatedVk2] = await backend2.prove(witnesses2, vks2); + validProofFields2 = proofFields2; + vk2 = generatedVk2; + + logger.info('Proofs generated, ready for batch tests'); + }); + + afterAll(async () => { + await bb.destroy(); + }); + + it('should flush a single proof without waiting for a full batch', async () => { + const results = await runBatchVerifier({ + vks: [vk], + numCores: 0, + batchSize: 4, + proofs: [{ vkIndex: 0, proofFields: validProofFields }], + }); + expect(results).toHaveLength(1); + expect(results[0].valid).toBe(true); + }); + + it('should verify multiple proofs in parallel', async () => { + const results = await runBatchVerifier({ + vks: [vk], + numCores: 0, + batchSize: 8, + proofs: Array.from({ length: 4 }, () => ({ vkIndex: 0, proofFields: validProofFields })), + }); + expect(results).toHaveLength(4); + expect(results.every(r => r.valid)).toBe(true); + }); + + it('should handle mixed valid and invalid proofs in one batch', async () => { + const results = await runBatchVerifier({ + vks: [vk], + numCores: 0, + batchSize: 8, + proofs: [ + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 0, proofFields: invalidProofFields }, + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 0, proofFields: invalidProofFields }, + { vkIndex: 0, proofFields: validProofFields }, + ], + }); + expect(results).toHaveLength(5); + expect(results[0].valid).toBe(true); + expect(results[1].valid).toBe(false); + expect(results[2].valid).toBe(true); + expect(results[3].valid).toBe(false); + expect(results[4].valid).toBe(true); + }); + + it('should verify proofs with multiple VKs', async () => { + const results = await runBatchVerifier({ + vks: [vk, vk2], + numCores: 0, + batchSize: 8, + proofs: [ + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 1, proofFields: validProofFields2 }, + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 1, proofFields: validProofFields2 }, + ], + }); + expect(results).toHaveLength(4); + expect(results.every(r => r.valid)).toBe(true); + }); +}); diff --git a/yarn-project/ivc-integration/src/batch_verifier_queue.test.ts b/yarn-project/ivc-integration/src/batch_verifier_queue.test.ts new file mode 100644 index 000000000000..466c2072512a --- /dev/null +++ b/yarn-project/ivc-integration/src/batch_verifier_queue.test.ts @@ -0,0 +1,257 @@ +/** + * Batch chonk verifier queue robustness tests. + * + * Exercises edge cases: sub-batch flush, single proof, degenerate batch sizes, + * all-invalid, mixed valid/invalid with bisection, sequential start/stop cycles, + * and core count extremes. + * + * All tests exercise the full BatchChonkVerifier TS class (not raw bb.js). + */ +import { AztecClientBackend, BackendType, Barretenberg } from '@aztec/bb.js'; +import { createLogger } from '@aztec/foundation/log'; + +import { jest } from '@jest/globals'; + +import { corruptProofFields, runBatchVerifier } from './batch_verifier_test_helpers.js'; +import { generateTestingIVCStack } from './witgen.js'; + +const logger = createLogger('ivc-integration:test:batch-verifier-queue'); + +jest.setTimeout(600_000); + +describe('Batch Chonk Verifier Queue', () => { + let bb: Barretenberg; + let validProofFields: Uint8Array[]; + let invalidProofFields: Uint8Array[]; + let vk: Uint8Array; + + beforeAll(async () => { + bb = await Barretenberg.new({ backend: BackendType.NativeUnixSocket }); + await bb.initSRSChonk(); + + logger.info('Generating proof for tests...'); + const [bytecodes, witnesses, , vks] = await generateTestingIVCStack(1, 0); + const backend = new AztecClientBackend(bytecodes, bb); + const [proofFields, , generatedVk] = await backend.prove(witnesses, vks); + validProofFields = proofFields; + invalidProofFields = corruptProofFields(validProofFields); + vk = generatedVk; + logger.info('Proof generated'); + }); + + afterAll(async () => { + await bb.destroy(); + }); + + /** Shorthand for runBatchVerifier with common config. */ + function run(opts: { + numCores?: number; + batchSize?: number; + proofs: { vkIndex: number; proofFields: Uint8Array[] }[]; + }) { + return runBatchVerifier({ + vks: [vk], + numCores: opts.numCores ?? 4, + batchSize: opts.batchSize ?? 8, + proofs: opts.proofs, + }); + } + + // -- Basic cases -- + + it('single valid proof', async () => { + const results = await run({ + proofs: [{ vkIndex: 0, proofFields: validProofFields }], + }); + expect(results).toHaveLength(1); + expect(results[0].valid).toBe(true); + }); + + it('single invalid proof', async () => { + const results = await run({ + proofs: [{ vkIndex: 0, proofFields: invalidProofFields }], + }); + expect(results).toHaveLength(1); + expect(results[0].valid).toBe(false); + }); + + // -- Sub-batch flush: N < batch_size -- + + for (const n of [1, 2, 3, 5, 7]) { + it(`flushes ${n} proof(s) with batch_size=8`, async () => { + const proofs = Array.from({ length: n }, () => ({ vkIndex: 0, proofFields: validProofFields })); + const results = await run({ proofs }); + expect(results).toHaveLength(n); + expect(results.every(r => r.valid)).toBe(true); + }); + } + + // -- Exact batch boundary -- + + it('N exactly equals batch_size', async () => { + const results = await run({ + batchSize: 4, + proofs: Array.from({ length: 4 }, () => ({ vkIndex: 0, proofFields: validProofFields })), + }); + expect(results).toHaveLength(4); + expect(results.every(r => r.valid)).toBe(true); + }); + + it('N is one more than batch_size', async () => { + const results = await run({ + batchSize: 4, + proofs: Array.from({ length: 5 }, () => ({ vkIndex: 0, proofFields: validProofFields })), + }); + expect(results).toHaveLength(5); + expect(results.every(r => r.valid)).toBe(true); + }); + + // -- Degenerate batch_size=1 (every proof is its own batch) -- + + it('batch_size=1 verifies each proof individually', async () => { + const results = await run({ + batchSize: 1, + proofs: [ + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 0, proofFields: invalidProofFields }, + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 0, proofFields: invalidProofFields }, + ], + }); + expect(results).toHaveLength(4); + expect(results[0].valid).toBe(true); + expect(results[1].valid).toBe(false); + expect(results[2].valid).toBe(true); + expect(results[3].valid).toBe(false); + }); + + // -- All invalid -- + + it('all proofs invalid', async () => { + const proofs = Array.from({ length: 8 }, () => ({ vkIndex: 0, proofFields: invalidProofFields })); + const results = await run({ batchSize: 4, proofs }); + expect(results).toHaveLength(8); + expect(results.every(r => !r.valid)).toBe(true); + }); + + // -- Mixed valid/invalid with bisection -- + + it('1 bad out of 8 (bisection identifies it)', async () => { + const proofs = Array.from({ length: 8 }, (_, i) => ({ + vkIndex: 0, + proofFields: i === 3 ? invalidProofFields : validProofFields, + })); + const results = await run({ batchSize: 8, proofs }); + expect(results).toHaveLength(8); + expect(results[3].valid).toBe(false); + expect(results.filter(r => r.valid)).toHaveLength(7); + }); + + it('bad proofs at batch boundaries', async () => { + const proofs = Array.from({ length: 8 }, (_, i) => ({ + vkIndex: 0, + proofFields: i === 0 || i === 4 ? invalidProofFields : validProofFields, + })); + const results = await run({ batchSize: 4, proofs }); + expect(results).toHaveLength(8); + expect(results[0].valid).toBe(false); + expect(results[4].valid).toBe(false); + expect(results.filter(r => r.valid)).toHaveLength(6); + }); + + it('half bad proofs', async () => { + const proofs = Array.from({ length: 16 }, (_, i) => ({ + vkIndex: 0, + proofFields: i % 2 === 0 ? invalidProofFields : validProofFields, + })); + const results = await run({ numCores: 8, batchSize: 8, proofs }); + expect(results).toHaveLength(16); + expect(results.filter(r => r.valid)).toHaveLength(8); + expect(results.filter(r => !r.valid)).toHaveLength(8); + }); + + // -- Core count extremes -- + + it('works with numCores=1', async () => { + const proofs = Array.from({ length: 4 }, () => ({ vkIndex: 0, proofFields: validProofFields })); + const results = await run({ numCores: 1, batchSize: 4, proofs }); + expect(results).toHaveLength(4); + expect(results.every(r => r.valid)).toBe(true); + }); + + it('16 cores, batch_size=16, 32 proofs', async () => { + const proofs = Array.from({ length: 32 }, () => ({ vkIndex: 0, proofFields: validProofFields })); + const results = await run({ numCores: 16, batchSize: 16, proofs }); + expect(results).toHaveLength(32); + expect(results.every(r => r.valid)).toBe(true); + }); + + // -- Sequential start/stop cycles -- + + it('can start, verify, stop, then start again', async () => { + const results1 = await run({ + batchSize: 4, + proofs: Array.from({ length: 4 }, () => ({ vkIndex: 0, proofFields: validProofFields })), + }); + expect(results1).toHaveLength(4); + expect(results1.every(r => r.valid)).toBe(true); + + const results2 = await run({ + numCores: 8, + batchSize: 2, + proofs: [ + { vkIndex: 0, proofFields: validProofFields }, + { vkIndex: 0, proofFields: invalidProofFields }, + { vkIndex: 0, proofFields: validProofFields }, + ], + }); + expect(results2).toHaveLength(3); + expect(results2[0].valid).toBe(true); + expect(results2[1].valid).toBe(false); + expect(results2[2].valid).toBe(true); + }); + + // -- Random bisection patterns -- + + /** Simple seeded PRNG for deterministic randomness in tests. */ + function seededRandom(seed: number): () => number { + let s = seed; + return () => { + s = (s * 1664525 + 1013904223) & 0xffffffff; + return (s >>> 0) / 0xffffffff; + }; + } + + for (const { totalProofs, batchSize, numBad, seed } of [ + { totalProofs: 8, batchSize: 8, numBad: 1, seed: 42 }, + { totalProofs: 8, batchSize: 8, numBad: 3, seed: 123 }, + { totalProofs: 8, batchSize: 4, numBad: 2, seed: 999 }, + { totalProofs: 12, batchSize: 4, numBad: 4, seed: 7 }, + { totalProofs: 16, batchSize: 8, numBad: 5, seed: 314 }, + { totalProofs: 6, batchSize: 3, numBad: 3, seed: 271 }, + { totalProofs: 10, batchSize: 10, numBad: 1, seed: 555 }, + { totalProofs: 4, batchSize: 2, numBad: 4, seed: 0 }, + ]) { + it(`random pattern: ${numBad} bad in ${totalProofs}, batchSize=${batchSize}, seed=${seed}`, async () => { + const rng = seededRandom(seed); + const indices = Array.from({ length: totalProofs }, (_, i) => i); + for (let i = indices.length - 1; i > 0; i--) { + const j = Math.floor(rng() * (i + 1)); + [indices[i], indices[j]] = [indices[j], indices[i]]; + } + const badIndices = new Set(indices.slice(0, numBad)); + + const proofs = Array.from({ length: totalProofs }, (_, i) => ({ + vkIndex: 0, + proofFields: badIndices.has(i) ? invalidProofFields : validProofFields, + })); + + const results = await run({ batchSize, proofs }); + expect(results).toHaveLength(totalProofs); + + for (let i = 0; i < totalProofs; i++) { + expect(results[i].valid).toBe(!badIndices.has(i)); + } + }); + } +}); diff --git a/yarn-project/ivc-integration/src/batch_verifier_test_helpers.ts b/yarn-project/ivc-integration/src/batch_verifier_test_helpers.ts new file mode 100644 index 000000000000..18f7417230bc --- /dev/null +++ b/yarn-project/ivc-integration/src/batch_verifier_test_helpers.ts @@ -0,0 +1,32 @@ +import { BatchChonkVerifier } from '@aztec/bb-prover'; +import type { IVCProofVerificationResult } from '@aztec/stdlib/interfaces/server'; + +/** Corrupt flat proof fields by flipping bytes in an early field element. */ +export function corruptProofFields(fields: Uint8Array[]): Uint8Array[] { + const corrupted = fields.map(f => Uint8Array.from(f)); + corrupted[2] = Uint8Array.from(corrupted[2]); + corrupted[2][0] ^= 0xff; + corrupted[2][1] ^= 0xff; + return corrupted; +} + +/** Create a BatchChonkVerifier, enqueue proofs, collect results, and stop. */ +export async function runBatchVerifier(opts: { + vks: Uint8Array[]; + numCores: number; + batchSize: number; + proofs: { vkIndex: number; proofFields: Uint8Array[] }[]; +}): Promise { + const verifier = await BatchChonkVerifier.newForTesting( + { bbChonkVerifyConcurrency: opts.numCores }, + opts.vks, + opts.batchSize, + ); + + try { + const promises = opts.proofs.map(p => verifier.enqueueProof(p.vkIndex, p.proofFields)); + return await Promise.all(promises); + } finally { + await verifier.stop(); + } +} diff --git a/yarn-project/prover-client/src/config.ts b/yarn-project/prover-client/src/config.ts index 658558f9eb44..6ec2ac91bf54 100644 --- a/yarn-project/prover-client/src/config.ts +++ b/yarn-project/prover-client/src/config.ts @@ -44,14 +44,25 @@ export const bbConfigMappings: ConfigMappingsType = { }, numConcurrentIVCVerifiers: { env: 'BB_NUM_IVC_VERIFIERS', - description: 'Max number of chonk verifiers to run concurrently', + description: 'Max concurrent verifications for the RPC verifier (QueuedIVCVerifier).', ...numberConfigHelper(8), }, bbIVCConcurrency: { env: 'BB_IVC_CONCURRENCY', - description: 'Number of threads to use for IVC verification', + description: 'Thread count for the RPC IVC verifier.', ...numberConfigHelper(1), }, + bbChonkVerifyMaxBatch: { + env: 'BB_CHONK_VERIFY_MAX_BATCH', + description: + 'Upper bound on proofs per batch for the peer chonk batch verifier. Proofs are verified immediately as they arrive; this only caps how many can accumulate while a batch is already being processed.', + ...numberConfigHelper(16), + }, + bbChonkVerifyConcurrency: { + env: 'BB_CHONK_VERIFY_BATCH_CONCURRENCY', + description: 'Thread count for the peer batch verifier parallel reduce. 0 = auto.', + ...numberConfigHelper(0), + }, }; export const proverClientConfigMappings: ConfigMappingsType = { diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 73c71c03e3e9..671a0bc68890 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -104,8 +104,10 @@ export class TestContext { bbBinaryPath: config.expectedBBPath, bbWorkingDirectory: config.bbWorkingDirectory, bbSkipCleanup: config.bbSkipCleanup, - numConcurrentIVCVerifiers: 2, + numConcurrentIVCVerifiers: 8, bbIVCConcurrency: 1, + bbChonkVerifyMaxBatch: 16, + bbChonkVerifyConcurrency: 0, }; localProver = await createProver(bbConfig); } diff --git a/yarn-project/txe/src/state_machine/index.ts b/yarn-project/txe/src/state_machine/index.ts index 5976e9f346a6..ca44362beee2 100644 --- a/yarn-project/txe/src/state_machine/index.ts +++ b/yarn-project/txe/src/state_machine/index.ts @@ -59,6 +59,7 @@ export class TXEStateMachine { new MockEpochCache(), getPackageVersion() ?? '', new TestCircuitVerifier(), + new TestCircuitVerifier(), undefined, log, ); diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index a62105bd4c69..5e3e539b3f0f 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -931,6 +931,7 @@ __metadata: commander: "npm:^12.1.0" jest: "npm:^30.0.0" jest-mock-extended: "npm:^4.0.0" + msgpackr: "npm:^1.11.2" pako: "npm:^2.1.0" source-map-support: "npm:^0.5.21" ts-node: "npm:^10.9.1" From d4175e574706fba9e952857513feb4f157a4f4a5 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 23:08:57 -0300 Subject: [PATCH 33/43] fix: floor next L1 slot timestamp with latest block to prevent early proposals When the dateProvider wall clock is ahead of the latest mined L1 block, getNextL1SlotTimestamp can return a timestamp in a future L2 slot. The canProposeAt simulation passes (with time override) but the actual tx lands in an L1 block still in the previous slot, causing silent rejection. Floor the computed timestamp with latestBlock.timestamp + slotDuration to ensure we never target a slot beyond what the next L1 block can reach. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/publisher/sequencer-publisher.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts index 0d73ea840afc..90c125d0ac19 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts @@ -605,7 +605,7 @@ export class SequencerPublisher { * @param tipArchive - The archive to check * @returns The slot and block number if it is possible to propose, undefined otherwise */ - public canProposeAt( + public async canProposeAt( tipArchive: Fr, msgSender: EthAddress, opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {}, @@ -615,8 +615,7 @@ export class SequencerPublisher { const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled(); const slotOffset = pipelined ? this.aztecSlotDuration : 0n; - const l1Constants = this.epochCache.getL1Constants(); - const nextL1SlotTs = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants) + slotOffset; + const nextL1SlotTs = (await this.getNextL1SlotTimestampWithL1Floor()) + slotOffset; return this.rollupContract .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, { @@ -657,7 +656,7 @@ export class SequencerPublisher { flags, ] as const; - const ts = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), this.epochCache.getL1Constants()); + const ts = await this.getNextL1SlotTimestampWithL1Floor(); const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride( opts?.forcePendingCheckpointNumber, ); @@ -1590,4 +1589,21 @@ export class SequencerPublisher { }, }); } + + /** + * Returns the timestamp to use when simulating L1 proposal calls. + * Uses the wall-clock-based next L1 slot boundary, but floors it with the latest L1 block timestamp + * plus one slot duration. This prevents the sequencer from targeting a future L2 slot when the L1 + * chain hasn't caught up to the wall clock yet (e.g., the dateProvider is one L1 slot ahead of the + * latest mined block), which would cause the propose tx to land in an L1 block with block.timestamp + * still in the previous L2 slot. + * TODO(palla): Properly fix by keeping dateProvider synced with anvil's chain time on every block. + */ + private async getNextL1SlotTimestampWithL1Floor(): Promise { + const l1Constants = this.epochCache.getL1Constants(); + const fromWallClock = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants); + const latestBlock = await this.l1TxUtils.client.getBlock(); + const fromL1Block = latestBlock.timestamp + BigInt(l1Constants.ethereumSlotDuration); + return fromWallClock > fromL1Block ? fromWallClock : fromL1Block; + } } From 562e53d23d4ef04d61c35e6359068016ff5dc6e8 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 19 Mar 2026 23:19:11 -0300 Subject: [PATCH 34/43] fix: sync dateProvider from anvil stdout on every mined block Anvil logs block timestamps to stdout on each mined block. Parse these and update the TestDateProvider so it stays in lockstep with the L1 chain, eliminating drift between wall clock and anvil chain time. Also rename cheatCodes.timestamp() to lastBlockTimestamp() to clarify it returns the latest block's discrete timestamp, not the current time. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../aztec/src/testing/anvil_test_watcher.ts | 2 +- yarn-project/aztec/src/testing/cheat_codes.ts | 2 +- .../end-to-end/src/e2e_cheat_codes.test.ts | 8 ++-- .../src/e2e_crowdfunding_and_claim.test.ts | 2 +- .../end-to-end/src/e2e_p2p/add_rollup.test.ts | 2 +- .../end-to-end/src/e2e_synching.test.ts | 2 +- yarn-project/end-to-end/src/fixtures/setup.ts | 16 ++++---- .../src/simulators/lending_simulator.ts | 6 ++- .../ethereum/src/test/eth_cheat_codes.ts | 10 +++-- .../ethereum/src/test/start_anvil.test.ts | 37 +++++++++++++++++++ yarn-project/ethereum/src/test/start_anvil.ts | 27 +++++++++++++- 11 files changed, 90 insertions(+), 24 deletions(-) diff --git a/yarn-project/aztec/src/testing/anvil_test_watcher.ts b/yarn-project/aztec/src/testing/anvil_test_watcher.ts index 44ef6662a9c8..4eabc6583949 100644 --- a/yarn-project/aztec/src/testing/anvil_test_watcher.ts +++ b/yarn-project/aztec/src/testing/anvil_test_watcher.ts @@ -130,7 +130,7 @@ export class AnvilTestWatcher { return; } - const l1Time = (await this.cheatcodes.timestamp()) * 1000; + const l1Time = (await this.cheatcodes.lastBlockTimestamp()) * 1000; const wallTime = this.dateProvider.now(); if (l1Time > wallTime) { this.logger.warn(`L1 is ahead of wall time. Syncing wall time to L1 time`); diff --git a/yarn-project/aztec/src/testing/cheat_codes.ts b/yarn-project/aztec/src/testing/cheat_codes.ts index 94ebe0b87046..14413fd4ddd3 100644 --- a/yarn-project/aztec/src/testing/cheat_codes.ts +++ b/yarn-project/aztec/src/testing/cheat_codes.ts @@ -72,7 +72,7 @@ export class CheatCodes { * @param duration - The duration to advance time by (in seconds) */ async warpL2TimeAtLeastBy(sequencerClient: SequencerClient, node: AztecNode, duration: bigint | number) { - const currentTimestamp = await this.eth.timestamp(); + const currentTimestamp = await this.eth.lastBlockTimestamp(); const targetTimestamp = BigInt(currentTimestamp) + BigInt(duration); await this.warpL2TimeAtLeastTo(sequencerClient, node, targetTimestamp); } 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 27b0bf124dfc..5a4646542537 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 @@ -46,19 +46,19 @@ describe('e2e_cheat_codes', () => { it.each([100, 42, 99])(`setNextBlockTimestamp by %i`, async increment => { const blockNumber = await ethCheatCodes.blockNumber(); - const timestamp = await ethCheatCodes.timestamp(); + const timestamp = await ethCheatCodes.lastBlockTimestamp(); await ethCheatCodes.setNextBlockTimestamp(timestamp + increment); - expect(await ethCheatCodes.timestamp()).toBe(timestamp); + expect(await ethCheatCodes.lastBlockTimestamp()).toBe(timestamp); await ethCheatCodes.mine(); expect(await ethCheatCodes.blockNumber()).toBe(blockNumber + 1); - expect(await ethCheatCodes.timestamp()).toBe(timestamp + increment); + expect(await ethCheatCodes.lastBlockTimestamp()).toBe(timestamp + increment); }); it('setNextBlockTimestamp to a past timestamp throws', async () => { - const timestamp = await ethCheatCodes.timestamp(); + const timestamp = await ethCheatCodes.lastBlockTimestamp(); const pastTimestamp = timestamp - 1000; await expect(async () => await ethCheatCodes.setNextBlockTimestamp(pastTimestamp)).rejects.toThrow( 'Timestamp error', diff --git a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts index 68711711b23b..5668ba4e3e77 100644 --- a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts +++ b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts @@ -60,7 +60,7 @@ describe('e2e_crowdfunding_and_claim', () => { } = await setup(3)); // We set the deadline to a week from now - deadline = (await cheatCodes.eth.timestamp()) + 7 * 24 * 60 * 60; + deadline = (await cheatCodes.eth.lastBlockTimestamp()) + 7 * 24 * 60 * 60; ({ contract: donationToken } = await TokenContract.deploy( wallet, diff --git a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts index 2b3d00198fa4..c1a2c1db8da6 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts @@ -517,7 +517,7 @@ describe('e2e_p2p_add_rollup', () => { const futureEpoch = EpochNumber.fromBigInt(500n + BigInt(await newRollup.getCurrentEpochNumber())); const futureSlot = SlotNumber.fromBigInt(BigInt(futureEpoch) * BigInt(t.ctx.aztecNodeConfig.aztecEpochDuration)); const time = await newRollup.getTimestampForSlot(futureSlot); - if (time > BigInt(await t.ctx.cheatCodes.eth.timestamp())) { + if (time > BigInt(await t.ctx.cheatCodes.eth.lastBlockTimestamp())) { await t.ctx.cheatCodes.eth.warp(Number(time)); await waitL1Block(); } diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index dbf533d16e94..ece38c7340aa 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -470,7 +470,7 @@ describe('e2e_synching', () => { for (const checkpoint of checkpoints) { const lastBlock = checkpoint.blocks.at(-1)!; const targetTime = Number(lastBlock.header.globalVariables.timestamp) - ETHEREUM_SLOT_DURATION; - while ((await cheatCodes.eth.timestamp()) < targetTime) { + while ((await cheatCodes.eth.lastBlockTimestamp()) < targetTime) { await cheatCodes.eth.mine(); } // If it breaks here, first place you should look is the pruning. diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index 059c7c295899..5352d81ec8ca 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -302,6 +302,8 @@ export async function setup( config.dataDirectory = directoryToCleanup; } + const dateProvider = new TestDateProvider(); + if (!config.l1RpcUrls?.length) { if (!isAnvilTestChain(chain.id)) { throw new Error(`No ETHEREUM_HOSTS set but non anvil chain requested`); @@ -311,6 +313,7 @@ export async function setup( accounts: opts.anvilAccounts, port: opts.anvilPort ?? (process.env.ANVIL_PORT ? parseInt(process.env.ANVIL_PORT) : undefined), slotsInAnEpoch: opts.anvilSlotsInAnEpoch, + dateProvider, }); anvil = res.anvil; config.l1RpcUrls = [res.rpcUrl]; @@ -322,8 +325,6 @@ export async function setup( logger.info(`Logging metrics to ${filename}`); setupMetricsLogger(filename); } - - const dateProvider = new TestDateProvider(); const ethCheatCodes = new EthCheatCodesWithState(config.l1RpcUrls, dateProvider); if (opts.stateLoad) { @@ -419,11 +420,12 @@ export async function setup( await ethCheatCodes.setIntervalMining(config.ethereumSlotDuration); } - // Always sync dateProvider to L1 time after deploying L1 contracts, regardless of mining mode. - // In compose mode, L1 time may have drifted ahead of system time due to the local-network watcher - // warping time forward on each filled slot. Without this sync, the sequencer computes the wrong - // slot from its dateProvider and cannot propose blocks. - dateProvider.setTime((await ethCheatCodes.timestamp()) * 1000); + // In compose mode (no local anvil), sync dateProvider to L1 time since it may have drifted + // ahead of system time due to the local-network watcher warping time forward on each filled slot. + // When running with a local anvil, the dateProvider is kept in sync via the stdout listener. + if (!anvil) { + dateProvider.setTime((await ethCheatCodes.lastBlockTimestamp()) * 1000); + } if (opts.l2StartTime) { await ethCheatCodes.warp(opts.l2StartTime, { resetBlockInterval: true }); diff --git a/yarn-project/end-to-end/src/simulators/lending_simulator.ts b/yarn-project/end-to-end/src/simulators/lending_simulator.ts index 404bb3d5ad8d..ae299b31e249 100644 --- a/yarn-project/end-to-end/src/simulators/lending_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/lending_simulator.ts @@ -94,7 +94,9 @@ export class LendingSimulator { async prepare() { this.accumulator = BASE; - const slot = await this.rollup.getSlotAt(BigInt(await this.cc.eth.timestamp()) + BigInt(this.ethereumSlotDuration)); + const slot = await this.rollup.getSlotAt( + BigInt(await this.cc.eth.lastBlockTimestamp()) + BigInt(this.ethereumSlotDuration), + ); this.time = Number(await this.rollup.getTimestampForSlot(slot)); } @@ -103,7 +105,7 @@ export class LendingSimulator { return; } - const slot = await this.rollup.getSlotAt(BigInt(await this.cc.eth.timestamp())); + const slot = await this.rollup.getSlotAt(BigInt(await this.cc.eth.lastBlockTimestamp())); const targetSlot = SlotNumber(slot + diff); const ts = Number(await this.rollup.getTimestampForSlot(targetSlot)); const timeDiff = ts - this.time; diff --git a/yarn-project/ethereum/src/test/eth_cheat_codes.ts b/yarn-project/ethereum/src/test/eth_cheat_codes.ts index d62b194c0241..b1cbdd199d6f 100644 --- a/yarn-project/ethereum/src/test/eth_cheat_codes.ts +++ b/yarn-project/ethereum/src/test/eth_cheat_codes.ts @@ -85,10 +85,12 @@ export class EthCheatCodes { } /** - * Get the current timestamp - * @returns The current timestamp + * Get the timestamp of the latest mined L1 block. + * Note: this is NOT the current time — it's the discrete timestamp of the last block. + * Between blocks, the actual chain time advances but no new block reflects it. + * @returns The latest block timestamp in seconds */ - public async timestamp(): Promise { + public async lastBlockTimestamp(): Promise { const res = await this.doRpcCall('eth_getBlockByNumber', ['latest', true]); return parseInt(res.timestamp, 16); } @@ -552,7 +554,7 @@ export class EthCheatCodes { } public async syncDateProvider() { - const timestamp = await this.timestamp(); + const timestamp = await this.lastBlockTimestamp(); if ('setTime' in this.dateProvider) { this.dateProvider.setTime(timestamp * 1000); } diff --git a/yarn-project/ethereum/src/test/start_anvil.test.ts b/yarn-project/ethereum/src/test/start_anvil.test.ts index 7e3b899ef7f6..ebd0fd80a304 100644 --- a/yarn-project/ethereum/src/test/start_anvil.test.ts +++ b/yarn-project/ethereum/src/test/start_anvil.test.ts @@ -1,5 +1,6 @@ import { type Logger, createLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; +import { TestDateProvider } from '@aztec/foundation/timer'; import { createPublicClient, http, parseAbiItem } from 'viem'; @@ -52,4 +53,40 @@ describe('start_anvil', () => { stopWatching(); await sleep(100); }); + + it('syncs dateProvider to anvil block time on each mined block', async () => { + // Stop the default anvil instance (no dateProvider). + await anvil.stop(); + + const dateProvider = new TestDateProvider(); + const res = await startAnvil({ dateProvider }); + anvil = res.anvil; + rpcUrl = res.rpcUrl; + + const publicClient = createPublicClient({ transport: http(rpcUrl, { batch: false }) }); + + // Mine a block so anvil emits a "Block Time" line. + await publicClient.request({ method: 'evm_mine', params: [] } as any); + // Give the stdout listener time to fire. + await sleep(200); + + const block = await publicClient.getBlock({ blockTag: 'latest' }); + const blockTimeMs = Number(block.timestamp) * 1000; + // The dateProvider should now be within 2 seconds of the anvil block time. + // TestDateProvider.now() = Date.now() + offset, and setTime sets offset = blockTimeMs - Date.now(), + // so subsequent now() calls return blockTimeMs + elapsed. We check the difference is small. + expect(Math.abs(dateProvider.now() - blockTimeMs)).toBeLessThan(2000); + + // Warp anvil forward by 1000 seconds and verify the dateProvider follows. + const futureTimestamp = Number(block.timestamp) + 1000; + await publicClient.request({ + method: 'evm_setNextBlockTimestamp', + params: [futureTimestamp], + } as any); + await publicClient.request({ method: 'evm_mine', params: [] } as any); + await sleep(200); + + const futureTimeMs = futureTimestamp * 1000; + expect(Math.abs(dateProvider.now() - futureTimeMs)).toBeLessThan(2000); + }); }); diff --git a/yarn-project/ethereum/src/test/start_anvil.ts b/yarn-project/ethereum/src/test/start_anvil.ts index ba2f8b573604..f5ba8609e15f 100644 --- a/yarn-project/ethereum/src/test/start_anvil.ts +++ b/yarn-project/ethereum/src/test/start_anvil.ts @@ -1,5 +1,6 @@ import { createLogger } from '@aztec/foundation/log'; import { makeBackoff, retry } from '@aztec/foundation/retry'; +import type { TestDateProvider } from '@aztec/foundation/timer'; import { fileURLToPath } from '@aztec/foundation/url'; import { type ChildProcess, spawn } from 'child_process'; @@ -33,6 +34,12 @@ export async function startAnvil( * L1-finality-based logic work without needing hundreds of mined blocks. */ slotsInAnEpoch?: number; + /** + * If provided, the date provider will be synced to anvil's block time on every mined block. + * This keeps the dateProvider in lockstep with anvil's chain time, avoiding drift between + * the wall clock and the L1 chain when computing L1 slot timestamps. + */ + dateProvider?: TestDateProvider; } = {}, ): Promise<{ anvil: Anvil; methodCalls?: string[]; rpcUrl: string; stop: () => Promise }> { const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh'); @@ -108,12 +115,15 @@ export async function startAnvil( child.once('close', onClose); }); - // Continue piping for logging / method-call capture after startup. - if (logger || opts.captureMethodCalls) { + // Continue piping for logging, method-call capture, and/or dateProvider sync after startup. + if (logger || opts.captureMethodCalls || opts.dateProvider) { child.stdout?.on('data', (data: Buffer) => { const text = data.toString(); logger?.debug(text.trim()); methodCalls?.push(...(text.match(/eth_[^\s]+/g) || [])); + if (opts.dateProvider) { + syncDateProviderFromAnvilOutput(text, opts.dateProvider); + } }); child.stderr?.on('data', (data: Buffer) => { logger?.debug(data.toString().trim()); @@ -160,6 +170,19 @@ export async function startAnvil( return { anvil: anvilObj, methodCalls, stop, rpcUrl: `http://127.0.0.1:${port}` }; } +/** Extracts block time from anvil stdout and syncs the dateProvider. */ +function syncDateProviderFromAnvilOutput(text: string, dateProvider: TestDateProvider): void { + // Anvil logs mined blocks as: + // Block Time: "Fri, 20 Mar 2026 02:10:46 +0000" + const match = text.match(/Block Time:\s*"([^"]+)"/); + if (match) { + const blockTimeMs = new Date(match[1]).getTime(); + if (!isNaN(blockTimeMs)) { + dateProvider.setTime(blockTimeMs); + } + } +} + /** Send SIGTERM, wait up to 5 s, then SIGKILL. All timers are always cleared. */ function killChild(child: ChildProcess): Promise { return new Promise(resolve => { From fe065a5c1c8f3a4547116a7eec9ed8f2548f17f8 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Fri, 20 Mar 2026 11:34:52 -0300 Subject: [PATCH 35/43] fix suggestions --- .../ethereum/src/publisher_manager.test.ts | 124 +++++------------- .../ethereum/src/publisher_manager.ts | 105 +++++++-------- yarn-project/prover-node/src/factory.ts | 2 +- .../src/prover-publisher-factory.ts | 6 +- .../src/client/sequencer-client.ts | 17 +-- .../publisher/sequencer-publisher-factory.ts | 6 +- .../src/sequencer/sequencer.ts | 2 +- 7 files changed, 102 insertions(+), 160 deletions(-) diff --git a/yarn-project/ethereum/src/publisher_manager.test.ts b/yarn-project/ethereum/src/publisher_manager.test.ts index a94853bb8ba5..834f517dfe45 100644 --- a/yarn-project/ethereum/src/publisher_manager.test.ts +++ b/yarn-project/ethereum/src/publisher_manager.test.ts @@ -1,6 +1,5 @@ import { times } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { ManualDateProvider } from '@aztec/foundation/timer'; import { jest } from '@jest/globals'; import { type Hex, encodeFunctionData } from 'viem'; @@ -28,18 +27,20 @@ function expectedFundingData(addresses: EthAddress[], fundingAmount: bigint): He describe('PublisherManager', () => { let mockPublishers: (TestL1TxUtils & L1TxUtils)[]; let publisherManager: PublisherManager; - let dateProvider: ManualDateProvider; beforeEach(() => { jest.clearAllMocks(); - dateProvider = new ManualDateProvider(); + }); + + afterEach(async () => { + await publisherManager?.stop(); }); describe('constructor', () => { it('should initialize with publishers', () => { mockPublishers = createMockPublishers(3); - expect(() => new PublisherManager(mockPublishers, {}, dateProvider)).not.toThrow(); + expect(() => new PublisherManager(mockPublishers, {})).not.toThrow(); }); }); @@ -48,7 +49,7 @@ describe('PublisherManager', () => { beforeEach(() => { addresses = Array.from({ length: 3 }, () => EthAddress.random()); mockPublishers = createMockPublishers(3, addresses); - publisherManager = new PublisherManager(mockPublishers, {}, dateProvider); + publisherManager = new PublisherManager(mockPublishers, {}); }); it('should throw error when no valid publishers found', async () => { @@ -73,7 +74,7 @@ describe('PublisherManager', () => { mockPublishers[1].state = TxUtilsState.CANCELLED; mockPublishers[2].state = TxUtilsState.NOT_MINED; - publisherManager = new PublisherManager(mockPublishers, { publisherAllowInvalidStates: true }, dateProvider); + publisherManager = new PublisherManager(mockPublishers, { publisherAllowInvalidStates: true }); await expect(publisherManager.getAvailablePublisher(p => p.state === TxUtilsState.CANCELLED)).resolves.toBe( mockPublishers[1], ); @@ -150,7 +151,7 @@ describe('PublisherManager', () => { it('should prioritise same state publishers based on balance and then least recently used', async () => { const ethAddresses = Array.from({ length: 5 }, () => EthAddress.random()); mockPublishers = createMockPublishers(5, ethAddresses); - publisherManager = new PublisherManager(mockPublishers, {}, dateProvider); + publisherManager = new PublisherManager(mockPublishers, {}); const filter = (utils: L1TxUtils) => utils.getSenderAddress() !== mockPublishers[2].getSenderAddress(); // Filter out publisher in index 2 @@ -206,13 +207,17 @@ describe('PublisherManager', () => { return new PublisherManager( publishers, { publisherFundingThreshold: threshold, publisherFundingAmount: fundingAmount, ...config }, - dateProvider, { funder: funderInstance }, ); }; - /** Wait for the background funding promise to settle. */ - const waitForFunding = () => new Promise(resolve => setTimeout(resolve, 10)); + /** Start the manager and trigger one funding cycle via the RunningPromise. */ + const triggerFunding = async (manager: PublisherManager) => { + await manager.start(); + // RunningPromise calls the fn immediately on start, so we just need to wait for it to settle + await new Promise(resolve => setTimeout(resolve, 10)); + await manager.stop(); + }; beforeEach(() => { funder = new TestL1TxUtils(EthAddress.random()) as TestL1TxUtils & L1TxUtils; @@ -224,8 +229,7 @@ describe('PublisherManager', () => { mockPublishers[0].balance = 50n; // below threshold publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ @@ -240,8 +244,7 @@ describe('PublisherManager', () => { mockPublishers[0].balance = 200n; // above threshold publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); @@ -253,8 +256,7 @@ describe('PublisherManager', () => { mockPublishers[2].balance = 30n; // below publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); // Single multicall for both underfunded publishers expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); @@ -273,8 +275,7 @@ describe('PublisherManager', () => { mockPublishers[0].balance = 50n; publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ to: MULTI_CALL_3_ADDRESS, @@ -291,52 +292,21 @@ describe('PublisherManager', () => { funder.sendAndMonitorTransaction.mockRejectedValueOnce(new Error('tx failed')); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); - - // Single multicall attempted and failed — error caught by triggerFundingIfNeeded - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); - }); - - it('concurrent guard prevents overlapping funding runs', async () => { - mockPublishers = createMockPublishers(1); - mockPublishers[0].balance = 50n; - publisherManager = createFundedManager(mockPublishers, funder); - - // Block the first funding call on a deferred promise we control - let resolveFunding!: () => void; - const fundingBlocked = new Promise(r => (resolveFunding = r)); - funder.sendAndMonitorTransaction.mockImplementation(async () => { - await fundingBlocked; - return { receipt: { transactionHash: '0x1', status: 'success' }, state: {} }; - }); - - // First call triggers funding (sets isFunding = true) - await publisherManager.getAvailablePublisher(); - // Second call while funding is still in progress — should skip - await publisherManager.getAvailablePublisher(); - - // Release the blocked funding and let it complete - resolveFunding(); - await waitForFunding(); + await triggerFunding(publisherManager); + // Single multicall attempted and failed — error caught by RunningPromise expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); }); it('no funding triggered when no funder configured', async () => { mockPublishers = createMockPublishers(1); mockPublishers[0].balance = 50n; - publisherManager = new PublisherManager( - mockPublishers, - { - publisherFundingThreshold: threshold, - publisherFundingAmount: fundingAmount, - }, - dateProvider, - ); + publisherManager = new PublisherManager(mockPublishers, { + publisherFundingThreshold: threshold, + publisherFundingAmount: fundingAmount, + }); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); @@ -349,8 +319,7 @@ describe('PublisherManager', () => { publisherFundingAmount: undefined, }); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); @@ -361,8 +330,7 @@ describe('PublisherManager', () => { funder.balance = 30n; // less than fundingAmount (50n) publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); @@ -375,8 +343,7 @@ describe('PublisherManager', () => { funder.balance = 2n * fundingAmount; // enough for 2, not 3 publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); expect(funder.sendAndMonitorTransaction).toHaveBeenCalledWith({ @@ -398,36 +365,12 @@ describe('PublisherManager', () => { funder.balance = 5000n; publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); // Funding is fully disabled because funder overlaps with a publisher expect(funder.sendAndMonitorTransaction).not.toHaveBeenCalled(); }); - it('skips funding within cooldown and resumes after cooldown expires', async () => { - mockPublishers = createMockPublishers(1); - mockPublishers[0].balance = 50n; - publisherManager = createFundedManager(mockPublishers, funder); - - // First call triggers funding - await publisherManager.getAvailablePublisher(); - await waitForFunding(); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); - - // Second call within cooldown — funding should be skipped - dateProvider.advanceTime(60); // 1 minute, less than 2 min cooldown - await publisherManager.getAvailablePublisher(); - await waitForFunding(); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); - - // Third call after cooldown expires — funding should trigger again - dateProvider.advanceTime(2 * 60); // another 2 minutes - await publisherManager.getAvailablePublisher(); - await waitForFunding(); - expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(2); - }); - it('funds publishers in busy states', async () => { mockPublishers = createMockPublishers(2); mockPublishers[0].balance = 50n; @@ -436,8 +379,7 @@ describe('PublisherManager', () => { mockPublishers[1].state = TxUtilsState.SENT; // busy publisherManager = createFundedManager(mockPublishers, funder); - await publisherManager.getAvailablePublisher(); - await waitForFunding(); + await triggerFunding(publisherManager); // Single multicall funds both, even the busy one expect(funder.sendAndMonitorTransaction).toHaveBeenCalledTimes(1); @@ -479,4 +421,8 @@ class TestL1TxUtils { public getSenderAddress() { return this.senderAddress; } + + public async loadStateAndResumeMonitoring() {} + + public interrupt() {} } diff --git a/yarn-project/ethereum/src/publisher_manager.ts b/yarn-project/ethereum/src/publisher_manager.ts index f4c6e2dc0061..11149b7b0465 100644 --- a/yarn-project/ethereum/src/publisher_manager.ts +++ b/yarn-project/ethereum/src/publisher_manager.ts @@ -1,6 +1,6 @@ import { pick } from '@aztec/foundation/collection'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; -import { DateProvider } from '@aztec/foundation/timer'; +import { RunningPromise } from '@aztec/foundation/running-promise'; import { Multicall3 } from './contracts/multicall.js'; import { L1TxUtils, TxUtilsState } from './l1_tx_utils/index.js'; @@ -40,14 +40,12 @@ export class PublisherManager { private log: Logger; private config: PublisherManagerConfig; private static readonly FUNDING_CHECK_INTERVAL_MS = 2 * 60 * 1000; - private isFunding = false; - private lastFundingCheckAt = 0; private funder?: UtilsType; + private fundingPromise?: RunningPromise; constructor( private publishers: UtilsType[], config: PublisherManagerConfig, - private dateProvider: DateProvider, opts?: { bindings?: LoggerBindings; funder?: UtilsType }, ) { this.funder = opts?.funder; @@ -71,9 +69,32 @@ export class PublisherManager { } } - /** Loads the state of all publishers and resumes monitoring any pending txs */ - public async loadState(): Promise { - await Promise.all(this.publishers.map(pub => pub.loadStateAndResumeMonitoring())); + /** Loads the state of all publishers and the funder, and starts periodic funding checks. */ + public async start(): Promise { + await Promise.all([ + ...this.publishers.map(pub => pub.loadStateAndResumeMonitoring()), + this.funder?.loadStateAndResumeMonitoring(), + ]); + + if ( + this.funder && + this.config.publisherFundingThreshold !== undefined && + this.config.publisherFundingAmount !== undefined + ) { + this.fundingPromise = new RunningPromise( + () => this.triggerFundingIfNeeded(), + this.log, + PublisherManager.FUNDING_CHECK_INTERVAL_MS, + ); + this.fundingPromise.start(); + } + } + + /** Stops the funding loop and interrupts all publishers. */ + public async stop(): Promise { + await this.fundingPromise?.stop(); + this.publishers.forEach(pub => pub.interrupt()); + this.funder?.interrupt(); } // Finds and prioritises available publishers based on @@ -128,64 +149,44 @@ export class PublisherManager { return lastUsedComparison; }); - void this.triggerFundingIfNeeded().catch(err => this.log.error('Error in funding check', { err })); - return sortedPublishers[0].publisher; } - public interrupt() { - this.publishers.forEach(pub => pub.interrupt()); - } - - /** Check all publisher balances and fund those below threshold (background, non-blocking). */ + /** Check all publisher balances and fund those below threshold. */ private async triggerFundingIfNeeded(): Promise { const { funder, config } = this; if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) { return; } - const now = this.dateProvider.now(); - if (now - this.lastFundingCheckAt < PublisherManager.FUNDING_CHECK_INTERVAL_MS) { - this.log.trace(`Skipping funding check`, { msSinceLastCheck: now - this.lastFundingCheckAt }); - return; - } - this.lastFundingCheckAt = now; - if (this.isFunding) { + + const allBalances = await Promise.all( + this.publishers.map(async pub => ({ balance: await pub.getSenderBalance(), publisher: pub })), + ); + const lowBalance = allBalances.filter(p => p.balance < config.publisherFundingThreshold!); + if (lowBalance.length === 0) { return; } - this.isFunding = true; - try { - const allBalances = await Promise.all( - this.publishers.map(async pub => ({ balance: await pub.getSenderBalance(), publisher: pub })), - ); - const lowBalance = allBalances.filter(p => p.balance < config.publisherFundingThreshold!); - if (lowBalance.length === 0) { - return; - } - - const fundingAmount = config.publisherFundingAmount!; - const funderBalance = await funder.getSenderBalance(); - - if (funderBalance < 10n * fundingAmount) { - this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount }); - } - const affordableCount = Number(funderBalance / fundingAmount); - if (affordableCount === 0) { - this.log.error(`Funding account balance too low to fund any publisher`, { funderBalance, fundingAmount }); - return; - } - if (affordableCount < lowBalance.length) { - this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, { - funderBalance, - fundingAmount, - }); - } + const fundingAmount = config.publisherFundingAmount!; + const funderBalance = await funder.getSenderBalance(); - const toFund = lowBalance.slice(0, affordableCount).map(p => p.publisher); - await this.fundPublishers(toFund); - } finally { - this.isFunding = false; + if (funderBalance < 10n * fundingAmount) { + this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount }); + } + const affordableCount = Number(funderBalance / fundingAmount); + if (affordableCount === 0) { + this.log.error(`Funding account balance too low to fund any publisher`, { funderBalance, fundingAmount }); + return; + } + if (affordableCount < lowBalance.length) { + this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, { + funderBalance, + fundingAmount, + }); } + + const toFund = lowBalance.slice(0, affordableCount).map(p => p.publisher); + await this.fundPublishers(toFund); } /** Fund publishers via a single Multicall3 aggregate3Value transaction. */ diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index afbb111bc2ab..6f2ceda0926d 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -136,7 +136,7 @@ export async function createProverNode( deps.publisherFactory ?? new ProverPublisherFactory(config, { rollupContract, - publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), dateProvider, { + publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), { bindings: log.getBindings(), funder: funderL1TxUtils, }), diff --git a/yarn-project/prover-node/src/prover-publisher-factory.ts b/yarn-project/prover-node/src/prover-publisher-factory.ts index 8e1b88d1560e..054d742cf02a 100644 --- a/yarn-project/prover-node/src/prover-publisher-factory.ts +++ b/yarn-project/prover-node/src/prover-publisher-factory.ts @@ -19,11 +19,11 @@ export class ProverPublisherFactory { ) {} public async start() { - await this.deps.publisherManager.loadState(); + await this.deps.publisherManager.start(); } - public stop() { - this.deps.publisherManager.interrupt(); + public async stop() { + await this.deps.publisherManager.stop(); } /** diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index 43de60b2514a..077ca90eb54b 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -93,15 +93,10 @@ export class SequencerClient { publicClient, l1TxUtils.map(x => x.getSenderAddress()), ); - const publisherManager = new PublisherManager( - l1TxUtils, - getPublisherConfigFromSequencerConfig(config), - deps.dateProvider, - { - bindings: log.getBindings(), - funder: deps.funderL1TxUtils, - }, - ); + const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), { + bindings: log.getBindings(), + funder: deps.funderL1TxUtils, + }); const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString()); const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([ rollupContract.getL1GenesisTime(), @@ -216,7 +211,7 @@ export class SequencerClient { await this.validatorClient?.start(); this.sequencer.start(); this.l1Metrics?.start(); - await this.publisherManager.loadState(); + await this.publisherManager.start(); } /** @@ -225,7 +220,7 @@ export class SequencerClient { public async stop() { await this.sequencer.stop(); await this.validatorClient?.stop(); - this.publisherManager.interrupt(); + await this.publisherManager.stop(); this.l1Metrics?.stop(); } diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher-factory.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher-factory.ts index 0583f969d49e..d9ed0a7ccc30 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher-factory.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher-factory.ts @@ -117,8 +117,8 @@ export class SequencerPublisherFactory { }; } - /** Interrupts all publishers managed by this factory. Used during sequencer shutdown. */ - public interruptAll(): void { - this.deps.publisherManager.interrupt(); + /** Stops all publishers managed by this factory. Used during sequencer shutdown. */ + public async stopAll(): Promise { + await this.deps.publisherManager.stop(); } } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index d75788ea3cf4..79cc973c9ab4 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -147,7 +147,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter { this.log.info(`Stopping sequencer`); this.setState(SequencerState.STOPPING, undefined, { force: true }); - this.publisherFactory.interruptAll(); + await this.publisherFactory.stopAll(); await this.runningPromise?.stop(); this.setState(SequencerState.STOPPED, undefined, { force: true }); this.log.info('Stopped sequencer'); From d78f6c9ec1754096df09729ff61991d3749767d5 Mon Sep 17 00:00:00 2001 From: Nikita Meshcheriakov Date: Fri, 20 Mar 2026 12:06:16 -0300 Subject: [PATCH 36/43] increase timeouts --- .../end-to-end/src/e2e_publisher_funding_multi.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts index f378036041a9..e48477c8fefa 100644 --- a/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts +++ b/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts @@ -126,8 +126,8 @@ describe('e2e_publisher_funding_multi', () => { const funderBalanceBefore = await ethCheatCodes.getBalance(funderAddress); - // The sequencer periodically calls getAvailablePublisher(), which triggers funding - // when it sees publisher balances are below threshold. Both should be funded in a single multicall. + // The RunningPromise checks funding every 2 minutes, so we need to wait long enough + // for the next cycle to detect the low balances and fund both publishers. await retryUntil( async () => { const balance1 = await ethCheatCodes.getBalance(publisher1Address); @@ -135,7 +135,7 @@ describe('e2e_publisher_funding_multi', () => { return balance1 > LOW_BALANCE && balance2 > LOW_BALANCE ? true : undefined; }, 'waiting for both publishers to be funded', - 60, + 180, 1, ); @@ -168,7 +168,7 @@ describe('e2e_publisher_funding_multi', () => { return spent >= FUNDING_AMOUNT ? true : undefined; }, 'waiting for second funding round', - 120, + 180, 1, ); From af4228dffbf492a7496bd8f9d3e26823835abc76 Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:11:28 -0400 Subject: [PATCH 37/43] fix: use correct EthCheatCodes method name in epochs_missed_l1_slot test (#21848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fix TypeScript compilation error in the new `epochs_missed_l1_slot.test.ts` introduced by #21769. The test calls `eth.timestamp()` but the method on `EthCheatCodes` is actually `lastBlockTimestamp()`. This caused `yarn tsgo -b --emitDeclarationOnly` to fail with: ``` error TS2339: Property 'timestamp' does not exist on type 'EthCheatCodes'. ``` ## Fix Changed `eth.timestamp()` → `eth.lastBlockTimestamp()` on line 104 of the test file. ClaudeBox log: https://claudebox.work/s/e62ab0794047aa8c?run=1 --- .../end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts index 1fe3b1a9a47f..3e863302f2c3 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_slot.test.ts @@ -101,7 +101,7 @@ describe('e2e_epochs/epochs_missed_l1_slot', () => { await eth.setAutomine(false); await eth.setIntervalMining(0, { silent: true }); - const frozenL1Timestamp = await eth.timestamp(); + const frozenL1Timestamp = await eth.lastBlockTimestamp(); logger.info(`L1 mining paused at L1 timestamp ${frozenL1Timestamp}`); // Step 3: Wait until the sequencer reaches PUBLISHING_CHECKPOINT during the mining pause. From c3c62308d86075e3934f3306622414aa76968fb9 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 20 Mar 2026 12:20:23 -0300 Subject: [PATCH 38/43] feat(p2p): add tx validator for contract class id verification (#21788) ## Motivation Contract class registration events contain a class ID alongside the fields needed to recompute it (artifactHash, privateFunctionsRoot, packed bytecode). This adds a validation to avoid a malicious tx from registering a class with a mismatched ID if it found a bug in the registry contract, poisoning the archiver's contract data. ## Approach Made `toContractClassPublic()` a simple synchronous conversion with no validation (symmetric with how `toContractInstance()` works for contract instances). Validation is done explicitly at each call site that needs it: the `DataTxValidator` and the archiver's `updatePublishedContractClasses`. ## Changes - **protocol-contracts**: `toContractClassPublic()` is now sync and returns `ContractClassPublic` without validation or bytecode commitment; `toContractClassPublicWithBytecodeCommitment()` adds the commitment but also does not validate - **stdlib**: Added `TX_ERROR_INCORRECT_CONTRACT_CLASS_ID` and `TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG` error constants; added elapsed timing test for `computeContractClassId` with real artifacts - **p2p**: Extended `DataTxValidator` with explicit contract class ID verification (recomputes class ID from event fields and compares) - **p2p (tests)**: Added contract class ID validation tests to `data_validator.test.ts`; updated factory tests - **archiver**: `updatePublishedContractClasses` explicitly validates class IDs and collects bytecode commitments in a single pass; `addContractClasses` now takes `ContractClassPublicWithCommitment[]` instead of separate arrays - **simulator**: Simplified `addContractClassesFromEvents` and callers from async to sync since `toContractClassPublic()` is no longer async Co-authored-by: Claude Opus 4.6 (1M context) --- .claude/settings.local.json | 11 ++ yarn-project/archiver/src/factory.ts | 9 +- .../src/modules/data_store_updater.ts | 45 +++++-- .../src/store/kv_archiver_store.test.ts | 37 +++--- .../archiver/src/store/kv_archiver_store.ts | 12 +- .../tx_validator/data_validator.test.ts | 123 +++++++++++++++++- .../tx_validator/data_validator.ts | 43 +++++- .../tx_validator/phases_validator.ts | 2 +- .../contract_class_published_event.ts | 29 ++--- .../simulator/src/public/public_db_sources.ts | 24 ++-- .../public_processor/public_processor.test.ts | 4 +- .../public_processor/public_processor.ts | 2 +- .../contract_provider_for_cpp.ts | 3 +- .../public_tx_simulator.ts | 4 +- .../src/contract/contract_class_id.test.ts | 19 +++ .../stdlib/src/tx/validator/error_texts.ts | 4 + 16 files changed, 289 insertions(+), 82 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000000..9ddcfd61b65a --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "allow": [ + "Bash(gh pr:*)", + "Bash(curl -s -X POST https://ethereum-sepolia-rpc.publicnode.com -H \"Content-Type: application/json\" -d '{\"\"\"\"jsonrpc\"\"\"\":\"\"\"\"2.0\"\"\"\",\"\"\"\"method\"\"\"\":\"\"\"\"eth_getBlockByNumber\"\"\"\",\"\"\"\"params\"\"\"\":[\"\"\"\"finalized\"\"\"\",false],\"\"\"\"id\"\"\"\":1}')", + "Bash(curl -s -X POST https://ethereum-rpc.publicnode.com -H \"Content-Type: application/json\" -d '{\"\"\"\"jsonrpc\"\"\"\":\"\"\"\"2.0\"\"\"\",\"\"\"\"method\"\"\"\":\"\"\"\"eth_getBlockByNumber\"\"\"\",\"\"\"\"params\"\"\"\":[\"\"\"\"finalized\"\"\"\",false],\"\"\"\"id\"\"\"\":1}')", + "Bash(curl -s -X POST https://ethereum-sepolia-rpc.publicnode.com -H \"Content-Type: application/json\" -d '{\"\"\"\"jsonrpc\"\"\"\":\"\"\"\"2.0\"\"\"\",\"\"\"\"method\"\"\"\":\"\"\"\"eth_getBlockByNumber\"\"\"\",\"\"\"\"params\"\"\"\":[\"\"\"\"latest\"\"\"\",false],\"\"\"\"id\"\"\"\":1}')", + "Bash(curl -s -X POST https://ethereum-rpc.publicnode.com -H \"Content-Type: application/json\" -d '{\"\"\"\"jsonrpc\"\"\"\":\"\"\"\"2.0\"\"\"\",\"\"\"\"method\"\"\"\":\"\"\"\"eth_getBlockByNumber\"\"\"\",\"\"\"\"params\"\"\"\":[\"\"\"\"latest\"\"\"\",false],\"\"\"\"id\"\"\"\":1}')" + ] + } +} diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index 71571e94c1c6..db7b06b5b1da 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -13,7 +13,7 @@ import { protocolContractNames } from '@aztec/protocol-contracts'; import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle'; import { FunctionType, decodeFunctionSignature } from '@aztec/stdlib/abi'; import type { ArchiverEmitter } from '@aztec/stdlib/block'; -import { type ContractClassPublic, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; +import { type ContractClassPublicWithCommitment, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import type { DataStoreConfig } from '@aztec/stdlib/kv-store'; import { getTelemetryClient } from '@aztec/telemetry-client'; @@ -185,8 +185,10 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) { continue; } - const contractClassPublic: ContractClassPublic = { + const publicBytecodeCommitment = await computePublicBytecodeCommitment(contract.contractClass.packedBytecode); + const contractClassPublic: ContractClassPublicWithCommitment = { ...contract.contractClass, + publicBytecodeCommitment, }; const publicFunctionSignatures = contract.artifact.functions @@ -194,8 +196,7 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) { .map(fn => decodeFunctionSignature(fn.name, fn.parameters)); await store.registerContractFunctionSignatures(publicFunctionSignatures); - const bytecodeCommitment = await computePublicBytecodeCommitment(contractClassPublic.packedBytecode); - await store.addContractClasses([contractClassPublic], [bytecodeCommitment], BlockNumber(blockNumber)); + await store.addContractClasses([contractClassPublic], BlockNumber(blockNumber)); await store.addContractInstances([contract.instance], BlockNumber(blockNumber)); } } diff --git a/yarn-project/archiver/src/modules/data_store_updater.ts b/yarn-project/archiver/src/modules/data_store_updater.ts index 3c0513bdaa59..cd165b562e28 100644 --- a/yarn-project/archiver/src/modules/data_store_updater.ts +++ b/yarn-project/archiver/src/modules/data_store_updater.ts @@ -8,7 +8,11 @@ import { } from '@aztec/protocol-contracts/instance-registry'; import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block'; import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint'; -import { computeContractAddressFromInstance, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; +import { + type ContractClassPublicWithCommitment, + computeContractAddressFromInstance, + computeContractClassId, +} from '@aztec/stdlib/contract'; import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs'; import type { UInt64 } from '@aztec/stdlib/types'; @@ -315,18 +319,37 @@ export class ArchiverDataStoreUpdater { .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log)) .map(log => ContractClassPublishedEvent.fromLog(log)); - const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic())); - if (contractClasses.length > 0) { - contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`)); - if (operation == Operation.Store) { - // TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive - const commitments = await Promise.all( - contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)), - ); - return await this.store.addContractClasses(contractClasses, commitments, blockNum); - } else if (operation == Operation.Delete) { + if (operation == Operation.Delete) { + const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic()); + if (contractClasses.length > 0) { + contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`)); return await this.store.deleteContractClasses(contractClasses, blockNum); } + return true; + } + + // Compute bytecode commitments and validate class IDs in a single pass. + const contractClasses: ContractClassPublicWithCommitment[] = []; + for (const event of contractClassPublishedEvents) { + const contractClass = await event.toContractClassPublicWithBytecodeCommitment(); + const computedClassId = await computeContractClassId({ + artifactHash: contractClass.artifactHash, + privateFunctionsRoot: contractClass.privateFunctionsRoot, + publicBytecodeCommitment: contractClass.publicBytecodeCommitment, + }); + if (!computedClassId.equals(contractClass.id)) { + this.log.warn( + `Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`, + { blockNum, contractClassId: event.contractClassId.toString() }, + ); + continue; + } + contractClasses.push(contractClass); + } + + if (contractClasses.length > 0) { + contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`)); + return await this.store.addContractClasses(contractClasses, blockNum); } return true; } diff --git a/yarn-project/archiver/src/store/kv_archiver_store.test.ts b/yarn-project/archiver/src/store/kv_archiver_store.test.ts index c15b0e38113c..19620de355c0 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.test.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.test.ts @@ -24,6 +24,7 @@ import { import { Checkpoint, PublishedCheckpoint, randomCheckpointInfo } from '@aztec/stdlib/checkpoint'; import { type ContractClassPublic, + type ContractClassPublicWithCommitment, type ContractInstanceWithAddress, SerializableContractInstance, computePublicBytecodeCommitment, @@ -75,6 +76,13 @@ async function addProposedBlocks( return result; } +async function withCommitment(contractClass: ContractClassPublic): Promise { + return { + ...contractClass, + publicBytecodeCommitment: await computePublicBytecodeCommitment(contractClass.packedBytecode), + }; +} + describe('KVArchiverDataStore', () => { let store: KVArchiverDataStore; let publishedCheckpoints: PublishedCheckpoint[]; @@ -2346,11 +2354,7 @@ describe('KVArchiverDataStore', () => { beforeEach(async () => { contractClass = await makeContractClassPublic(); - await store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(blockNum), - ); + await store.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum)); }); it('returns previously stored contract class', async () => { @@ -2364,14 +2368,15 @@ describe('KVArchiverDataStore', () => { it('throws if the same contract class is added again', async () => { await expect( - store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(blockNum + 1), - ), + store.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum + 1)), ).rejects.toThrow(/already exists/); }); + it('returns contract class if deleted at a later block number', async () => { + await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1)); + await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass); + }); + it('returns undefined if contract class is not found', async () => { await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined(); }); @@ -3398,21 +3403,17 @@ describe('KVArchiverDataStore', () => { it('throws when adding the same contract class twice', async () => { const contractClass = await makeContractClassPublic(); - const commitment = await computePublicBytecodeCommitment(contractClass.packedBytecode); + const contractClassWithCommitment = await withCommitment(contractClass); - await store.addContractClasses([contractClass], [commitment], BlockNumber(1)); - await expect(store.addContractClasses([contractClass], [commitment], BlockNumber(2))).rejects.toThrow( + await store.addContractClasses([contractClassWithCommitment], BlockNumber(1)); + await expect(store.addContractClasses([contractClassWithCommitment], BlockNumber(2))).rejects.toThrow( /already exists/, ); }); it('throws when adding the same contract instance twice', async () => { const contractClass = await makeContractClassPublic(); - await store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(1), - ); + await store.addContractClasses([await withCommitment(contractClass)], BlockNumber(1)); const instance = { ...(await SerializableContractInstance.random({ diff --git a/yarn-project/archiver/src/store/kv_archiver_store.ts b/yarn-project/archiver/src/store/kv_archiver_store.ts index d8aa479ad77c..36e27644f0ae 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.ts @@ -16,6 +16,7 @@ import { import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import type { ContractClassPublic, + ContractClassPublicWithCommitment, ContractDataSource, ContractInstanceUpdateWithAddress, ContractInstanceWithAddress, @@ -165,19 +166,14 @@ export class KVArchiverDataStore implements ContractDataSource { /** * Add new contract classes from an L2 block to the store's list. - * @param data - List of contract classes to be added. - * @param bytecodeCommitments - Bytecode commitments for the contract classes. + * @param data - List of contract classes (with bytecode commitments) to be added. * @param blockNumber - Number of the L2 block the contracts were registered in. * @returns True if the operation is successful. */ - async addContractClasses( - data: ContractClassPublic[], - bytecodeCommitments: Fr[], - blockNumber: BlockNumber, - ): Promise { + async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: BlockNumber): Promise { return ( await Promise.all( - data.map((c, i) => this.#contractClassStore.addContractClass(c, bytecodeCommitments[i], blockNumber)), + data.map(c => this.#contractClassStore.addContractClass(c, c.publicBytecodeCommitment, blockNumber)), ) ).every(Boolean); } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts index 2820c3a108b6..3f3c4ae17d00 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts @@ -1,14 +1,18 @@ import { CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, + CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE, MAX_CONTRACT_CLASS_LOGS_PER_TX, MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS, } from '@aztec/constants'; import { timesParallel } from '@aztec/foundation/collection'; import { randomInt } from '@aztec/foundation/crypto/random'; import { Fr } from '@aztec/foundation/curves/bn254'; +import { ProtocolContractAddress } from '@aztec/protocol-contracts'; +import { bufferAsFields } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { computeContractClassId, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import { LogHash, ScopedLogHash } from '@aztec/stdlib/kernel'; -import { ContractClassLogFields } from '@aztec/stdlib/logs'; +import { ContractClassLog, ContractClassLogFields } from '@aztec/stdlib/logs'; import { mockTx } from '@aztec/stdlib/testing'; import { TX_ERROR_CALLDATA_COUNT_MISMATCH, @@ -17,6 +21,8 @@ import { TX_ERROR_CONTRACT_CLASS_LOG_COUNT, TX_ERROR_CONTRACT_CLASS_LOG_LENGTH, TX_ERROR_INCORRECT_CALLDATA, + TX_ERROR_INCORRECT_CONTRACT_CLASS_ID, + TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG, type Tx, } from '@aztec/stdlib/tx'; @@ -243,4 +249,119 @@ describe('TxDataValidator', () => { await expectInvalid(badTxs[0], TX_ERROR_CONTRACT_CLASS_LOG_LENGTH); }); + + describe('contract class id validation', () => { + /** + * Builds a ContractClassLog encoding a ContractClassPublishedEvent. + * Layout: [magic, contractClassId, version, artifactHash, privateFunctionsRoot, ...bytecodeAsFields] + */ + async function buildContractClassLog(opts?: { contractClassId?: Fr }): Promise<{ + log: ContractClassLog; + emittedLength: number; + }> { + const artifactHash = Fr.random(); + const privateFunctionsRoot = Fr.random(); + const packedBytecode = Buffer.from('aabbccdd', 'hex'); + + const bytecodeCommitment = await computePublicBytecodeCommitment(packedBytecode); + const correctClassId = await computeContractClassId({ + artifactHash, + privateFunctionsRoot, + publicBytecodeCommitment: bytecodeCommitment, + }); + const contractClassId = opts?.contractClassId ?? correctClassId; + + const bytecodeFields = bufferAsFields(packedBytecode, CONTRACT_CLASS_LOG_SIZE_IN_FIELDS); + let lastNonZero = bytecodeFields.length - 1; + while (lastNonZero >= 0 && bytecodeFields[lastNonZero].isZero()) { + lastNonZero--; + } + const bytecodeEmittedFields = bytecodeFields.slice(0, lastNonZero + 1); + + const headerFields = [ + new Fr(CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE), + contractClassId, + new Fr(1), // version + artifactHash, + privateFunctionsRoot, + ]; + + const emittedFields = [...headerFields, ...bytecodeEmittedFields]; + const emittedLength = emittedFields.length; + + const allFields = [ + ...emittedFields, + ...Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - emittedFields.length).fill(Fr.ZERO), + ]; + + const fields = new ContractClassLogFields(allFields); + const log = new ContractClassLog(ProtocolContractAddress.ContractClassRegistry, fields, emittedLength); + return { log, emittedLength }; + } + + async function injectContractClassLog(tx: Tx, log: ContractClassLog, emittedLength: number) { + tx.contractClassLogFields.push(log.fields); + const logHashes = tx.data.forPublic!.nonRevertibleAccumulatedData.contractClassLogsHashes; + const emptyIdx = logHashes.findIndex(h => h.isEmpty()); + if (emptyIdx >= 0) { + logHashes[emptyIdx] = LogHash.from({ + value: await log.fields.hash(), + length: emittedLength, + }).scope(log.contractAddress); + } + } + + it('allows transactions with correct contract class ids', async () => { + const tx = await mockTx(2, { + numberOfNonRevertiblePublicCallRequests: 1, + numberOfRevertiblePublicCallRequests: 0, + }); + const { log, emittedLength } = await buildContractClassLog(); + await injectContractClassLog(tx, log, emittedLength); + await tx.recomputeHash(); + await expect(validator.validateTx(tx)).resolves.toEqual({ result: 'valid' }); + }); + + it('rejects transactions with incorrect contract class ids', async () => { + const tx = await mockTx(3, { + numberOfNonRevertiblePublicCallRequests: 1, + numberOfRevertiblePublicCallRequests: 0, + }); + const { log, emittedLength } = await buildContractClassLog({ contractClassId: Fr.random() }); + await injectContractClassLog(tx, log, emittedLength); + await tx.recomputeHash(); + await expect(validator.validateTx(tx)).resolves.toEqual({ + result: 'invalid', + reason: [TX_ERROR_INCORRECT_CONTRACT_CLASS_ID], + }); + }); + + it('rejects transactions with malformed contract class logs', async () => { + const tx = await mockTx(4, { + numberOfNonRevertiblePublicCallRequests: 1, + numberOfRevertiblePublicCallRequests: 0, + }); + const headerFields = [ + new Fr(CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE), + Fr.random(), + new Fr(1), + Fr.random(), + Fr.random(), + new Fr(999999), // bogus bytecode length + ]; + const allFields = [ + ...headerFields, + ...Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - headerFields.length).fill(Fr.ZERO), + ]; + const fields = new ContractClassLogFields(allFields); + const log = new ContractClassLog(ProtocolContractAddress.ContractClassRegistry, fields, headerFields.length); + await injectContractClassLog(tx, log, headerFields.length); + await tx.recomputeHash(); + const result = await validator.validateTx(tx); + expect(result.result).toBe('invalid'); + expect(result.result === 'invalid' && result.reason[0]).toMatch( + new RegExp(`${TX_ERROR_INCORRECT_CONTRACT_CLASS_ID}|${TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG}`), + ); + }); + }); }); diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts index 692e4705e186..7c284b6d0ce3 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts @@ -1,5 +1,7 @@ import { MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS } from '@aztec/constants'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; +import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry'; +import { computeContractClassId } from '@aztec/stdlib/contract'; import { computeCalldataHash } from '@aztec/stdlib/hash'; import { TX_ERROR_CALLDATA_COUNT_MISMATCH, @@ -9,7 +11,9 @@ import { TX_ERROR_CONTRACT_CLASS_LOG_LENGTH, TX_ERROR_CONTRACT_CLASS_LOG_SORTING, TX_ERROR_INCORRECT_CALLDATA, + TX_ERROR_INCORRECT_CONTRACT_CLASS_ID, TX_ERROR_INCORRECT_HASH, + TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG, Tx, type TxValidationResult, type TxValidator, @@ -26,7 +30,8 @@ export class DataTxValidator implements TxValidator { const reason = (await this.#hasCorrectHash(tx)) ?? (await this.#hasCorrectCalldata(tx)) ?? - (await this.#hasCorrectContractClassLogs(tx)); + (await this.#hasCorrectContractClassLogs(tx)) ?? + (await this.#hasCorrectContractClassIds(tx)); return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' }; } @@ -127,4 +132,40 @@ export class DataTxValidator implements TxValidator { return undefined; } + + async #hasCorrectContractClassIds(tx: Tx): Promise { + const contractClassLogs = tx.getContractClassLogs(); + for (const log of contractClassLogs) { + if (!ContractClassPublishedEvent.isContractClassPublishedEvent(log)) { + continue; + } + + let event; + try { + event = ContractClassPublishedEvent.fromLog(log); + } catch (e) { + this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract class event: ${e}`); + return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG; + } + + try { + const { publicBytecodeCommitment } = await event.toContractClassPublicWithBytecodeCommitment(); + const computedClassId = await computeContractClassId({ + artifactHash: event.artifactHash, + privateFunctionsRoot: event.privateFunctionsRoot, + publicBytecodeCommitment, + }); + if (!computedClassId.equals(event.contractClassId)) { + this.#log.warn( + `Rejecting tx ${tx.getTxHash()}: contract class id mismatch. Claimed ${event.contractClassId}, computed ${computedClassId}`, + ); + return TX_ERROR_INCORRECT_CONTRACT_CLASS_ID; + } + } catch (e) { + this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract class id: ${e}`); + return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG; + } + } + return undefined; + } } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts index 69d5bd9f0cab..39362bd39dd2 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts @@ -40,7 +40,7 @@ export class PhasesTxValidator implements TxValidator { // which are needed for public FPC flows, but fail if the account contract hasnt been deployed yet, // which is what we're trying to do as part of the current txs. // We only need to create/revert checkpoint here because of this addNewContracts call. - await this.contractsDB.addNewContracts(tx); + this.contractsDB.addNewContracts(tx); if (!tx.data.forPublic) { this.#log.debug( diff --git a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts index 057db11ca9e8..4e1dfecf7244 100644 --- a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts +++ b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts @@ -4,7 +4,7 @@ import { FieldReader } from '@aztec/foundation/serialize'; import { bufferFromFields } from '@aztec/stdlib/abi'; import { type ContractClassPublic, - computeContractClassId, + type ContractClassPublicWithCommitment, computePublicBytecodeCommitment, } from '@aztec/stdlib/contract'; import type { ContractClassLog } from '@aztec/stdlib/logs'; @@ -47,32 +47,23 @@ export class ContractClassPublishedEvent { ); } - async toContractClassPublic(): Promise { - const computedClassId = await computeContractClassId({ - artifactHash: this.artifactHash, - privateFunctionsRoot: this.privateFunctionsRoot, - publicBytecodeCommitment: await computePublicBytecodeCommitment(this.packedPublicBytecode), - }); - - if (!computedClassId.equals(this.contractClassId)) { - throw new Error( - `Invalid contract class id: computed ${computedClassId.toString()} but event broadcasted ${this.contractClassId.toString()}`, - ); - } - - if (this.version !== 1) { - throw new Error(`Unexpected contract class version ${this.version}`); - } - + /** Converts the event to a contract class, without computing or validating the bytecode commitment. */ + toContractClassPublic(): ContractClassPublic { return { id: this.contractClassId, artifactHash: this.artifactHash, packedBytecode: this.packedPublicBytecode, privateFunctionsRoot: this.privateFunctionsRoot, - version: this.version, + version: this.version as 1, }; } + /** Converts the event to a contract class with its bytecode commitment (expensive). */ + async toContractClassPublicWithBytecodeCommitment(): Promise { + const publicBytecodeCommitment = await computePublicBytecodeCommitment(this.packedPublicBytecode); + return { ...this.toContractClassPublic(), publicBytecodeCommitment }; + } + public static extractContractClassEvents(logs: ContractClassLog[]): ContractClassPublishedEvent[] { return logs .filter((log: ContractClassLog) => ContractClassPublishedEvent.isContractClassPublishedEvent(log)) diff --git a/yarn-project/simulator/src/public/public_db_sources.ts b/yarn-project/simulator/src/public/public_db_sources.ts index 445949934694..144a39182563 100644 --- a/yarn-project/simulator/src/public/public_db_sources.ts +++ b/yarn-project/simulator/src/public/public_db_sources.ts @@ -55,10 +55,10 @@ export class PublicContractsDB implements PublicContractsDBInterface { this.log = createLogger('simulator:contracts-data-source', bindings); } - public async addContracts(contractDeploymentData: ContractDeploymentData): Promise { + public addContracts(contractDeploymentData: ContractDeploymentData): void { const currentState = this.getCurrentState(); - await this.addContractClassesFromEvents( + this.addContractClassesFromEvents( ContractClassPublishedEvent.extractContractClassEvents(contractDeploymentData.getContractClassLogs()), currentState, ); @@ -69,10 +69,10 @@ export class PublicContractsDB implements PublicContractsDBInterface { ); } - public async addNewContracts(tx: Tx): Promise { + public addNewContracts(tx: Tx): void { const contractDeploymentData = AllContractDeploymentData.fromTx(tx); - await this.addContracts(contractDeploymentData.getNonRevertibleContractDeploymentData()); - await this.addContracts(contractDeploymentData.getRevertibleContractDeploymentData()); + this.addContracts(contractDeploymentData.getNonRevertibleContractDeploymentData()); + this.addContracts(contractDeploymentData.getRevertibleContractDeploymentData()); } /** @@ -174,17 +174,15 @@ export class PublicContractsDB implements PublicContractsDBInterface { return await this.dataSource.getDebugFunctionName(address, selector); } - private async addContractClassesFromEvents( + private addContractClassesFromEvents( contractClassEvents: ContractClassPublishedEvent[], state: ContractsDbCheckpoint, ) { - await Promise.all( - contractClassEvents.map(async (event: ContractClassPublishedEvent) => { - this.log.debug(`Adding class ${event.contractClassId.toString()} to contract state`); - const contractClass = await event.toContractClassPublic(); - state.addClass(event.contractClassId, contractClass); - }), - ); + for (const event of contractClassEvents) { + this.log.debug(`Adding class ${event.contractClassId.toString()} to contract state`); + const contractClass = event.toContractClassPublic(); + state.addClass(event.contractClassId, contractClass); + } } private addContractInstancesFromEvents( diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts index 23a019bb6080..abc3aedf918e 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts @@ -361,8 +361,8 @@ describe('public_processor', () => { // we want to confirm that even non-revertibles get cleared const contractClassId = await mockContractClassForTx(tx, /*revertible=*/ false); - publicTxSimulator.simulate.mockImplementation(async (simulatedTx: Tx) => { - await contractsDB.addNewContracts(simulatedTx); + publicTxSimulator.simulate.mockImplementation((simulatedTx: Tx) => { + contractsDB.addNewContracts(simulatedTx); throw new Error('Uncaught error'); }); diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.ts b/yarn-project/simulator/src/public/public_processor/public_processor.ts index f8b708d0a568..29a4f6aaadbf 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.ts @@ -548,7 +548,7 @@ export class PublicProcessor implements Traceable { // Fee payment insertion has already been done. Do the rest. await this.doTreeInsertionsForPrivateOnlyTx(processedTx); - await this.contractsDB.addNewContracts(tx); + this.contractsDB.addNewContracts(tx); return [processedTx, undefined, []]; } diff --git a/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts b/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts index 6568a47228e9..9077b8676c1e 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts @@ -52,6 +52,7 @@ export class ContractProviderForCpp implements ContractProvider { return serializeWithMessagePack(contractClass); }; + // eslint-disable-next-line require-await public addContracts = async (contractDeploymentDataBuffer: Buffer): Promise => { this.log.trace(`Contract provider callback: addContracts`); @@ -62,7 +63,7 @@ export class ContractProviderForCpp implements ContractProvider { // Add contracts to the contracts DB this.log.trace(`Calling contractsDB.addContracts`); - await this.contractsDB.addContracts(contractDeploymentData); + this.contractsDB.addContracts(contractDeploymentData); }; public getBytecodeCommitment = async (classId: string): Promise => { diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts index c07f0d04945c..4e2b95c21c7c 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts @@ -401,7 +401,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { // However, things work as expected because later calls to getters on the hintingContractsDB // will pick up the new contracts and will generate the necessary hints. // So, a consumer of the hints will always see the new contracts. - await this.contractsDB.addContracts(context.nonRevertibleContractDeploymentData); + this.contractsDB.addContracts(context.nonRevertibleContractDeploymentData); } /** @@ -486,7 +486,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { // However, things work as expected because later calls to getters on the hintingContractsDB // will pick up the new contracts and will generate the necessary hints. // So, a consumer of the hints will always see the new contracts. - await this.contractsDB.addContracts(context.revertibleContractDeploymentData); + this.contractsDB.addContracts(context.revertibleContractDeploymentData); } private async payFee(context: PublicTxContext) { diff --git a/yarn-project/stdlib/src/contract/contract_class_id.test.ts b/yarn-project/stdlib/src/contract/contract_class_id.test.ts index 224957aa6431..04df3321ab02 100644 --- a/yarn-project/stdlib/src/contract/contract_class_id.test.ts +++ b/yarn-project/stdlib/src/contract/contract_class_id.test.ts @@ -1,6 +1,10 @@ import { Fr } from '@aztec/foundation/curves/bn254'; +import { createLogger } from '@aztec/foundation/log'; +import { elapsed } from '@aztec/foundation/timer'; import { FunctionSelector } from '../abi/function_selector.js'; +import { getBenchmarkContractArtifact, getTestContractArtifact, getTokenContractArtifact } from '../tests/fixtures.js'; +import { getContractClassFromArtifact } from './contract_class.js'; import { computeContractClassId } from './contract_class_id.js'; import type { ContractClass } from './interfaces/contract_class.js'; @@ -18,5 +22,20 @@ describe('ContractClass', () => { `"0x2926577ccab09f8e4600550792066ed9d6ce530a973ac2b81a36eaebee56ad44"`, ); }); + + it('calculates the contract class id for a real contract artifact', async () => { + const artifacts = [getBenchmarkContractArtifact(), getTokenContractArtifact(), getTestContractArtifact()]; + const logger = createLogger('stdlib:contract_class_id:test'); + + for (const artifact of artifacts) { + const contractClass = await getContractClassFromArtifact(artifact); + + const [ms, contractClassId] = await elapsed(computeContractClassId(contractClass)); + logger.info(`Computed contract class id ${contractClassId} in ${ms}ms`); + + expect(contractClassId.toString()).toHaveLength(66); // 0x + 64 hex chars + expect(contractClassId.toBigInt()).toBeGreaterThan(0n); + } + }); }); }); diff --git a/yarn-project/stdlib/src/tx/validator/error_texts.ts b/yarn-project/stdlib/src/tx/validator/error_texts.ts index 57599c12d9d4..bcc100c2d9b0 100644 --- a/yarn-project/stdlib/src/tx/validator/error_texts.ts +++ b/yarn-project/stdlib/src/tx/validator/error_texts.ts @@ -45,5 +45,9 @@ export const TX_ERROR_BLOCK_HEADER = 'Block header not found'; export const TX_ERROR_INCORRECT_CONTRACT_ADDRESS = 'Incorrect contract instance deployment address'; export const TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG = 'Failed to parse contract instance deployment log'; +// Contract class +export const TX_ERROR_INCORRECT_CONTRACT_CLASS_ID = 'Incorrect contract class id'; +export const TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG = 'Failed to parse contract class registration log'; + // General export const TX_ERROR_DURING_VALIDATION = 'Unexpected error during validation'; From 4c4e5b376bb81a596a07416e7a5b962d0ab6ea0e Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 20 Mar 2026 12:37:18 -0300 Subject: [PATCH 39/43] fix(sequencer): remove l1 block timestamp check Was needed due to clock drift between sequencer and anvil, but PR #21829 should now remove the need for that. --- .../src/publisher/sequencer-publisher.ts | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts index 90c125d0ac19..284b83c3870b 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts @@ -605,7 +605,7 @@ export class SequencerPublisher { * @param tipArchive - The archive to check * @returns The slot and block number if it is possible to propose, undefined otherwise */ - public async canProposeAt( + public canProposeAt( tipArchive: Fr, msgSender: EthAddress, opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {}, @@ -615,7 +615,7 @@ export class SequencerPublisher { const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled(); const slotOffset = pipelined ? this.aztecSlotDuration : 0n; - const nextL1SlotTs = (await this.getNextL1SlotTimestampWithL1Floor()) + slotOffset; + const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset; return this.rollupContract .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, { @@ -656,7 +656,7 @@ export class SequencerPublisher { flags, ] as const; - const ts = await this.getNextL1SlotTimestampWithL1Floor(); + const ts = this.getNextL1SlotTimestamp(); const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride( opts?.forcePendingCheckpointNumber, ); @@ -1590,20 +1590,9 @@ export class SequencerPublisher { }); } - /** - * Returns the timestamp to use when simulating L1 proposal calls. - * Uses the wall-clock-based next L1 slot boundary, but floors it with the latest L1 block timestamp - * plus one slot duration. This prevents the sequencer from targeting a future L2 slot when the L1 - * chain hasn't caught up to the wall clock yet (e.g., the dateProvider is one L1 slot ahead of the - * latest mined block), which would cause the propose tx to land in an L1 block with block.timestamp - * still in the previous L2 slot. - * TODO(palla): Properly fix by keeping dateProvider synced with anvil's chain time on every block. - */ - private async getNextL1SlotTimestampWithL1Floor(): Promise { + /** Returns the timestamp to use when simulating L1 proposal calls */ + private getNextL1SlotTimestamp(): bigint { const l1Constants = this.epochCache.getL1Constants(); - const fromWallClock = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants); - const latestBlock = await this.l1TxUtils.client.getBlock(); - const fromL1Block = latestBlock.timestamp + BigInt(l1Constants.ethereumSlotDuration); - return fromWallClock > fromL1Block ? fromWallClock : fromL1Block; + return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants); } } From 7ba0e4f66367189a2719ec6288e855fbcf74f527 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 20 Mar 2026 16:15:41 +0000 Subject: [PATCH 40/43] chore: set batch verifier concurrency default to 6, wire env vars through terraform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BB_CHONK_VERIFY_BATCH_CONCURRENCY default 0 (auto) → 6 to leave cores for the rest of the node on recommended 8-core machines - Wire BB_CHONK_VERIFY_MAX_BATCH and BB_CHONK_VERIFY_BATCH_CONCURRENCY through terraform variables.tf and main.tf to all 5 node types --- spartan/terraform/deploy-aztec-infra/main.tf | 10 ++++++++++ spartan/terraform/deploy-aztec-infra/variables.tf | 12 ++++++++++++ yarn-project/bb-prover/src/config.ts | 2 +- .../end-to-end/src/fixtures/get_bb_config.ts | 2 +- yarn-project/prover-client/src/config.ts | 2 +- yarn-project/prover-client/src/mocks/test_context.ts | 2 +- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index d1fc361a0ea3..d8b7163b8e72 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -224,6 +224,8 @@ locals { "validator.node.env.BLOB_ALLOW_EMPTY_SOURCES" = var.BLOB_ALLOW_EMPTY_SOURCES "validator.node.env.P2P_MAX_TX_POOL_SIZE" = var.P2P_MAX_TX_POOL_SIZE "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 @@ -379,6 +381,8 @@ locals { "node.node.env.L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE" = var.PROVER_L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE "node.node.env.P2P_MAX_TX_POOL_SIZE" = var.P2P_MAX_TX_POOL_SIZE "node.node.env.PROVER_TEST_VERIFICATION_DELAY_MS" = var.PROVER_TEST_VERIFICATION_DELAY_MS + "node.node.env.BB_CHONK_VERIFY_MAX_BATCH" = var.BB_CHONK_VERIFY_MAX_BATCH + "node.node.env.BB_CHONK_VERIFY_BATCH_CONCURRENCY" = var.BB_CHONK_VERIFY_BATCH_CONCURRENCY "node.node.env.DEBUG_P2P_INSTRUMENT_MESSAGES" = var.DEBUG_P2P_INSTRUMENT_MESSAGES "node.node.env.P2P_GOSSIPSUB_D" = var.P2P_GOSSIPSUB_D "node.node.env.P2P_GOSSIPSUB_DLO" = var.P2P_GOSSIPSUB_DLO @@ -461,6 +465,8 @@ locals { "node.env.BLOB_ALLOW_EMPTY_SOURCES" = var.BLOB_ALLOW_EMPTY_SOURCES "node.env.P2P_MAX_TX_POOL_SIZE" = var.P2P_MAX_TX_POOL_SIZE "node.env.PROVER_TEST_VERIFICATION_DELAY_MS" = var.PROVER_TEST_VERIFICATION_DELAY_MS + "node.env.BB_CHONK_VERIFY_MAX_BATCH" = var.BB_CHONK_VERIFY_MAX_BATCH + "node.env.BB_CHONK_VERIFY_BATCH_CONCURRENCY" = var.BB_CHONK_VERIFY_BATCH_CONCURRENCY "node.env.DEBUG_P2P_INSTRUMENT_MESSAGES" = var.DEBUG_P2P_INSTRUMENT_MESSAGES "node.env.P2P_GOSSIPSUB_D" = var.P2P_GOSSIPSUB_D "node.env.P2P_GOSSIPSUB_DLO" = var.P2P_GOSSIPSUB_DLO @@ -515,6 +521,8 @@ locals { "node.env.BLOB_ALLOW_EMPTY_SOURCES" = var.BLOB_ALLOW_EMPTY_SOURCES "node.env.P2P_MAX_TX_POOL_SIZE" = var.P2P_MAX_TX_POOL_SIZE "node.env.PROVER_TEST_VERIFICATION_DELAY_MS" = var.PROVER_TEST_VERIFICATION_DELAY_MS + "node.env.BB_CHONK_VERIFY_MAX_BATCH" = var.BB_CHONK_VERIFY_MAX_BATCH + "node.env.BB_CHONK_VERIFY_BATCH_CONCURRENCY" = var.BB_CHONK_VERIFY_BATCH_CONCURRENCY "node.env.DEBUG_P2P_INSTRUMENT_MESSAGES" = var.DEBUG_P2P_INSTRUMENT_MESSAGES "node.otelIncludeMetrics" = var.FULL_NODE_INCLUDE_METRICS "node.env.P2P_GOSSIPSUB_D" = var.P2P_GOSSIPSUB_D @@ -551,6 +559,8 @@ locals { "node.env.P2P_ARCHIVED_TX_LIMIT" = "10000000" "node.proverRealProofs" = var.PROVER_REAL_PROOFS "node.env.PROVER_TEST_VERIFICATION_DELAY_MS" = var.PROVER_TEST_VERIFICATION_DELAY_MS + "node.env.BB_CHONK_VERIFY_MAX_BATCH" = var.BB_CHONK_VERIFY_MAX_BATCH + "node.env.BB_CHONK_VERIFY_BATCH_CONCURRENCY" = var.BB_CHONK_VERIFY_BATCH_CONCURRENCY "node.env.DEBUG_FORCE_TX_PROOF_VERIFICATION" = var.DEBUG_FORCE_TX_PROOF_VERIFICATION "node.env.DEBUG_P2P_INSTRUMENT_MESSAGES" = var.DEBUG_P2P_INSTRUMENT_MESSAGES "node.env.P2P_TX_POOL_DELETE_TXS_AFTER_REORG" = var.P2P_TX_POOL_DELETE_TXS_AFTER_REORG diff --git a/spartan/terraform/deploy-aztec-infra/variables.tf b/spartan/terraform/deploy-aztec-infra/variables.tf index 67165fde6035..80c2ef51d306 100644 --- a/spartan/terraform/deploy-aztec-infra/variables.tf +++ b/spartan/terraform/deploy-aztec-infra/variables.tf @@ -81,6 +81,18 @@ variable "PROVER_TEST_VERIFICATION_DELAY_MS" { default = 10 } +variable "BB_CHONK_VERIFY_MAX_BATCH" { + description = "Upper bound on proofs per batch for the peer chonk batch verifier" + type = number + default = 16 +} + +variable "BB_CHONK_VERIFY_BATCH_CONCURRENCY" { + description = "Thread count for the peer batch verifier parallel reduce (0 = auto)" + type = number + default = 6 +} + variable "K8S_CLUSTER_CONTEXT" { description = "GKE cluster context" type = string diff --git a/yarn-project/bb-prover/src/config.ts b/yarn-project/bb-prover/src/config.ts index bc3f7b6f8794..19389258a3c9 100644 --- a/yarn-project/bb-prover/src/config.ts +++ b/yarn-project/bb-prover/src/config.ts @@ -14,7 +14,7 @@ export interface BBConfig { * Default 16: at 4 cores, a full batch of 16 verifies in ~245ms wall time. */ bbChonkVerifyMaxBatch: number; - /** Thread count for the peer batch verifier parallel reduce. 0 = auto. */ + /** Thread count for the peer batch verifier parallel reduce. Default 6 to leave cores for the rest of the node. */ bbChonkVerifyConcurrency: number; } diff --git a/yarn-project/end-to-end/src/fixtures/get_bb_config.ts b/yarn-project/end-to-end/src/fixtures/get_bb_config.ts index 5cf241dfcce9..4985664b301c 100644 --- a/yarn-project/end-to-end/src/fixtures/get_bb_config.ts +++ b/yarn-project/end-to-end/src/fixtures/get_bb_config.ts @@ -16,7 +16,7 @@ const { BB_NUM_IVC_VERIFIERS = '8', BB_IVC_CONCURRENCY = '1', BB_CHONK_VERIFY_MAX_BATCH = '16', - BB_CHONK_VERIFY_BATCH_CONCURRENCY = '0', + BB_CHONK_VERIFY_BATCH_CONCURRENCY = '6', } = process.env; export const getBBConfig = async ( diff --git a/yarn-project/prover-client/src/config.ts b/yarn-project/prover-client/src/config.ts index 6ec2ac91bf54..7aef9717fbe6 100644 --- a/yarn-project/prover-client/src/config.ts +++ b/yarn-project/prover-client/src/config.ts @@ -61,7 +61,7 @@ export const bbConfigMappings: ConfigMappingsType = { bbChonkVerifyConcurrency: { env: 'BB_CHONK_VERIFY_BATCH_CONCURRENCY', description: 'Thread count for the peer batch verifier parallel reduce. 0 = auto.', - ...numberConfigHelper(0), + ...numberConfigHelper(6), }, }; diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 671a0bc68890..1d5d878fb99d 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -107,7 +107,7 @@ export class TestContext { numConcurrentIVCVerifiers: 8, bbIVCConcurrency: 1, bbChonkVerifyMaxBatch: 16, - bbChonkVerifyConcurrency: 0, + bbChonkVerifyConcurrency: 6, }; localProver = await createProver(bbConfig); } From 24a742ba92cc62ccd7fe39774f3b2684ec391277 Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Fri, 20 Mar 2026 13:43:06 -0400 Subject: [PATCH 41/43] fix: use local IVC inputs for batch_verifier bench test (#21857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The `batch_verifier.bench.test.ts` benchmark (added in #21823) was downloading pinned IVC inputs from S3 at test runtime, but bench tests run in Docker containers with `--net=none` (no network), causing all 7 tests to fail. Instead of adding network access, this uses the pre-generated `example-app-ivc-inputs-out` folder that's already built by `end-to-end/bootstrap.sh build_bench` — the same pattern used by the IVC flow benchmarks in `barretenberg/cpp/scripts/ci_benchmark_ivc_flows.sh`. ## Changes - `batch_verifier.bench.test.ts`: Replace S3 download with local `../end-to-end/example-app-ivc-inputs-out` path - `yarn-project/bootstrap.sh`: Keep original bench command (no `NET=1` needed) ## CI Log http://ci.aztec-labs.com/fafcdc0ea9a5d52b --- .../src/batch_verifier.bench.test.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts b/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts index 51e1dcff38ec..8282aa50b54e 100644 --- a/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts +++ b/yarn-project/ivc-integration/src/batch_verifier.bench.test.ts @@ -1,7 +1,8 @@ /** * Batch chonk verifier benchmarks using real protocol proofs. * - * Downloads pinned IVC inputs from S3, proves a representative transaction, + * Uses pre-generated IVC inputs from example-app-ivc-inputs-out (built by + * end-to-end/bootstrap.sh build_bench), proves a representative transaction, * then benchmarks batch verification throughput at various configurations. */ import { BatchChonkVerifier } from '@aztec/bb-prover'; @@ -20,8 +21,7 @@ import { corruptProofFields } from './batch_verifier_test_helpers.js'; const execFileAsync = promisify(execFile); const logger = createLogger('ivc-integration:bench:batch-verifier'); -const PINNED_HASH = 'b99f5b94'; -const PINNED_URL = `https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-chonk-inputs-${PINNED_HASH}.tar.gz`; +const INPUTS_DIR = resolve('../end-to-end/example-app-ivc-inputs-out'); const BB_PATH = process.env.BB_BINARY_PATH ?? resolve('../../barretenberg/cpp/build/bin/bb'); jest.setTimeout(1_200_000); // 20 min — proving is slow @@ -41,26 +41,22 @@ describe('Batch Chonk Verifier Benchmarks (Real Proofs)', () => { let validProofFields: Uint8Array[]; let invalidProofFields: Uint8Array[]; let vk: Uint8Array; - let inputsDir: string; + let proofDir: string; const benchResults: BenchEntry[] = []; beforeAll(async () => { - // Download pinned IVC inputs - inputsDir = resolve(tmpdir(), `bb-bench-inputs-${Date.now()}`); - await mkdir(inputsDir, { recursive: true }); - logger.info(`Downloading pinned IVC inputs from ${PINNED_URL}...`); - await execFileAsync('curl', ['-sf', PINNED_URL, '-o', `${inputsDir}/inputs.tar.gz`]); - await execFileAsync('tar', ['-xzf', `${inputsDir}/inputs.tar.gz`, '-C', inputsDir]); + // Use pre-generated IVC inputs from example-app-ivc-inputs-out + logger.info(`Using local IVC inputs from ${INPUTS_DIR}...`); // Pick the largest flow for a realistic proof - const flows = await readdir(inputsDir, { withFileTypes: true }); + const flows = await readdir(INPUTS_DIR, { withFileTypes: true }); const flowDirs = flows.filter(f => f.isDirectory()).map(f => f.name); logger.info(`Available flows: ${flowDirs.join(', ')}`); // Use a transfer flow (representative of typical txs) const flow = flowDirs.find(f => f.includes('transfer_0_recursions+sponsored')) ?? flowDirs[0]; - const ivcInputsPath = resolve(inputsDir, flow, 'ivc-inputs.msgpack'); - const proofDir = resolve(inputsDir, 'proof-out'); + const ivcInputsPath = resolve(INPUTS_DIR, flow, 'ivc-inputs.msgpack'); + proofDir = resolve(tmpdir(), `bb-bench-proof-${Date.now()}`); await mkdir(proofDir, { recursive: true }); // Prove the flow @@ -93,7 +89,7 @@ describe('Batch Chonk Verifier Benchmarks (Real Proofs)', () => { logger.info(` ${r.name}: ${r.value.toFixed(1)} ${r.unit}`); } } - await rm(inputsDir, { recursive: true, force: true }).catch(() => {}); + await rm(proofDir, { recursive: true, force: true }).catch(() => {}); }); // -- Core count sweep -- From 4d3aebc514fa653c6bd9223d8d573d0822232d7b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 20 Mar 2026 15:50:28 -0300 Subject: [PATCH 42/43] fix(p2p): centralize gossipsub penalization and fix inconsistencies (#21863) ## Summary Gossipsub message validation had double-penalization paths: inner validation functions called `penalizePeer` directly, and the outer `validateReceivedMessage` wrapper could penalize again on errors. Attestation cap exceeded was also inconsistently handled (ignored instead of rejected like proposals). ## Approach Centralized all gossipsub penalization into `validateReceivedMessage` by adding a `severity` field to the `Reject` variant of `ReceivedMessageValidationResult`. Inner functions now return severity instead of calling `penalizePeer` directly. Added `tryDeserialize` helper for graceful deserialization failure handling. ## Changes - **p2p (libp2p_service)**: Centralized penalization in `validateReceivedMessage`, removed direct `penalizePeer` calls from `handleGossipedTx`, `validateAndStoreBlockProposal`, `validateAndStoreCheckpointProposal`, and `validateAndStoreCheckpointAttestation`. Changed attestation cap exceeded from `Ignore` to `Reject` with `HighToleranceError`. Fixes A-705 Co-authored-by: Claude Opus 4.6 (1M context) --- .../p2p/src/services/libp2p/libp2p_service.ts | 113 ++++++++++++------ .../batch-tx-requester/batch_tx_requester.ts | 3 + 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index f7dae99a092f..7235ecb51bd8 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -130,7 +130,7 @@ type ValidationOutcome = { allPassed: true } | { allPassed: false; failure: Vali // REFACTOR: Unify with the type above type ReceivedMessageValidationResult = | { obj: T; result: Exclude; metadata?: M } - | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M }; + | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M; severity: PeerErrorSeverity }; /** * Lib P2P implementation of the P2PService interface. @@ -882,30 +882,56 @@ export class LibP2PService extends WithTracer implements P2PService { source: PeerId, topicType: TopicType, ): Promise> { - let resultAndObj: ReceivedMessageValidationResult = { result: TopicValidatorResult.Reject }; + // Default to reject result with a penalty if validation function throws an error + let resultAndObj: ReceivedMessageValidationResult = { + result: TopicValidatorResult.Reject, + severity: PeerErrorSeverity.MidToleranceError, + }; const timer = new Timer(); try { resultAndObj = await validationFunc(); } catch (err) { - this.peerManager.penalizePeer(source, PeerErrorSeverity.LowToleranceError); - this.logger.error(`Error deserializing and validating gossipsub message`, err, { - msgId, - source: source.toString(), - topicType, - }); + this.logger.error(`Error validating gossipsub message`, err, { msgId, source: source.toString(), topicType }); } if (resultAndObj.result === TopicValidatorResult.Accept) { + this.logger.debug(`Message ${topicType} accepted by validator`, { msgId, source: source.toString(), topicType }); this.instrumentation.recordMessageValidation(topicType, timer); + } else if (resultAndObj.result === TopicValidatorResult.Reject) { + this.logger.warn(`Message ${topicType} rejected by validator with severity ${resultAndObj.severity}`, { + msgId, + source: source.toString(), + topicType, + severity: resultAndObj.severity, + }); + this.peerManager.penalizePeer(source, resultAndObj.severity); + } else { + this.logger.trace(`Message ${topicType} ignored by validator`, { msgId, source: source.toString(), topicType }); } this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), resultAndObj.result); return resultAndObj; } + private tryDeserialize(deserializeFunc: () => T, msgId: string, source: PeerId): T | undefined { + try { + return deserializeFunc(); + } catch (err) { + this.logger.warn(`Failed to deserialize gossipsub message from buffer`, { + err, + msgId, + source: source.toString(), + }); + return undefined; + } + } + protected async handleGossipedTx(payloadData: Buffer, msgId: string, source: PeerId) { const validationFunc: () => Promise> = async () => { - const tx = Tx.fromBuffer(payloadData); + const tx = this.tryDeserialize(() => Tx.fromBuffer(payloadData), msgId, source); + if (!tx) { + return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.LowToleranceError }; + } const currentBlockNumber = await this.archiver.getBlockNumber(); const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot(); @@ -930,8 +956,7 @@ export class LibP2PService extends WithTracer implements P2PService { severity, source: source.toString(), }); - this.peerManager.penalizePeer(source, severity); - return { result: TopicValidatorResult.Reject }; + return { result: TopicValidatorResult.Reject, severity }; } // Pool pre-check: see if the pool would accept this tx before doing expensive proof verification @@ -953,8 +978,7 @@ export class LibP2PService extends WithTracer implements P2PService { severity, source: source.toString(), }); - this.peerManager.penalizePeer(source, severity); - return { result: TopicValidatorResult.Reject }; + return { result: TopicValidatorResult.Reject, severity }; } // Pool add: persist the tx @@ -979,8 +1003,7 @@ export class LibP2PService extends WithTracer implements P2PService { source: source.toString(), txHash: txHash.toString(), }); - this.peerManager.penalizePeer(source, PeerErrorSeverity.HighToleranceError); - return { result: TopicValidatorResult.Reject }; + return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError }; } }; @@ -1010,7 +1033,16 @@ export class LibP2PService extends WithTracer implements P2PService { source: PeerId, ): Promise { const { result, obj: attestation } = await this.validateReceivedMessage( - () => this.validateAndStoreCheckpointAttestation(source, CheckpointAttestation.fromBuffer(payloadData)), + () => { + const attestation = this.tryDeserialize(() => CheckpointAttestation.fromBuffer(payloadData), msgId, source); + if (!attestation) { + return Promise.resolve({ + result: TopicValidatorResult.Reject, + severity: PeerErrorSeverity.LowToleranceError, + }); + } + return this.validateAndStoreCheckpointAttestation(source, attestation); + }, msgId, source, TopicType.checkpoint_attestation, @@ -1043,8 +1075,7 @@ export class LibP2PService extends WithTracer implements P2PService { if (validationResult.result === 'reject') { this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`); - this.peerManager.penalizePeer(peerId, validationResult.severity); - return { result: TopicValidatorResult.Reject }; + return { result: TopicValidatorResult.Reject, severity: validationResult.severity }; } if (validationResult.result === 'ignore') { @@ -1070,16 +1101,16 @@ export class LibP2PService extends WithTracer implements P2PService { return { result: TopicValidatorResult.Ignore, obj: attestation }; } - // Could not add (cap reached for signer), no need to re-broadcast + // Could not add (cap reached for signer), penalize and do not re-broadcast if (!added) { - this.logger.warn(`Dropping checkpoint attestation due to cap`, { + this.logger.warn(`Rejecting checkpoint attestation due to cap`, { slot: slot.toString(), archive: attestation.archive.toString(), source: peerId.toString(), attester: attestation.getSender()?.toString(), count, }); - return { result: TopicValidatorResult.Ignore, obj: attestation }; + return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError }; } // Check if this is a duplicate attestation (signer attested to a different proposal at the same slot) @@ -1134,8 +1165,7 @@ export class LibP2PService extends WithTracer implements P2PService { if (validationResult.result === 'reject') { this.logger.warn(`Penalizing peer ${peerId} for block proposal validation failure`); - this.peerManager.penalizePeer(peerId, validationResult.severity); - return { result: TopicValidatorResult.Reject }; + return { result: TopicValidatorResult.Reject, severity: validationResult.severity }; } if (validationResult.result === 'ignore') { @@ -1159,7 +1189,6 @@ export class LibP2PService extends WithTracer implements P2PService { // Too many blocks received for this slot and index, penalize peer and do not re-broadcast if (!added) { - this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, { ...block.toBlockInfo(), indexWithinCheckpoint: block.indexWithinCheckpoint, @@ -1167,7 +1196,11 @@ export class LibP2PService extends WithTracer implements P2PService { proposer: block.getSender()?.toString(), source: peerId.toString(), }); - return { result: TopicValidatorResult.Reject, metadata: { isEquivocated } }; + return { + result: TopicValidatorResult.Reject, + metadata: { isEquivocated }, + severity: PeerErrorSeverity.HighToleranceError, + }; } // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback, @@ -1260,8 +1293,7 @@ export class LibP2PService extends WithTracer implements P2PService { if (validationResult.result === 'reject') { this.logger.warn(`Penalizing peer ${peerId} for checkpoint proposal validation failure`); - this.peerManager.penalizePeer(peerId, validationResult.severity); - return { result: TopicValidatorResult.Reject }; + return { result: TopicValidatorResult.Reject, severity: validationResult.severity }; } if (validationResult.result === 'ignore') { @@ -1276,20 +1308,21 @@ export class LibP2PService extends WithTracer implements P2PService { [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(), [Attributes.P2P_ID]: peerId.toString(), }); - const { - result, - obj, - metadata: { isEquivocated } = {}, - } = await this.validateAndStoreBlockProposal(peerId, blockProposal); - if (result === TopicValidatorResult.Reject || !obj || isEquivocated) { + const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal); + const { obj, metadata: { isEquivocated } = {} } = blockProposalResult; + if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) { this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, { [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(), [Attributes.P2P_ID]: peerId.toString(), isEquivocated, - result, + result: blockProposalResult.result, }); - return { result: TopicValidatorResult.Reject }; - } else if (result === TopicValidatorResult.Accept && obj && !isEquivocated) { + return { + result: TopicValidatorResult.Reject, + severity: + 'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError, + }; + } else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated) { processBlock = true; } } @@ -1316,13 +1349,17 @@ export class LibP2PService extends WithTracer implements P2PService { // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast // Note: We still return the checkpoint obj so the lastBlock can be processed if valid if (!added) { - this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, { ...checkpoint.toCheckpointInfo(), count, source: peerId.toString(), }); - return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } }; + return { + result: TopicValidatorResult.Reject, + obj: checkpoint, + metadata: { isEquivocated, processBlock }, + severity: PeerErrorSeverity.HighToleranceError, + }; } // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback, diff --git a/yarn-project/p2p/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts b/yarn-project/p2p/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts index 98e34b1e9c2e..08d2c613a569 100644 --- a/yarn-project/p2p/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +++ b/yarn-project/p2p/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts @@ -514,6 +514,9 @@ export class BatchTxRequester { }); if (hasInvalidTx) { + this.logger.warn(`Penalizing peer ${peerId.toString()} for sending invalid transactions in batch response`, { + peerId, + }); this.peers.penalisePeer(peerId, PeerErrorSeverity.LowToleranceError); } else { // If we have received successful response from the peer, they have "redeemed" themselves and not considered bad anymore From cd76e3a45b9c55addefb2e07924d52ea03292a96 Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:29:38 -0400 Subject: [PATCH 43/43] chore: publish GitHub releases to AztecProtocol/barretenberg (#21775) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Move automatic GitHub release creation from `AztecProtocol/aztec-packages` to `AztecProtocol/barretenberg` — bb artifacts are the only reason we need programmatic releases - Update all bb artifact download URLs (`barretenberg-rs build.rs`, rust bootstrap) to point to `AztecProtocol/barretenberg` releases - `bbup` tries `AztecProtocol/barretenberg` first, falls back to `aztec-packages` indefinitely - Users can still create `aztec-packages` releases manually via the GitHub "Create a release" button when needed --------- Co-authored-by: ludamad --- .../workflows/copy-bb-release-artifacts.yml | 71 +++++++++++++++++++ barretenberg/bbup/bbup | 12 ++-- barretenberg/cpp/bootstrap.sh | 4 +- barretenberg/rust/barretenberg-rs/README.md | 2 +- barretenberg/rust/barretenberg-rs/build.rs | 2 +- barretenberg/rust/bootstrap.sh | 4 +- bootstrap.sh | 46 +++++------- 7 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 .github-new/workflows/copy-bb-release-artifacts.yml diff --git a/.github-new/workflows/copy-bb-release-artifacts.yml b/.github-new/workflows/copy-bb-release-artifacts.yml new file mode 100644 index 000000000000..3448935bfa3e --- /dev/null +++ b/.github-new/workflows/copy-bb-release-artifacts.yml @@ -0,0 +1,71 @@ +name: Copy BB Release Artifacts +# When a release is published on aztec-packages, copy barretenberg artifacts +# from AztecProtocol/barretenberg to the aztec-packages release. +# Historical bbup versions (~before April 2026) benefit from having these artifacts here. +# Newer bbup versions are able to download experimental nightlies from barretenberg repo. +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: "Release tag to copy artifacts for (e.g. v0.82.0)" + required: true + +permissions: + contents: write + +jobs: + copy-artifacts: + runs-on: ubuntu-latest + steps: + - name: Determine tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" + else + echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" + fi + + - name: Download artifacts from AztecProtocol/barretenberg + env: + GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + run: | + tag="${{ steps.tag.outputs.tag }}" + echo "Downloading artifacts for tag: $tag" + mkdir -p /tmp/bb-artifacts + + # Download all release assets from the barretenberg repo + if ! gh release download "$tag" \ + --repo AztecProtocol/barretenberg \ + --dir /tmp/bb-artifacts; then + echo "::error::No release found for $tag in AztecProtocol/barretenberg" + exit 1 + fi + + echo "Downloaded artifacts:" + ls -la /tmp/bb-artifacts/ + + - name: Upload artifacts to aztec-packages release + env: + GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} + run: | + tag="${{ steps.tag.outputs.tag }}" + + # Ensure the release exists on aztec-packages (it should if triggered by release event, + # but for workflow_dispatch we may need to verify) + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if ! gh release view "$tag" --repo AztecProtocol/aztec-packages &>/dev/null; then + echo "::error::No release found for $tag in AztecProtocol/aztec-packages. Create the release first, then re-run." + exit 1 + fi + fi + + echo "Uploading artifacts to aztec-packages release $tag" + gh release upload "$tag" \ + --repo AztecProtocol/aztec-packages \ + --clobber \ + /tmp/bb-artifacts/* + + echo "Done! Artifacts copied to https://github.com/AztecProtocol/aztec-packages/releases/tag/$tag" diff --git a/barretenberg/bbup/bbup b/barretenberg/bbup/bbup index 29d81be28f41..e50950c8e734 100755 --- a/barretenberg/bbup/bbup +++ b/barretenberg/bbup/bbup @@ -114,15 +114,19 @@ install_bb() { local temp_dir=$(mktemp -d) local temp_tar="${temp_dir}/temp.tar.gz" - # Download and extract - local release_url_base="https://github.com/AztecProtocol/aztec-packages/releases/download" + # Download and extract — try AztecProtocol/barretenberg first, fall back to aztec-packages local release_tag="v${version}" if ! version_gte "$version" "0.77.0"; then release_tag="aztec-packages-v${version}" fi - local binary_url="${release_url_base}/${release_tag}/barretenberg-${architecture}-${platform}.tar.gz" + local artifact="barretenberg-${architecture}-${platform}.tar.gz" + local primary_url="https://github.com/AztecProtocol/barretenberg/releases/download/${release_tag}/${artifact}" + local fallback_url="https://github.com/AztecProtocol/aztec-packages/releases/download/${release_tag}/${artifact}" - curl -L --fail "$binary_url" -o "$temp_tar" + if ! curl -sL --fail "$primary_url" -o "$temp_tar"; then + printf "${BLUE}Trying fallback URL...${NC}\n" + curl -L --fail "$fallback_url" -o "$temp_tar" + fi mkdir -p "$BB_PATH" tar xzf "$temp_tar" -C "$BB_PATH" rm -rf "$temp_dir" diff --git a/barretenberg/cpp/bootstrap.sh b/barretenberg/cpp/bootstrap.sh index 2b8b9b031541..98d6f120c560 100755 --- a/barretenberg/cpp/bootstrap.sh +++ b/barretenberg/cpp/bootstrap.sh @@ -323,10 +323,10 @@ function bench { bench_cmds | STRICT_SCHEDULING=1 parallelize } -# Upload assets to release. +# Upload assets to release in AztecProtocol/barretenberg. function release { echo_header "bb cpp release" - do_or_dryrun gh release upload $REF_NAME build-release/* --clobber + do_or_dryrun gh release upload $REF_NAME build-release/* --repo AztecProtocol/barretenberg --clobber } function bench_ivc { diff --git a/barretenberg/rust/barretenberg-rs/README.md b/barretenberg/rust/barretenberg-rs/README.md index 5b182c7ec465..167dd47f2bea 100644 --- a/barretenberg/rust/barretenberg-rs/README.md +++ b/barretenberg/rust/barretenberg-rs/README.md @@ -25,7 +25,7 @@ barretenberg-rs = "0.1" ### PipeBackend (default) -Requires the `bb` binary to be available. Download from [Barretenberg releases](https://github.com/AztecProtocol/aztec-packages/releases). +Requires the `bb` binary to be available. Download from [Barretenberg releases](https://github.com/AztecProtocol/barretenberg/releases). ```rust use barretenberg_rs::{BarretenbergApi, backends::PipeBackend}; diff --git a/barretenberg/rust/barretenberg-rs/build.rs b/barretenberg/rust/barretenberg-rs/build.rs index 3afeaee020d9..db55b653cd11 100644 --- a/barretenberg/rust/barretenberg-rs/build.rs +++ b/barretenberg/rust/barretenberg-rs/build.rs @@ -85,7 +85,7 @@ fn download_lib(out_dir: &PathBuf) { } let url = format!( - "https://github.com/AztecProtocol/aztec-packages/releases/download/v{}/barretenberg-static-{}.tar.gz", + "https://github.com/AztecProtocol/barretenberg/releases/download/v{}/barretenberg-static-{}.tar.gz", version, arch ); diff --git a/barretenberg/rust/bootstrap.sh b/barretenberg/rust/bootstrap.sh index aff9c768d7cf..6c3938fc2d58 100755 --- a/barretenberg/rust/bootstrap.sh +++ b/barretenberg/rust/bootstrap.sh @@ -68,7 +68,7 @@ function release { # Publish to crates.io (--allow-dirty because version was just set and generated files are gitignored) local extra_flags="" - if ! gh release view "v$version" --repo AztecProtocol/aztec-packages &>/dev/null; then + if ! gh release view "v$version" --repo AztecProtocol/barretenberg &>/dev/null; then # No matching GitHub release yet — skip verification build (which would try to download libbb-external.a) echo "No GitHub release found for v$version, adding --no-verify (pass REF_NAME matching a release for full verification)" extra_flags="--no-verify" @@ -96,7 +96,7 @@ function test_download { cargo clean -p barretenberg-rs 2>/dev/null || true # Build with a known release version - local version=${BARRETENBERG_VERSION:-$(gh release list --repo AztecProtocol/aztec-packages --limit 1 --json tagName --jq '.[0].tagName' | sed 's/^v//')} + local version=${BARRETENBERG_VERSION:-$(gh release list --repo AztecProtocol/barretenberg --limit 1 --json tagName --jq '.[0].tagName' | sed 's/^v//')} echo "Testing download with version: $version" # Retry logic for network flakiness (GitHub releases can be flaky) diff --git a/bootstrap.sh b/bootstrap.sh index b8d7ab383e05..81cc1b33155a 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -486,36 +486,22 @@ function versions { echo "wasi-sdk: $wasi_sdk_version" } -function release_github { - # Add an easy link for comparing to previous release. - local compare_link="" - if gh release view "v$CURRENT_VERSION" &>/dev/null; then - compare_link=$(echo -e "See changes: https://github.com/AztecProtocol/aztec-packages/compare/v${CURRENT_VERSION}...${COMMIT_HASH}") - fi - # Legacy releases. TODO: Eventually remove. - if gh release view "aztec-packages-v$CURRENT_VERSION" &>/dev/null; then - compare_link=$(echo -e "See changes: https://github.com/AztecProtocol/aztec-packages/compare/aztec-packages-v${CURRENT_VERSION}...${COMMIT_HASH}") +function release_bb_github { + # Create a GitHub release in AztecProtocol/barretenberg for bb artifacts. + # Users can manually create releases in aztec-packages via the GitHub UI if needed. + local bb_repo="AztecProtocol/barretenberg" + if gh release view "$REF_NAME" --repo "$bb_repo" &>/dev/null; then + return fi - # Determine if this is a prerelease (has a prerelease tag like -rc.1, -alpha, etc.) - local is_prerelease=false + local prerelease_flag="" if [ -n "$(semver prerelease $REF_NAME)" ]; then - is_prerelease=true - fi - # Ensure we have a commit release. - if ! gh release view "$REF_NAME" &>/dev/null; then - local prerelease_flag="" - if $is_prerelease; then - prerelease_flag="--prerelease" - fi - do_or_dryrun gh release create "$REF_NAME" \ - $prerelease_flag \ - --target $COMMIT_HASH \ - --title "$REF_NAME" \ - --notes "$compare_link" - elif ! $is_prerelease; then - # Release exists but this is not a prerelease version - ensure it's marked as a full release - do_or_dryrun gh release edit "$REF_NAME" --prerelease=false + prerelease_flag="--prerelease" fi + do_or_dryrun gh release create "$REF_NAME" \ + --repo "$bb_repo" \ + $prerelease_flag \ + --title "$REF_NAME" \ + --notes "Release $REF_NAME — see https://github.com/AztecProtocol/aztec-packages/commits/$COMMIT_HASH" } function release { @@ -525,9 +511,9 @@ function release { echo_header "release all" set -x - # Ensure we have a github release for our REF_NAME. - # This is in case were are not going through release-please. - release_github + # Ensure we have a github release in AztecProtocol/barretenberg for bb artifacts. + # Users can create aztec-packages releases manually via the GitHub "Create a release" button. + release_bb_github projects=( barretenberg/cpp