diff --git a/yarn-project/end-to-end/src/automine/pxe.test.ts b/yarn-project/end-to-end/src/automine/pxe.test.ts index 3fdb95dbe14b..f08ab86b61e1 100644 --- a/yarn-project/end-to-end/src/automine/pxe.test.ts +++ b/yarn-project/end-to-end/src/automine/pxe.test.ts @@ -18,12 +18,15 @@ describe('automine/pxe', () => { let contract: TestContract; beforeAll(async () => { + const test = await AutomineTestContext.setup({ numberOfAccounts: 1 }); ({ teardown, wallet, accounts: [defaultAccountAddress], - } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); - ({ contract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); + } = test.context); + // The test only calls the noinitcheck private `emit_nullifier`, so register the contract instead of + // deploying it — this avoids a deployment tx and its checkpoint cycle. + contract = await test.registerContract(wallet, TestContract); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index c96708cba2b2..3dc87c409dd2 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -821,7 +821,12 @@ export async function registerSponsoredFPC(wallet: Wallet): Promise { await wallet.registerContract(await getSponsoredFPCInstance(), SponsoredFPCContract.artifact); } -export async function waitForProvenChain(node: AztecNode, targetBlock?: BlockNumber, timeoutSec = 60, intervalSec = 1) { +export async function waitForProvenChain( + node: AztecNode, + targetBlock?: BlockNumber, + timeoutSec = 60, + intervalSec = 0.25, +) { targetBlock ??= await node.getBlockNumber(); await retryUntil( @@ -923,14 +928,16 @@ export async function expectMappingDelta( * surface as generic "Assertion failed:" and tests that match on the real message fail). */ export async function ensureAuthRegistryPublished(wallet: Wallet, from: AztecAddress) { - const { instance, contractClass } = await getStandardAuthRegistry(); - if (!(await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered) { - await (await publishContractClass(wallet, AuthRegistryArtifact)).send({ from }); - } - if (!(await wallet.getContractMetadata(instance.address)).isContractPublished) { - await publishInstance(wallet, instance).send({ from }); - } - await wallet.registerContract(instance, AuthRegistryArtifact); + await testSpan('setup:auth-registry', async () => { + const { instance, contractClass } = await getStandardAuthRegistry(); + if (!(await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered) { + await (await publishContractClass(wallet, AuthRegistryArtifact)).send({ from }); + } + if (!(await wallet.getContractMetadata(instance.address)).isContractPublished) { + await publishInstance(wallet, instance).send({ from }); + } + await wallet.registerContract(instance, AuthRegistryArtifact); + }); } /** diff --git a/yarn-project/end-to-end/src/fixtures/wait_helpers.ts b/yarn-project/end-to-end/src/fixtures/wait_helpers.ts index 0d4c1091a0bd..1c347d11be38 100644 --- a/yarn-project/end-to-end/src/fixtures/wait_helpers.ts +++ b/yarn-project/end-to-end/src/fixtures/wait_helpers.ts @@ -21,7 +21,7 @@ export type WaitForBlockOpts = { compare?: (actual: number, target: number) => boolean; /** Seconds before the poll rejects; defaults to 60. */ timeout?: number; - /** Seconds between polls; defaults to 1. */ + /** Seconds between polls; defaults to 0.25. */ interval?: number; }; @@ -45,7 +45,7 @@ export function waitForBlockNumber(node: AztecNode, target: number, opts: WaitFo }, `block ${tag} ${compare} ${target}`, opts.timeout ?? 60, - opts.interval ?? 1, + opts.interval ?? 0.25, ).then(({ blockNumber }) => blockNumber), ); } @@ -118,7 +118,10 @@ export function waitForNodeProvenCheckpoint( * {@link waitForTx}; resolves with the receipts in input order. */ export function waitForTxs(node: AztecNode, txHashes: TxHash[], opts?: WaitOpts): Promise { - return testSpan('wait:tx-mined', () => Promise.all(txHashes.map(txHash => waitForTx(node, txHash, opts)))); + const optsWithInterval = { ...opts, interval: opts?.interval ?? 0.25 }; + return testSpan('wait:tx-mined', () => + Promise.all(txHashes.map(txHash => waitForTx(node, txHash, optsWithInterval))), + ); } /** Options for {@link waitForBlocksAtSlots}. */ @@ -129,7 +132,7 @@ export type WaitForBlocksAtSlotsOpts = { limit?: number; /** Seconds before the poll rejects; defaults to 20. */ timeout?: number; - /** Seconds between polls; defaults to 1. */ + /** Seconds between polls; defaults to 0.25. */ interval?: number; }; @@ -153,7 +156,7 @@ export async function waitForBlocksAtSlots( }, `blocks at slots ${slots.join(', ')}`, opts.timeout ?? 20, - opts.interval ?? 1, + opts.interval ?? 0.25, ), ); } @@ -174,7 +177,7 @@ export function waitForL2ToL1Witness( () => node.getL2ToL1MembershipWitness(txHash, message), `L2-to-L1 membership witness for ${txHash.toString()}`, opts.timeout ?? 30, - opts.interval ?? 1, + opts.interval ?? 0.25, ), ); } @@ -183,7 +186,7 @@ export function waitForL2ToL1Witness( export type WaitForTxReceiptOpts = { /** Seconds before the poll rejects; defaults to 30. */ timeout?: number; - /** Seconds between polls; defaults to 1. */ + /** Seconds between polls; defaults to 0.25. */ interval?: number; }; @@ -207,7 +210,7 @@ export function waitForTxReceipt( }, `tx receipt for ${txHash.toString()}`, opts.timeout ?? 30, - opts.interval ?? 1, + opts.interval ?? 0.25, ).then(({ receipt }) => receipt), ); } @@ -231,7 +234,7 @@ export type WaitForPendingTxCountOpts = { compare?: PendingTxCountComparator; /** Seconds before the poll rejects; defaults to 30. */ timeout?: number; - /** Seconds between polls; defaults to 1. */ + /** Seconds between polls; defaults to 0.25. */ interval?: number; }; @@ -256,7 +259,7 @@ export function waitForPendingTxCount( }, `pending tx count ${compare} ${target}`, opts.timeout ?? 30, - opts.interval ?? 1, + opts.interval ?? 0.25, ).then(({ count }) => count), ); } diff --git a/yarn-project/end-to-end/src/multi-node/block-production/multi_validator_node.parallel.test.ts b/yarn-project/end-to-end/src/multi-node/block-production/multi_validator_node.parallel.test.ts index 15084cbec107..36078b5643c0 100644 --- a/yarn-project/end-to-end/src/multi-node/block-production/multi_validator_node.parallel.test.ts +++ b/yarn-project/end-to-end/src/multi-node/block-production/multi_validator_node.parallel.test.ts @@ -27,7 +27,7 @@ const COMMITTEE_SIZE = VALIDATOR_COUNT - 2; // Tests that a single AztecNodeService hosting multiple validator keys correctly signs attestations // and filters signing to only active committee members. One node, 5 validators staked, committee // size 3. Uses MultiNodeTestContext on the mock-gossip bus: all 5 validators on a single physical -// node, ethSlot=8s, aztecSlot=36s, epoch=2, proofSubEpochs=NO_REORG_SUBMISSION_EPOCHS. Each it is an isolated CI job +// node, ethSlot=8s, aztecSlot=16s, epoch=2, proofSubEpochs=NO_REORG_SUBMISSION_EPOCHS. Each it is an isolated CI job // (parallel convention). describe('multi-node/block-production/multi_validator_node', () => { jest.setTimeout(15 * 60 * 1000); @@ -51,10 +51,10 @@ describe('multi-node/block-production/multi_validator_node', () => { aztecTargetCommitteeSize: COMMITTEE_SIZE, aztecEpochDuration: 2, ethereumSlotDuration: 8, - aztecSlotDuration: 36, + aztecSlotDuration: 16, aztecProofSubmissionEpochs: NO_REORG_SUBMISSION_EPOCHS, anvilSlotsInAnEpoch: 4, - blockDurationMs: 6000, + blockDurationMs: 2000, minTxsPerBlock: 0, inboxLag: 2, }); diff --git a/yarn-project/end-to-end/src/multi-node/block-production/setup.ts b/yarn-project/end-to-end/src/multi-node/block-production/setup.ts index 20a4bec1ca73..595ae29cdea5 100644 --- a/yarn-project/end-to-end/src/multi-node/block-production/setup.ts +++ b/yarn-project/end-to-end/src/multi-node/block-production/setup.ts @@ -24,6 +24,7 @@ import { TxStatus } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { sendL1ToL2Message } from '../../fixtures/l1_to_l2_messaging.js'; +import { testSpan } from '../../fixtures/timing.js'; import type { EndToEndContext } from '../../fixtures/utils.js'; import { waitForBlockNumber, waitForTxs } from '../../fixtures/wait_helpers.js'; import type { TestWallet } from '../../test-wallet/test_wallet.js'; @@ -209,6 +210,17 @@ export async function waitForProvenCheckpoint( logger.warn(`Stopping validator sequencers before waiting for checkpoint ${targetCheckpoint} to be proven`); await Promise.all(nodes.map(n => n.getSequencer()?.stop())); + // With the sequencers stopped, no further blocks are produced, so waiting out the rest of the epoch in + // wall-clock is dead time. Warp the L1 clock forward past the epoch boundary so the epoch containing + // targetCheckpoint closes and the fake prover can prove+submit it; the subsequent wait then only covers + // the (real-time) proving+submission. A single next-epoch jump stays inside the proof-submission window + // (proofSubmissionEpochs >= 1), so it never crosses the submission deadline. Skipped if already proven, + // and forward-only since advanceToNextEpoch never rewinds. + const { proven } = await test.context.cheatCodes.rollup.getTips(); + if (proven < targetCheckpoint) { + await testSpan('warp:proven-checkpoint-epoch', () => test.context.cheatCodes.rollup.advanceToNextEpoch()); + } + const provenTimeout = test.L2_SLOT_DURATION_IN_S * test.epochDuration * 4; logger.warn(`Waiting for checkpoint ${targetCheckpoint} to be proven (timeout=${provenTimeout}s)`); await test.waitUntilProvenCheckpointNumber(targetCheckpoint, provenTimeout); diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index 722eebc4db7a..b2cfe4ac419c 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -24,6 +24,7 @@ import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge'; import { type Hex, getContract } from 'viem'; +import { testSpan } from '../fixtures/timing.js'; import { mintTokensToPrivate } from '../fixtures/token_utils.js'; import { waitForL1ToL2MessageSeen } from './wait_for_l1_to_l2_message.js'; @@ -76,14 +77,18 @@ export async function deployAndInitializeTokenAndBridgeContracts( }); // deploy l2 token - const { contract: token } = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ - from: owner, - }); + const { contract: token } = await testSpan('deploy:token', () => + TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ + from: owner, + }), + ); // deploy l2 token bridge and attach to the portal - const { contract: bridge } = await TokenBridgeContract.deploy(wallet, token.address, tokenPortalAddress).send({ - from: owner, - }); + const { contract: bridge } = await testSpan('deploy:bridge', () => + TokenBridgeContract.deploy(wallet, token.address, tokenPortalAddress).send({ + from: owner, + }), + ); if ((await token.methods.get_admin().simulate({ from: owner })).result !== owner.toBigInt()) { throw new Error(`Token admin is not ${owner}`); @@ -241,11 +246,13 @@ export class CrossChainTestHarness { async mintTokensPublicOnL2(amount: bigint) { this.logger.info('Minting tokens on L2 publicly'); - await this.l2Token.methods.mint_to_public(this.ownerAddress, amount).send({ from: this.ownerAddress }); + await testSpan('tx:mint', () => + this.l2Token.methods.mint_to_public(this.ownerAddress, amount).send({ from: this.ownerAddress }), + ); } async mintTokensPrivateOnL2(amount: bigint) { - await mintTokensToPrivate(this.l2Token, this.ownerAddress, this.ownerAddress, amount); + await testSpan('tx:mint', () => mintTokensToPrivate(this.l2Token, this.ownerAddress, this.ownerAddress, amount)); } async sendL2PublicTransfer(transferAmount: bigint, receiverAddress: AztecAddress) { diff --git a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts index 0d3285247727..017721cf5e48 100644 --- a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts @@ -10,6 +10,7 @@ import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import type { AztecNodeAdmin, AztecNodeDebug } from '@aztec/stdlib/interfaces/client'; +import { testSpan } from '../fixtures/timing.js'; import { waitForL1ToL2MessageSeen } from './wait_for_l1_to_l2_message.js'; /** Aztec node that may expose the debug mining API in local e2e setups. */ @@ -159,11 +160,13 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness { } async bridgeFromL1ToL2(owner: AztecAddress, claimer: AztecAddress) { - // Prepare the tokens on the L1 side - const claim = await this.prepareTokensOnL1(owner); + await testSpan('setup:bridge', async () => { + // Prepare the tokens on the L1 side + const claim = await this.prepareTokensOnL1(owner); - // Consume L1 -> L2 message and claim tokens privately on L2 - await this.consumeMessageOnAztecAndClaimPrivately(owner, claimer, claim); + // Consume L1 -> L2 message and claim tokens privately on L2 + await this.consumeMessageOnAztecAndClaimPrivately(owner, claimer, claim); + }); } private async advanceL2Block() { @@ -181,7 +184,12 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness { } try { - await retryUntil(async () => (await this.aztecNode.getBlockNumber()) >= initialBlockNumber + 1); + await retryUntil( + async () => (await this.aztecNode.getBlockNumber()) >= initialBlockNumber + 1, + 'gas portal block advance', + 0, + 0.25, + ); } finally { if (this.aztecNodeAdmin && minTxsPerBlock !== undefined) { await this.aztecNodeAdmin.setConfig({ minTxsPerBlock }); diff --git a/yarn-project/end-to-end/src/shared/wait_for_l1_to_l2_message.ts b/yarn-project/end-to-end/src/shared/wait_for_l1_to_l2_message.ts index ddce9a05c061..c8cc9ac455b5 100644 --- a/yarn-project/end-to-end/src/shared/wait_for_l1_to_l2_message.ts +++ b/yarn-project/end-to-end/src/shared/wait_for_l1_to_l2_message.ts @@ -18,6 +18,6 @@ export function waitForL1ToL2MessageSeen( async () => (await node.getL1ToL2MessageCheckpoint(l1ToL2MessageHash)) !== undefined, `L1 to L2 message ${l1ToL2MessageHash.toString()} seen`, opts.timeoutSeconds, - 1, + 0.25, ); } diff --git a/yarn-project/end-to-end/src/single-node/fees/fees_test.ts b/yarn-project/end-to-end/src/single-node/fees/fees_test.ts index 12ecbe89c029..0cc1d3a38a88 100644 --- a/yarn-project/end-to-end/src/single-node/fees/fees_test.ts +++ b/yarn-project/end-to-end/src/single-node/fees/fees_test.ts @@ -24,6 +24,7 @@ import { getContract } from 'viem'; import { L1_DIRECT_WRITE_ACCOUNT_INDEX, MNEMONIC, getPaddedMaxFeesPerGas } from '../../fixtures/fixtures.js'; import { type SetupOptions, ensureAuthRegistryPublished, setup } from '../../fixtures/setup.js'; +import { testSpan } from '../../fixtures/timing.js'; import { mintTokensToPrivate } from '../../fixtures/token_utils.js'; import { type BalancesFn, getBalancesFn, setupSponsoredFPC } from '../../fixtures/utils.js'; import { @@ -158,7 +159,9 @@ export class FeesTest extends SingleNodeTestContext { async mintAndBridgeFeeJuice(minter: AztecAddress, recipient: AztecAddress) { const claim = await this.feeJuiceBridgeTestHarness.prepareTokensOnL1(recipient); const { claimSecret: secret, messageLeafIndex: index } = claim; - await this.feeJuiceContract.methods.claim(recipient, claim.claimAmount, secret, index).send({ from: minter }); + await testSpan('setup:bridge', () => + this.feeJuiceContract.methods.claim(recipient, claim.claimAmount, secret, index).send({ from: minter }), + ); } /** Alice mints bananaCoin tokens privately to the target address and redeems them. */ @@ -167,7 +170,7 @@ export class FeesTest extends SingleNodeTestContext { .balance_of_private(address) .simulate({ from: address }); - await mintTokensToPrivate(this.bananaCoin, this.aliceAddress, address, amount); + await testSpan('tx:mint', () => mintTokensToPrivate(this.bananaCoin, this.aliceAddress, address, amount)); const { result: balanceAfter } = await this.bananaCoin.methods .balance_of_private(address) @@ -236,9 +239,11 @@ export class FeesTest extends SingleNodeTestContext { async applyDeployBananaToken() { this.logger.info('Applying deploy banana token setup'); - const { contract: bananaCoin } = await BananaCoin.deploy(this.wallet, this.aliceAddress, 'BC', 'BC', 18n).send({ - from: this.aliceAddress, - }); + const { contract: bananaCoin } = await testSpan('deploy:token', () => + BananaCoin.deploy(this.wallet, this.aliceAddress, 'BC', 'BC', 18n).send({ + from: this.aliceAddress, + }), + ); this.logger.info(`BananaCoin deployed at ${bananaCoin.address}`); this.bananaCoin = bananaCoin; @@ -257,12 +262,15 @@ export class FeesTest extends SingleNodeTestContext { expect((await this.wallet.getContractMetadata(feeJuiceContract.address)).isContractPublished).toBe(true); const bananaCoin = this.bananaCoin; - const { contract: bananaFPC } = await FPCContract.deploy(this.wallet, bananaCoin.address, this.fpcAdmin).send({ - from: this.aliceAddress, - }); + const { contract: bananaFPC } = await testSpan('deploy:fpc', () => + FPCContract.deploy(this.wallet, bananaCoin.address, this.fpcAdmin).send({ + from: this.aliceAddress, + }), + ); this.logger.info(`BananaPay deployed at ${bananaFPC.address}`); + // bridgeFromL1ToL2 carries its own setup:bridge span. await this.feeJuiceBridgeTestHarness.bridgeFromL1ToL2(bananaFPC.address, this.aliceAddress); this.bananaFPC = bananaFPC; @@ -345,9 +353,11 @@ export class FeesTest extends SingleNodeTestContext { this.logger.info('Applying fund Alice with bananas setup'); await this.mintPrivateBananas(this.ALICE_INITIAL_BANANAS, this.aliceAddress); - await this.bananaCoin.methods - .mint_to_public(this.aliceAddress, this.ALICE_INITIAL_BANANAS) - .send({ from: this.aliceAddress }); + await testSpan('tx:mint', () => + this.bananaCoin.methods.mint_to_public(this.aliceAddress, this.ALICE_INITIAL_BANANAS).send({ + from: this.aliceAddress, + }), + ); } public async applyFundAliceWithPrivateBananas() { diff --git a/yarn-project/end-to-end/src/single-node/proving/multi_proof.test.ts b/yarn-project/end-to-end/src/single-node/proving/multi_proof.test.ts index d2c9a17e0afa..9ea23b9d00fc 100644 --- a/yarn-project/end-to-end/src/single-node/proving/multi_proof.test.ts +++ b/yarn-project/end-to-end/src/single-node/proving/multi_proof.test.ts @@ -80,12 +80,12 @@ describe('single-node/proving/multi_proof', () => { const proverIds = test.proverNodes.map(node => node.getProverNode()!.getProverId()); logger.info(`Prover nodes running with ids ${proverIds.map(id => id.toString()).join(', ')}`); - // Anchor on a freshly-started epoch with the provers already running, then wait for it to fully - // elapse. We can't use epoch 0: under CI load the sequencer can come up after the chain has already + // Anchor on a freshly-started epoch with the provers already running, then warp past it so it fully + // elapses. We can't use epoch 0: under CI load the sequencer can come up after the chain has already // advanced past epoch 0's slots, leaving it with no blocks, and the snapshot below would then have // nothing to read. Anchoring on the next epoch guarantees its full slot range is ahead of us. const epoch = await test.waitUntilNextEpochStarts(); - await test.waitUntilEpochStarts(epoch + 1); + await test.warpToEpochStart(epoch + 1); // Snapshot the anchored epoch's checkpoints. The epoch is now closed on L1 (no more epoch-N // checkpoints can land once epoch N+1 has begun), but the node's archiver may still be catching up. diff --git a/yarn-project/end-to-end/src/single-node/proving/upload_failed_proof.test.ts b/yarn-project/end-to-end/src/single-node/proving/upload_failed_proof.test.ts index f4832796d4f1..81988c55569d 100644 --- a/yarn-project/end-to-end/src/single-node/proving/upload_failed_proof.test.ts +++ b/yarn-project/end-to-end/src/single-node/proving/upload_failed_proof.test.ts @@ -88,9 +88,9 @@ describe('single-node/proving/upload_failed_proof', () => { return url; }; - // Wait until the start of epoch one so prover node starts proving epoch 0, + // Warp to the start of epoch one so prover node starts proving epoch 0, // and wait for the data to be uploaded to the remote file store - await test.waitUntilEpochStarts(1); + await test.warpToEpochStart(1); const epochUploadUrl = await epochUploaded; // Stop everything, we're going to prove on a fresh instance diff --git a/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts b/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts index 985920f898df..82aa05d41b96 100644 --- a/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts +++ b/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts @@ -1,7 +1,7 @@ import { generateSchnorrAccounts } from '@aztec/accounts/testing'; import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; -import { NO_WAIT } from '@aztec/aztec.js/contracts'; +import { DefaultWaitOpts, NO_WAIT } from '@aztec/aztec.js/contracts'; import { L1FeeJuicePortalManager } from '@aztec/aztec.js/ethereum'; import { FeeJuicePaymentMethodWithClaim } from '@aztec/aztec.js/fee'; import { type FeePaymentMethod, SponsoredFeePaymentMethod } from '@aztec/aztec.js/fee'; @@ -56,6 +56,8 @@ export async function setupTestAccountsWithTokens( const aztecNode = createAztecNodeClient(nodeUrl); const wallet = await TestWallet.create(aztecNode); + // Remote JSON-RPC node: keep the 1s poll cadence rather than the in-process TestWallet fast default. + wallet.setDefaultWaitInterval(DefaultWaitOpts.interval); const [recipientAccount, ...accounts] = (await registerInitialLocalNetworkAccountsInWallet(wallet)).slice( 0, @@ -276,6 +278,8 @@ export async function deployTestAccountsWithTokens( ): Promise { const aztecNode = createAztecNodeClient(nodeUrl); const wallet = await TestWallet.create(aztecNode); + // Remote JSON-RPC node: keep the 1s poll cadence rather than the in-process TestWallet fast default. + wallet.setDefaultWaitInterval(DefaultWaitOpts.interval); const [recipient, ...funded] = await generateSchnorrAccounts(numberOfFundedWallets + 1); const recipientAccount = await wallet.createSchnorrAccount(recipient.secret, recipient.salt); @@ -459,6 +463,8 @@ export async function createWalletAndAztecNodeClient( proverEnabled, }; const wallet = await TestWallet.create(aztecNode, pxeConfig); + // Remote JSON-RPC node: keep the 1s poll cadence rather than the in-process TestWallet fast default. + wallet.setDefaultWaitInterval(DefaultWaitOpts.interval); return { wallet, diff --git a/yarn-project/end-to-end/src/test-wallet/test_wallet.ts b/yarn-project/end-to-end/src/test-wallet/test_wallet.ts index f6b4389e34d4..d74142e03d08 100644 --- a/yarn-project/end-to-end/src/test-wallet/test_wallet.ts +++ b/yarn-project/end-to-end/src/test-wallet/test_wallet.ts @@ -59,6 +59,13 @@ export interface AccountData { * utilities * It is intended to be used in e2e tests. */ +/** + * Poll interval (in seconds) for in-process TestWallet tx waits. In-process nodes reach CHECKPOINTED synchronously + * under automine and cheaply otherwise, so a sub-second cadence removes almost-pure dead time from every send().wait(). + * Spartan tests run against remote JSON-RPC nodes and restore the 1s default via setDefaultWaitInterval. + */ +export const IN_PROCESS_WAIT_INTERVAL_SECONDS = 0.25; + export class TestWallet extends BaseWallet { constructor( pxe: PXE, @@ -66,6 +73,15 @@ export class TestWallet extends BaseWallet { ) { super(pxe, nodeRef); this.minFeePadding = DEFAULT_MIN_FEE_PADDING; + this.defaultWaitInterval = IN_PROCESS_WAIT_INTERVAL_SECONDS; + } + + /** + * Overrides the poll interval (in seconds) used when a send().wait() caller does not specify one. Pass `undefined` + * to fall back to the DefaultWaitOpts cadence. Spartan tests set this to 1 so they do not hammer remote nodes. + */ + setDefaultWaitInterval(interval?: number): void { + this.defaultWaitInterval = interval; } static async create( diff --git a/yarn-project/end-to-end/src/test-wallet/wallet_worker_script.ts b/yarn-project/end-to-end/src/test-wallet/wallet_worker_script.ts index de104bc4a964..e11c336b9583 100644 --- a/yarn-project/end-to-end/src/test-wallet/wallet_worker_script.ts +++ b/yarn-project/end-to-end/src/test-wallet/wallet_worker_script.ts @@ -1,3 +1,4 @@ +import { DefaultWaitOpts } from '@aztec/aztec.js/contracts'; import { createAztecNodeClient } from '@aztec/aztec.js/node'; import type { SendOptions } from '@aztec/aztec.js/wallet'; import { BackendType, BarretenbergSync } from '@aztec/bb.js'; @@ -23,6 +24,8 @@ try { // Worker sync bb use is limited to crypto and proof serialization helpers. await BarretenbergSync.initSingleton({ backend: BackendType.Wasm }); const wallet = await TestWallet.create(node, pxeConfig); + // Worker wallets are only used by spartan tests against remote JSON-RPC nodes: keep the 1s poll cadence. + wallet.setDefaultWaitInterval(DefaultWaitOpts.interval); logger.info('Worker wallet initialized'); const customMethods = { diff --git a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts index 932d602c0301..8ddf690bbb3e 100644 --- a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts +++ b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts @@ -111,6 +111,9 @@ export type CompleteFeeOptionsConfig = { export abstract class BaseWallet implements Wallet { protected minFeePadding = 0.5; protected cancellableTransactions = false; + // Poll interval (in seconds) injected into sendTx waits when the caller does not specify one. Left undefined on + // production wallets so the DefaultWaitOpts 1s cadence stands; test wallets talking to in-process nodes lower it. + protected defaultWaitInterval?: number; // A wallet is instantiated for a particular chain, so chain info never changes during its lifetime. // We cache it here because getChainInfo is called frequently (every tx simulation, send, auth wit, etc.). private nodeInfoPromise: Promise | undefined; @@ -536,7 +539,11 @@ export abstract class BaseWallet implements Wallet { } // Otherwise, wait for the full receipt (default behavior on wait: undefined) - const waitOpts = typeof opts.wait === 'object' ? opts.wait : undefined; + const callerWaitOpts = typeof opts.wait === 'object' ? opts.wait : undefined; + const waitOpts = + this.defaultWaitInterval !== undefined && callerWaitOpts?.interval === undefined + ? { ...callerWaitOpts, interval: this.defaultWaitInterval } + : callerWaitOpts; const receipt = await waitForTx(this.aztecNode, txHash, waitOpts); // Display debug logs from public execution if present (served in test mode only) diff --git a/yarn-project/world-state/src/testing.test.ts b/yarn-project/world-state/src/testing.test.ts new file mode 100644 index 000000000000..d6d98d463ebc --- /dev/null +++ b/yarn-project/world-state/src/testing.test.ts @@ -0,0 +1,40 @@ +import { Fr } from '@aztec/foundation/curves/bn254'; +import { MerkleTreeId, PublicDataTreeLeaf } from '@aztec/stdlib/trees'; +import type { GenesisData } from '@aztec/stdlib/world-state'; + +import { jest } from '@jest/globals'; + +import { NativeWorldStateService } from './native/index.js'; + +jest.setTimeout(60_000); + +describe('generateGenesisValues world state backend equivalence', () => { + // A genesis with both non-empty prefilled public data and a non-zero timestamp, so the + // fast-return branch in generateGenesisValues is not taken and the archive root is computed + // from an actual world state. + const genesis: GenesisData = { + prefilledPublicData: [ + new PublicDataTreeLeaf(new Fr(1000), new Fr(2000)), + new PublicDataTreeLeaf(new Fr(3000), new Fr(4000)), + ], + genesisTimestamp: 1234567890n, + }; + + const archiveRoot = async (ws: NativeWorldStateService) => + new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root); + + // The consensus-critical guarantee behind computing genesis values on the fsync-off ephemeral + // backend instead of tmp: both backends must derive the exact same on-chain genesis archive root. + it('ephemeral and tmp produce identical genesis archive roots', async () => { + const tmpWs = await NativeWorldStateService.tmp(undefined /* rollupAddress */, true /* cleanupTmpDir */, genesis); + const ephemeralWs = await NativeWorldStateService.ephemeral(genesis); + try { + const tmpRoot = await archiveRoot(tmpWs); + const ephemeralRoot = await archiveRoot(ephemeralWs); + expect(ephemeralRoot).toEqual(tmpRoot); + } finally { + await tmpWs.close(); + await ephemeralWs.close(); + } + }); +}); diff --git a/yarn-project/world-state/src/testing.ts b/yarn-project/world-state/src/testing.ts index 14347f010556..5f30262bae2d 100644 --- a/yarn-project/world-state/src/testing.ts +++ b/yarn-project/world-state/src/testing.ts @@ -14,8 +14,11 @@ async function generateGenesisValues(genesis: GenesisData) { }; } - // Create a temporary world state to compute the genesis values. - const ws = await NativeWorldStateService.tmp(undefined /* rollupAddress */, true /* cleanupTmpDir */, genesis); + // Compute the genesis values on a throwaway world state. The archive root derives only from the + // prefilled public data and the genesis timestamp, so the fsync-off ephemeral store (no version + // manager, no crash-recoverability) produces an identical root while skipping the fsync overhead + // that `tmp` pays. close() removes the tmpdir. + const ws = await NativeWorldStateService.ephemeral(genesis); const genesisArchiveRoot = new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root); await ws.close();