Skip to content

Commit

Permalink
chore: Fast epoch building test
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Nov 19, 2024
1 parent 9643dcd commit ec0d99b
Show file tree
Hide file tree
Showing 27 changed files with 651 additions and 175 deletions.
3 changes: 2 additions & 1 deletion yarn-project/archiver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"exports": {
".": "./dest/index.js",
"./data-retrieval": "./dest/archiver/data_retrieval.js",
"./epoch": "./dest/archiver/epoch_helpers.js",
"./test": "./dest/test/index.js"
},
"typedocOptions": {
Expand Down Expand Up @@ -106,4 +107,4 @@
"engines": {
"node": ">=18"
}
}
}
36 changes: 30 additions & 6 deletions yarn-project/archiver/src/archiver/epoch_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,54 @@
type TimeConstants = {
// REFACTOR: This file should go in a package lower in the dependency graph.

export type EpochConstants = {
l1GenesisBlock: bigint;
l1GenesisTime: bigint;
epochDuration: number;
slotDuration: number;
};

/** Returns the slot number for a given timestamp. */
export function getSlotAtTimestamp(ts: bigint, constants: Pick<TimeConstants, 'l1GenesisTime' | 'slotDuration'>) {
export function getSlotAtTimestamp(ts: bigint, constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration'>) {
return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(constants.slotDuration);
}

/** Returns the epoch number for a given timestamp. */
export function getEpochNumberAtTimestamp(ts: bigint, constants: TimeConstants) {
export function getEpochNumberAtTimestamp(
ts: bigint,
constants: Pick<EpochConstants, 'epochDuration' | 'slotDuration' | 'l1GenesisTime'>,
) {
return getSlotAtTimestamp(ts, constants) / BigInt(constants.epochDuration);
}

/** Returns the range of slots (inclusive) for a given epoch number. */
export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick<TimeConstants, 'epochDuration'>) {
/** Returns the range of L2 slots (inclusive) for a given epoch number. */
export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick<EpochConstants, 'epochDuration'>) {
const startSlot = epochNumber * BigInt(constants.epochDuration);
return [startSlot, startSlot + BigInt(constants.epochDuration) - 1n];
}

/** Returns the range of L1 timestamps (inclusive) for a given epoch number. */
export function getTimestampRangeForEpoch(epochNumber: bigint, constants: TimeConstants) {
export function getTimestampRangeForEpoch(
epochNumber: bigint,
constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration' | 'epochDuration'>,
) {
const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, constants);
return [
constants.l1GenesisTime + startSlot * BigInt(constants.slotDuration),
constants.l1GenesisTime + endSlot * BigInt(constants.slotDuration),
];
}

/**
* Returns the range of L1 blocks (inclusive) for a given epoch number.
* @remarks This assumes no time warp has happened.
*/
export function getL1BlockRangeForEpoch(
epochNumber: bigint,
constants: Pick<EpochConstants, 'l1GenesisBlock' | 'epochDuration' | 'slotDuration'>,
) {
const epochDurationInL1Blocks = BigInt(constants.epochDuration) * BigInt(constants.slotDuration);
return [
epochNumber * epochDurationInL1Blocks + constants.l1GenesisBlock,
(epochNumber + 1n) * epochDurationInL1Blocks + constants.l1GenesisBlock - 1n,
];
}
26 changes: 15 additions & 11 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import { type L1ContractAddresses, createEthereumChain } from '@aztec/ethereum';
import { type ContractArtifact } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { padArrayEnd } from '@aztec/foundation/collection';
import { createDebugLogger } from '@aztec/foundation/log';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { Timer } from '@aztec/foundation/timer';
import { type AztecKVStore } from '@aztec/kv-store';
import { openTmpStore } from '@aztec/kv-store/utils';
Expand All @@ -70,7 +70,7 @@ import {
createP2PClient,
} from '@aztec/p2p';
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
import { GlobalVariableBuilder, SequencerClient } from '@aztec/sequencer-client';
import { GlobalVariableBuilder, type L1Publisher, SequencerClient } from '@aztec/sequencer-client';
import { PublicProcessorFactory } from '@aztec/simulator';
import { type TelemetryClient } from '@aztec/telemetry-client';
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
Expand Down Expand Up @@ -132,10 +132,14 @@ export class AztecNodeService implements AztecNode {
*/
public static async createAndSync(
config: AztecNodeConfig,
telemetry?: TelemetryClient,
log = createDebugLogger('aztec:node'),
deps: {
telemetry?: TelemetryClient;
logger?: DebugLogger;
publisher?: L1Publisher;
} = {},
): Promise<AztecNodeService> {
telemetry ??= new NoopTelemetryClient();
const telemetry = deps.telemetry ?? new NoopTelemetryClient();
const log = deps.logger ?? createDebugLogger('aztec:node');
const ethereumChain = createEthereumChain(config.l1RpcUrl, config.l1ChainId);
//validate that the actual chain id matches that specified in configuration
if (config.l1ChainId !== ethereumChain.chainInfo.id) {
Expand Down Expand Up @@ -165,16 +169,16 @@ export class AztecNodeService implements AztecNode {
// now create the sequencer
const sequencer = config.disableValidator
? undefined
: await SequencerClient.new(
config,
: await SequencerClient.new(config, {
validatorClient,
p2pClient,
worldStateSynchronizer,
archiver,
archiver,
archiver,
contractDataSource: archiver,
l2BlockSource: archiver,
l1ToL2MessageSource: archiver,
telemetry,
);
...deps,
});

return new AztecNodeService(
config,
Expand Down
3 changes: 1 addition & 2 deletions yarn-project/aztec-node/src/bin/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env -S node --no-warnings
import { createDebugLogger } from '@aztec/foundation/log';
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';

import http from 'http';

Expand All @@ -16,7 +15,7 @@ const logger = createDebugLogger('aztec:node');
async function createAndDeployAztecNode() {
const aztecNodeConfig: AztecNodeConfig = { ...getConfigEnvVars() };

return await AztecNodeService.createAndSync(aztecNodeConfig, new NoopTelemetryClient());
return await AztecNodeService.createAndSync(aztecNodeConfig);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec/src/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export async function createSandbox(config: Partial<SandboxConfig> = {}) {
*/
export async function createAztecNode(config: Partial<AztecNodeConfig> = {}, telemetryClient?: TelemetryClient) {
const aztecNodeConfig: AztecNodeConfig = { ...getConfigEnvVars(), ...config };
const node = await AztecNodeService.createAndSync(aztecNodeConfig, telemetryClient);
const node = await AztecNodeService.createAndSync(aztecNodeConfig, { telemetry: telemetryClient });
return node;
}

Expand Down
127 changes: 127 additions & 0 deletions yarn-project/end-to-end/src/e2e_epochs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { type EpochConstants, getTimestampRangeForEpoch } from '@aztec/archiver/epoch';
import { type DebugLogger, retryUntil } from '@aztec/aztec.js';
import { RollupContract } from '@aztec/ethereum/contracts';
import { type Delayer, waitUntilL1Timestamp } from '@aztec/ethereum/test';

import { type PublicClient } from 'viem';

import { type EndToEndContext, setup } from './fixtures/utils.js';

describe('e2e_epochs', () => {
let context: EndToEndContext;
let l1Client: PublicClient;
let rollup: RollupContract;
let constants: EpochConstants;
let logger: DebugLogger;
let proverDelayer: Delayer;
let sequencerDelayer: Delayer;

let l2BlockNumber: number;
let l1BlockNumber: number;
let handle: NodeJS.Timeout;

const EPOCH_DURATION = 4;
const L1_BLOCK_TIME = 3;
const L2_SLOT_DURATION_IN_L1_BLOCKS = 2;

beforeAll(async () => {
context = await setup(0, {
assumeProvenThrough: undefined,
skipProtocolContracts: true,
salt: 1,
aztecEpochDuration: EPOCH_DURATION,
aztecSlotDuration: L1_BLOCK_TIME * L2_SLOT_DURATION_IN_L1_BLOCKS,
ethereumSlotDuration: L1_BLOCK_TIME,
aztecEpochProofClaimWindowInL2Slots: EPOCH_DURATION / 2,
minTxsPerBlock: 0,
realProofs: false,
startProverNode: true,
});

logger = context.logger;
l1Client = context.deployL1ContractsValues.publicClient;
rollup = RollupContract.getFromConfig(context.config);

handle = setInterval(async () => {
const newL1BlockNumber = Number(await l1Client.getBlockNumber({ cacheTime: 0 }));
if (l1BlockNumber === newL1BlockNumber) {
return;
}
const block = await l1Client.getBlock({ blockNumber: BigInt(newL1BlockNumber), includeTransactions: false });
const timestamp = block.timestamp;
l1BlockNumber = newL1BlockNumber;

const newL2BlockNumber = Number(await rollup.getBlockNumber());
if (l2BlockNumber !== newL2BlockNumber) {
logger.info(`Mined new L2 block ${newL2BlockNumber} at L1 block ${newL1BlockNumber} (timestamp ${timestamp})`);
l2BlockNumber = newL2BlockNumber;
} else {
logger.info(`Mined new L1 block ${newL1BlockNumber} (timestamp ${timestamp})`);
}
}, 200);

proverDelayer = (context.proverNode as any).publisher.delayer;
sequencerDelayer = (context.sequencer as any).sequencer.publisher.delayer;
expect(proverDelayer).toBeDefined();
expect(sequencerDelayer).toBeDefined();

constants = {
epochDuration: EPOCH_DURATION,
slotDuration: L1_BLOCK_TIME * L2_SLOT_DURATION_IN_L1_BLOCKS,
l1GenesisBlock: await rollup.getL1StartBlock(),
l1GenesisTime: await rollup.getL1GenesisTime(),
};
logger.info(`L2 genesis at L1 block ${constants.l1GenesisBlock} (timestamp ${constants.l1GenesisTime})`);
});

afterAll(async () => {
clearInterval(handle);
await context.teardown();
});

/** Waits until the epoch begins (ie until the immediately previous L1 block is mined). */
const waitUntilEpochStarts = async (epoch: number) => {
const [start] = getTimestampRangeForEpoch(BigInt(epoch), constants);
logger.info(`Waiting until before L1 timestamp ${start} is reached as the start of epoch ${epoch}`);
await waitUntilL1Timestamp(l1Client, start - 1n);
return start;
};

const waitUntilL2BlockNumber = async (target: number) => {
await retryUntil(() => Promise.resolve(target === l2BlockNumber), `Wait until L2 block ${l2BlockNumber}`, 60, 0.1);
};

/** Asserts the current L2 block number against the rollup contract directly. */
const expectL2BlockNumber = async (blockNumber: number) => {
const currentBlock = await rollup.getBlockNumber();
expect(currentBlock).toEqual(BigInt(blockNumber));
};

it('does not allow submitting proof after epoch end', async () => {
await waitUntilEpochStarts(1);
const initialBlockNumber = Number(await rollup.getBlockNumber());
logger.info(`Starting epoch 1 after L2 block ${initialBlockNumber}`);

// Hold off prover tx until end of current epoch
const [nextEpochStart] = getTimestampRangeForEpoch(2n, constants);
proverDelayer.pauseNextTxUntilTimestamp(nextEpochStart);

// Wait until the last block of epoch 1 is published and hold off the sequencer
await waitUntilL2BlockNumber(initialBlockNumber + EPOCH_DURATION);
sequencerDelayer.pauseNextTxUntilTimestamp(nextEpochStart + BigInt(L1_BLOCK_TIME));

// Next sequencer to publish a block should trigger a rollback, let's give it time to publish
await waitUntilL1Timestamp(l1Client, nextEpochStart + BigInt(L1_BLOCK_TIME * L2_SLOT_DURATION_IN_L1_BLOCKS));
await expectL2BlockNumber(initialBlockNumber + 1);

// The prover tx should have been rejected, and mined strictly before the one that triggered the rollback
const [hash] = proverDelayer.getTxs();
const receipt = await l1Client.getTransactionReceipt({ hash });
expect(receipt.status).toEqual('reverted');

const lastL2BlockTxHash = sequencerDelayer.getTxs().at(-1);
const lastL2BlockTxReceipt = await l1Client.getTransactionReceipt({ hash: lastL2BlockTxHash! });
expect(lastL2BlockTxReceipt.status).toEqual('success');
expect(lastL2BlockTxReceipt.blockNumber).toBeGreaterThan(receipt.blockNumber);
});
});
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_l1_with_wall_time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('e2e_l1_with_wall_time', () => {

({ teardown, logger, pxe } = await setup(0, {
initialValidators,
l1BlockTime: ethereumSlotDuration,
ethereumSlotDuration,
salt: 420,
}));
});
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class P2PNetworkTest {

this.snapshotManager = createSnapshotManager(`e2e_p2p_network/${testName}`, process.env.E2E_DATA_PATH, {
...initialValidatorConfig,
l1BlockTime: l1ContractsConfig.ethereumSlotDuration,
ethereumSlotDuration: l1ContractsConfig.ethereumSlotDuration,
salt: 420,
initialValidators,
metricsPort: metricsPort,
Expand Down
11 changes: 4 additions & 7 deletions yarn-project/end-to-end/src/e2e_synching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,7 @@ describe('e2e_synching', () => {
async (opts: Partial<EndToEndContext>, variant: TestVariant) => {
// All the blocks have been "re-played" and we are now to simply get a new node up to speed
const timer = new Timer();
const freshNode = await AztecNodeService.createAndSync(
{ ...opts.config!, disableValidator: true },
new NoopTelemetryClient(),
);
const freshNode = await AztecNodeService.createAndSync({ ...opts.config!, disableValidator: true });
const syncTime = timer.s();

const blockNumber = await freshNode.getBlockNumber();
Expand Down Expand Up @@ -468,7 +465,7 @@ describe('e2e_synching', () => {
);
await watcher.start();

const aztecNode = await AztecNodeService.createAndSync(opts.config!, new NoopTelemetryClient());
const aztecNode = await AztecNodeService.createAndSync(opts.config!);
const sequencer = aztecNode.getSequencer();

const { pxe } = await setupPXEService(aztecNode!);
Expand Down Expand Up @@ -579,7 +576,7 @@ describe('e2e_synching', () => {
const pendingBlockNumber = await rollup.read.getPendingBlockNumber();
await rollup.write.setAssumeProvenThroughBlockNumber([pendingBlockNumber - BigInt(variant.blockCount) / 2n]);

const aztecNode = await AztecNodeService.createAndSync(opts.config!, new NoopTelemetryClient());
const aztecNode = await AztecNodeService.createAndSync(opts.config!);
const sequencer = aztecNode.getSequencer();

const blockBeforePrune = await aztecNode.getBlockNumber();
Expand Down Expand Up @@ -660,7 +657,7 @@ describe('e2e_synching', () => {
await watcher.start();

// The sync here could likely be avoided by using the node we just synched.
const aztecNode = await AztecNodeService.createAndSync(opts.config!, new NoopTelemetryClient());
const aztecNode = await AztecNodeService.createAndSync(opts.config!);
const sequencer = aztecNode.getSequencer();

const { pxe } = await setupPXEService(aztecNode!);
Expand Down
9 changes: 4 additions & 5 deletions yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,10 @@ export async function createNode(

const telemetryClient = await getEndToEndTestTelemetryClient(metricsPort, /*serviceName*/ `node:${tcpPort}`);

return await AztecNodeService.createAndSync(
validatorConfig,
telemetryClient,
createDebugLogger(`aztec:node-${tcpPort}`),
);
return await AztecNodeService.createAndSync(validatorConfig, {
telemetry: telemetryClient,
logger: createDebugLogger(`aztec:node-${tcpPort}`),
});
}

export async function createValidatorConfig(
Expand Down
Loading

0 comments on commit ec0d99b

Please sign in to comment.