import type { DateProvider } from '@aztec/foundation/timer'; import type { BaseSignerConfig } from '@aztec/stdlib/ha-signing'; import { type CheckAndRecordParams, type DeleteDutyParams, type RecordSuccessParams } from './db/types.js'; import type { HASignerMetrics } from './metrics.js'; import type { SlashingProtectionDatabase } from './types.js'; export interface SlashingProtectionServiceDeps { metrics: HASignerMetrics; dateProvider: DateProvider; } /** Default max age (ms) of a stuck SIGNING duty before cleanup reclaims it: 2x the 72s Aztec slot duration. */ export declare const DEFAULT_MAX_STUCK_DUTIES_AGE_MS = 144000; /** * Slashing Protection Service * * This service ensures that a validator only signs one block/attestation per slot, * even when running multiple redundant nodes (HA setup). * * All nodes in the HA setup try to sign - the first one wins, others get * DutyAlreadySignedError (normal) or SlashingProtectionError (if different data). * * Flow: * 1. checkAndRecord() - Atomically try to acquire lock via tryInsertOrGetExisting * 2. Caller performs the signing operation * 3. recordSuccess() - Update to 'signed' status with signature * OR deleteDuty() - Delete the record to allow retry */ export declare class SlashingProtectionService { private readonly db; private readonly config; private readonly log; private readonly pollingIntervalMs; private readonly peerSigningTimeoutMs; private readonly maxStuckDutiesAgeMs; private readonly metrics; private readonly dateProvider; private cleanupRunningPromise; private lastOldDutiesCleanupAtMs?; constructor(db: SlashingProtectionDatabase, config: BaseSignerConfig, deps: SlashingProtectionServiceDeps); /** * Check if a duty can be performed and acquire the lock if so. * * This method uses an atomic insert-or-get operation. * It will: * 1. Try to insert a new record with 'signing' status * 2. If insert succeeds, we acquired the lock - return the lockToken * 3. If a record exists, handle based on status: * - SIGNED: Throw appropriate error (already signed or slashing protection) * - SIGNING: Wait and poll until status changes, then handle result * * @returns The lockToken that must be used for recordSuccess/deleteDuty * @throws DutyAlreadySignedError if the duty was already completed * @throws SlashingProtectionError if attempting to sign different data for same slot/duty */ checkAndRecord(params: CheckAndRecordParams): Promise; /** * Record a successful signing operation. * Updates the duty status to 'signed' and stores the signature. * Only succeeds if the lockToken matches (caller must be the one who created the duty). * * @returns true if the update succeeded, false if token didn't match */ recordSuccess(params: RecordSuccessParams): Promise; /** * Delete a duty record after a failed signing operation. * Removes the record to allow another node/attempt to retry. * Only succeeds if the lockToken matches (caller must be the one who created the duty). * * @returns true if the delete succeeded, false if token didn't match */ deleteDuty(params: DeleteDutyParams): Promise; /** * Get the node ID for this service */ get nodeId(): string; /** * Start running tasks. * Cleanup runs immediately on start to recover from any previous crashes. */ /** * Start the background cleanup task. * Also performs one-time cleanup of duties with outdated rollup addresses. */ start(): Promise; /** * Stop the background cleanup task. */ stop(): Promise; /** * Close the database connection. * Should be called after stop() during graceful shutdown. */ close(): Promise; private cleanup; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVNBLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFakUsT0FBTyxFQUNMLEtBQUssb0JBQW9CLEVBQ3pCLEtBQUssZ0JBQWdCLEVBRXJCLEtBQUssbUJBQW1CLEVBRXpCLE1BQU0sZUFBZSxDQUFDO0FBRXZCLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUU3RCxNQUFNLFdBQVcsNkJBQTZCO0lBQzVDLE9BQU8sRUFBRSxlQUFlLENBQUM7SUFDekIsWUFBWSxFQUFFLFlBQVksQ0FBQztDQUM1QjtBQUVELCtHQUErRztBQUMvRyxlQUFPLE1BQU0sK0JBQStCLFNBQVUsQ0FBQztBQUV2RDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHFCQUFhLHlCQUF5QjtJQWFsQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUU7SUFDbkIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0lBYnpCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQVM7SUFDM0MsT0FBTyxDQUFDLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBUztJQUM5QyxPQUFPLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFTO0lBRTdDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFrQjtJQUMxQyxPQUFPLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBZTtJQUU1QyxPQUFPLENBQUMscUJBQXFCLENBQWlCO0lBQzlDLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQUFTO0lBRTFDLFlBQ21CLEVBQUUsRUFBRSwwQkFBMEIsRUFDOUIsTUFBTSxFQUFFLGdCQUFnQixFQUN6QyxJQUFJLEVBQUUsNkJBQTZCLEVBVXBDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDRyxjQUFjLENBQUMsTUFBTSxFQUFFLG9CQUFvQixHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FxRWxFO0lBRUQ7Ozs7OztPQU1HO0lBQ0csYUFBYSxDQUFDLE1BQU0sRUFBRSxtQkFBbUIsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBMkJqRTtJQUVEOzs7Ozs7T0FNRztJQUNHLFVBQVUsQ0FBQyxNQUFNLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQXdCM0Q7SUFFRDs7T0FFRztJQUNILElBQUksTUFBTSxJQUFJLE1BQU0sQ0FFbkI7SUFFRDs7O09BR0c7SUFDSDs7O09BR0c7SUFDRyxLQUFLLGtCQVlWO0lBRUQ7O09BRUc7SUFDRyxJQUFJLGtCQUdUO0lBRUQ7OztPQUdHO0lBQ0csS0FBSyxrQkFHVjtZQU1hLE9BQU87Q0FrQ3RCIn0=