@aztec/slasher 0.0.1-commit.f504929 → 0.0.1-commit.f5a9928
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -79
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +28 -28
- package/dest/factory/create_facade.d.ts +7 -4
- package/dest/factory/create_facade.d.ts.map +1 -1
- package/dest/factory/create_facade.js +5 -2
- package/dest/factory/create_implementation.d.ts +6 -7
- package/dest/factory/create_implementation.d.ts.map +1 -1
- package/dest/factory/create_implementation.js +7 -59
- package/dest/factory/get_settings.d.ts +4 -4
- package/dest/factory/get_settings.d.ts.map +1 -1
- package/dest/factory/get_settings.js +3 -3
- package/dest/factory/index.d.ts +2 -2
- package/dest/factory/index.d.ts.map +1 -1
- package/dest/factory/index.js +1 -1
- package/dest/generated/slasher-defaults.d.ts +4 -4
- package/dest/generated/slasher-defaults.js +4 -4
- package/dest/index.d.ts +6 -4
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +5 -3
- package/dest/metrics.d.ts +23 -0
- package/dest/metrics.d.ts.map +1 -0
- package/dest/metrics.js +38 -0
- package/dest/null_slasher_client.d.ts +3 -4
- package/dest/null_slasher_client.d.ts.map +1 -1
- package/dest/null_slasher_client.js +1 -4
- package/dest/own_validator_slash_monitor.d.ts +66 -0
- package/dest/own_validator_slash_monitor.d.ts.map +1 -0
- package/dest/own_validator_slash_monitor.js +172 -0
- package/dest/slash_offenses_collector.d.ts +6 -8
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_offenses_collector.js +48 -28
- package/dest/slasher_client.d.ts +118 -0
- package/dest/slasher_client.d.ts.map +1 -0
- package/dest/{tally_slasher_client.js → slasher_client.js} +62 -49
- package/dest/slasher_client_facade.d.ts +7 -8
- package/dest/slasher_client_facade.d.ts.map +1 -1
- package/dest/slasher_client_facade.js +6 -9
- package/dest/slasher_client_interface.d.ts +7 -21
- package/dest/slasher_client_interface.d.ts.map +1 -1
- package/dest/slasher_client_interface.js +1 -4
- package/dest/stores/offenses_store.d.ts +12 -12
- package/dest/stores/offenses_store.d.ts.map +1 -1
- package/dest/stores/offenses_store.js +61 -38
- package/dest/watcher.d.ts +8 -1
- package/dest/watcher.d.ts.map +1 -1
- package/dest/watcher.js +1 -0
- package/dest/watchers/attestations_block_watcher.d.ts +26 -13
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +76 -61
- package/dest/watchers/attested_invalid_proposal_watcher.d.ts +42 -0
- package/dest/watchers/attested_invalid_proposal_watcher.d.ts.map +1 -0
- package/dest/watchers/attested_invalid_proposal_watcher.js +117 -0
- package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts +38 -0
- package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts.map +1 -0
- package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.js +138 -0
- package/dest/watchers/checkpoint_equivocation_watcher.d.ts +30 -0
- package/dest/watchers/checkpoint_equivocation_watcher.d.ts.map +1 -0
- package/dest/watchers/checkpoint_equivocation_watcher.js +69 -0
- package/dest/watchers/data_withholding_watcher.d.ts +69 -0
- package/dest/watchers/data_withholding_watcher.d.ts.map +1 -0
- package/dest/watchers/data_withholding_watcher.js +223 -0
- package/package.json +10 -10
- package/src/config.ts +33 -28
- package/src/factory/create_facade.ts +7 -4
- package/src/factory/create_implementation.ts +17 -118
- package/src/factory/get_settings.ts +8 -8
- package/src/factory/index.ts +1 -1
- package/src/generated/slasher-defaults.ts +4 -4
- package/src/index.ts +5 -3
- package/src/metrics.ts +59 -0
- package/src/null_slasher_client.ts +2 -6
- package/src/own_validator_slash_monitor.ts +195 -0
- package/src/slash_offenses_collector.ts +62 -29
- package/src/{tally_slasher_client.ts → slasher_client.ts} +79 -55
- package/src/slasher_client_facade.ts +7 -11
- package/src/slasher_client_interface.ts +6 -21
- package/src/stores/offenses_store.ts +73 -47
- package/src/watcher.ts +8 -0
- package/src/watchers/attestations_block_watcher.ts +88 -82
- package/src/watchers/attested_invalid_proposal_watcher.ts +168 -0
- package/src/watchers/broadcasted_invalid_checkpoint_proposal_watcher.ts +192 -0
- package/src/watchers/checkpoint_equivocation_watcher.ts +96 -0
- package/src/watchers/data_withholding_watcher.ts +257 -0
- package/dest/empire_slasher_client.d.ts +0 -190
- package/dest/empire_slasher_client.d.ts.map +0 -1
- package/dest/empire_slasher_client.js +0 -564
- package/dest/stores/payloads_store.d.ts +0 -29
- package/dest/stores/payloads_store.d.ts.map +0 -1
- package/dest/stores/payloads_store.js +0 -128
- package/dest/tally_slasher_client.d.ts +0 -125
- package/dest/tally_slasher_client.d.ts.map +0 -1
- package/dest/watchers/epoch_prune_watcher.d.ts +0 -39
- package/dest/watchers/epoch_prune_watcher.d.ts.map +0 -1
- package/dest/watchers/epoch_prune_watcher.js +0 -176
- package/src/empire_slasher_client.ts +0 -649
- package/src/stores/payloads_store.ts +0 -149
- package/src/watchers/epoch_prune_watcher.ts +0 -253
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
4
|
+
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
5
|
+
import type { P2PClient, SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
6
|
+
import { type Watcher, type WatcherEmitter } from '../watcher.js';
|
|
7
|
+
declare const AttestedInvalidProposalWatcherConfigKeys: readonly ["slashAttestInvalidCheckpointProposalPenalty"];
|
|
8
|
+
type AttestedInvalidProposalWatcherConfig = Pick<SlasherConfig, (typeof AttestedInvalidProposalWatcherConfigKeys)[number]>;
|
|
9
|
+
type P2PCheckpointAttestationSource = Pick<P2PClient, 'getCheckpointAttestationsForSlot'>;
|
|
10
|
+
type AttestedInvalidProposalWatcherOptions = {
|
|
11
|
+
scanSlotLookback?: number;
|
|
12
|
+
log?: Logger;
|
|
13
|
+
};
|
|
14
|
+
export type InvalidProposalSlotSource = {
|
|
15
|
+
hasInvalidProposals(slot: SlotNumber): boolean;
|
|
16
|
+
hasProposalEquivocation(slot: SlotNumber): boolean;
|
|
17
|
+
};
|
|
18
|
+
declare const AttestedInvalidProposalWatcher_base: new () => WatcherEmitter;
|
|
19
|
+
export declare class AttestedInvalidProposalWatcher extends AttestedInvalidProposalWatcher_base implements Watcher {
|
|
20
|
+
private readonly p2pClient;
|
|
21
|
+
private readonly invalidProposalSlotSource;
|
|
22
|
+
private readonly l2BlockSource;
|
|
23
|
+
private readonly epochCache;
|
|
24
|
+
private readonly log;
|
|
25
|
+
private readonly runningPromise;
|
|
26
|
+
private readonly emittedOffenses;
|
|
27
|
+
private readonly scanSlotLookback;
|
|
28
|
+
private config;
|
|
29
|
+
private lastScannedSlot;
|
|
30
|
+
constructor(p2pClient: P2PCheckpointAttestationSource, invalidProposalSlotSource: InvalidProposalSlotSource, l2BlockSource: Pick<L2BlockSource, 'getSyncedL2SlotNumber'>, epochCache: Pick<EpochCacheInterface, 'getSlotNow' | 'getL1Constants'>, config: AttestedInvalidProposalWatcherConfig, options?: AttestedInvalidProposalWatcherOptions);
|
|
31
|
+
updateConfig(config: Partial<SlasherConfig>): void;
|
|
32
|
+
start(): Promise<void>;
|
|
33
|
+
stop(): Promise<void>;
|
|
34
|
+
scan(): Promise<void>;
|
|
35
|
+
/** Scans a single invalid-proposal slot. */
|
|
36
|
+
scanSlot(slot: SlotNumber): Promise<void>;
|
|
37
|
+
private getSlashArgs;
|
|
38
|
+
private getSlashArgsForAttester;
|
|
39
|
+
private markAsNewOffense;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0ZXN0ZWRfaW52YWxpZF9wcm9wb3NhbF93YXRjaGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvd2F0Y2hlcnMvYXR0ZXN0ZWRfaW52YWxpZF9wcm9wb3NhbF93YXRjaGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDOUQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBSTdELE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUVsRSxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN6RCxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFNaEYsT0FBTyxFQUE2QyxLQUFLLE9BQU8sRUFBRSxLQUFLLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU3RyxRQUFBLE1BQU0sd0NBQXdDLDBEQUEyRCxDQUFDO0FBTTFHLEtBQUssb0NBQW9DLEdBQUcsSUFBSSxDQUM5QyxhQUFhLEVBQ2IsQ0FBQyxPQUFPLHdDQUF3QyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQzFELENBQUM7QUFFRixLQUFLLDhCQUE4QixHQUFHLElBQUksQ0FBQyxTQUFTLEVBQUUsa0NBQWtDLENBQUMsQ0FBQztBQUUxRixLQUFLLHFDQUFxQyxHQUFHO0lBQzNDLGdCQUFnQixDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQzFCLEdBQUcsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUNkLENBQUM7QUFFRixNQUFNLE1BQU0seUJBQXlCLEdBQUc7SUFDdEMsbUJBQW1CLENBQUMsSUFBSSxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUM7SUFDL0MsdUJBQXVCLENBQUMsSUFBSSxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUM7Q0FDcEQsQ0FBQzs7QUFFRixxQkFBYSw4QkFBK0IsU0FBUSxtQ0FBMkMsWUFBVyxPQUFPO0lBUzdHLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUztJQUMxQixPQUFPLENBQUMsUUFBUSxDQUFDLHlCQUF5QjtJQUMxQyxPQUFPLENBQUMsUUFBUSxDQUFDLGFBQWE7SUFDOUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVO0lBWDdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFpQjtJQUNoRCxPQUFPLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBMkQ7SUFDM0YsT0FBTyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBUztJQUMxQyxPQUFPLENBQUMsTUFBTSxDQUF1QztJQUNyRCxPQUFPLENBQUMsZUFBZSxDQUF5QjtJQUVoRCxZQUNtQixTQUFTLEVBQUUsOEJBQThCLEVBQ3pDLHlCQUF5QixFQUFFLHlCQUF5QixFQUNwRCxhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSx1QkFBdUIsQ0FBQyxFQUMzRCxVQUFVLEVBQUUsSUFBSSxDQUFDLG1CQUFtQixFQUFFLFlBQVksR0FBRyxnQkFBZ0IsQ0FBQyxFQUN2RixNQUFNLEVBQUUsb0NBQW9DLEVBQzVDLE9BQU8sR0FBRSxxQ0FBMEMsRUFXcEQ7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxJQUFJLENBR3hEO0lBRU0sS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FHNUI7SUFFTSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUzQjtJQUVZLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBcUJqQztJQUVELDRDQUE0QztJQUMvQixRQUFRLENBQUMsSUFBSSxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBbUNyRDtJQUVELE9BQU8sQ0FBQyxZQUFZO0lBYXBCLE9BQU8sQ0FBQyx1QkFBdUI7SUFTL0IsT0FBTyxDQUFDLGdCQUFnQjtDQUl6QiJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attested_invalid_proposal_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/attested_invalid_proposal_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAI7D,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMhF,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,wCAAwC,0DAA2D,CAAC;AAM1G,KAAK,oCAAoC,GAAG,IAAI,CAC9C,aAAa,EACb,CAAC,OAAO,wCAAwC,CAAC,CAAC,MAAM,CAAC,CAC1D,CAAC;AAEF,KAAK,8BAA8B,GAAG,IAAI,CAAC,SAAS,EAAE,kCAAkC,CAAC,CAAC;AAE1F,KAAK,qCAAqC,GAAG;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAC/C,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;CACpD,CAAC;;AAEF,qBAAa,8BAA+B,SAAQ,mCAA2C,YAAW,OAAO;IAS7G,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,yBAAyB;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAX7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2D;IAC3F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,eAAe,CAAyB;IAEhD,YACmB,SAAS,EAAE,8BAA8B,EACzC,yBAAyB,EAAE,yBAAyB,EACpD,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,uBAAuB,CAAC,EAC3D,UAAU,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,gBAAgB,CAAC,EACvF,MAAM,EAAE,oCAAoC,EAC5C,OAAO,GAAE,qCAA0C,EAWpD;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAGxD;IAEM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5B;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBjC;IAED,4CAA4C;IAC/B,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrD;IAED,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,gBAAgB;CAIzB"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
3
|
+
import { FifoSet } from '@aztec/foundation/fifo-set';
|
|
4
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
6
|
+
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
7
|
+
import EventEmitter from 'node:events';
|
|
8
|
+
import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
9
|
+
const AttestedInvalidProposalWatcherConfigKeys = [
|
|
10
|
+
'slashAttestInvalidCheckpointProposalPenalty'
|
|
11
|
+
];
|
|
12
|
+
const SCAN_SLOT_LAG = 1;
|
|
13
|
+
const DEFAULT_SCAN_SLOT_LOOKBACK = 4;
|
|
14
|
+
const MAX_TRACKED_BAD_ATTESTATIONS = 10_000;
|
|
15
|
+
export class AttestedInvalidProposalWatcher extends EventEmitter {
|
|
16
|
+
p2pClient;
|
|
17
|
+
invalidProposalSlotSource;
|
|
18
|
+
l2BlockSource;
|
|
19
|
+
epochCache;
|
|
20
|
+
log;
|
|
21
|
+
runningPromise;
|
|
22
|
+
emittedOffenses;
|
|
23
|
+
scanSlotLookback;
|
|
24
|
+
config;
|
|
25
|
+
lastScannedSlot;
|
|
26
|
+
constructor(p2pClient, invalidProposalSlotSource, l2BlockSource, epochCache, config, options = {}){
|
|
27
|
+
super(), this.p2pClient = p2pClient, this.invalidProposalSlotSource = invalidProposalSlotSource, this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.emittedOffenses = FifoSet.withLimit(MAX_TRACKED_BAD_ATTESTATIONS);
|
|
28
|
+
const constants = epochCache.getL1Constants();
|
|
29
|
+
this.log = options.log ?? createLogger('attested-invalid-proposal-watcher');
|
|
30
|
+
this.config = pick(config, ...AttestedInvalidProposalWatcherConfigKeys);
|
|
31
|
+
this.scanSlotLookback = Math.max(1, options.scanSlotLookback ?? DEFAULT_SCAN_SLOT_LOOKBACK);
|
|
32
|
+
const intervalMs = Math.max(1000, constants.ethereumSlotDuration * 1000 / 4);
|
|
33
|
+
this.runningPromise = new RunningPromise(()=>this.scan(), this.log, intervalMs);
|
|
34
|
+
this.log.info('AttestedInvalidProposalWatcher initialized', {
|
|
35
|
+
scanSlotLookback: this.scanSlotLookback
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
updateConfig(config) {
|
|
39
|
+
this.config = merge(this.config, pick(config, ...AttestedInvalidProposalWatcherConfigKeys));
|
|
40
|
+
this.log.verbose('AttestedInvalidProposalWatcher config updated', this.config);
|
|
41
|
+
}
|
|
42
|
+
start() {
|
|
43
|
+
this.runningPromise.start();
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
}
|
|
46
|
+
stop() {
|
|
47
|
+
return this.runningPromise.stop();
|
|
48
|
+
}
|
|
49
|
+
async scan() {
|
|
50
|
+
const currentSlot = await this.l2BlockSource.getSyncedL2SlotNumber() ?? this.epochCache.getSlotNow();
|
|
51
|
+
// genesis
|
|
52
|
+
if (currentSlot <= SlotNumber(SCAN_SLOT_LAG)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const newestSlotToConsider = SlotNumber(currentSlot - SCAN_SLOT_LAG);
|
|
56
|
+
const oldestSlot = this.lastScannedSlot === undefined ? SlotNumber(Math.max(0, newestSlotToConsider - this.scanSlotLookback + 1)) : SlotNumber(this.lastScannedSlot + 1);
|
|
57
|
+
if (oldestSlot > newestSlotToConsider) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
for(let slot = oldestSlot; slot <= newestSlotToConsider; slot++){
|
|
61
|
+
await this.scanSlot(slot);
|
|
62
|
+
}
|
|
63
|
+
this.lastScannedSlot = newestSlotToConsider;
|
|
64
|
+
}
|
|
65
|
+
/** Scans a single invalid-proposal slot. */ async scanSlot(slot) {
|
|
66
|
+
if (this.invalidProposalSlotSource.hasProposalEquivocation(slot) || !this.invalidProposalSlotSource.hasInvalidProposals(slot)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
let attestations;
|
|
70
|
+
try {
|
|
71
|
+
attestations = await this.p2pClient.getCheckpointAttestationsForSlot(slot);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
this.log.warn('Error getting checkpoint attestations for invalid proposal slot', {
|
|
74
|
+
err,
|
|
75
|
+
slot
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const slashArgs = attestations.map((attestation)=>this.getSlashArgs(slot, attestation)).filter((args)=>args !== undefined).filter((args)=>this.markAsNewOffense(args));
|
|
80
|
+
if (slashArgs.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.log.info('Detected attestations to invalid checkpoint proposal', {
|
|
84
|
+
slot,
|
|
85
|
+
offenses: slashArgs.map((args)=>({
|
|
86
|
+
validator: args.validator.toString(),
|
|
87
|
+
amount: args.amount,
|
|
88
|
+
offenseType: getOffenseTypeName(args.offenseType),
|
|
89
|
+
epochOrSlot: args.epochOrSlot
|
|
90
|
+
}))
|
|
91
|
+
});
|
|
92
|
+
this.emit(WANT_TO_SLASH_EVENT, slashArgs);
|
|
93
|
+
}
|
|
94
|
+
getSlashArgs(slot, attestation) {
|
|
95
|
+
const attester = attestation.getSender();
|
|
96
|
+
if (!attester) {
|
|
97
|
+
this.log.warn('Cannot slash checkpoint attestation with invalid signature', {
|
|
98
|
+
slot,
|
|
99
|
+
archive: attestation.archive.toString()
|
|
100
|
+
});
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return this.getSlashArgsForAttester(slot, attester);
|
|
104
|
+
}
|
|
105
|
+
getSlashArgsForAttester(slot, attester) {
|
|
106
|
+
return {
|
|
107
|
+
validator: attester,
|
|
108
|
+
amount: this.config.slashAttestInvalidCheckpointProposalPenalty,
|
|
109
|
+
offenseType: OffenseType.ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL,
|
|
110
|
+
epochOrSlot: BigInt(slot)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
markAsNewOffense(args) {
|
|
114
|
+
const key = `${args.validator.toString()}-${args.offenseType}-${args.epochOrSlot}`;
|
|
115
|
+
return this.emittedOffenses.addIfAbsent(key);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
4
|
+
import type { P2PClient, SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
5
|
+
import { type Watcher, type WatcherEmitter } from '../watcher.js';
|
|
6
|
+
declare const BroadcastedInvalidCheckpointProposalWatcherConfigKeys: readonly ["slashBroadcastedInvalidCheckpointProposalPenalty"];
|
|
7
|
+
type BroadcastedInvalidCheckpointProposalWatcherConfig = Pick<SlasherConfig, (typeof BroadcastedInvalidCheckpointProposalWatcherConfigKeys)[number]>;
|
|
8
|
+
type P2PProposalsForSlotSource = Pick<P2PClient, 'getProposalsForSlot'>;
|
|
9
|
+
declare const BroadcastedInvalidCheckpointProposalWatcher_base: new () => WatcherEmitter;
|
|
10
|
+
/** Detects truncated-checkpoint proposal offenses from retained signed P2P proposals. */
|
|
11
|
+
export declare class BroadcastedInvalidCheckpointProposalWatcher extends BroadcastedInvalidCheckpointProposalWatcher_base implements Watcher {
|
|
12
|
+
private readonly p2pClient;
|
|
13
|
+
private readonly l2BlockSource;
|
|
14
|
+
private readonly epochCache;
|
|
15
|
+
private readonly log;
|
|
16
|
+
private readonly runningPromise;
|
|
17
|
+
private readonly emittedOffenses;
|
|
18
|
+
private readonly scanSlotLookback;
|
|
19
|
+
private config;
|
|
20
|
+
private lastScannedSlot;
|
|
21
|
+
constructor(p2pClient: P2PProposalsForSlotSource, l2BlockSource: Pick<L2BlockSource, 'getSyncedL2SlotNumber'>, epochCache: Pick<EpochCacheInterface, 'getSlotNow' | 'getL1Constants'>, config: BroadcastedInvalidCheckpointProposalWatcherConfig, scanSlotLookback?: number);
|
|
22
|
+
updateConfig(config: Partial<BroadcastedInvalidCheckpointProposalWatcherConfig>): void;
|
|
23
|
+
start(): Promise<void>;
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Scans newly closed slots, plus a small lookback for late-arriving proposals. Anchors
|
|
27
|
+
* `currentSlot` at the archiver's last synced L2 slot.
|
|
28
|
+
*/
|
|
29
|
+
scan(): Promise<void>;
|
|
30
|
+
/** Scans a single slot. Public for tests. */
|
|
31
|
+
scanSlot(slot: SlotNumber): Promise<void>;
|
|
32
|
+
private getSlashArgsForProposals;
|
|
33
|
+
private findOffenders;
|
|
34
|
+
private getSignedBlocksBySigner;
|
|
35
|
+
private markAsNewOffense;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvYWRjYXN0ZWRfaW52YWxpZF9jaGVja3BvaW50X3Byb3Bvc2FsX3dhdGNoZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93YXRjaGVycy9icm9hZGNhc3RlZF9pbnZhbGlkX2NoZWNrcG9pbnRfcHJvcG9zYWxfd2F0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzlELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQU03RCxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN6RCxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFNaEYsT0FBTyxFQUE2QyxLQUFLLE9BQU8sRUFBRSxLQUFLLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU3RyxRQUFBLE1BQU0scURBQXFELCtEQUVqRCxDQUFDO0FBS1gsS0FBSyxpREFBaUQsR0FBRyxJQUFJLENBQzNELGFBQWEsRUFDYixDQUFDLE9BQU8scURBQXFELENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FDdkUsQ0FBQztBQUdGLEtBQUsseUJBQXlCLEdBQUcsSUFBSSxDQUFDLFNBQVMsRUFBRSxxQkFBcUIsQ0FBQyxDQUFDOztBQU94RSx5RkFBeUY7QUFDekYscUJBQWEsMkNBQ1gsU0FBUSxnREFDUixZQUFXLE9BQU87SUFVaEIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxTQUFTO0lBQzFCLE9BQU8sQ0FBQyxRQUFRLENBQUMsYUFBYTtJQUM5QixPQUFPLENBQUMsUUFBUSxDQUFDLFVBQVU7SUFWN0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQTJFO0lBQy9GLE9BQU8sQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFpQjtJQUNoRCxPQUFPLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBa0I7SUFDbEQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBUztJQUMxQyxPQUFPLENBQUMsTUFBTSxDQUFvRDtJQUNsRSxPQUFPLENBQUMsZUFBZSxDQUF5QjtJQUVoRCxZQUNtQixTQUFTLEVBQUUseUJBQXlCLEVBQ3BDLGFBQWEsRUFBRSxJQUFJLENBQUMsYUFBYSxFQUFFLHVCQUF1QixDQUFDLEVBQzNELFVBQVUsRUFBRSxJQUFJLENBQUMsbUJBQW1CLEVBQUUsWUFBWSxHQUFHLGdCQUFnQixDQUFDLEVBQ3ZGLE1BQU0sRUFBRSxpREFBaUQsRUFDekQsZ0JBQWdCLFNBQTZCLEVBaUI5QztJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLGlEQUFpRCxDQUFDLEdBQUcsSUFBSSxDQUc1RjtJQUVNLEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBRzVCO0lBRU0sSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FFM0I7SUFFRDs7O09BR0c7SUFDVSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQWVqQztJQUVELDZDQUE2QztJQUNoQyxRQUFRLENBQUMsSUFBSSxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBaUJyRDtJQUVELE9BQU8sQ0FBQyx3QkFBd0I7SUFXaEMsT0FBTyxDQUFDLGFBQWE7SUFrQ3JCLE9BQU8sQ0FBQyx1QkFBdUI7SUFlL0IsT0FBTyxDQUFDLGdCQUFnQjtDQUl6QiJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcasted_invalid_checkpoint_proposal_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/broadcasted_invalid_checkpoint_proposal_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAM7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMhF,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,qDAAqD,+DAEjD,CAAC;AAKX,KAAK,iDAAiD,GAAG,IAAI,CAC3D,aAAa,EACb,CAAC,OAAO,qDAAqD,CAAC,CAAC,MAAM,CAAC,CACvE,CAAC;AAGF,KAAK,yBAAyB,GAAG,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;;AAOxE,yFAAyF;AACzF,qBAAa,2CACX,SAAQ,gDACR,YAAW,OAAO;IAUhB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAV7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA2E;IAC/F,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,MAAM,CAAoD;IAClE,OAAO,CAAC,eAAe,CAAyB;IAEhD,YACmB,SAAS,EAAE,yBAAyB,EACpC,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,uBAAuB,CAAC,EAC3D,UAAU,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,gBAAgB,CAAC,EACvF,MAAM,EAAE,iDAAiD,EACzD,gBAAgB,SAA6B,EAiB9C;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,iDAAiD,CAAC,GAAG,IAAI,CAG5F;IAEM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5B;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED;;;OAGG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAejC;IAED,6CAA6C;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBrD;IAED,OAAO,CAAC,wBAAwB;IAWhC,OAAO,CAAC,aAAa;IAkCrB,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,gBAAgB;CAIzB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
3
|
+
import { FifoSet } from '@aztec/foundation/fifo-set';
|
|
4
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
6
|
+
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
7
|
+
import EventEmitter from 'node:events';
|
|
8
|
+
import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
9
|
+
const BroadcastedInvalidCheckpointProposalWatcherConfigKeys = [
|
|
10
|
+
'slashBroadcastedInvalidCheckpointProposalPenalty'
|
|
11
|
+
];
|
|
12
|
+
const SCAN_SLOT_LAG = 1;
|
|
13
|
+
const DEFAULT_SCAN_SLOT_LOOKBACK = 4;
|
|
14
|
+
/** Detects truncated-checkpoint proposal offenses from retained signed P2P proposals. */ export class BroadcastedInvalidCheckpointProposalWatcher extends EventEmitter {
|
|
15
|
+
p2pClient;
|
|
16
|
+
l2BlockSource;
|
|
17
|
+
epochCache;
|
|
18
|
+
log;
|
|
19
|
+
runningPromise;
|
|
20
|
+
emittedOffenses;
|
|
21
|
+
scanSlotLookback;
|
|
22
|
+
config;
|
|
23
|
+
lastScannedSlot;
|
|
24
|
+
constructor(p2pClient, l2BlockSource, epochCache, config, scanSlotLookback = DEFAULT_SCAN_SLOT_LOOKBACK){
|
|
25
|
+
super(), this.p2pClient = p2pClient, this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.log = createLogger('broadcasted-invalid-checkpoint-proposal-watcher');
|
|
26
|
+
const constants = epochCache.getL1Constants();
|
|
27
|
+
this.config = pick(config, ...BroadcastedInvalidCheckpointProposalWatcherConfigKeys);
|
|
28
|
+
this.scanSlotLookback = Math.max(1, scanSlotLookback);
|
|
29
|
+
// Bound emitted offenses to the number of slots we rescan. This watcher currently tracks one offense type,
|
|
30
|
+
// and at most one offense of that type can be emitted per slot.
|
|
31
|
+
const offenseTypes = 1;
|
|
32
|
+
this.emittedOffenses = FifoSet.withLimit(offenseTypes * this.scanSlotLookback);
|
|
33
|
+
const intervalMs = Math.max(1000, constants.ethereumSlotDuration * 1000 / 4);
|
|
34
|
+
this.runningPromise = new RunningPromise(()=>this.scan(), this.log, intervalMs);
|
|
35
|
+
this.log.info('BroadcastedInvalidCheckpointProposalWatcher initialized', {
|
|
36
|
+
scanSlotLookback: this.scanSlotLookback
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
updateConfig(config) {
|
|
40
|
+
this.config = merge(this.config, pick(config, ...BroadcastedInvalidCheckpointProposalWatcherConfigKeys));
|
|
41
|
+
this.log.verbose('BroadcastedInvalidCheckpointProposalWatcher config updated', this.config);
|
|
42
|
+
}
|
|
43
|
+
start() {
|
|
44
|
+
this.runningPromise.start();
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
stop() {
|
|
48
|
+
return this.runningPromise.stop();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Scans newly closed slots, plus a small lookback for late-arriving proposals. Anchors
|
|
52
|
+
* `currentSlot` at the archiver's last synced L2 slot.
|
|
53
|
+
*/ async scan() {
|
|
54
|
+
const currentSlot = await this.l2BlockSource.getSyncedL2SlotNumber() ?? this.epochCache.getSlotNow();
|
|
55
|
+
if (currentSlot <= SlotNumber(SCAN_SLOT_LAG)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const newestSlotToConsider = SlotNumber(currentSlot - 1 - SCAN_SLOT_LAG);
|
|
59
|
+
const oldestLookbackSlot = SlotNumber(Math.max(0, newestSlotToConsider - this.scanSlotLookback + 1));
|
|
60
|
+
const oldestUnscannedSlot = this.lastScannedSlot === undefined ? oldestLookbackSlot : SlotNumber(this.lastScannedSlot + 1);
|
|
61
|
+
const oldestSlot = SlotNumber(Math.min(oldestLookbackSlot, oldestUnscannedSlot));
|
|
62
|
+
for(let slot = oldestSlot; slot <= newestSlotToConsider; slot++){
|
|
63
|
+
await this.scanSlot(SlotNumber(slot));
|
|
64
|
+
}
|
|
65
|
+
this.lastScannedSlot = newestSlotToConsider;
|
|
66
|
+
}
|
|
67
|
+
/** Scans a single slot. Public for tests. */ async scanSlot(slot) {
|
|
68
|
+
const proposals = await this.p2pClient.getProposalsForSlot(slot);
|
|
69
|
+
const slashArgs = this.getSlashArgsForProposals(slot, proposals).filter((args)=>this.markAsNewOffense(args));
|
|
70
|
+
if (slashArgs.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.log.info(`Detected broadcasted invalid checkpoint proposal offense`, {
|
|
74
|
+
slot,
|
|
75
|
+
offenses: slashArgs.map((args)=>({
|
|
76
|
+
validator: args.validator.toString(),
|
|
77
|
+
amount: args.amount,
|
|
78
|
+
offenseType: getOffenseTypeName(args.offenseType),
|
|
79
|
+
epochOrSlot: args.epochOrSlot
|
|
80
|
+
}))
|
|
81
|
+
});
|
|
82
|
+
this.emit(WANT_TO_SLASH_EVENT, slashArgs);
|
|
83
|
+
}
|
|
84
|
+
getSlashArgsForProposals(slot, proposals) {
|
|
85
|
+
const offenders = this.findOffenders(proposals.blockProposals, proposals.checkpointProposals);
|
|
86
|
+
// we expect one proposer per slot today.
|
|
87
|
+
return [
|
|
88
|
+
...offenders.values()
|
|
89
|
+
].map((validator)=>({
|
|
90
|
+
validator,
|
|
91
|
+
amount: this.config.slashBroadcastedInvalidCheckpointProposalPenalty,
|
|
92
|
+
offenseType: OffenseType.BROADCASTED_INVALID_CHECKPOINT_PROPOSAL,
|
|
93
|
+
epochOrSlot: BigInt(slot)
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
findOffenders(blockProposals, checkpointProposals) {
|
|
97
|
+
const blocksBySigner = this.getSignedBlocksBySigner(blockProposals);
|
|
98
|
+
const offenders = new Map();
|
|
99
|
+
for (const checkpoint of checkpointProposals){
|
|
100
|
+
const checkpointSigner = checkpoint.getSender();
|
|
101
|
+
if (!checkpointSigner) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const signerKey = checkpointSigner.toString();
|
|
105
|
+
const signerBlocks = blocksBySigner.get(signerKey) ?? [];
|
|
106
|
+
const terminalBlocks = signerBlocks.filter(({ proposal })=>proposal.slotNumber === checkpoint.slotNumber && proposal.archive.equals(checkpoint.archive));
|
|
107
|
+
if (terminalBlocks.length === 0) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const hasTruncatedHigherBlock = terminalBlocks.some((terminalBlock)=>signerBlocks.some(({ proposal })=>proposal.slotNumber === checkpoint.slotNumber && proposal.indexWithinCheckpoint > terminalBlock.proposal.indexWithinCheckpoint));
|
|
111
|
+
if (hasTruncatedHigherBlock) {
|
|
112
|
+
offenders.set(signerKey, checkpointSigner);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return offenders;
|
|
116
|
+
}
|
|
117
|
+
getSignedBlocksBySigner(blockProposals) {
|
|
118
|
+
const blocksBySigner = new Map();
|
|
119
|
+
for (const proposal of blockProposals){
|
|
120
|
+
const signer = proposal.getSender();
|
|
121
|
+
if (!signer) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const signerKey = signer.toString();
|
|
125
|
+
const signerBlocks = blocksBySigner.get(signerKey) ?? [];
|
|
126
|
+
signerBlocks.push({
|
|
127
|
+
proposal,
|
|
128
|
+
signer
|
|
129
|
+
});
|
|
130
|
+
blocksBySigner.set(signerKey, signerBlocks);
|
|
131
|
+
}
|
|
132
|
+
return blocksBySigner;
|
|
133
|
+
}
|
|
134
|
+
markAsNewOffense(args) {
|
|
135
|
+
const key = `${args.validator.toString()}-${args.offenseType}-${args.epochOrSlot}`;
|
|
136
|
+
return this.emittedOffenses.addIfAbsent(key);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { type CheckpointEquivocationDetectedEvent, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
+
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
4
|
+
import { type Watcher, type WatcherEmitter } from '../watcher.js';
|
|
5
|
+
declare const CheckpointEquivocationWatcherConfigKeys: readonly ["slashDuplicateProposalPenalty"];
|
|
6
|
+
type CheckpointEquivocationWatcherConfig = Pick<SlasherConfig, (typeof CheckpointEquivocationWatcherConfigKeys)[number]>;
|
|
7
|
+
type EquivocationEventSource = Pick<L2BlockSourceEventEmitter, 'events'>;
|
|
8
|
+
type ProposerLookup = Pick<EpochCacheInterface, 'getProposerAttesterAddressInSlot'>;
|
|
9
|
+
declare const CheckpointEquivocationWatcher_base: new () => WatcherEmitter;
|
|
10
|
+
/**
|
|
11
|
+
* Slashes the slot proposer for DUPLICATE_PROPOSAL when the archiver detects that a
|
|
12
|
+
* locally-stored proposed checkpoint disagrees with the L1-confirmed checkpoint at the
|
|
13
|
+
* same slot. Both are signed by the slot proposer (the proposed one by accepting it via
|
|
14
|
+
* P2P or building it locally; the L1 one by submission), so the proposer equivocated.
|
|
15
|
+
*/
|
|
16
|
+
export declare class CheckpointEquivocationWatcher extends CheckpointEquivocationWatcher_base implements Watcher {
|
|
17
|
+
private readonly l2BlockSource;
|
|
18
|
+
private readonly epochCache;
|
|
19
|
+
private readonly log;
|
|
20
|
+
private readonly handler;
|
|
21
|
+
private config;
|
|
22
|
+
constructor(l2BlockSource: EquivocationEventSource, epochCache: ProposerLookup, config: CheckpointEquivocationWatcherConfig);
|
|
23
|
+
updateConfig(config: Partial<CheckpointEquivocationWatcherConfig>): void;
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
/** Public for tests. */
|
|
27
|
+
onEquivocationDetected(event: CheckpointEquivocationDetectedEvent): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9lcXVpdm9jYXRpb25fd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhdGNoZXJzL2NoZWNrcG9pbnRfZXF1aXZvY2F0aW9uX3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUc5RCxPQUFPLEVBQ0wsS0FBSyxtQ0FBbUMsRUFDeEMsS0FBSyx5QkFBeUIsRUFFL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUtyRSxPQUFPLEVBQTZDLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTdHLFFBQUEsTUFBTSx1Q0FBdUMsNENBQTZDLENBQUM7QUFFM0YsS0FBSyxtQ0FBbUMsR0FBRyxJQUFJLENBQzdDLGFBQWEsRUFDYixDQUFDLE9BQU8sdUNBQXVDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FDekQsQ0FBQztBQUVGLEtBQUssdUJBQXVCLEdBQUcsSUFBSSxDQUFDLHlCQUF5QixFQUFFLFFBQVEsQ0FBQyxDQUFDO0FBQ3pFLEtBQUssY0FBYyxHQUFHLElBQUksQ0FBQyxtQkFBbUIsRUFBRSxrQ0FBa0MsQ0FBQyxDQUFDOztBQUVwRjs7Ozs7R0FLRztBQUNILHFCQUFhLDZCQUE4QixTQUFRLGtDQUEyQyxZQUFXLE9BQU87SUFNNUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhO0lBQzlCLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVTtJQU43QixPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBMkQ7SUFDL0UsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQXNEO0lBQzlFLE9BQU8sQ0FBQyxNQUFNLENBQXNDO0lBRXBELFlBQ21CLGFBQWEsRUFBRSx1QkFBdUIsRUFDdEMsVUFBVSxFQUFFLGNBQWMsRUFDM0MsTUFBTSxFQUFFLG1DQUFtQyxFQVU1QztJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLG1DQUFtQyxDQUFDLEdBQUcsSUFBSSxDQUc5RTtJQUVNLEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBRzVCO0lBRU0sSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FHM0I7SUFFRCx3QkFBd0I7SUFDWCxzQkFBc0IsQ0FBQyxLQUFLLEVBQUUsbUNBQW1DLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTJCN0Y7Q0FDRiJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoint_equivocation_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/checkpoint_equivocation_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,EACL,KAAK,mCAAmC,EACxC,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAKrE,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,uCAAuC,4CAA6C,CAAC;AAE3F,KAAK,mCAAmC,GAAG,IAAI,CAC7C,aAAa,EACb,CAAC,OAAO,uCAAuC,CAAC,CAAC,MAAM,CAAC,CACzD,CAAC;AAEF,KAAK,uBAAuB,GAAG,IAAI,CAAC,yBAAyB,EAAE,QAAQ,CAAC,CAAC;AACzE,KAAK,cAAc,GAAG,IAAI,CAAC,mBAAmB,EAAE,kCAAkC,CAAC,CAAC;;AAEpF;;;;;GAKG;AACH,qBAAa,6BAA8B,SAAQ,kCAA2C,YAAW,OAAO;IAM5G,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAN7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA2D;IAC/E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsD;IAC9E,OAAO,CAAC,MAAM,CAAsC;IAEpD,YACmB,aAAa,EAAE,uBAAuB,EACtC,UAAU,EAAE,cAAc,EAC3C,MAAM,EAAE,mCAAmC,EAU5C;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,mCAAmC,CAAC,GAAG,IAAI,CAG9E;IAEM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5B;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,wBAAwB;IACX,sBAAsB,CAAC,KAAK,EAAE,mCAAmC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B7F;CACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
2
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
4
|
+
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
5
|
+
import EventEmitter from 'node:events';
|
|
6
|
+
import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
7
|
+
const CheckpointEquivocationWatcherConfigKeys = [
|
|
8
|
+
'slashDuplicateProposalPenalty'
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Slashes the slot proposer for DUPLICATE_PROPOSAL when the archiver detects that a
|
|
12
|
+
* locally-stored proposed checkpoint disagrees with the L1-confirmed checkpoint at the
|
|
13
|
+
* same slot. Both are signed by the slot proposer (the proposed one by accepting it via
|
|
14
|
+
* P2P or building it locally; the L1 one by submission), so the proposer equivocated.
|
|
15
|
+
*/ export class CheckpointEquivocationWatcher extends EventEmitter {
|
|
16
|
+
l2BlockSource;
|
|
17
|
+
epochCache;
|
|
18
|
+
log;
|
|
19
|
+
handler;
|
|
20
|
+
config;
|
|
21
|
+
constructor(l2BlockSource, epochCache, config){
|
|
22
|
+
super(), this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.log = createLogger('checkpoint-equivocation-watcher');
|
|
23
|
+
this.config = pick(config, ...CheckpointEquivocationWatcherConfigKeys);
|
|
24
|
+
this.handler = (event)=>{
|
|
25
|
+
this.onEquivocationDetected(event).catch((err)=>this.log.error('Failed to handle checkpoint equivocation event', err));
|
|
26
|
+
};
|
|
27
|
+
this.log.info('CheckpointEquivocationWatcher initialized');
|
|
28
|
+
}
|
|
29
|
+
updateConfig(config) {
|
|
30
|
+
this.config = merge(this.config, pick(config, ...CheckpointEquivocationWatcherConfigKeys));
|
|
31
|
+
this.log.verbose('CheckpointEquivocationWatcher config updated', this.config);
|
|
32
|
+
}
|
|
33
|
+
start() {
|
|
34
|
+
this.l2BlockSource.events.on(L2BlockSourceEvents.CheckpointEquivocationDetected, this.handler);
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
}
|
|
37
|
+
stop() {
|
|
38
|
+
this.l2BlockSource.events.off(L2BlockSourceEvents.CheckpointEquivocationDetected, this.handler);
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
/** Public for tests. */ async onEquivocationDetected(event) {
|
|
42
|
+
const proposer = await this.epochCache.getProposerAttesterAddressInSlot(event.slotNumber);
|
|
43
|
+
if (!proposer) {
|
|
44
|
+
this.log.warn(`Cannot attribute checkpoint equivocation: no proposer for slot ${event.slotNumber}`, {
|
|
45
|
+
slotNumber: event.slotNumber,
|
|
46
|
+
checkpointNumber: event.checkpointNumber
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const slashArgs = {
|
|
51
|
+
validator: proposer,
|
|
52
|
+
amount: this.config.slashDuplicateProposalPenalty,
|
|
53
|
+
offenseType: OffenseType.DUPLICATE_PROPOSAL,
|
|
54
|
+
epochOrSlot: BigInt(event.slotNumber)
|
|
55
|
+
};
|
|
56
|
+
this.log.info(`Detected checkpoint equivocation offense`, {
|
|
57
|
+
slotNumber: event.slotNumber,
|
|
58
|
+
checkpointNumber: event.checkpointNumber,
|
|
59
|
+
amount: slashArgs.amount,
|
|
60
|
+
offenseType: getOffenseTypeName(slashArgs.offenseType),
|
|
61
|
+
l1ArchiveRoot: event.l1ArchiveRoot.toString(),
|
|
62
|
+
proposedArchiveRoot: event.proposedArchiveRoot.toString(),
|
|
63
|
+
validator: proposer.toString()
|
|
64
|
+
});
|
|
65
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
66
|
+
slashArgs
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
4
|
+
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
5
|
+
import type { CheckpointReexecutionTracker, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
6
|
+
import type { ITxProvider, P2PApi, SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
7
|
+
import { type CoordinationSignatureContext } from '@aztec/stdlib/p2p';
|
|
8
|
+
import { type Watcher, type WatcherEmitter } from '../watcher.js';
|
|
9
|
+
declare const DataWithholdingWatcherConfigKeys: readonly ["slashDataWithholdingPenalty", "slashDataWithholdingToleranceSlots"];
|
|
10
|
+
type DataWithholdingWatcherConfig = Pick<SlasherConfig, (typeof DataWithholdingWatcherConfigKeys)[number]>;
|
|
11
|
+
declare const DataWithholdingWatcher_base: new () => WatcherEmitter;
|
|
12
|
+
/**
|
|
13
|
+
* Detects data-withholding offenses by probing the local mempool for the txs in published
|
|
14
|
+
* checkpoints once they are old enough that an honest node should have collected them.
|
|
15
|
+
*
|
|
16
|
+
* Per AZIP-7: once `slashDataWithholdingToleranceSlots` full slots have elapsed after the
|
|
17
|
+
* checkpoint's slot — i.e. at `slotStart(checkpoint.slot + slashDataWithholdingToleranceSlots
|
|
18
|
+
* + 1)` — if any tx from the checkpoint's blocks is still missing locally, the checkpoint's
|
|
19
|
+
* attesters are considered at fault for not making the data available, and we emit a slash
|
|
20
|
+
* for them.
|
|
21
|
+
*
|
|
22
|
+
* The watcher ticks at quarter-eth-slot cadence (matching the Sentinel template). On boot it
|
|
23
|
+
* floors processing at the current slot — restart-time gaps are accepted and not back-filled,
|
|
24
|
+
* matching the Sentinel approach.
|
|
25
|
+
*
|
|
26
|
+
* Since the evidence for an offense is the absence of txs in the local pool, the probe is gated on
|
|
27
|
+
* p2p connectivity: while there are no connected peers, slots are skipped instead of probed, since
|
|
28
|
+
* missing txs then say nothing about the attesters. Nodes running with p2p disabled do not run this
|
|
29
|
+
* watcher at all.
|
|
30
|
+
*/
|
|
31
|
+
export declare class DataWithholdingWatcher extends DataWithholdingWatcher_base implements Watcher {
|
|
32
|
+
private readonly epochCache;
|
|
33
|
+
private readonly l2BlockSource;
|
|
34
|
+
private readonly txProvider;
|
|
35
|
+
private readonly p2p;
|
|
36
|
+
private readonly reexecutionTracker;
|
|
37
|
+
private readonly signatureContext;
|
|
38
|
+
private readonly log;
|
|
39
|
+
private runningPromise;
|
|
40
|
+
private initialSlot;
|
|
41
|
+
private lastCheckedSlot;
|
|
42
|
+
private config;
|
|
43
|
+
private gossipDegraded;
|
|
44
|
+
constructor(epochCache: EpochCache, l2BlockSource: Pick<L2BlockSource, 'getCheckpoint' | 'getSyncedL2SlotNumber'>, txProvider: Pick<ITxProvider, 'hasTxs'>, p2p: Pick<P2PApi, 'getCheckpointAttestationsForSlot' | 'getP2PConnectivity'>, reexecutionTracker: Pick<CheckpointReexecutionTracker, 'getTxsCollectedRecord'>, signatureContext: CoordinationSignatureContext, config: DataWithholdingWatcherConfig, log?: Logger);
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
stop(): Promise<void>;
|
|
47
|
+
updateConfig(config: Partial<SlasherConfig>): void;
|
|
48
|
+
/**
|
|
49
|
+
* Runs every tick. Walks newly-eligible slots and probes their checkpoints for data
|
|
50
|
+
* availability; emits a DATA_WITHHOLDING slash for any checkpoint whose txs are missing.
|
|
51
|
+
*/
|
|
52
|
+
work(): Promise<void>;
|
|
53
|
+
private processSlot;
|
|
54
|
+
/**
|
|
55
|
+
* Returns the union of:
|
|
56
|
+
* 1. attesters whose signatures landed in the published checkpoint on L1, and
|
|
57
|
+
* 2. attesters we observed signing the same proposal on p2p (the proposer publishes as
|
|
58
|
+
* soon as it has hit committee quorum, so honest peer attestations that arrive after
|
|
59
|
+
* that point are dropped — but they still vouched for the data and
|
|
60
|
+
* should be slashed for withholding it).
|
|
61
|
+
*
|
|
62
|
+
*
|
|
63
|
+
* Exposed as protected so tests can substitute a deterministic recovery without having
|
|
64
|
+
* to construct real secp256k1 signatures.
|
|
65
|
+
*/
|
|
66
|
+
protected extractAttesters(published: PublishedCheckpoint): Promise<EthAddress[]>;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YV93aXRoaG9sZGluZ193YXRjaGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvd2F0Y2hlcnMvZGF0YV93aXRoaG9sZGluZ193YXRjaGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR3JELE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ2hFLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUVsRSxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUV6RCxPQUFPLEtBQUssRUFBRSw0QkFBNEIsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ2xHLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDMUYsT0FBTyxFQUFvQixLQUFLLDRCQUE0QixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFNeEYsT0FBTyxFQUE2QyxLQUFLLE9BQU8sRUFBRSxLQUFLLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU3RyxRQUFBLE1BQU0sZ0NBQWdDLGdGQUFpRixDQUFDO0FBRXhILEtBQUssNEJBQTRCLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDLE9BQU8sZ0NBQWdDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDOztBQUUzRzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBQ0gscUJBQWEsc0JBQXVCLFNBQVEsMkJBQTJDLFlBQVcsT0FBTztJQVFyRyxPQUFPLENBQUMsUUFBUSxDQUFDLFVBQVU7SUFDM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhO0lBQzlCLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVTtJQUMzQixPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUc7SUFDcEIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxrQkFBa0I7SUFDbkMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0I7SUFFakMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHO0lBZHRCLE9BQU8sQ0FBQyxjQUFjLENBQWlCO0lBQ3ZDLE9BQU8sQ0FBQyxXQUFXLENBQXlCO0lBQzVDLE9BQU8sQ0FBQyxlQUFlLENBQXlCO0lBQ2hELE9BQU8sQ0FBQyxNQUFNLENBQStCO0lBQzdDLE9BQU8sQ0FBQyxjQUFjLENBQVM7SUFFL0IsWUFDbUIsVUFBVSxFQUFFLFVBQVUsRUFDdEIsYUFBYSxFQUFFLElBQUksQ0FBQyxhQUFhLEVBQUUsZUFBZSxHQUFHLHVCQUF1QixDQUFDLEVBQzdFLFVBQVUsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLFFBQVEsQ0FBQyxFQUN2QyxHQUFHLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxrQ0FBa0MsR0FBRyxvQkFBb0IsQ0FBQyxFQUM1RSxrQkFBa0IsRUFBRSxJQUFJLENBQUMsNEJBQTRCLEVBQUUsdUJBQXVCLENBQUMsRUFDL0UsZ0JBQWdCLEVBQUUsNEJBQTRCLEVBQy9ELE1BQU0sRUFBRSw0QkFBNEIsRUFDbkIsR0FBRyxHQUFFLE1BQWlELEVBT3hFO0lBRVksS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FRbEM7SUFFTSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUzQjtJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxHQUFHLElBQUksQ0FHeEQ7SUFFRDs7O09BR0c7SUFDVSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQXdEakM7WUFHYSxXQUFXO0lBNkV6Qjs7Ozs7Ozs7Ozs7T0FXRztJQUNILFVBQWdCLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxtQkFBbUIsR0FBRyxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FtQnRGO0NBQ0YifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data_withholding_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/data_withholding_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAoB,KAAK,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAMxF,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,gCAAgC,gFAAiF,CAAC;AAExH,KAAK,4BAA4B,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;;AAE3G;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,sBAAuB,SAAQ,2BAA2C,YAAW,OAAO;IAQrG,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAEjC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdtB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,cAAc,CAAS;IAE/B,YACmB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,eAAe,GAAG,uBAAuB,CAAC,EAC7E,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EACvC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,kCAAkC,GAAG,oBAAoB,CAAC,EAC5E,kBAAkB,EAAE,IAAI,CAAC,4BAA4B,EAAE,uBAAuB,CAAC,EAC/E,gBAAgB,EAAE,4BAA4B,EAC/D,MAAM,EAAE,4BAA4B,EACnB,GAAG,GAAE,MAAiD,EAOxE;IAEY,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQlC;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAGxD;IAED;;;OAGG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAwDjC;YAGa,WAAW;IA6EzB;;;;;;;;;;;OAWG;IACH,UAAgB,gBAAgB,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAmBtF;CACF"}
|