@aztec/slasher 0.0.1-commit.b655e406 → 0.0.1-commit.c0b82b2
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 +23 -13
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +28 -16
- package/dest/empire_slasher_client.d.ts +6 -5
- package/dest/empire_slasher_client.d.ts.map +1 -1
- package/dest/empire_slasher_client.js +1 -9
- package/dest/factory/create_facade.d.ts +3 -2
- package/dest/factory/create_facade.d.ts.map +1 -1
- package/dest/factory/create_facade.js +1 -1
- package/dest/factory/create_implementation.d.ts +3 -3
- package/dest/factory/create_implementation.d.ts.map +1 -1
- package/dest/factory/get_settings.d.ts +2 -2
- package/dest/factory/get_settings.d.ts.map +1 -1
- package/dest/factory/index.d.ts +1 -1
- package/dest/generated/slasher-defaults.d.ts +21 -0
- package/dest/generated/slasher-defaults.d.ts.map +1 -0
- package/dest/generated/slasher-defaults.js +21 -0
- package/dest/index.d.ts +1 -1
- package/dest/null_slasher_client.d.ts +3 -2
- package/dest/null_slasher_client.d.ts.map +1 -1
- package/dest/slash_offenses_collector.d.ts +1 -1
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_round_monitor.d.ts +5 -4
- package/dest/slash_round_monitor.d.ts.map +1 -1
- package/dest/slasher_client_facade.d.ts +4 -3
- package/dest/slasher_client_facade.d.ts.map +1 -1
- package/dest/slasher_client_interface.d.ts +3 -2
- package/dest/slasher_client_interface.d.ts.map +1 -1
- package/dest/stores/offenses_store.d.ts +1 -1
- package/dest/stores/offenses_store.d.ts.map +1 -1
- package/dest/stores/offenses_store.js +4 -2
- package/dest/stores/payloads_store.d.ts +2 -2
- package/dest/stores/payloads_store.d.ts.map +1 -1
- package/dest/stores/payloads_store.js +6 -3
- package/dest/stores/schema_version.d.ts +1 -1
- package/dest/tally_slasher_client.d.ts +5 -9
- package/dest/tally_slasher_client.d.ts.map +1 -1
- package/dest/tally_slasher_client.js +11 -6
- package/dest/test/dummy_watcher.d.ts +1 -1
- package/dest/test/dummy_watcher.d.ts.map +1 -1
- package/dest/watcher.d.ts +1 -1
- package/dest/watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.d.ts +7 -6
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +42 -35
- package/dest/watchers/epoch_prune_watcher.d.ts +9 -7
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +58 -17
- package/package.json +17 -14
- package/src/config.ts +30 -16
- package/src/empire_slasher_client.ts +6 -13
- package/src/factory/create_facade.ts +3 -2
- package/src/factory/create_implementation.ts +1 -1
- package/src/factory/get_settings.ts +1 -1
- package/src/generated/slasher-defaults.ts +23 -0
- package/src/null_slasher_client.ts +2 -1
- package/src/slash_round_monitor.ts +3 -2
- package/src/slasher_client_facade.ts +3 -2
- package/src/slasher_client_interface.ts +2 -1
- package/src/stores/offenses_store.ts +4 -2
- package/src/stores/payloads_store.ts +7 -4
- package/src/tally_slasher_client.ts +21 -10
- package/src/watcher.ts +1 -1
- package/src/watchers/attestations_block_watcher.ts +59 -45
- package/src/watchers/epoch_prune_watcher.ts +90 -29
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import {
|
|
2
|
+
import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { chunkBy, merge, pick } from '@aztec/foundation/collection';
|
|
4
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
5
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
6
|
import {
|
|
5
7
|
EthAddress,
|
|
6
8
|
L2Block,
|
|
7
|
-
type L2BlockPruneEvent,
|
|
8
9
|
type L2BlockSourceEventEmitter,
|
|
9
10
|
L2BlockSourceEvents,
|
|
11
|
+
type L2PruneUnprovenEvent,
|
|
10
12
|
} from '@aztec/stdlib/block';
|
|
11
13
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
12
14
|
import type {
|
|
13
|
-
|
|
15
|
+
ICheckpointBlockBuilder,
|
|
16
|
+
ICheckpointsBuilder,
|
|
14
17
|
ITxProvider,
|
|
15
18
|
MerkleTreeWriteOperations,
|
|
16
19
|
SlasherConfig,
|
|
17
20
|
} from '@aztec/stdlib/interfaces/server';
|
|
18
|
-
import type
|
|
21
|
+
import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging';
|
|
19
22
|
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
23
|
+
import type { CheckpointGlobalVariables } from '@aztec/stdlib/tx';
|
|
20
24
|
import {
|
|
21
25
|
ReExFailedTxsError,
|
|
22
26
|
ReExStateMismatchError,
|
|
@@ -51,7 +55,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
51
55
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
52
56
|
private epochCache: EpochCache,
|
|
53
57
|
private txProvider: Pick<ITxProvider, 'getAvailableTxs'>,
|
|
54
|
-
private
|
|
58
|
+
private checkpointsBuilder: ICheckpointsBuilder,
|
|
55
59
|
penalties: EpochPruneWatcherPenalties,
|
|
56
60
|
) {
|
|
57
61
|
super();
|
|
@@ -62,12 +66,12 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
public start() {
|
|
65
|
-
this.l2BlockSource.on(L2BlockSourceEvents.
|
|
69
|
+
this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
|
|
66
70
|
return Promise.resolve();
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
public stop() {
|
|
70
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.
|
|
74
|
+
this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
|
|
71
75
|
return Promise.resolve();
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -76,34 +80,34 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
76
80
|
this.log.verbose('EpochPruneWatcher config updated', this.penalties);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
private handlePruneL2Blocks(event:
|
|
83
|
+
private handlePruneL2Blocks(event: L2PruneUnprovenEvent): void {
|
|
80
84
|
const { blocks, epochNumber } = event;
|
|
81
85
|
void this.processPruneL2Blocks(blocks, epochNumber).catch(err =>
|
|
82
86
|
this.log.error('Error processing pruned L2 blocks', err, { epochNumber }),
|
|
83
87
|
);
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
private async emitSlashForEpoch(offense: OffenseType, epochNumber:
|
|
90
|
+
private async emitSlashForEpoch(offense: OffenseType, epochNumber: EpochNumber): Promise<void> {
|
|
87
91
|
const validators = await this.getValidatorsForEpoch(epochNumber);
|
|
88
92
|
if (validators.length === 0) {
|
|
89
93
|
this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
|
-
const args = this.validatorsToSlashingArgs(validators, offense,
|
|
96
|
+
const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
|
|
93
97
|
this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
|
|
94
98
|
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
private async processPruneL2Blocks(blocks: L2Block[], epochNumber:
|
|
101
|
+
private async processPruneL2Blocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
|
|
98
102
|
try {
|
|
99
103
|
const l1Constants = this.epochCache.getL1Constants();
|
|
100
|
-
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.
|
|
104
|
+
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
101
105
|
this.log.info(
|
|
102
106
|
`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
|
|
103
107
|
{ blocks: epochBlocks.map(b => b.toBlockInfo()) },
|
|
104
108
|
);
|
|
105
109
|
|
|
106
|
-
await this.validateBlocks(epochBlocks);
|
|
110
|
+
await this.validateBlocks(epochBlocks, epochNumber);
|
|
107
111
|
this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
|
|
108
112
|
await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
|
|
109
113
|
} catch (error) {
|
|
@@ -118,21 +122,83 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
public async validateBlocks(blocks: L2Block[]): Promise<void> {
|
|
125
|
+
public async validateBlocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
|
|
122
126
|
if (blocks.length === 0) {
|
|
123
127
|
return;
|
|
124
128
|
}
|
|
125
|
-
|
|
129
|
+
|
|
130
|
+
// Sort blocks by block number and group by checkpoint
|
|
131
|
+
const sortedBlocks = [...blocks].sort((a, b) => a.number - b.number);
|
|
132
|
+
const blocksByCheckpoint = chunkBy(sortedBlocks, b => b.checkpointNumber);
|
|
133
|
+
|
|
134
|
+
// Get prior checkpoints in the epoch (in case this was a partial prune) to extract the out hashes
|
|
135
|
+
const priorCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(epochNumber))
|
|
136
|
+
.filter(c => c.checkpointNumber < sortedBlocks[0].checkpointNumber)
|
|
137
|
+
.map(c => c.checkpointOutHash);
|
|
138
|
+
let previousCheckpointOutHashes: Fr[] = [...priorCheckpointOutHashes];
|
|
139
|
+
|
|
140
|
+
const fork = await this.checkpointsBuilder.getFork(
|
|
141
|
+
BlockNumber(sortedBlocks[0].header.globalVariables.blockNumber - 1),
|
|
142
|
+
);
|
|
126
143
|
try {
|
|
127
|
-
for (const
|
|
128
|
-
await this.
|
|
144
|
+
for (const checkpointBlocks of blocksByCheckpoint) {
|
|
145
|
+
await this.validateCheckpoint(checkpointBlocks, previousCheckpointOutHashes, fork);
|
|
146
|
+
|
|
147
|
+
// Compute checkpoint out hash from all blocks in this checkpoint
|
|
148
|
+
const checkpointOutHash = computeCheckpointOutHash(
|
|
149
|
+
checkpointBlocks.map(b => b.body.txEffects.map(tx => tx.l2ToL1Msgs)),
|
|
150
|
+
);
|
|
151
|
+
previousCheckpointOutHashes = [...previousCheckpointOutHashes, checkpointOutHash];
|
|
129
152
|
}
|
|
130
153
|
} finally {
|
|
131
154
|
await fork.close();
|
|
132
155
|
}
|
|
133
156
|
}
|
|
134
157
|
|
|
135
|
-
|
|
158
|
+
private async validateCheckpoint(
|
|
159
|
+
checkpointBlocks: L2Block[],
|
|
160
|
+
previousCheckpointOutHashes: Fr[],
|
|
161
|
+
fork: MerkleTreeWriteOperations,
|
|
162
|
+
): Promise<void> {
|
|
163
|
+
const checkpointNumber = checkpointBlocks[0].checkpointNumber;
|
|
164
|
+
this.log.debug(`Validating pruned checkpoint ${checkpointNumber} with ${checkpointBlocks.length} blocks`);
|
|
165
|
+
|
|
166
|
+
// Get L1ToL2Messages once for the entire checkpoint
|
|
167
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
168
|
+
|
|
169
|
+
// Build checkpoint constants from first block's global variables
|
|
170
|
+
const gv = checkpointBlocks[0].header.globalVariables;
|
|
171
|
+
const constants: CheckpointGlobalVariables = {
|
|
172
|
+
chainId: gv.chainId,
|
|
173
|
+
version: gv.version,
|
|
174
|
+
slotNumber: gv.slotNumber,
|
|
175
|
+
timestamp: gv.timestamp,
|
|
176
|
+
coinbase: gv.coinbase,
|
|
177
|
+
feeRecipient: gv.feeRecipient,
|
|
178
|
+
gasFees: gv.gasFees,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Start checkpoint builder once for all blocks in this checkpoint
|
|
182
|
+
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
|
|
183
|
+
checkpointNumber,
|
|
184
|
+
constants,
|
|
185
|
+
0n, // feeAssetPriceModifier is not used for validation of the checkpoint content
|
|
186
|
+
l1ToL2Messages,
|
|
187
|
+
previousCheckpointOutHashes,
|
|
188
|
+
fork,
|
|
189
|
+
this.log.getBindings(),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Validate all blocks in the checkpoint sequentially
|
|
193
|
+
for (const block of checkpointBlocks) {
|
|
194
|
+
await this.validateBlockInCheckpoint(block, checkpointBuilder);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async validateBlockInCheckpoint(
|
|
199
|
+
blockFromL1: L2Block,
|
|
200
|
+
checkpointBuilder: ICheckpointBlockBuilder,
|
|
201
|
+
): Promise<void> {
|
|
136
202
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
137
203
|
const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
|
|
138
204
|
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
@@ -144,14 +210,9 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
144
210
|
throw new TransactionsNotAvailableError(missingTxs);
|
|
145
211
|
}
|
|
146
212
|
|
|
147
|
-
const
|
|
148
|
-
const { block, failedTxs, numTxs } = await
|
|
149
|
-
|
|
150
|
-
l1ToL2Messages,
|
|
151
|
-
blockFromL1.header.globalVariables,
|
|
152
|
-
{},
|
|
153
|
-
fork,
|
|
154
|
-
);
|
|
213
|
+
const gv = blockFromL1.header.globalVariables;
|
|
214
|
+
const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
|
|
215
|
+
|
|
155
216
|
if (numTxs !== txs.length) {
|
|
156
217
|
// This should be detected by state mismatch, but this makes it easier to debug.
|
|
157
218
|
throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
|
|
@@ -164,7 +225,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
164
225
|
}
|
|
165
226
|
}
|
|
166
227
|
|
|
167
|
-
private async getValidatorsForEpoch(epochNumber:
|
|
228
|
+
private async getValidatorsForEpoch(epochNumber: EpochNumber): Promise<EthAddress[]> {
|
|
168
229
|
const { committee } = await this.epochCache.getCommitteeForEpoch(epochNumber);
|
|
169
230
|
if (!committee) {
|
|
170
231
|
this.log.trace(`No committee found for epoch ${epochNumber}`);
|
|
@@ -176,7 +237,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
176
237
|
private validatorsToSlashingArgs(
|
|
177
238
|
validators: EthAddress[],
|
|
178
239
|
offenseType: OffenseType,
|
|
179
|
-
epochOrSlot:
|
|
240
|
+
epochOrSlot: EpochNumber,
|
|
180
241
|
): WantToSlashArgs[] {
|
|
181
242
|
const penalty =
|
|
182
243
|
offenseType === OffenseType.DATA_WITHHOLDING
|
|
@@ -186,7 +247,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
186
247
|
validator: v,
|
|
187
248
|
amount: penalty,
|
|
188
249
|
offenseType,
|
|
189
|
-
epochOrSlot,
|
|
250
|
+
epochOrSlot: BigInt(epochOrSlot),
|
|
190
251
|
}));
|
|
191
252
|
}
|
|
192
253
|
}
|