diff --git a/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts b/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts index 81684c98279d..a2ca7a0f93b9 100644 --- a/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts +++ b/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts @@ -2,6 +2,7 @@ import type { EpochCache } from '@aztec/epoch-cache'; import { EthAddress } from '@aztec/foundation/eth-address'; import { sleep } from '@aztec/foundation/sleep'; import { L2Block, type L2BlockSourceEventEmitter, L2BlockSourceEvents } from '@aztec/stdlib/block'; +import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import type { BuildBlockResult, IFullNodeBlockBuilder, @@ -28,6 +29,10 @@ describe('EpochPruneWatcher', () => { let txProvider: MockProxy>; let blockBuilder: MockProxy; let fork: MockProxy; + + let ts: bigint; + let l1Constants: L1RollupConstants; + const validEpochPrunedPenalty = BigInt(1000000000000000000n); const dataWithholdingPenalty = BigInt(2000000000000000000n); @@ -41,6 +46,18 @@ describe('EpochPruneWatcher', () => { fork = mock(); blockBuilder.getFork.mockResolvedValue(fork); + ts = BigInt(Math.ceil(Date.now() / 1000)); + l1Constants = { + l1StartBlock: 1n, + l1GenesisTime: ts, + slotDuration: 24, + epochDuration: 8, + ethereumSlotDuration: 12, + proofSubmissionEpochs: 1, + }; + + epochCache.getL1Constants.mockReturnValue(l1Constants); + watcher = new EpochPruneWatcher(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, blockBuilder, { slashPrunePenalty: validEpochPrunedPenalty, slashDataWithholdingPenalty: dataWithholdingPenalty, @@ -54,9 +71,10 @@ describe('EpochPruneWatcher', () => { it('should emit WANT_TO_SLASH_EVENT when a validator is in a pruned epoch when data is unavailable', async () => { const emitSpy = jest.spyOn(watcher, 'emit'); + const epochNumber = 1n; const block = await L2Block.random( - 1, // block number + 12, // block number 4, // txs per block ); txProvider.getAvailableTxs.mockResolvedValue({ txs: [], missingTxs: [block.body.txEffects[0].txHash] }); @@ -68,11 +86,11 @@ describe('EpochPruneWatcher', () => { epochCache.getCommitteeForEpoch.mockResolvedValue({ committee: committee.map(EthAddress.fromString), seed: 0n, - epoch: 1n, + epoch: epochNumber, }); l2BlockSource.emit(L2BlockSourceEvents.L2PruneDetected, { - epochNumber: 1n, + epochNumber, blocks: [block], type: L2BlockSourceEvents.L2PruneDetected, }); @@ -85,13 +103,13 @@ describe('EpochPruneWatcher', () => { validator: EthAddress.fromString(committee[0]), amount: dataWithholdingPenalty, offenseType: OffenseType.DATA_WITHHOLDING, - epochOrSlot: 1n, + epochOrSlot: epochNumber, }, { validator: EthAddress.fromString(committee[1]), amount: dataWithholdingPenalty, offenseType: OffenseType.DATA_WITHHOLDING, - epochOrSlot: 1n, + epochOrSlot: epochNumber, }, ] satisfies WantToSlashArgs[]); }); @@ -100,7 +118,7 @@ describe('EpochPruneWatcher', () => { const emitSpy = jest.spyOn(watcher, 'emit'); const block = await L2Block.random( - 1, // block number + 12, // block number 4, // txs per block ); const tx = Tx.random(); @@ -152,11 +170,11 @@ describe('EpochPruneWatcher', () => { const emitSpy = jest.spyOn(watcher, 'emit'); const blockFromL1 = await L2Block.random( - 1, // block number + 12, // block number 1, // txs per block ); const blockFromBuilder = await L2Block.random( - 2, // block number + 13, // block number 1, // txs per block ); const tx = Tx.random(); diff --git a/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts b/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts index 87cb535abcb1..465364399f47 100644 --- a/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts +++ b/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts @@ -9,6 +9,7 @@ import { type L2BlockSourceEventEmitter, L2BlockSourceEvents, } from '@aztec/stdlib/block'; +import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers'; import type { IFullNodeBlockBuilder, ITxProvider, @@ -78,9 +79,14 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter private handlePruneL2Blocks(event: L2BlockPruneEvent): void { const { blocks, epochNumber } = event; - this.log.info(`Detected chain prune. Validating epoch ${epochNumber}`); + const l1Constants = this.epochCache.getL1Constants(); + const epochBlocks = blocks.filter(b => getEpochAtSlot(b.slot, l1Constants) === epochNumber); + this.log.info( + `Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, + { blocks: epochBlocks.map(b => b.toBlockInfo()) }, + ); - this.validateBlocks(blocks) + this.validateBlocks(epochBlocks) .then(async () => { this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`); const validators = await this.getValidatorsForEpoch(epochNumber);