Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ import { EpochsTestContext } from './epochs_test.js';
jest.setTimeout(1000 * 60 * 20);

const NODE_COUNT = 4;
const EXPECTED_BLOCKS_PER_CHECKPOINT = 3;
const EXPECTED_BLOCKS_PER_CHECKPOINT = 8;

// Send enough transactions to trigger multiple blocks within a checkpoint assuming 2 txs per block.
const TX_COUNT = 10;
const TX_COUNT = 24;

/**
* E2E tests for proposer pipelining with Multiple Blocks Per Slot (MBPS).
Expand Down Expand Up @@ -72,16 +72,15 @@ describe('e2e_epochs/epochs_mbps_pipeline', () => {
initialValidators: validators,
enableProposerPipelining: true, // <- yehaw
mockGossipSubNetwork: true,
mockGossipSubNetworkLatency: 500, // adverse network conditions
disableAnvilTestWatcher: true,
startProverNode: true,
perBlockAllocationMultiplier: 1,
perBlockAllocationMultiplier: 8,
aztecEpochDuration: 4,
enforceTimeTable: true,
ethereumSlotDuration: 4,
aztecSlotDuration: 36,
ethereumSlotDuration: 12,
aztecSlotDuration: 72,
blockDurationMs: 8000,
l1PublishingTime: 2,
attestationPropagationTime: 0.5,
aztecTargetCommitteeSize: 3,
inboxLag: 2,
...setupOpts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { EpochCache } from '@aztec/epoch-cache';
import { createEthereumChain } from '@aztec/ethereum/chain';
import { createExtendedL1Client } from '@aztec/ethereum/client';
import { getL1ContractsConfigEnvVars } from '@aztec/ethereum/config';
import { GovernanceProposerContract, RollupContract } from '@aztec/ethereum/contracts';
import { GovernanceProposerContract, RollupContract, SimulationOverridesBuilder } from '@aztec/ethereum/contracts';
import { type DeployAztecL1ContractsArgs, deployAztecL1Contracts } from '@aztec/ethereum/deploy-aztec-l1-contracts';
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
import { TxUtilsState, createL1TxUtils } from '@aztec/ethereum/l1-tx-utils';
Expand Down Expand Up @@ -743,20 +743,21 @@ describe('L1Publisher integration', () => {
expect(invalidateRequest).toBeDefined();
const forcePendingCheckpointNumber = invalidateRequest?.forcePendingCheckpointNumber;
expect(forcePendingCheckpointNumber).toEqual(0);
const invalidationSimulationOverridesPlan = new SimulationOverridesBuilder()
.forPendingCheckpoint(forcePendingCheckpointNumber ?? CheckpointNumber.ZERO)
.build();

// We cannot propose directly, we need to assume the previous checkpoint is invalidated
const genesis = new Fr(GENESIS_ARCHIVE_ROOT);
logger.warn(`Checking can propose at next eth block on top of genesis ${genesis}`);
expect(await publisher.canProposeAt(genesis, proposer!)).toBeUndefined();
const canPropose = await publisher.canProposeAt(genesis, proposer!, { forcePendingCheckpointNumber });
const canPropose = await publisher.canProposeAt(genesis, proposer!, invalidationSimulationOverridesPlan);
expect(canPropose?.slot).toEqual(block.header.getSlot());

// Same for validation
logger.warn('Checking validate block header');
await expect(publisher.validateBlockHeader(checkpoint.header)).rejects.toThrow(/Rollup__InvalidArchive/);
await publisher.validateBlockHeader(checkpoint.header, {
forcePendingCheckpointNumber: forcePendingCheckpointNumber ?? CheckpointNumber.ZERO,
});
await publisher.validateBlockHeader(checkpoint.header, invalidationSimulationOverridesPlan);

// At this point I'm gonna need to propose the correct signature ye? So confused actually here.
const attestationsAndSigners = new CommitteeAttestationsAndSigners(attestations);
Expand All @@ -769,7 +770,7 @@ describe('L1Publisher integration', () => {
logger.warn('Enqueuing requests to invalidate and propose the checkpoint');
publisher.enqueueInvalidateCheckpoint(invalidateRequest);
await publisher.enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, {
forcePendingCheckpointNumber: forcePendingCheckpointNumber ?? CheckpointNumber.ZERO,
simulationOverridesPlan: invalidationSimulationOverridesPlan,
});
const result = await publisher.sendRequests();
expect(result!.successfulActions).toEqual(['invalidate-by-insufficient-attestations', 'propose']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('e2e_p2p_duplicate_attestation_slash', () => {
slashAmountLarge: slashingUnit * 3n,
enforceTimeTable: true,
blockDurationMs: BLOCK_DURATION * 1000,
l1PublishingTime: 1,
slashDuplicateProposalPenalty: slashingUnit,
slashDuplicateAttestationPenalty: slashingUnit,
slashingOffsetInRounds: 1,
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/end-to-end/src/fixtures/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ export type SetupOptions = {
proverNodeConfig?: Partial<ProverNodeConfig>;
/** Whether to use a mock gossip sub network for p2p clients. */
mockGossipSubNetwork?: boolean;
/** Whether to add simulated latency to the mock gossipsub network (in ms) */
mockGossipSubNetworkLatency?: number;
/** Whether to disable the anvil test watcher (can still be manually started) */
disableAnvilTestWatcher?: boolean;
/** Whether to enable anvil automine during deployment of L1 contracts (consider defaulting this to true). */
Expand Down Expand Up @@ -470,7 +472,7 @@ export async function setup(
let p2pClientDeps: P2PClientDeps | undefined = undefined;

if (opts.mockGossipSubNetwork) {
mockGossipSubNetwork = new MockGossipSubNetwork();
mockGossipSubNetwork = new MockGossipSubNetwork(opts.mockGossipSubNetworkLatency);
p2pClientDeps = { p2pServiceFactory: getMockPubSubP2PServiceFactory(mockGossipSubNetwork) };
}

Expand Down
147 changes: 147 additions & 0 deletions yarn-project/ethereum/src/contracts/chain_state_override.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
import type { CheckpointNumber } from '@aztec/foundation/branded-types';
import type { Fr } from '@aztec/foundation/curves/bn254';

import type { StateOverride } from 'viem';

import { type FeeHeader, RollupContract } from './rollup.js';

export type PendingCheckpointOverrideState = {
archive?: Fr;
feeHeader?: FeeHeader;
};

/** Describes the simulated L1 rollup state that downstream calls should observe. */
export type SimulationOverridesPlan = {
pendingCheckpointNumber?: CheckpointNumber;
pendingCheckpointState?: PendingCheckpointOverrideState;
disableBlobCheck?: boolean;
};

/** Builds a single-checkpoint simulation plan before it is translated into a viem state override. */
export class SimulationOverridesBuilder {
private pendingCheckpointNumber?: CheckpointNumber;
private pendingCheckpointState?: PendingCheckpointOverrideState;
private disableBlobCheck = false;

/** Starts from an existing plan so callers can extend or specialize it. */
public static from(plan: SimulationOverridesPlan | undefined): SimulationOverridesBuilder {
return new SimulationOverridesBuilder().merge(plan);
}

/** Merges another plan into this builder. Later values win. */
public merge(plan: SimulationOverridesPlan | undefined): this {
if (!plan) {
return this;
}

this.pendingCheckpointNumber = plan.pendingCheckpointNumber;
this.pendingCheckpointState = plan.pendingCheckpointState
? { ...(this.pendingCheckpointState ?? {}), ...plan.pendingCheckpointState }
: this.pendingCheckpointState;
this.disableBlobCheck = this.disableBlobCheck || (plan.disableBlobCheck ?? false);

return this;
}

/** Sets the checkpoint number that archive and fee header overrides should attach to. */
public forPendingCheckpoint(pendingCheckpointNumber: CheckpointNumber | undefined): this {
this.pendingCheckpointNumber = pendingCheckpointNumber;
return this;
}

/** Overrides the archive root for the configured pending checkpoint. */
public withPendingArchive(archive: Fr): this {
this.assertPendingCheckpointNumber();
this.pendingCheckpointState = { ...(this.pendingCheckpointState ?? {}), archive };
return this;
}

/** Overrides the fee header for the configured pending checkpoint. */
public withPendingFeeHeader(feeHeader: FeeHeader): this {
this.assertPendingCheckpointNumber();
this.pendingCheckpointState = { ...(this.pendingCheckpointState ?? {}), feeHeader };
return this;
}

/** Disables blob checking for simulations that cannot provide DA inputs. */
public withoutBlobCheck(): this {
this.disableBlobCheck = true;
return this;
}

/** Builds the final plan, or `undefined` when no overrides were configured. */
public build(): SimulationOverridesPlan | undefined {
if (!this.pendingCheckpointState && this.pendingCheckpointNumber === undefined && !this.disableBlobCheck) {
return undefined;
}

return {
pendingCheckpointNumber: this.pendingCheckpointNumber,
pendingCheckpointState: this.pendingCheckpointState,
disableBlobCheck: this.disableBlobCheck || undefined,
};
}

private assertPendingCheckpointNumber(): void {
if (this.pendingCheckpointNumber === undefined) {
throw new Error('pendingCheckpointNumber must be set before attaching archive or fee header overrides');
}
}
}

/** Translates a simulation plan into the viem state override shape expected by rollup calls. */
export async function buildSimulationOverridesStateOverride(
rollup: RollupContract,
plan: SimulationOverridesPlan | undefined,
): Promise<StateOverride> {
if (!plan) {
return [];
}

const rollupStateDiff: NonNullable<StateOverride[number]['stateDiff']> = [];

if (plan.pendingCheckpointNumber !== undefined) {
rollupStateDiff.push(
...extractRollupStateDiff(await rollup.makePendingCheckpointNumberOverride(plan.pendingCheckpointNumber)),
);
}

if (plan.pendingCheckpointState && plan.pendingCheckpointNumber === undefined) {
throw new Error('pendingCheckpointState requires pendingCheckpointNumber to be set');
}

if (plan.pendingCheckpointState?.archive) {
rollupStateDiff.push(
...extractRollupStateDiff(
rollup.makeArchiveOverride(plan.pendingCheckpointNumber!, plan.pendingCheckpointState.archive),
),
);
}

if (plan.pendingCheckpointState?.feeHeader) {
rollupStateDiff.push(
...extractRollupStateDiff(
await rollup.makeFeeHeaderOverride(plan.pendingCheckpointNumber!, plan.pendingCheckpointState.feeHeader),
),
);
}

if (plan.disableBlobCheck) {
rollupStateDiff.push({
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
value: toPaddedHex(0n, true),
});
}

if (rollupStateDiff.length === 0) {
return [];
}

return [{ address: rollup.address, stateDiff: rollupStateDiff }];
}

function extractRollupStateDiff(override: StateOverride | StateOverride[number] | undefined) {
const entries = Array.isArray(override) ? override : override ? [override] : [];
return entries.flatMap(entry => entry.stateDiff ?? []);
}
1 change: 1 addition & 0 deletions yarn-project/ethereum/src/contracts/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './chain_state_override.js';
export * from './empire_base.js';
export * from './errors.js';
export * from './fee_asset_handler.js';
Expand Down
21 changes: 21 additions & 0 deletions yarn-project/ethereum/src/contracts/rollup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,27 @@ describe('Rollup', () => {
});
});

describe('makeArchiveOverride', () => {
it('creates state override that correctly sets archive for a checkpoint number', async () => {
const checkpointNumber = CheckpointNumber(5);
const expectedArchive = Fr.random();

// Create the override
const stateOverride = rollup.makeArchiveOverride(checkpointNumber, expectedArchive);

// Test the override using simulateContract to read archiveAt(checkpointNumber)
const { result: overriddenArchive } = await publicClient.simulateContract({
address: rollupAddress,
abi: RollupAbi as Abi,
functionName: 'archiveAt',
args: [BigInt(checkpointNumber)],
stateOverride,
});

expect(Fr.fromString(overriddenArchive as string).equals(expectedArchive)).toBe(true);
});
});

describe('getSlashingProposer', () => {
it('returns a slashing proposer', async () => {
const slashingProposer = await rollup.getSlashingProposer();
Expand Down
10 changes: 1 addition & 9 deletions yarn-project/ethereum/src/contracts/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,19 +765,11 @@ export class RollupContract {
archive: Buffer,
account: `0x${string}` | Account,
timestamp: bigint,
opts: {
forcePendingCheckpointNumber?: CheckpointNumber;
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
} = {},
stateOverride: StateOverride = [],
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
const timeOfNextL1Slot = timestamp;
const who = typeof account === 'string' ? account : account.address;

const stateOverride = RollupContract.mergeStateOverrides(
await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
opts.forceArchive ? this.makeArchiveOverride(opts.forceArchive.checkpointNumber, opts.forceArchive.archive) : [],
);

try {
const {
result: [slot, checkpointNumber],
Expand Down
14 changes: 13 additions & 1 deletion yarn-project/p2p/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ export interface P2PConfig
ChainConfig,
TxCollectionConfig,
TxFileStoreConfig,
Pick<SequencerConfig, 'blockDurationMs' | 'expectedBlockProposalsPerSlot' | 'maxTxsPerBlock'> {
Pick<
SequencerConfig,
| 'blockDurationMs'
| 'expectedBlockProposalsPerSlot'
| 'l1PublishingTime'
| 'maxTxsPerBlock'
| 'attestationPropagationTime'
> {
/** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
validateMaxTxsPerBlock?: number;

Expand Down Expand Up @@ -495,6 +502,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
description: 'Alters the format of p2p messages to include things like broadcast timestamp FOR TESTING ONLY',
...booleanConfigHelper(false),
},
l1PublishingTime: {
env: 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT',
description: 'How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot).',
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
},
fishermanMode: {
env: 'FISHERMAN_MODE',
description:
Expand Down
Loading
Loading