From ddc52b1e056690fbba3614919a174ed14ed8de78 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Fri, 27 Feb 2026 09:10:09 +0000 Subject: [PATCH 1/2] feat(slasher): make slash grace period relative to rollup upgrade time Anchor SLASH_GRACE_PERIOD_L2_SLOTS to the CanonicalRollupUpdated event instead of genesis. The rollup can be deployed weeks before becoming canonical, so a grace period from genesis is impractical. Now operators set a duration (e.g. 3600 slots = 3 days) and it's automatically anchored to when the rollup was registered in the Registry. --- .../src/spartan/slash_inactivity.test.ts | 2 +- .../ethereum/src/contracts/registry.ts | 23 +++++++++++++- yarn-project/slasher/src/config.ts | 3 +- .../slasher/src/empire_slasher_client.test.ts | 1 + .../slasher/src/factory/create_facade.ts | 31 +++++++++++++++++-- .../src/factory/create_implementation.ts | 30 ++++++++++++++++-- .../slasher/src/factory/get_settings.ts | 4 +-- .../src/slash_offenses_collector.test.ts | 25 ++++++++------- .../slasher/src/slash_offenses_collector.ts | 10 ++++-- .../slasher/src/slasher_client_facade.ts | 2 ++ .../slasher/src/tally_slasher_client.test.ts | 1 + 11 files changed, 108 insertions(+), 24 deletions(-) diff --git a/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts b/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts index 6092f1af2605..afc6c6f9ba67 100644 --- a/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts +++ b/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts @@ -38,7 +38,7 @@ describe('slash inactivity test', () => { let client: ViemPublicClient; let rollup: RollupContract; - let slashSettings: TallySlasherSettings; + let slashSettings: Omit; let constants: Omit; let monitor: ChainMonitor; let offlineValidator: EthAddress; diff --git a/yarn-project/ethereum/src/contracts/registry.ts b/yarn-project/ethereum/src/contracts/registry.ts index 89156ec13c7a..ffffffbae1c8 100644 --- a/yarn-project/ethereum/src/contracts/registry.ts +++ b/yarn-project/ethereum/src/contracts/registry.ts @@ -3,7 +3,7 @@ import { createLogger } from '@aztec/foundation/log'; import { RegistryAbi } from '@aztec/l1-artifacts/RegistryAbi'; import { TestERC20Abi } from '@aztec/l1-artifacts/TestERC20Abi'; -import { type GetContractReturnType, type Hex, getContract } from 'viem'; +import { type GetContractReturnType, type Hex, getAbiItem, getContract } from 'viem'; import type { L1ContractAddresses } from '../l1_contract_addresses.js'; import type { ViemClient } from '../types.js'; @@ -128,4 +128,25 @@ export class RegistryContract { public async getRewardDistributor(): Promise { return EthAddress.fromString(await this.registry.read.getRewardDistributor()); } + + /** Returns the L1 timestamp at which the given rollup was registered via addRollup(). */ + public async getCanonicalRollupRegistrationTimestamp( + rollupAddress: EthAddress, + fromBlock?: bigint, + ): Promise { + const logs = await this.client.getLogs({ + address: this.address.toString(), + fromBlock: fromBlock ?? 0n, + strict: true, + event: getAbiItem({ abi: RegistryAbi, name: 'CanonicalRollupUpdated' }), + args: { instance: rollupAddress.toString() }, + }); + + if (logs.length === 0) { + return undefined; + } + + const block = await this.client.getBlock({ blockNumber: logs[0].blockNumber }); + return block.timestamp; + } } diff --git a/yarn-project/slasher/src/config.ts b/yarn-project/slasher/src/config.ts index 79cef1e58b1a..646225cc6e20 100644 --- a/yarn-project/slasher/src/config.ts +++ b/yarn-project/slasher/src/config.ts @@ -155,7 +155,8 @@ export const slasherConfigMappings: ConfigMappingsType = { ...numberConfigHelper(DefaultSlasherConfig.slashMaxPayloadSize), }, slashGracePeriodL2Slots: { - description: 'Number of L2 slots to wait before considering a slashing offense expired.', + description: + 'Number of L2 slots after the network upgrade during which slashing offenses are ignored. The upgrade time is determined from the CanonicalRollupUpdated event.', env: 'SLASH_GRACE_PERIOD_L2_SLOTS', ...numberConfigHelper(DefaultSlasherConfig.slashGracePeriodL2Slots), }, diff --git a/yarn-project/slasher/src/empire_slasher_client.test.ts b/yarn-project/slasher/src/empire_slasher_client.test.ts index 4aa40aee11f9..4375ebea1bd1 100644 --- a/yarn-project/slasher/src/empire_slasher_client.test.ts +++ b/yarn-project/slasher/src/empire_slasher_client.test.ts @@ -50,6 +50,7 @@ describe('EmpireSlasherClient', () => { slotDuration: 4, ethereumSlotDuration: 12, slashingAmounts: undefined, + canonicalRollupRegisteredAtL2Slot: 0, }; const config: SlasherConfig = { diff --git a/yarn-project/slasher/src/factory/create_facade.ts b/yarn-project/slasher/src/factory/create_facade.ts index af6f7ed9cd6c..fbfdeb67a870 100644 --- a/yarn-project/slasher/src/factory/create_facade.ts +++ b/yarn-project/slasher/src/factory/create_facade.ts @@ -1,5 +1,5 @@ import { EpochCache } from '@aztec/epoch-cache'; -import { RollupContract } from '@aztec/ethereum/contracts'; +import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts'; import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader'; import type { ViemClient } from '@aztec/ethereum/types'; import { unique } from '@aztec/foundation/collection'; @@ -8,6 +8,7 @@ import { createLogger } from '@aztec/foundation/log'; import { DateProvider } from '@aztec/foundation/timer'; import type { DataStoreConfig } from '@aztec/kv-store/config'; import { createStore } from '@aztec/kv-store/lmdb-v2'; +import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers'; import type { SlasherConfig } from '@aztec/stdlib/interfaces/server'; import { SlasherClientFacade } from '../slasher_client_facade.js'; @@ -18,7 +19,7 @@ import type { Watcher } from '../watcher.js'; /** Creates a slasher client facade that updates itself whenever the rollup slasher changes */ export async function createSlasherFacade( config: SlasherConfig & DataStoreConfig & { ethereumSlotDuration: number }, - l1Contracts: Pick, + l1Contracts: Pick, l1Client: ViemClient, watchers: Watcher[], dateProvider: DateProvider, @@ -34,6 +35,31 @@ export async function createSlasherFacade( const kvStore = await createStore('slasher', SCHEMA_VERSION, config, logger.getBindings()); const rollup = new RollupContract(l1Client, l1Contracts.rollupAddress); + // Compute and cache the L2 slot at which the rollup was registered as canonical + const settingsMap = kvStore.openMap('slasher-settings'); + const cacheKey = `registeredSlot:${l1Contracts.rollupAddress}`; + let canonicalRollupRegisteredAtL2Slot = await settingsMap.getAsync(cacheKey); + + if (canonicalRollupRegisteredAtL2Slot === undefined) { + const registry = new RegistryContract(l1Client, l1Contracts.registryAddress); + const l1StartBlock = await rollup.getL1StartBlock(); + const registrationTimestamp = await registry.getCanonicalRollupRegistrationTimestamp( + l1Contracts.rollupAddress, + l1StartBlock, + ); + if (registrationTimestamp !== undefined) { + const l1GenesisTime = await rollup.getL1GenesisTime(); + const slotDuration = await rollup.getSlotDuration(); + canonicalRollupRegisteredAtL2Slot = Number( + getSlotAtTimestamp(registrationTimestamp, { l1GenesisTime, slotDuration: Number(slotDuration) }), + ); + } else { + canonicalRollupRegisteredAtL2Slot = 0; // Fallback: no event found (e.g. test environments) + } + await settingsMap.set(cacheKey, canonicalRollupRegisteredAtL2Slot); + logger.info(`Canonical rollup registered at L2 slot ${canonicalRollupRegisteredAtL2Slot}`); + } + const slashValidatorsNever = config.slashSelfAllowed ? config.slashValidatorsNever : unique([...config.slashValidatorsNever, ...validatorAddresses].map(a => a.toString())).map(EthAddress.fromString); @@ -48,6 +74,7 @@ export async function createSlasherFacade( epochCache, dateProvider, kvStore, + canonicalRollupRegisteredAtL2Slot, logger, ); } diff --git a/yarn-project/slasher/src/factory/create_implementation.ts b/yarn-project/slasher/src/factory/create_implementation.ts index d793f3285709..8e2ef4a5c851 100644 --- a/yarn-project/slasher/src/factory/create_implementation.ts +++ b/yarn-project/slasher/src/factory/create_implementation.ts @@ -31,19 +31,40 @@ export async function createSlasherImplementation( epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, + canonicalRollupRegisteredAtL2Slot: number, logger = createLogger('slasher'), ) { const proposer = await rollup.getSlashingProposer(); if (!proposer) { return new NullSlasherClient(config); } else if (proposer.type === 'tally') { - return createTallySlasher(config, rollup, proposer, watchers, dateProvider, epochCache, kvStore, logger); + return createTallySlasher( + config, + rollup, + proposer, + watchers, + dateProvider, + epochCache, + kvStore, + canonicalRollupRegisteredAtL2Slot, + logger, + ); } else { if (!slashFactoryAddress || slashFactoryAddress.equals(EthAddress.ZERO)) { throw new Error('Cannot initialize an empire-based SlasherClient without a SlashFactory address'); } const slashFactory = new SlashFactoryContract(l1Client, slashFactoryAddress.toString()); - return createEmpireSlasher(config, rollup, proposer, slashFactory, watchers, dateProvider, kvStore, logger); + return createEmpireSlasher( + config, + rollup, + proposer, + slashFactory, + watchers, + dateProvider, + kvStore, + canonicalRollupRegisteredAtL2Slot, + logger, + ); } } @@ -55,6 +76,7 @@ async function createEmpireSlasher( watchers: Watcher[], dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, + canonicalRollupRegisteredAtL2Slot: number, logger = createLogger('slasher'), ): Promise { if (slashingProposer.type !== 'empire') { @@ -97,6 +119,7 @@ async function createEmpireSlasher( l1StartBlock, ethereumSlotDuration: config.ethereumSlotDuration, slashingAmounts: undefined, + canonicalRollupRegisteredAtL2Slot, }; const payloadsStore = new SlasherPayloadsStore(kvStore, { @@ -130,13 +153,14 @@ async function createTallySlasher( dateProvider: DateProvider, epochCache: EpochCache, kvStore: AztecLMDBStoreV2, + canonicalRollupRegisteredAtL2Slot: number, logger = createLogger('slasher'), ): Promise { if (slashingProposer.type !== 'tally') { throw new Error('Slashing proposer contract is not of type tally'); } - const settings = await getTallySlasherSettings(rollup, slashingProposer); + const settings = { ...(await getTallySlasherSettings(rollup, slashingProposer)), canonicalRollupRegisteredAtL2Slot }; const slasher = await rollup.getSlasherContract(); const offensesStore = new SlasherOffensesStore(kvStore, { diff --git a/yarn-project/slasher/src/factory/get_settings.ts b/yarn-project/slasher/src/factory/get_settings.ts index 078073847e13..ad87531bf367 100644 --- a/yarn-project/slasher/src/factory/get_settings.ts +++ b/yarn-project/slasher/src/factory/get_settings.ts @@ -5,7 +5,7 @@ import type { TallySlasherSettings } from '../tally_slasher_client.js'; export async function getTallySlasherSettings( rollup: RollupContract, slashingProposer?: TallySlashingProposerContract, -): Promise { +): Promise> { if (!slashingProposer) { const rollupSlashingProposer = await rollup.getSlashingProposer(); if (!rollupSlashingProposer || rollupSlashingProposer.type !== 'tally') { @@ -40,7 +40,7 @@ export async function getTallySlasherSettings( rollup.getTargetCommitteeSize(), ]); - const settings: TallySlasherSettings = { + const settings: Omit = { slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds), slashingRoundSize: Number(slashingRoundSize), slashingRoundSizeInEpochs: Number(slashingRoundSizeInEpochs), diff --git a/yarn-project/slasher/src/slash_offenses_collector.test.ts b/yarn-project/slasher/src/slash_offenses_collector.test.ts index f23d1824fb18..53791b26c39e 100644 --- a/yarn-project/slasher/src/slash_offenses_collector.test.ts +++ b/yarn-project/slasher/src/slash_offenses_collector.test.ts @@ -18,6 +18,7 @@ describe('SlashOffensesCollector', () => { const settings: SlashOffensesCollectorSettings = { epochDuration: 32, slashingAmounts: [100n, 200n, 300n], + canonicalRollupRegisteredAtL2Slot: 100, }; const config: SlasherConfig = { @@ -90,27 +91,28 @@ describe('SlashOffensesCollector', () => { }); }); - it('should skip offenses that happen during grace period', async () => { + it('should skip offenses that happen during grace period after upgrade', async () => { const validator1 = EthAddress.random(); const validator2 = EthAddress.random(); - // Create offense during grace period (slot < slashGracePeriodL2Slots = 10) + // Grace period is registeredSlot (100) + gracePeriodL2Slots (10) = 110 + // Create offense during grace period (slot 105 < 110) const gracePeriodOffense: WantToSlashArgs[] = [ { validator: validator1, amount: 1000000000000000000n, offenseType: OffenseType.PROPOSED_INSUFFICIENT_ATTESTATIONS, // Slot-based offense - epochOrSlot: 5n, // Within grace period (< 10) + epochOrSlot: 105n, // Within grace period (< 110) }, ]; - // Create offense after grace period + // Create offense after grace period (slot 115 >= 110) const validOffense: WantToSlashArgs[] = [ { validator: validator2, amount: 2000000000000000000n, offenseType: OffenseType.PROPOSED_INSUFFICIENT_ATTESTATIONS, // Slot-based offense - epochOrSlot: 20n, // After grace period (>= 10) + epochOrSlot: 115n, // After grace period (>= 110) }, ]; @@ -134,25 +136,26 @@ describe('SlashOffensesCollector', () => { const validator2 = EthAddress.random(); const validator3 = EthAddress.random(); - // Create an event with multiple offenses in a single array + // Grace period ends at registeredSlot (100) + gracePeriod (10) = 110 + // All offenses are after the grace period const multipleOffensesArgs: WantToSlashArgs[] = [ { validator: validator1, amount: 1000000000000000000n, offenseType: OffenseType.INACTIVITY, - epochOrSlot: 100n, + epochOrSlot: 100n, // epoch 100 → slot 3200, well past grace period }, { validator: validator2, amount: 2000000000000000000n, offenseType: OffenseType.PROPOSED_INSUFFICIENT_ATTESTATIONS, - epochOrSlot: 50n, + epochOrSlot: 150n, // slot 150 >= 110 }, { validator: validator3, amount: 1500000000000000000n, offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID, - epochOrSlot: 75n, + epochOrSlot: 175n, // slot 175 >= 110 }, ]; @@ -182,14 +185,14 @@ describe('SlashOffensesCollector', () => { validator: validator2, amount: 2000000000000000000n, offenseType: OffenseType.PROPOSED_INSUFFICIENT_ATTESTATIONS, - epochOrSlot: 50n, + epochOrSlot: 150n, }); expect(offensesByValidator[validator3.toString()]).toMatchObject({ validator: validator3, amount: 1500000000000000000n, offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID, - epochOrSlot: 75n, + epochOrSlot: 175n, }); }); }); diff --git a/yarn-project/slasher/src/slash_offenses_collector.ts b/yarn-project/slasher/src/slash_offenses_collector.ts index 551f868ccec3..71c7e43bfa24 100644 --- a/yarn-project/slasher/src/slash_offenses_collector.ts +++ b/yarn-project/slasher/src/slash_offenses_collector.ts @@ -9,7 +9,11 @@ import { WANT_TO_SLASH_EVENT, type WantToSlashArgs, type Watcher } from './watch export type SlashOffensesCollectorConfig = Prettify>; export type SlashOffensesCollectorSettings = Prettify< - Pick & { slashingAmounts: [bigint, bigint, bigint] | undefined } + Pick & { + slashingAmounts: [bigint, bigint, bigint] | undefined; + /** L2 slot at which the rollup was registered as canonical in the Registry. Used to anchor the slash grace period. */ + canonicalRollupRegisteredAtL2Slot: number; + } >; /** @@ -110,9 +114,9 @@ export class SlashOffensesCollector { return this.offensesStore.markAsSlashed(offenses); } - /** Returns whether to skip an offense if it happened during the grace period at the beginning of the chain */ + /** Returns whether to skip an offense if it happened during the grace period after the network upgrade */ private shouldSkipOffense(offense: Offense): boolean { const offenseSlot = getSlotForOffense(offense, this.settings); - return offenseSlot < this.config.slashGracePeriodL2Slots; + return offenseSlot < this.settings.canonicalRollupRegisteredAtL2Slot + this.config.slashGracePeriodL2Slots; } } diff --git a/yarn-project/slasher/src/slasher_client_facade.ts b/yarn-project/slasher/src/slasher_client_facade.ts index 943084816870..34a1206f0af3 100644 --- a/yarn-project/slasher/src/slasher_client_facade.ts +++ b/yarn-project/slasher/src/slasher_client_facade.ts @@ -32,6 +32,7 @@ export class SlasherClientFacade implements SlasherClientInterface { private epochCache: EpochCache, private dateProvider: DateProvider, private kvStore: AztecLMDBStoreV2, + private canonicalRollupRegisteredAtL2Slot: number, private logger = createLogger('slasher'), ) {} @@ -88,6 +89,7 @@ export class SlasherClientFacade implements SlasherClientInterface { this.epochCache, this.dateProvider, this.kvStore, + this.canonicalRollupRegisteredAtL2Slot, this.logger, ); } diff --git a/yarn-project/slasher/src/tally_slasher_client.test.ts b/yarn-project/slasher/src/tally_slasher_client.test.ts index 9ca14eaa7a44..87735a744336 100644 --- a/yarn-project/slasher/src/tally_slasher_client.test.ts +++ b/yarn-project/slasher/src/tally_slasher_client.test.ts @@ -50,6 +50,7 @@ describe('TallySlasherClient', () => { l1GenesisTime: BigInt(Math.floor(Date.now() / 1000) - 10000), slotDuration: 4, slashingQuorumSize: 110, + canonicalRollupRegisteredAtL2Slot: 0, }; const config: SlasherConfig = { From 65126d94daeda12fc776c2ec757e0160c4283ff7 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Fri, 27 Feb 2026 13:07:16 +0000 Subject: [PATCH 2/2] fix: address review comments on slash grace period PR - Rename canonicalRollupRegisteredAtL2Slot to rollupRegisteredAtL2Slot - Use branded SlotNumber type instead of plain number - Chunk getLogs query in registry to avoid RPC provider block range limits --- .../src/spartan/slash_inactivity.test.ts | 2 +- .../ethereum/src/contracts/registry.ts | 33 ++++++++++++------- .../slasher/src/empire_slasher_client.test.ts | 2 +- .../slasher/src/factory/create_facade.ts | 20 ++++++----- .../src/factory/create_implementation.ts | 15 +++++---- .../slasher/src/factory/get_settings.ts | 4 +-- .../src/slash_offenses_collector.test.ts | 3 +- .../slasher/src/slash_offenses_collector.ts | 5 +-- .../slasher/src/slasher_client_facade.ts | 4 +-- .../slasher/src/tally_slasher_client.test.ts | 2 +- 10 files changed, 52 insertions(+), 38 deletions(-) diff --git a/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts b/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts index afc6c6f9ba67..bdd7ea3e7fd0 100644 --- a/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts +++ b/yarn-project/end-to-end/src/spartan/slash_inactivity.test.ts @@ -38,7 +38,7 @@ describe('slash inactivity test', () => { let client: ViemPublicClient; let rollup: RollupContract; - let slashSettings: Omit; + let slashSettings: Omit; let constants: Omit; let monitor: ChainMonitor; let offlineValidator: EthAddress; diff --git a/yarn-project/ethereum/src/contracts/registry.ts b/yarn-project/ethereum/src/contracts/registry.ts index ffffffbae1c8..a4bf15f16bb8 100644 --- a/yarn-project/ethereum/src/contracts/registry.ts +++ b/yarn-project/ethereum/src/contracts/registry.ts @@ -134,19 +134,28 @@ export class RegistryContract { rollupAddress: EthAddress, fromBlock?: bigint, ): Promise { - const logs = await this.client.getLogs({ - address: this.address.toString(), - fromBlock: fromBlock ?? 0n, - strict: true, - event: getAbiItem({ abi: RegistryAbi, name: 'CanonicalRollupUpdated' }), - args: { instance: rollupAddress.toString() }, - }); - - if (logs.length === 0) { - return undefined; + const event = getAbiItem({ abi: RegistryAbi, name: 'CanonicalRollupUpdated' }); + const start = fromBlock ?? 0n; + const latestBlock = await this.client.getBlockNumber(); + const chunkSize = 1_000n; + + for (let from = start; from <= latestBlock; from += chunkSize) { + const to = from + chunkSize - 1n > latestBlock ? latestBlock : from + chunkSize - 1n; + const logs = await this.client.getLogs({ + address: this.address.toString(), + fromBlock: from, + toBlock: to, + strict: true, + event, + args: { instance: rollupAddress.toString() }, + }); + + if (logs.length > 0) { + const block = await this.client.getBlock({ blockNumber: logs[0].blockNumber }); + return block.timestamp; + } } - const block = await this.client.getBlock({ blockNumber: logs[0].blockNumber }); - return block.timestamp; + return undefined; } } diff --git a/yarn-project/slasher/src/empire_slasher_client.test.ts b/yarn-project/slasher/src/empire_slasher_client.test.ts index 4375ebea1bd1..1bff7848fdc5 100644 --- a/yarn-project/slasher/src/empire_slasher_client.test.ts +++ b/yarn-project/slasher/src/empire_slasher_client.test.ts @@ -50,7 +50,7 @@ describe('EmpireSlasherClient', () => { slotDuration: 4, ethereumSlotDuration: 12, slashingAmounts: undefined, - canonicalRollupRegisteredAtL2Slot: 0, + rollupRegisteredAtL2Slot: SlotNumber(0), }; const config: SlasherConfig = { diff --git a/yarn-project/slasher/src/factory/create_facade.ts b/yarn-project/slasher/src/factory/create_facade.ts index fbfdeb67a870..6787fc65ce75 100644 --- a/yarn-project/slasher/src/factory/create_facade.ts +++ b/yarn-project/slasher/src/factory/create_facade.ts @@ -2,6 +2,7 @@ import { EpochCache } from '@aztec/epoch-cache'; import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts'; import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader'; import type { ViemClient } from '@aztec/ethereum/types'; +import { SlotNumber } from '@aztec/foundation/branded-types'; import { unique } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; @@ -38,9 +39,9 @@ export async function createSlasherFacade( // Compute and cache the L2 slot at which the rollup was registered as canonical const settingsMap = kvStore.openMap('slasher-settings'); const cacheKey = `registeredSlot:${l1Contracts.rollupAddress}`; - let canonicalRollupRegisteredAtL2Slot = await settingsMap.getAsync(cacheKey); + let rollupRegisteredAtL2Slot = (await settingsMap.getAsync(cacheKey)) as SlotNumber | undefined; - if (canonicalRollupRegisteredAtL2Slot === undefined) { + if (rollupRegisteredAtL2Slot === undefined) { const registry = new RegistryContract(l1Client, l1Contracts.registryAddress); const l1StartBlock = await rollup.getL1StartBlock(); const registrationTimestamp = await registry.getCanonicalRollupRegistrationTimestamp( @@ -50,14 +51,15 @@ export async function createSlasherFacade( if (registrationTimestamp !== undefined) { const l1GenesisTime = await rollup.getL1GenesisTime(); const slotDuration = await rollup.getSlotDuration(); - canonicalRollupRegisteredAtL2Slot = Number( - getSlotAtTimestamp(registrationTimestamp, { l1GenesisTime, slotDuration: Number(slotDuration) }), - ); + rollupRegisteredAtL2Slot = getSlotAtTimestamp(registrationTimestamp, { + l1GenesisTime, + slotDuration: Number(slotDuration), + }); } else { - canonicalRollupRegisteredAtL2Slot = 0; // Fallback: no event found (e.g. test environments) + rollupRegisteredAtL2Slot = SlotNumber(0); } - await settingsMap.set(cacheKey, canonicalRollupRegisteredAtL2Slot); - logger.info(`Canonical rollup registered at L2 slot ${canonicalRollupRegisteredAtL2Slot}`); + await settingsMap.set(cacheKey, rollupRegisteredAtL2Slot); + logger.info(`Canonical rollup registered at L2 slot ${rollupRegisteredAtL2Slot}`); } const slashValidatorsNever = config.slashSelfAllowed @@ -74,7 +76,7 @@ export async function createSlasherFacade( epochCache, dateProvider, kvStore, - canonicalRollupRegisteredAtL2Slot, + rollupRegisteredAtL2Slot, logger, ); } diff --git a/yarn-project/slasher/src/factory/create_implementation.ts b/yarn-project/slasher/src/factory/create_implementation.ts index 8e2ef4a5c851..0c6eb8ce6d76 100644 --- a/yarn-project/slasher/src/factory/create_implementation.ts +++ b/yarn-project/slasher/src/factory/create_implementation.ts @@ -5,6 +5,7 @@ import { TallySlashingProposerContract, } from '@aztec/ethereum/contracts'; import type { ViemClient } from '@aztec/ethereum/types'; +import type { SlotNumber } from '@aztec/foundation/branded-types'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; import { DateProvider } from '@aztec/foundation/timer'; @@ -31,7 +32,7 @@ export async function createSlasherImplementation( epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, - canonicalRollupRegisteredAtL2Slot: number, + rollupRegisteredAtL2Slot: SlotNumber, logger = createLogger('slasher'), ) { const proposer = await rollup.getSlashingProposer(); @@ -46,7 +47,7 @@ export async function createSlasherImplementation( dateProvider, epochCache, kvStore, - canonicalRollupRegisteredAtL2Slot, + rollupRegisteredAtL2Slot, logger, ); } else { @@ -62,7 +63,7 @@ export async function createSlasherImplementation( watchers, dateProvider, kvStore, - canonicalRollupRegisteredAtL2Slot, + rollupRegisteredAtL2Slot, logger, ); } @@ -76,7 +77,7 @@ async function createEmpireSlasher( watchers: Watcher[], dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, - canonicalRollupRegisteredAtL2Slot: number, + rollupRegisteredAtL2Slot: SlotNumber, logger = createLogger('slasher'), ): Promise { if (slashingProposer.type !== 'empire') { @@ -119,7 +120,7 @@ async function createEmpireSlasher( l1StartBlock, ethereumSlotDuration: config.ethereumSlotDuration, slashingAmounts: undefined, - canonicalRollupRegisteredAtL2Slot, + rollupRegisteredAtL2Slot, }; const payloadsStore = new SlasherPayloadsStore(kvStore, { @@ -153,14 +154,14 @@ async function createTallySlasher( dateProvider: DateProvider, epochCache: EpochCache, kvStore: AztecLMDBStoreV2, - canonicalRollupRegisteredAtL2Slot: number, + rollupRegisteredAtL2Slot: SlotNumber, logger = createLogger('slasher'), ): Promise { if (slashingProposer.type !== 'tally') { throw new Error('Slashing proposer contract is not of type tally'); } - const settings = { ...(await getTallySlasherSettings(rollup, slashingProposer)), canonicalRollupRegisteredAtL2Slot }; + const settings = { ...(await getTallySlasherSettings(rollup, slashingProposer)), rollupRegisteredAtL2Slot }; const slasher = await rollup.getSlasherContract(); const offensesStore = new SlasherOffensesStore(kvStore, { diff --git a/yarn-project/slasher/src/factory/get_settings.ts b/yarn-project/slasher/src/factory/get_settings.ts index ad87531bf367..6fd10662edcd 100644 --- a/yarn-project/slasher/src/factory/get_settings.ts +++ b/yarn-project/slasher/src/factory/get_settings.ts @@ -5,7 +5,7 @@ import type { TallySlasherSettings } from '../tally_slasher_client.js'; export async function getTallySlasherSettings( rollup: RollupContract, slashingProposer?: TallySlashingProposerContract, -): Promise> { +): Promise> { if (!slashingProposer) { const rollupSlashingProposer = await rollup.getSlashingProposer(); if (!rollupSlashingProposer || rollupSlashingProposer.type !== 'tally') { @@ -40,7 +40,7 @@ export async function getTallySlasherSettings( rollup.getTargetCommitteeSize(), ]); - const settings: Omit = { + const settings: Omit = { slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds), slashingRoundSize: Number(slashingRoundSize), slashingRoundSizeInEpochs: Number(slashingRoundSizeInEpochs), diff --git a/yarn-project/slasher/src/slash_offenses_collector.test.ts b/yarn-project/slasher/src/slash_offenses_collector.test.ts index 53791b26c39e..3eeeb532494e 100644 --- a/yarn-project/slasher/src/slash_offenses_collector.test.ts +++ b/yarn-project/slasher/src/slash_offenses_collector.test.ts @@ -1,3 +1,4 @@ +import { SlotNumber } from '@aztec/foundation/branded-types'; import { EthAddress } from '@aztec/foundation/eth-address'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { openTmpStore } from '@aztec/kv-store/lmdb'; @@ -18,7 +19,7 @@ describe('SlashOffensesCollector', () => { const settings: SlashOffensesCollectorSettings = { epochDuration: 32, slashingAmounts: [100n, 200n, 300n], - canonicalRollupRegisteredAtL2Slot: 100, + rollupRegisteredAtL2Slot: 100 as SlotNumber, }; const config: SlasherConfig = { diff --git a/yarn-project/slasher/src/slash_offenses_collector.ts b/yarn-project/slasher/src/slash_offenses_collector.ts index 71c7e43bfa24..59cc7a0e1dc6 100644 --- a/yarn-project/slasher/src/slash_offenses_collector.ts +++ b/yarn-project/slasher/src/slash_offenses_collector.ts @@ -1,3 +1,4 @@ +import type { SlotNumber } from '@aztec/foundation/branded-types'; import { createLogger } from '@aztec/foundation/log'; import type { Prettify } from '@aztec/foundation/types'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; @@ -12,7 +13,7 @@ export type SlashOffensesCollectorSettings = Prettify< Pick & { slashingAmounts: [bigint, bigint, bigint] | undefined; /** L2 slot at which the rollup was registered as canonical in the Registry. Used to anchor the slash grace period. */ - canonicalRollupRegisteredAtL2Slot: number; + rollupRegisteredAtL2Slot: SlotNumber; } >; @@ -117,6 +118,6 @@ export class SlashOffensesCollector { /** Returns whether to skip an offense if it happened during the grace period after the network upgrade */ private shouldSkipOffense(offense: Offense): boolean { const offenseSlot = getSlotForOffense(offense, this.settings); - return offenseSlot < this.settings.canonicalRollupRegisteredAtL2Slot + this.config.slashGracePeriodL2Slots; + return offenseSlot < this.settings.rollupRegisteredAtL2Slot + this.config.slashGracePeriodL2Slots; } } diff --git a/yarn-project/slasher/src/slasher_client_facade.ts b/yarn-project/slasher/src/slasher_client_facade.ts index 34a1206f0af3..0ef4a677ac0a 100644 --- a/yarn-project/slasher/src/slasher_client_facade.ts +++ b/yarn-project/slasher/src/slasher_client_facade.ts @@ -32,7 +32,7 @@ export class SlasherClientFacade implements SlasherClientInterface { private epochCache: EpochCache, private dateProvider: DateProvider, private kvStore: AztecLMDBStoreV2, - private canonicalRollupRegisteredAtL2Slot: number, + private rollupRegisteredAtL2Slot: SlotNumber, private logger = createLogger('slasher'), ) {} @@ -89,7 +89,7 @@ export class SlasherClientFacade implements SlasherClientInterface { this.epochCache, this.dateProvider, this.kvStore, - this.canonicalRollupRegisteredAtL2Slot, + this.rollupRegisteredAtL2Slot, this.logger, ); } diff --git a/yarn-project/slasher/src/tally_slasher_client.test.ts b/yarn-project/slasher/src/tally_slasher_client.test.ts index 87735a744336..62f5b492fa99 100644 --- a/yarn-project/slasher/src/tally_slasher_client.test.ts +++ b/yarn-project/slasher/src/tally_slasher_client.test.ts @@ -50,7 +50,7 @@ describe('TallySlasherClient', () => { l1GenesisTime: BigInt(Math.floor(Date.now() / 1000) - 10000), slotDuration: 4, slashingQuorumSize: 110, - canonicalRollupRegisteredAtL2Slot: 0, + rollupRegisteredAtL2Slot: SlotNumber(0), }; const config: SlasherConfig = {