-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9643dcd
commit ec0d99b
Showing
27 changed files
with
651 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.