@aztec/validator-client 2.0.3 → 2.1.0-rc.2
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/dest/block_proposal_handler.d.ts +47 -0
- package/dest/block_proposal_handler.d.ts.map +1 -0
- package/dest/block_proposal_handler.js +257 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -0
- package/dest/duties/validation_service.d.ts +3 -0
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +8 -0
- package/dest/factory.d.ts +13 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +8 -0
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/metrics.d.ts +1 -1
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +3 -2
- package/dest/validator.d.ts +15 -19
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +66 -179
- package/package.json +12 -12
- package/src/block_proposal_handler.ts +314 -0
- package/src/config.ts +6 -0
- package/src/duties/validation_service.ts +16 -1
- package/src/factory.ts +32 -4
- package/src/index.ts +1 -0
- package/src/metrics.ts +2 -1
- package/src/validator.ts +108 -227
package/src/validator.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
2
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
+
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
4
4
|
import { Fr } from '@aztec/foundation/fields';
|
|
5
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
6
|
-
import { retryUntil } from '@aztec/foundation/retry';
|
|
7
6
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
8
7
|
import { sleep } from '@aztec/foundation/sleep';
|
|
9
|
-
import { DateProvider
|
|
8
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
10
9
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
11
|
-
import type { P2P, PeerId } from '@aztec/p2p';
|
|
12
|
-
import { AuthRequest, AuthResponse,
|
|
13
|
-
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
14
|
-
import { computeInHashFromL1ToL2Messages } from '@aztec/prover-client/helpers';
|
|
10
|
+
import type { P2P, PeerId, TxProvider } from '@aztec/p2p';
|
|
11
|
+
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
15
12
|
import {
|
|
16
13
|
OffenseType,
|
|
17
14
|
type SlasherConfig,
|
|
@@ -20,24 +17,18 @@ import {
|
|
|
20
17
|
type WatcherEmitter,
|
|
21
18
|
} from '@aztec/slasher';
|
|
22
19
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
23
|
-
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
24
|
-
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
20
|
+
import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdlib/block';
|
|
25
21
|
import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
|
|
26
22
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
27
23
|
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
AttestationTimeoutError,
|
|
31
|
-
ReExFailedTxsError,
|
|
32
|
-
ReExStateMismatchError,
|
|
33
|
-
ReExTimeoutError,
|
|
34
|
-
TransactionsNotAvailableError,
|
|
35
|
-
} from '@aztec/stdlib/validators';
|
|
24
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
25
|
+
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
36
26
|
import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
37
27
|
|
|
38
28
|
import { EventEmitter } from 'events';
|
|
39
29
|
import type { TypedDataDefinition } from 'viem';
|
|
40
30
|
|
|
31
|
+
import { BlockProposalHandler, type BlockProposalValidationFailureReason } from './block_proposal_handler.js';
|
|
41
32
|
import type { ValidatorClientConfig } from './config.js';
|
|
42
33
|
import { ValidationService } from './duties/validation_service.js';
|
|
43
34
|
import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
@@ -47,6 +38,12 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
47
38
|
// Just cap the set to avoid unbounded growth.
|
|
48
39
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
49
40
|
|
|
41
|
+
// What errors from the block proposal handler result in slashing
|
|
42
|
+
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT: BlockProposalValidationFailureReason[] = [
|
|
43
|
+
'state_mismatch',
|
|
44
|
+
'failed_txs',
|
|
45
|
+
];
|
|
46
|
+
|
|
50
47
|
/**
|
|
51
48
|
* Validator Client
|
|
52
49
|
*/
|
|
@@ -55,24 +52,22 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
55
52
|
private validationService: ValidationService;
|
|
56
53
|
private metrics: ValidatorMetrics;
|
|
57
54
|
|
|
55
|
+
// Whether it has already registered handlers on the p2p client
|
|
56
|
+
private hasRegisteredHandlers = false;
|
|
57
|
+
|
|
58
58
|
// Used to check if we are sending the same proposal twice
|
|
59
59
|
private previousProposal?: BlockProposal;
|
|
60
60
|
|
|
61
61
|
private lastEpochForCommitteeUpdateLoop: bigint | undefined;
|
|
62
62
|
private epochCacheUpdateLoop: RunningPromise;
|
|
63
63
|
|
|
64
|
-
private blockProposalValidator: BlockProposalValidator;
|
|
65
|
-
|
|
66
64
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
67
65
|
|
|
68
66
|
protected constructor(
|
|
69
|
-
private blockBuilder: IFullNodeBlockBuilder,
|
|
70
67
|
private keyStore: NodeKeystoreAdapter,
|
|
71
68
|
private epochCache: EpochCache,
|
|
72
69
|
private p2pClient: P2P,
|
|
73
|
-
private
|
|
74
|
-
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
75
|
-
private txProvider: TxProvider,
|
|
70
|
+
private blockProposalHandler: BlockProposalHandler,
|
|
76
71
|
private config: ValidatorClientFullConfig,
|
|
77
72
|
private dateProvider: DateProvider = new DateProvider(),
|
|
78
73
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
@@ -84,8 +79,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
84
79
|
|
|
85
80
|
this.validationService = new ValidationService(keyStore);
|
|
86
81
|
|
|
87
|
-
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
88
|
-
|
|
89
82
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
90
83
|
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
91
84
|
|
|
@@ -152,21 +145,30 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
152
145
|
dateProvider: DateProvider = new DateProvider(),
|
|
153
146
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
154
147
|
) {
|
|
155
|
-
const
|
|
148
|
+
const metrics = new ValidatorMetrics(telemetry);
|
|
149
|
+
const blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
150
|
+
const blockProposalHandler = new BlockProposalHandler(
|
|
156
151
|
blockBuilder,
|
|
157
|
-
NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager),
|
|
158
|
-
epochCache,
|
|
159
|
-
p2pClient,
|
|
160
152
|
blockSource,
|
|
161
153
|
l1ToL2MessageSource,
|
|
162
154
|
txProvider,
|
|
155
|
+
blockProposalValidator,
|
|
156
|
+
config,
|
|
157
|
+
metrics,
|
|
158
|
+
dateProvider,
|
|
159
|
+
telemetry,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const validator = new ValidatorClient(
|
|
163
|
+
NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager),
|
|
164
|
+
epochCache,
|
|
165
|
+
p2pClient,
|
|
166
|
+
blockProposalHandler,
|
|
163
167
|
config,
|
|
164
168
|
dateProvider,
|
|
165
169
|
telemetry,
|
|
166
170
|
);
|
|
167
171
|
|
|
168
|
-
// TODO(PhilWindle): This seems like it could/should be done inside start()
|
|
169
|
-
validator.registerBlockProposalHandler();
|
|
170
172
|
return validator;
|
|
171
173
|
}
|
|
172
174
|
|
|
@@ -176,6 +178,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
176
178
|
.filter(addr => !this.config.disabledValidators.some(disabled => disabled.equals(addr)));
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
public getBlockProposalHandler() {
|
|
182
|
+
return this.blockProposalHandler;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Proxy method for backwards compatibility with tests
|
|
186
|
+
public reExecuteTransactions(proposal: BlockProposal, txs: any[], l1ToL2Messages: Fr[]): Promise<any> {
|
|
187
|
+
return this.blockProposalHandler.reexecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
188
|
+
}
|
|
189
|
+
|
|
179
190
|
public signWithAddress(addr: EthAddress, msg: TypedDataDefinition) {
|
|
180
191
|
return this.keyStore.signTypedDataWithAddress(addr, msg);
|
|
181
192
|
}
|
|
@@ -197,11 +208,14 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
197
208
|
}
|
|
198
209
|
|
|
199
210
|
public async start() {
|
|
200
|
-
|
|
201
|
-
|
|
211
|
+
if (this.epochCacheUpdateLoop.isRunning()) {
|
|
212
|
+
this.log.warn(`Validator client already started`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
202
215
|
|
|
203
|
-
|
|
216
|
+
await this.registerHandlers();
|
|
204
217
|
|
|
218
|
+
const myAddresses = this.getValidatorAddresses();
|
|
205
219
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
206
220
|
if (inCommittee.length > 0) {
|
|
207
221
|
this.log.info(
|
|
@@ -214,9 +228,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
214
228
|
}
|
|
215
229
|
this.epochCacheUpdateLoop.start();
|
|
216
230
|
|
|
217
|
-
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
218
|
-
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
219
|
-
|
|
220
231
|
return Promise.resolve();
|
|
221
232
|
}
|
|
222
233
|
|
|
@@ -224,218 +235,80 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
224
235
|
await this.epochCacheUpdateLoop.stop();
|
|
225
236
|
}
|
|
226
237
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
/** Register handlers on the p2p client */
|
|
239
|
+
public async registerHandlers() {
|
|
240
|
+
if (!this.hasRegisteredHandlers) {
|
|
241
|
+
this.hasRegisteredHandlers = true;
|
|
242
|
+
this.log.debug(`Registering validator handlers for p2p client`);
|
|
243
|
+
|
|
244
|
+
const handler = (block: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> =>
|
|
245
|
+
this.attestToProposal(block, proposalSender);
|
|
246
|
+
this.p2pClient.registerBlockProposalHandler(handler);
|
|
247
|
+
|
|
248
|
+
const myAddresses = this.getValidatorAddresses();
|
|
249
|
+
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
250
|
+
|
|
251
|
+
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
252
|
+
}
|
|
231
253
|
}
|
|
232
254
|
|
|
233
255
|
async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> {
|
|
234
256
|
const slotNumber = proposal.slotNumber.toBigInt();
|
|
235
|
-
const blockNumber = proposal.blockNumber;
|
|
236
257
|
const proposer = proposal.getSender();
|
|
237
258
|
|
|
238
259
|
// Check that I have any address in current committee before attesting
|
|
239
260
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
240
261
|
const partOfCommittee = inCommittee.length > 0;
|
|
262
|
+
const incFailedAttestation = (reason: string) => this.metrics.incFailedAttestations(1, reason, partOfCommittee);
|
|
241
263
|
|
|
242
|
-
const proposalInfo = {
|
|
243
|
-
|
|
244
|
-
proposer: proposer.toString(),
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
264
|
+
const proposalInfo = { ...proposal.toBlockInfo(), proposer: proposer.toString() };
|
|
265
|
+
this.log.info(`Received proposal for block ${proposal.blockNumber} at slot ${slotNumber}`, {
|
|
248
266
|
...proposalInfo,
|
|
249
|
-
txHashes: proposal.txHashes.map(
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Collect txs from the proposal. Note that we do this before checking if we have an address in the
|
|
253
|
-
// current committee, since we want to collect txs anyway to facilitate propagation.
|
|
254
|
-
const { txs, missingTxs } = await this.txProvider.getTxsForBlockProposal(proposal, {
|
|
255
|
-
pinnedPeer: proposalSender,
|
|
256
|
-
deadline: this.getReexecutionDeadline(proposal, this.blockBuilder.getConfig()),
|
|
267
|
+
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
257
268
|
});
|
|
258
269
|
|
|
259
|
-
//
|
|
260
|
-
if
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Check that the parent proposal is a block we know, otherwise reexecution would fail.
|
|
277
|
-
// Q: Should we move this to the block proposal validator? If there, then p2p would check it
|
|
278
|
-
// before re-broadcasting it. This means that proposals built on top of an L1-reorg'ed-out block
|
|
279
|
-
// would not be rebroadcasted. But it also means that nodes that have not fully synced would
|
|
280
|
-
// not rebroadcast the proposal.
|
|
281
|
-
if (blockNumber > INITIAL_L2_BLOCK_NUM) {
|
|
282
|
-
const config = this.blockBuilder.getConfig();
|
|
283
|
-
const deadline = this.getReexecutionDeadline(proposal, config);
|
|
284
|
-
const currentTime = this.dateProvider.now();
|
|
285
|
-
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
286
|
-
const parentBlock =
|
|
287
|
-
timeoutDurationMs <= 0
|
|
288
|
-
? undefined
|
|
289
|
-
: await retryUntil(
|
|
290
|
-
async () => {
|
|
291
|
-
const block = await this.blockSource.getBlock(blockNumber - 1);
|
|
292
|
-
if (block) {
|
|
293
|
-
return block;
|
|
294
|
-
}
|
|
295
|
-
await this.blockSource.syncImmediate();
|
|
296
|
-
return await this.blockSource.getBlock(blockNumber - 1);
|
|
297
|
-
},
|
|
298
|
-
'Force Archiver Sync',
|
|
299
|
-
timeoutDurationMs / 1000, // Continue retrying until the deadline
|
|
300
|
-
0.5, // Retry every 500ms
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
if (parentBlock === undefined) {
|
|
304
|
-
this.log.warn(`Parent block for ${blockNumber} not found, skipping attestation`, proposalInfo);
|
|
305
|
-
if (partOfCommittee) {
|
|
306
|
-
this.metrics.incFailedAttestations(1, 'parent_block_not_found');
|
|
307
|
-
}
|
|
308
|
-
return undefined;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (!proposal.payload.header.lastArchiveRoot.equals(parentBlock.archive.root)) {
|
|
312
|
-
this.log.warn(`Parent block archive root for proposal does not match, skipping attestation`, {
|
|
313
|
-
proposalLastArchiveRoot: proposal.payload.header.lastArchiveRoot.toString(),
|
|
314
|
-
parentBlockArchiveRoot: parentBlock.archive.root.toString(),
|
|
315
|
-
...proposalInfo,
|
|
316
|
-
});
|
|
317
|
-
if (partOfCommittee) {
|
|
318
|
-
this.metrics.incFailedAttestations(1, 'parent_block_does_not_match');
|
|
319
|
-
}
|
|
320
|
-
return undefined;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
270
|
+
// Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
|
|
271
|
+
// invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
|
|
272
|
+
const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals } = this.config;
|
|
273
|
+
const shouldReexecute =
|
|
274
|
+
(slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute) ||
|
|
275
|
+
(partOfCommittee && validatorReexecute) ||
|
|
276
|
+
alwaysReexecuteBlockProposals;
|
|
277
|
+
|
|
278
|
+
const validationResult = await this.blockProposalHandler.handleBlockProposal(
|
|
279
|
+
proposal,
|
|
280
|
+
proposalSender,
|
|
281
|
+
!!shouldReexecute,
|
|
282
|
+
);
|
|
323
283
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const computedInHash = await computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
328
|
-
const proposalInHash = proposal.payload.header.contentCommitment.inHash;
|
|
329
|
-
if (!computedInHash.equals(proposalInHash)) {
|
|
330
|
-
this.log.warn(`L1 to L2 messages in hash mismatch, skipping attestation`, {
|
|
331
|
-
proposalInHash: proposalInHash.toString(),
|
|
332
|
-
computedInHash: computedInHash.toString(),
|
|
333
|
-
...proposalInfo,
|
|
334
|
-
});
|
|
335
|
-
if (partOfCommittee) {
|
|
336
|
-
this.metrics.incFailedAttestations(1, 'in_hash_mismatch');
|
|
337
|
-
}
|
|
338
|
-
return undefined;
|
|
339
|
-
}
|
|
284
|
+
if (!validationResult.isValid) {
|
|
285
|
+
this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
286
|
+
incFailedAttestation(validationResult.reason || 'unknown');
|
|
340
287
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
288
|
+
// Slash invalid block proposals
|
|
289
|
+
if (
|
|
290
|
+
validationResult.reason &&
|
|
291
|
+
SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason) &&
|
|
292
|
+
slashBroadcastedInvalidBlockPenalty > 0n
|
|
293
|
+
) {
|
|
294
|
+
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
295
|
+
this.slashInvalidBlock(proposal);
|
|
346
296
|
}
|
|
347
297
|
return undefined;
|
|
348
298
|
}
|
|
349
299
|
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
this.log.verbose(`
|
|
353
|
-
if (this.config.validatorReexecute) {
|
|
354
|
-
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
355
|
-
await this.reExecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
356
|
-
}
|
|
357
|
-
} catch (error: any) {
|
|
358
|
-
this.metrics.incFailedAttestations(1, error instanceof Error ? error.name : 'unknown');
|
|
359
|
-
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
360
|
-
if (error instanceof ReExStateMismatchError && this.config.slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
361
|
-
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
362
|
-
this.slashInvalidBlock(proposal);
|
|
363
|
-
}
|
|
300
|
+
// Check that I have any address in current committee before attesting
|
|
301
|
+
if (!partOfCommittee) {
|
|
302
|
+
this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
|
|
364
303
|
return undefined;
|
|
365
304
|
}
|
|
366
305
|
|
|
367
306
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
368
|
-
this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
|
|
307
|
+
this.log.info(`Attesting to proposal for block ${proposal.blockNumber} at slot ${slotNumber}`, proposalInfo);
|
|
369
308
|
this.metrics.incAttestations(inCommittee.length);
|
|
370
309
|
|
|
371
310
|
// If the above function does not throw an error, then we can attest to the proposal
|
|
372
|
-
return this.
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private getReexecutionDeadline(
|
|
376
|
-
proposal: BlockProposal,
|
|
377
|
-
config: { l1GenesisTime: bigint; slotDuration: number },
|
|
378
|
-
): Date {
|
|
379
|
-
const nextSlotTimestampSeconds = Number(getTimestampForSlot(proposal.slotNumber.toBigInt() + 1n, config));
|
|
380
|
-
const msNeededForPropagationAndPublishing = this.config.validatorReexecuteDeadlineMs;
|
|
381
|
-
return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
386
|
-
* @param proposal - The proposal to re-execute
|
|
387
|
-
*/
|
|
388
|
-
async reExecuteTransactions(proposal: BlockProposal, txs: Tx[], l1ToL2Messages: Fr[]): Promise<void> {
|
|
389
|
-
const { header } = proposal.payload;
|
|
390
|
-
const { txHashes } = proposal;
|
|
391
|
-
|
|
392
|
-
// If we do not have all of the transactions, then we should fail
|
|
393
|
-
if (txs.length !== txHashes.length) {
|
|
394
|
-
const foundTxHashes = txs.map(tx => tx.getTxHash());
|
|
395
|
-
const missingTxHashes = txHashes.filter(txHash => !foundTxHashes.includes(txHash));
|
|
396
|
-
throw new TransactionsNotAvailableError(missingTxHashes);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Use the sequencer's block building logic to re-execute the transactions
|
|
400
|
-
const timer = new Timer();
|
|
401
|
-
const config = this.blockBuilder.getConfig();
|
|
402
|
-
const globalVariables = GlobalVariables.from({
|
|
403
|
-
...proposal.payload.header,
|
|
404
|
-
blockNumber: proposal.blockNumber,
|
|
405
|
-
timestamp: header.timestamp,
|
|
406
|
-
chainId: new Fr(config.l1ChainId),
|
|
407
|
-
version: new Fr(config.rollupVersion),
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
const { block, failedTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, globalVariables, {
|
|
411
|
-
deadline: this.getReexecutionDeadline(proposal, config),
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
this.log.verbose(`Transaction re-execution complete`);
|
|
415
|
-
const numFailedTxs = failedTxs.length;
|
|
416
|
-
|
|
417
|
-
if (numFailedTxs > 0) {
|
|
418
|
-
this.metrics.recordFailedReexecution(proposal);
|
|
419
|
-
throw new ReExFailedTxsError(numFailedTxs);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
if (block.body.txEffects.length !== txHashes.length) {
|
|
423
|
-
this.metrics.recordFailedReexecution(proposal);
|
|
424
|
-
throw new ReExTimeoutError();
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// This function will throw an error if state updates do not match
|
|
428
|
-
if (!block.archive.root.equals(proposal.archive)) {
|
|
429
|
-
this.metrics.recordFailedReexecution(proposal);
|
|
430
|
-
throw new ReExStateMismatchError(
|
|
431
|
-
proposal.archive,
|
|
432
|
-
block.archive.root,
|
|
433
|
-
proposal.payload.stateReference,
|
|
434
|
-
block.header.state,
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
this.metrics.recordReex(timer.ms(), txs.length, block.header.totalManaUsed.toNumber() / 1e6);
|
|
311
|
+
return this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
439
312
|
}
|
|
440
313
|
|
|
441
314
|
private slashInvalidBlock(proposal: BlockProposal) {
|
|
@@ -490,11 +363,18 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
490
363
|
await this.p2pClient.broadcastProposal(proposal);
|
|
491
364
|
}
|
|
492
365
|
|
|
366
|
+
async signAttestationsAndSigners(
|
|
367
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
368
|
+
proposer: EthAddress | undefined,
|
|
369
|
+
): Promise<Signature> {
|
|
370
|
+
return await this.validationService.signAttestationsAndSigners(attestationsAndSigners, proposer);
|
|
371
|
+
}
|
|
372
|
+
|
|
493
373
|
async collectOwnAttestations(proposal: BlockProposal): Promise<BlockAttestation[]> {
|
|
494
374
|
const slot = proposal.payload.header.slotNumber.toBigInt();
|
|
495
375
|
const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
|
|
496
376
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, { inCommittee });
|
|
497
|
-
return this.
|
|
377
|
+
return this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
498
378
|
}
|
|
499
379
|
|
|
500
380
|
async collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]> {
|
|
@@ -544,7 +424,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
544
424
|
}
|
|
545
425
|
}
|
|
546
426
|
|
|
547
|
-
private async
|
|
427
|
+
private async createBlockAttestationsFromProposal(
|
|
428
|
+
proposal: BlockProposal,
|
|
429
|
+
attestors: EthAddress[] = [],
|
|
430
|
+
): Promise<BlockAttestation[]> {
|
|
548
431
|
const attestations = await this.validationService.attestToProposal(proposal, attestors);
|
|
549
432
|
await this.p2pClient.addAttestations(attestations);
|
|
550
433
|
return attestations;
|
|
@@ -573,5 +456,3 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
573
456
|
return authResponse.toBuffer();
|
|
574
457
|
}
|
|
575
458
|
}
|
|
576
|
-
|
|
577
|
-
// Conversion helpers moved into NodeKeystoreAdapter.
|