@aztec/slasher 0.0.1-commit.2eb6648a → 0.0.1-commit.2f68f620

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.
Files changed (95) hide show
  1. package/README.md +78 -78
  2. package/dest/config.d.ts +1 -1
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +35 -29
  5. package/dest/factory/create_facade.d.ts +3 -3
  6. package/dest/factory/create_facade.d.ts.map +1 -1
  7. package/dest/factory/create_facade.js +25 -2
  8. package/dest/factory/create_implementation.d.ts +6 -7
  9. package/dest/factory/create_implementation.d.ts.map +1 -1
  10. package/dest/factory/create_implementation.js +8 -56
  11. package/dest/factory/get_settings.d.ts +4 -4
  12. package/dest/factory/get_settings.d.ts.map +1 -1
  13. package/dest/factory/get_settings.js +3 -3
  14. package/dest/factory/index.d.ts +2 -2
  15. package/dest/factory/index.d.ts.map +1 -1
  16. package/dest/factory/index.js +1 -1
  17. package/dest/generated/slasher-defaults.d.ts +8 -7
  18. package/dest/generated/slasher-defaults.d.ts.map +1 -1
  19. package/dest/generated/slasher-defaults.js +7 -6
  20. package/dest/index.d.ts +6 -4
  21. package/dest/index.d.ts.map +1 -1
  22. package/dest/index.js +5 -3
  23. package/dest/metrics.d.ts +7 -0
  24. package/dest/metrics.d.ts.map +1 -0
  25. package/dest/metrics.js +11 -0
  26. package/dest/null_slasher_client.d.ts +3 -4
  27. package/dest/null_slasher_client.d.ts.map +1 -1
  28. package/dest/null_slasher_client.js +1 -4
  29. package/dest/slash_offenses_collector.d.ts +10 -9
  30. package/dest/slash_offenses_collector.d.ts.map +1 -1
  31. package/dest/slash_offenses_collector.js +50 -34
  32. package/dest/{tally_slasher_client.d.ts → slasher_client.d.ts} +17 -28
  33. package/dest/slasher_client.d.ts.map +1 -0
  34. package/dest/{tally_slasher_client.js → slasher_client.js} +50 -45
  35. package/dest/slasher_client_facade.d.ts +6 -8
  36. package/dest/slasher_client_facade.d.ts.map +1 -1
  37. package/dest/slasher_client_facade.js +6 -9
  38. package/dest/slasher_client_interface.d.ts +7 -21
  39. package/dest/slasher_client_interface.d.ts.map +1 -1
  40. package/dest/slasher_client_interface.js +1 -4
  41. package/dest/stores/offenses_store.d.ts +12 -12
  42. package/dest/stores/offenses_store.d.ts.map +1 -1
  43. package/dest/stores/offenses_store.js +61 -38
  44. package/dest/watcher.d.ts +8 -1
  45. package/dest/watcher.d.ts.map +1 -1
  46. package/dest/watcher.js +1 -0
  47. package/dest/watchers/attestations_block_watcher.d.ts +26 -13
  48. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  49. package/dest/watchers/attestations_block_watcher.js +76 -61
  50. package/dest/watchers/attested_invalid_proposal_watcher.d.ts +42 -0
  51. package/dest/watchers/attested_invalid_proposal_watcher.d.ts.map +1 -0
  52. package/dest/watchers/attested_invalid_proposal_watcher.js +117 -0
  53. package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts +38 -0
  54. package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts.map +1 -0
  55. package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.js +138 -0
  56. package/dest/watchers/checkpoint_equivocation_watcher.d.ts +30 -0
  57. package/dest/watchers/checkpoint_equivocation_watcher.d.ts.map +1 -0
  58. package/dest/watchers/checkpoint_equivocation_watcher.js +69 -0
  59. package/dest/watchers/data_withholding_watcher.d.ts +63 -0
  60. package/dest/watchers/data_withholding_watcher.d.ts.map +1 -0
  61. package/dest/watchers/data_withholding_watcher.js +193 -0
  62. package/package.json +10 -10
  63. package/src/config.ts +42 -29
  64. package/src/factory/create_facade.ts +32 -4
  65. package/src/factory/create_implementation.ts +24 -105
  66. package/src/factory/get_settings.ts +8 -8
  67. package/src/factory/index.ts +1 -1
  68. package/src/generated/slasher-defaults.ts +7 -6
  69. package/src/index.ts +5 -3
  70. package/src/metrics.ts +19 -0
  71. package/src/null_slasher_client.ts +2 -6
  72. package/src/slash_offenses_collector.ts +70 -36
  73. package/src/{tally_slasher_client.ts → slasher_client.ts} +67 -54
  74. package/src/slasher_client_facade.ts +6 -11
  75. package/src/slasher_client_interface.ts +6 -21
  76. package/src/stores/offenses_store.ts +73 -47
  77. package/src/watcher.ts +8 -0
  78. package/src/watchers/attestations_block_watcher.ts +88 -82
  79. package/src/watchers/attested_invalid_proposal_watcher.ts +168 -0
  80. package/src/watchers/broadcasted_invalid_checkpoint_proposal_watcher.ts +192 -0
  81. package/src/watchers/checkpoint_equivocation_watcher.ts +96 -0
  82. package/src/watchers/data_withholding_watcher.ts +225 -0
  83. package/dest/empire_slasher_client.d.ts +0 -190
  84. package/dest/empire_slasher_client.d.ts.map +0 -1
  85. package/dest/empire_slasher_client.js +0 -564
  86. package/dest/stores/payloads_store.d.ts +0 -29
  87. package/dest/stores/payloads_store.d.ts.map +0 -1
  88. package/dest/stores/payloads_store.js +0 -128
  89. package/dest/tally_slasher_client.d.ts.map +0 -1
  90. package/dest/watchers/epoch_prune_watcher.d.ts +0 -39
  91. package/dest/watchers/epoch_prune_watcher.d.ts.map +0 -1
  92. package/dest/watchers/epoch_prune_watcher.js +0 -175
  93. package/src/empire_slasher_client.ts +0 -649
  94. package/src/stores/payloads_store.ts +0 -149
  95. package/src/watchers/epoch_prune_watcher.ts +0 -251
@@ -1,128 +0,0 @@
1
- import { deserializeSlashPayload, serializeSlashPayload } from '@aztec/stdlib/slashing';
2
- export class SlasherPayloadsStore {
3
- kvStore;
4
- settings;
5
- /** Map from payload address to payload data */ payloads;
6
- /** Map from `round:payload` to votes */ roundPayloadVotes;
7
- constructor(kvStore, settings){
8
- this.kvStore = kvStore;
9
- this.settings = settings;
10
- this.payloads = kvStore.openMap('slash-payloads');
11
- this.roundPayloadVotes = kvStore.openMap('round-payload-votes');
12
- }
13
- async getPayloadsForRound(round) {
14
- const payloads = [];
15
- const votes = await this.getVotesForRound(round);
16
- for (const [address, votesCount] of votes){
17
- const payload = await this.getPayload(address);
18
- if (payload) {
19
- payloads.push({
20
- ...payload,
21
- votes: votesCount,
22
- round
23
- });
24
- }
25
- }
26
- return payloads;
27
- }
28
- async getPayloadAtRound(payloadAddress, round) {
29
- const address = payloadAddress.toString();
30
- const buffer = await this.payloads.getAsync(address);
31
- if (!buffer) {
32
- return undefined;
33
- }
34
- const data = deserializeSlashPayload(buffer);
35
- const votes = await this.roundPayloadVotes.getAsync(this.getPayloadVotesKey(round, address)) ?? 0n;
36
- return {
37
- ...data,
38
- votes,
39
- round
40
- };
41
- }
42
- async getVotesForRound(round) {
43
- const votes = [];
44
- for await (const [fullKey, roundVotes] of this.roundPayloadVotes.entriesAsync(this.getPayloadVotesKeyRangeForRound(round))){
45
- // Extract just the address part from the key (remove "round:" prefix)
46
- const address = fullKey.split(':')[1];
47
- votes.push([
48
- address,
49
- roundVotes
50
- ]);
51
- }
52
- return votes;
53
- }
54
- getRoundKey(round) {
55
- return round.toString().padStart(16, '0');
56
- }
57
- getPayloadVotesKey(round, payloadAddress) {
58
- return `${this.getRoundKey(round)}:${payloadAddress.toString()}`;
59
- }
60
- getPayloadVotesKeyRangeForRound(round) {
61
- const start = `${this.getRoundKey(round)}:`;
62
- const end = `${this.getRoundKey(round)}:Z`; // 'Z' sorts after any hex address, 0x-prefixed or not
63
- return {
64
- start,
65
- end
66
- };
67
- }
68
- /**
69
- * Purge vote payload data for expired rounds. Does not delete actual payload data.
70
- */ async clearExpiredPayloads(currentRound) {
71
- const lifetimeInRounds = this.settings?.slashingPayloadLifetimeInRounds ?? 0;
72
- if (lifetimeInRounds <= 0) {
73
- return; // No lifetime configured
74
- }
75
- const expiredBefore = currentRound - BigInt(lifetimeInRounds);
76
- if (expiredBefore < 0) {
77
- return; // Not enough rounds have passed to expire anything
78
- }
79
- // Collect expired payload votes by scanning round-payload keys
80
- const expiredPayloads = [];
81
- const expiredVoteKeys = [];
82
- for await (const key of this.roundPayloadVotes.keysAsync({
83
- end: `${this.getRoundKey(expiredBefore)}:Z`
84
- })){
85
- const [roundStr, payloadAddress] = key.split(':');
86
- if (BigInt(roundStr) <= expiredBefore) {
87
- expiredVoteKeys.push(key);
88
- expiredPayloads.push(payloadAddress);
89
- }
90
- }
91
- if (expiredVoteKeys.length === 0) {
92
- return; // No expired payloads to clean up
93
- }
94
- // Remove expired payload vote records
95
- // Note that we do not delete payload data since these could be repurposed in future votes
96
- await this.kvStore.transactionAsync(async ()=>{
97
- for (const key of expiredVoteKeys){
98
- await this.roundPayloadVotes.delete(key);
99
- }
100
- });
101
- }
102
- async incrementPayloadVotes(payloadAddress, round) {
103
- const key = this.getPayloadVotesKey(round, payloadAddress);
104
- let newVotes;
105
- await this.kvStore.transactionAsync(async ()=>{
106
- const currentVotes = await this.roundPayloadVotes.getAsync(key) || 0n;
107
- newVotes = currentVotes + 1n;
108
- await this.roundPayloadVotes.set(key, newVotes);
109
- });
110
- return newVotes;
111
- }
112
- async addPayload(payload) {
113
- const address = payload.address.toString();
114
- await this.kvStore.transactionAsync(async ()=>{
115
- await this.payloads.set(address, serializeSlashPayload(payload));
116
- await this.roundPayloadVotes.set(this.getPayloadVotesKey(payload.round, address), payload.votes);
117
- });
118
- }
119
- async getPayload(payloadAddress) {
120
- const address = payloadAddress.toString();
121
- const buffer = await this.payloads.getAsync(address);
122
- return buffer ? deserializeSlashPayload(buffer) : undefined;
123
- }
124
- async hasPayload(payload) {
125
- const address = payload.toString();
126
- return await this.payloads.getAsync(address) !== undefined;
127
- }
128
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"tally_slasher_client.d.ts","sourceRoot":"","sources":["../src/tally_slasher_client.ts"],"names":[],"mappings":"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"}
@@ -1,39 +0,0 @@
1
- import { EpochCache } from '@aztec/epoch-cache';
2
- import { EpochNumber } from '@aztec/foundation/branded-types';
3
- import { L2Block, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
4
- import type { ICheckpointsBuilder, ITxProvider, SlasherConfig } from '@aztec/stdlib/interfaces/server';
5
- import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
6
- import { type Watcher, type WatcherEmitter } from '../watcher.js';
7
- declare const EpochPruneWatcherPenaltiesConfigKeys: readonly ["slashPrunePenalty", "slashDataWithholdingPenalty"];
8
- type EpochPruneWatcherPenalties = Pick<SlasherConfig, (typeof EpochPruneWatcherPenaltiesConfigKeys)[number]>;
9
- declare const EpochPruneWatcher_base: new () => WatcherEmitter;
10
- /**
11
- * This watcher is responsible for detecting chain prunes and creating slashing arguments for the committee.
12
- * It only wants to slash if:
13
- * - the transactions are not available
14
- * - OR the archive roots match when re-building all the blocks in the epoch (i.e. the epoch *could* have been proven)
15
- */
16
- export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements Watcher {
17
- private l2BlockSource;
18
- private l1ToL2MessageSource;
19
- private epochCache;
20
- private txProvider;
21
- private checkpointsBuilder;
22
- private log;
23
- private boundHandlePruneL2Blocks;
24
- private penalties;
25
- constructor(l2BlockSource: L2BlockSourceEventEmitter, l1ToL2MessageSource: L1ToL2MessageSource, epochCache: EpochCache, txProvider: Pick<ITxProvider, 'getAvailableTxs'>, checkpointsBuilder: ICheckpointsBuilder, penalties: EpochPruneWatcherPenalties);
26
- start(): Promise<void>;
27
- stop(): Promise<void>;
28
- updateConfig(config: Partial<SlasherConfig>): void;
29
- private handlePruneL2Blocks;
30
- private emitSlashForEpoch;
31
- private processPruneL2Blocks;
32
- validateBlocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void>;
33
- private validateCheckpoint;
34
- private validateBlockInCheckpoint;
35
- private getValidatorsForEpoch;
36
- private validatorsToSlashingArgs;
37
- }
38
- export {};
39
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXBvY2hfcHJ1bmVfd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhdGNoZXJzL2Vwb2NoX3BydW5lX3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sRUFBZSxXQUFXLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUkzRSxPQUFPLEVBRUwsT0FBTyxFQUNQLEtBQUsseUJBQXlCLEVBRy9CLE1BQU0scUJBQXFCLENBQUM7QUFFN0IsT0FBTyxLQUFLLEVBRVYsbUJBQW1CLEVBQ25CLFdBQVcsRUFFWCxhQUFhLEVBQ2QsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBNEIsTUFBTSx5QkFBeUIsQ0FBQztBQVk3RixPQUFPLEVBQTZDLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTdHLFFBQUEsTUFBTSxvQ0FBb0MsK0RBQWdFLENBQUM7QUFFM0csS0FBSywwQkFBMEIsR0FBRyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsT0FBTyxvQ0FBb0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7O0FBRTdHOzs7OztHQUtHO0FBQ0gscUJBQWEsaUJBQWtCLFNBQVEsc0JBQTJDLFlBQVcsT0FBTztJQVNoRyxPQUFPLENBQUMsYUFBYTtJQUNyQixPQUFPLENBQUMsbUJBQW1CO0lBQzNCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxrQkFBa0I7SUFaNUIsT0FBTyxDQUFDLEdBQUcsQ0FBK0M7SUFHMUQsT0FBTyxDQUFDLHdCQUF3QixDQUF1QztJQUV2RSxPQUFPLENBQUMsU0FBUyxDQUE2QjtJQUU5QyxZQUNVLGFBQWEsRUFBRSx5QkFBeUIsRUFDeEMsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFVBQVUsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLGlCQUFpQixDQUFDLEVBQ2hELGtCQUFrQixFQUFFLG1CQUFtQixFQUMvQyxTQUFTLEVBQUUsMEJBQTBCLEVBT3RDO0lBRU0sS0FBSyxrQkFHWDtJQUVNLElBQUksa0JBR1Y7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxJQUFJLENBR3hEO0lBRUQsT0FBTyxDQUFDLG1CQUFtQjtZQU9iLGlCQUFpQjtZQVdqQixvQkFBb0I7SUF3QnJCLGNBQWMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEVBQUUsV0FBVyxFQUFFLFdBQVcsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBK0J0RjtZQUVhLGtCQUFrQjtZQXNDbEIseUJBQXlCO1lBOEJ6QixxQkFBcUI7SUFTbkMsT0FBTyxDQUFDLHdCQUF3QjtDQWdCakMifQ==
@@ -1 +0,0 @@
1
- {"version":3,"file":"epoch_prune_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/epoch_prune_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAe,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAI3E,OAAO,EAEL,OAAO,EACP,KAAK,yBAAyB,EAG/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAEV,mBAAmB,EACnB,WAAW,EAEX,aAAa,EACd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AAY7F,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,oCAAoC,+DAAgE,CAAC;AAE3G,KAAK,0BAA0B,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,oCAAoC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;;AAE7G;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,sBAA2C,YAAW,OAAO;IAShG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,kBAAkB;IAZ5B,OAAO,CAAC,GAAG,CAA+C;IAG1D,OAAO,CAAC,wBAAwB,CAAuC;IAEvE,OAAO,CAAC,SAAS,CAA6B;IAE9C,YACU,aAAa,EAAE,yBAAyB,EACxC,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAChD,kBAAkB,EAAE,mBAAmB,EAC/C,SAAS,EAAE,0BAA0B,EAOtC;IAEM,KAAK,kBAGX;IAEM,IAAI,kBAGV;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAGxD;IAED,OAAO,CAAC,mBAAmB;YAOb,iBAAiB;YAWjB,oBAAoB;IAwBrB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BtF;YAEa,kBAAkB;YAsClB,yBAAyB;YA8BzB,qBAAqB;IASnC,OAAO,CAAC,wBAAwB;CAgBjC"}
@@ -1,175 +0,0 @@
1
- import { BlockNumber } from '@aztec/foundation/branded-types';
2
- import { chunkBy, merge, pick } from '@aztec/foundation/collection';
3
- import { createLogger } from '@aztec/foundation/log';
4
- import { L2BlockSourceEvents } from '@aztec/stdlib/block';
5
- import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
6
- import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
7
- import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
8
- import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
9
- import EventEmitter from 'node:events';
10
- import { WANT_TO_SLASH_EVENT } from '../watcher.js';
11
- const EpochPruneWatcherPenaltiesConfigKeys = [
12
- 'slashPrunePenalty',
13
- 'slashDataWithholdingPenalty'
14
- ];
15
- /**
16
- * This watcher is responsible for detecting chain prunes and creating slashing arguments for the committee.
17
- * It only wants to slash if:
18
- * - the transactions are not available
19
- * - OR the archive roots match when re-building all the blocks in the epoch (i.e. the epoch *could* have been proven)
20
- */ export class EpochPruneWatcher extends EventEmitter {
21
- l2BlockSource;
22
- l1ToL2MessageSource;
23
- epochCache;
24
- txProvider;
25
- checkpointsBuilder;
26
- log;
27
- // Store bound function reference for proper listener removal
28
- boundHandlePruneL2Blocks;
29
- penalties;
30
- constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, checkpointsBuilder, penalties){
31
- super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.checkpointsBuilder = checkpointsBuilder, this.log = createLogger('epoch-prune-watcher'), this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(this);
32
- this.penalties = pick(penalties, ...EpochPruneWatcherPenaltiesConfigKeys);
33
- this.log.verbose(`EpochPruneWatcher initialized with penalties: valid epoch pruned=${penalties.slashPrunePenalty} data withholding=${penalties.slashDataWithholdingPenalty}`);
34
- }
35
- start() {
36
- this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
37
- return Promise.resolve();
38
- }
39
- stop() {
40
- this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
41
- return Promise.resolve();
42
- }
43
- updateConfig(config) {
44
- this.penalties = merge(this.penalties, pick(config, ...EpochPruneWatcherPenaltiesConfigKeys));
45
- this.log.verbose('EpochPruneWatcher config updated', this.penalties);
46
- }
47
- handlePruneL2Blocks(event) {
48
- const { blocks, epochNumber } = event;
49
- void this.processPruneL2Blocks(blocks, epochNumber).catch((err)=>this.log.error('Error processing pruned L2 blocks', err, {
50
- epochNumber
51
- }));
52
- }
53
- async emitSlashForEpoch(offense, epochNumber) {
54
- const validators = await this.getValidatorsForEpoch(epochNumber);
55
- if (validators.length === 0) {
56
- this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
57
- return;
58
- }
59
- const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
60
- this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
61
- this.emit(WANT_TO_SLASH_EVENT, args);
62
- }
63
- async processPruneL2Blocks(blocks, epochNumber) {
64
- try {
65
- const l1Constants = this.epochCache.getL1Constants();
66
- const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
67
- this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
68
- blocks: epochBlocks.map((b)=>b.toBlockInfo())
69
- });
70
- await this.validateBlocks(epochBlocks, epochNumber);
71
- this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
72
- await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
73
- } catch (error) {
74
- if (error instanceof TransactionsNotAvailableError) {
75
- this.log.info(`Data for pruned epoch ${epochNumber} was not available. Will want to slash.`, {
76
- message: error.message
77
- });
78
- await this.emitSlashForEpoch(OffenseType.DATA_WITHHOLDING, epochNumber);
79
- } else {
80
- this.log.error(`Error while validating pruned epoch ${epochNumber}. Will not want to slash.`, error);
81
- }
82
- }
83
- }
84
- async validateBlocks(blocks, epochNumber) {
85
- if (blocks.length === 0) {
86
- return;
87
- }
88
- // Sort blocks by block number and group by checkpoint
89
- const sortedBlocks = [
90
- ...blocks
91
- ].sort((a, b)=>a.number - b.number);
92
- const blocksByCheckpoint = chunkBy(sortedBlocks, (b)=>b.checkpointNumber);
93
- // Get prior checkpoints in the epoch (in case this was a partial prune) to extract the out hashes
94
- const priorCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsForEpoch(epochNumber)).filter((c)=>c.number < sortedBlocks[0].checkpointNumber).map((c)=>c.getCheckpointOutHash());
95
- let previousCheckpointOutHashes = [
96
- ...priorCheckpointOutHashes
97
- ];
98
- const fork = await this.checkpointsBuilder.getFork(BlockNumber(sortedBlocks[0].header.globalVariables.blockNumber - 1));
99
- try {
100
- for (const checkpointBlocks of blocksByCheckpoint){
101
- await this.validateCheckpoint(checkpointBlocks, previousCheckpointOutHashes, fork);
102
- // Compute checkpoint out hash from all blocks in this checkpoint
103
- const checkpointOutHash = computeCheckpointOutHash(checkpointBlocks.map((b)=>b.body.txEffects.map((tx)=>tx.l2ToL1Msgs)));
104
- previousCheckpointOutHashes = [
105
- ...previousCheckpointOutHashes,
106
- checkpointOutHash
107
- ];
108
- }
109
- } finally{
110
- await fork.close();
111
- }
112
- }
113
- async validateCheckpoint(checkpointBlocks, previousCheckpointOutHashes, fork) {
114
- const checkpointNumber = checkpointBlocks[0].checkpointNumber;
115
- this.log.debug(`Validating pruned checkpoint ${checkpointNumber} with ${checkpointBlocks.length} blocks`);
116
- // Get L1ToL2Messages once for the entire checkpoint
117
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
118
- // Build checkpoint constants from first block's global variables
119
- const gv = checkpointBlocks[0].header.globalVariables;
120
- const constants = {
121
- chainId: gv.chainId,
122
- version: gv.version,
123
- slotNumber: gv.slotNumber,
124
- coinbase: gv.coinbase,
125
- feeRecipient: gv.feeRecipient,
126
- gasFees: gv.gasFees
127
- };
128
- // Start checkpoint builder once for all blocks in this checkpoint
129
- const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork, this.log.getBindings());
130
- // Validate all blocks in the checkpoint sequentially
131
- for (const block of checkpointBlocks){
132
- await this.validateBlockInCheckpoint(block, checkpointBuilder);
133
- }
134
- }
135
- async validateBlockInCheckpoint(blockFromL1, checkpointBuilder) {
136
- this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
137
- const txHashes = blockFromL1.body.txEffects.map((txEffect)=>txEffect.txHash);
138
- // We load txs from the mempool directly, since the TxCollector running in the background has already been
139
- // trying to fetch them from nodes or via reqresp. If we haven't managed to collect them by now,
140
- // it's likely that they are not available in the network at all.
141
- const { txs, missingTxs } = await this.txProvider.getAvailableTxs(txHashes);
142
- if (missingTxs && missingTxs.length > 0) {
143
- throw new TransactionsNotAvailableError(missingTxs);
144
- }
145
- const gv = blockFromL1.header.globalVariables;
146
- const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
147
- if (numTxs !== txs.length) {
148
- // This should be detected by state mismatch, but this makes it easier to debug.
149
- throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
150
- }
151
- if (failedTxs.length > 0) {
152
- throw new ReExFailedTxsError(failedTxs.length);
153
- }
154
- if (!block.archive.root.equals(blockFromL1.archive.root)) {
155
- throw new ReExStateMismatchError(blockFromL1.archive.root, block.archive.root);
156
- }
157
- }
158
- async getValidatorsForEpoch(epochNumber) {
159
- const { committee } = await this.epochCache.getCommitteeForEpoch(epochNumber);
160
- if (!committee) {
161
- this.log.trace(`No committee found for epoch ${epochNumber}`);
162
- return [];
163
- }
164
- return committee;
165
- }
166
- validatorsToSlashingArgs(validators, offenseType, epochOrSlot) {
167
- const penalty = offenseType === OffenseType.DATA_WITHHOLDING ? this.penalties.slashDataWithholdingPenalty : this.penalties.slashPrunePenalty;
168
- return validators.map((v)=>({
169
- validator: v,
170
- amount: penalty,
171
- offenseType,
172
- epochOrSlot: BigInt(epochOrSlot)
173
- }));
174
- }
175
- }