@aztec/validator-client 2.0.3-rc.16 → 2.0.3-rc.18
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/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 +11 -17
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +61 -177
- package/package.json +11 -11
- package/src/block_proposal_handler.ts +314 -0
- package/src/config.ts +6 -0
- package/src/factory.ts +32 -4
- package/src/index.ts +1 -0
- package/src/metrics.ts +2 -1
- package/src/validator.ts +97 -224
package/src/validator.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
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';
|
|
4
3
|
import { Fr } from '@aztec/foundation/fields';
|
|
5
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
6
|
-
import { retryUntil } from '@aztec/foundation/retry';
|
|
7
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
8
6
|
import { sleep } from '@aztec/foundation/sleep';
|
|
9
|
-
import { DateProvider
|
|
7
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
10
8
|
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';
|
|
9
|
+
import type { P2P, PeerId, TxProvider } from '@aztec/p2p';
|
|
10
|
+
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
15
11
|
import {
|
|
16
12
|
OffenseType,
|
|
17
13
|
type SlasherConfig,
|
|
@@ -21,23 +17,17 @@ import {
|
|
|
21
17
|
} from '@aztec/slasher';
|
|
22
18
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
23
19
|
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
24
|
-
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
25
20
|
import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
|
|
26
21
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
27
22
|
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';
|
|
23
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
24
|
+
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
36
25
|
import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
37
26
|
|
|
38
27
|
import { EventEmitter } from 'events';
|
|
39
28
|
import type { TypedDataDefinition } from 'viem';
|
|
40
29
|
|
|
30
|
+
import { BlockProposalHandler, type BlockProposalValidationFailureReason } from './block_proposal_handler.js';
|
|
41
31
|
import type { ValidatorClientConfig } from './config.js';
|
|
42
32
|
import { ValidationService } from './duties/validation_service.js';
|
|
43
33
|
import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
@@ -47,6 +37,12 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
47
37
|
// Just cap the set to avoid unbounded growth.
|
|
48
38
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
49
39
|
|
|
40
|
+
// What errors from the block proposal handler result in slashing
|
|
41
|
+
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT: BlockProposalValidationFailureReason[] = [
|
|
42
|
+
'state_mismatch',
|
|
43
|
+
'failed_txs',
|
|
44
|
+
];
|
|
45
|
+
|
|
50
46
|
/**
|
|
51
47
|
* Validator Client
|
|
52
48
|
*/
|
|
@@ -55,24 +51,22 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
55
51
|
private validationService: ValidationService;
|
|
56
52
|
private metrics: ValidatorMetrics;
|
|
57
53
|
|
|
54
|
+
// Whether it has already registered handlers on the p2p client
|
|
55
|
+
private hasRegisteredHandlers = false;
|
|
56
|
+
|
|
58
57
|
// Used to check if we are sending the same proposal twice
|
|
59
58
|
private previousProposal?: BlockProposal;
|
|
60
59
|
|
|
61
60
|
private lastEpochForCommitteeUpdateLoop: bigint | undefined;
|
|
62
61
|
private epochCacheUpdateLoop: RunningPromise;
|
|
63
62
|
|
|
64
|
-
private blockProposalValidator: BlockProposalValidator;
|
|
65
|
-
|
|
66
63
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
67
64
|
|
|
68
65
|
protected constructor(
|
|
69
|
-
private blockBuilder: IFullNodeBlockBuilder,
|
|
70
66
|
private keyStore: NodeKeystoreAdapter,
|
|
71
67
|
private epochCache: EpochCache,
|
|
72
68
|
private p2pClient: P2P,
|
|
73
|
-
private
|
|
74
|
-
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
75
|
-
private txProvider: TxProvider,
|
|
69
|
+
private blockProposalHandler: BlockProposalHandler,
|
|
76
70
|
private config: ValidatorClientFullConfig,
|
|
77
71
|
private dateProvider: DateProvider = new DateProvider(),
|
|
78
72
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
@@ -84,8 +78,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
84
78
|
|
|
85
79
|
this.validationService = new ValidationService(keyStore);
|
|
86
80
|
|
|
87
|
-
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
88
|
-
|
|
89
81
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
90
82
|
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
91
83
|
|
|
@@ -152,21 +144,30 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
152
144
|
dateProvider: DateProvider = new DateProvider(),
|
|
153
145
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
154
146
|
) {
|
|
155
|
-
const
|
|
147
|
+
const metrics = new ValidatorMetrics(telemetry);
|
|
148
|
+
const blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
149
|
+
const blockProposalHandler = new BlockProposalHandler(
|
|
156
150
|
blockBuilder,
|
|
157
|
-
NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager),
|
|
158
|
-
epochCache,
|
|
159
|
-
p2pClient,
|
|
160
151
|
blockSource,
|
|
161
152
|
l1ToL2MessageSource,
|
|
162
153
|
txProvider,
|
|
154
|
+
blockProposalValidator,
|
|
155
|
+
config,
|
|
156
|
+
metrics,
|
|
157
|
+
dateProvider,
|
|
158
|
+
telemetry,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const validator = new ValidatorClient(
|
|
162
|
+
NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager),
|
|
163
|
+
epochCache,
|
|
164
|
+
p2pClient,
|
|
165
|
+
blockProposalHandler,
|
|
163
166
|
config,
|
|
164
167
|
dateProvider,
|
|
165
168
|
telemetry,
|
|
166
169
|
);
|
|
167
170
|
|
|
168
|
-
// TODO(PhilWindle): This seems like it could/should be done inside start()
|
|
169
|
-
validator.registerBlockProposalHandler();
|
|
170
171
|
return validator;
|
|
171
172
|
}
|
|
172
173
|
|
|
@@ -176,6 +177,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
176
177
|
.filter(addr => !this.config.disabledValidators.some(disabled => disabled.equals(addr)));
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
public getBlockProposalHandler() {
|
|
181
|
+
return this.blockProposalHandler;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Proxy method for backwards compatibility with tests
|
|
185
|
+
public reExecuteTransactions(proposal: BlockProposal, txs: any[], l1ToL2Messages: Fr[]): Promise<any> {
|
|
186
|
+
return this.blockProposalHandler.reexecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
public signWithAddress(addr: EthAddress, msg: TypedDataDefinition) {
|
|
180
190
|
return this.keyStore.signTypedDataWithAddress(addr, msg);
|
|
181
191
|
}
|
|
@@ -197,11 +207,14 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
197
207
|
}
|
|
198
208
|
|
|
199
209
|
public async start() {
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
if (this.epochCacheUpdateLoop.isRunning()) {
|
|
211
|
+
this.log.warn(`Validator client already started`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
202
214
|
|
|
203
|
-
|
|
215
|
+
await this.registerHandlers();
|
|
204
216
|
|
|
217
|
+
const myAddresses = this.getValidatorAddresses();
|
|
205
218
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
206
219
|
if (inCommittee.length > 0) {
|
|
207
220
|
this.log.info(
|
|
@@ -214,9 +227,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
214
227
|
}
|
|
215
228
|
this.epochCacheUpdateLoop.start();
|
|
216
229
|
|
|
217
|
-
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
218
|
-
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
219
|
-
|
|
220
230
|
return Promise.resolve();
|
|
221
231
|
}
|
|
222
232
|
|
|
@@ -224,143 +234,71 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
224
234
|
await this.epochCacheUpdateLoop.stop();
|
|
225
235
|
}
|
|
226
236
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
/** Register handlers on the p2p client */
|
|
238
|
+
public async registerHandlers() {
|
|
239
|
+
if (!this.hasRegisteredHandlers) {
|
|
240
|
+
this.hasRegisteredHandlers = true;
|
|
241
|
+
this.log.debug(`Registering validator handlers for p2p client`);
|
|
242
|
+
|
|
243
|
+
const handler = (block: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> =>
|
|
244
|
+
this.attestToProposal(block, proposalSender);
|
|
245
|
+
this.p2pClient.registerBlockProposalHandler(handler);
|
|
246
|
+
|
|
247
|
+
const myAddresses = this.getValidatorAddresses();
|
|
248
|
+
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
249
|
+
|
|
250
|
+
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
251
|
+
}
|
|
231
252
|
}
|
|
232
253
|
|
|
233
254
|
async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> {
|
|
234
255
|
const slotNumber = proposal.slotNumber.toBigInt();
|
|
235
|
-
const blockNumber = proposal.blockNumber;
|
|
236
256
|
const proposer = proposal.getSender();
|
|
237
257
|
|
|
238
258
|
// Check that I have any address in current committee before attesting
|
|
239
259
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
240
260
|
const partOfCommittee = inCommittee.length > 0;
|
|
261
|
+
const incFailedAttestation = (reason: string) => this.metrics.incFailedAttestations(1, reason, partOfCommittee);
|
|
241
262
|
|
|
242
|
-
const proposalInfo = {
|
|
243
|
-
...proposal.toBlockInfo(),
|
|
244
|
-
proposer: proposer.toString(),
|
|
245
|
-
};
|
|
246
|
-
|
|
263
|
+
const proposalInfo = { ...proposal.toBlockInfo(), proposer: proposer.toString() };
|
|
247
264
|
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
248
265
|
...proposalInfo,
|
|
249
|
-
txHashes: proposal.txHashes.map(
|
|
266
|
+
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
250
267
|
});
|
|
251
268
|
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
const {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Check that the proposal is from the current proposer, or the next proposer.
|
|
266
|
-
// Q: Should this be moved to the block proposal validator, so we disregard proposals from anyone?
|
|
267
|
-
const invalidProposal = await this.blockProposalValidator.validate(proposal);
|
|
268
|
-
if (invalidProposal) {
|
|
269
|
-
this.log.warn(`Proposal is not valid, skipping attestation`, proposalInfo);
|
|
270
|
-
if (partOfCommittee) {
|
|
271
|
-
this.metrics.incFailedAttestations(1, 'invalid_proposal');
|
|
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
|
-
}
|
|
269
|
+
// Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
|
|
270
|
+
// invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
|
|
271
|
+
const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals } = this.config;
|
|
272
|
+
const shouldReexecute =
|
|
273
|
+
(slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute) ||
|
|
274
|
+
(partOfCommittee && validatorReexecute) ||
|
|
275
|
+
alwaysReexecuteBlockProposals;
|
|
276
|
+
|
|
277
|
+
const validationResult = await this.blockProposalHandler.handleBlockProposal(
|
|
278
|
+
proposal,
|
|
279
|
+
proposalSender,
|
|
280
|
+
!!shouldReexecute,
|
|
281
|
+
);
|
|
323
282
|
|
|
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
|
-
}
|
|
283
|
+
if (!validationResult.isValid) {
|
|
284
|
+
this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
285
|
+
incFailedAttestation(validationResult.reason || 'unknown');
|
|
340
286
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
287
|
+
// Slash invalid block proposals
|
|
288
|
+
if (
|
|
289
|
+
validationResult.reason &&
|
|
290
|
+
SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason) &&
|
|
291
|
+
slashBroadcastedInvalidBlockPenalty > 0n
|
|
292
|
+
) {
|
|
293
|
+
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
294
|
+
this.slashInvalidBlock(proposal);
|
|
346
295
|
}
|
|
347
296
|
return undefined;
|
|
348
297
|
}
|
|
349
298
|
|
|
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
|
-
}
|
|
299
|
+
// Check that I have any address in current committee before attesting
|
|
300
|
+
if (!partOfCommittee) {
|
|
301
|
+
this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
|
|
364
302
|
return undefined;
|
|
365
303
|
}
|
|
366
304
|
|
|
@@ -369,73 +307,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
369
307
|
this.metrics.incAttestations(inCommittee.length);
|
|
370
308
|
|
|
371
309
|
// 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);
|
|
310
|
+
return this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
439
311
|
}
|
|
440
312
|
|
|
441
313
|
private slashInvalidBlock(proposal: BlockProposal) {
|
|
@@ -494,7 +366,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
494
366
|
const slot = proposal.payload.header.slotNumber.toBigInt();
|
|
495
367
|
const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
|
|
496
368
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, { inCommittee });
|
|
497
|
-
return this.
|
|
369
|
+
return this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
498
370
|
}
|
|
499
371
|
|
|
500
372
|
async collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]> {
|
|
@@ -544,7 +416,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
544
416
|
}
|
|
545
417
|
}
|
|
546
418
|
|
|
547
|
-
private async
|
|
419
|
+
private async createBlockAttestationsFromProposal(
|
|
420
|
+
proposal: BlockProposal,
|
|
421
|
+
attestors: EthAddress[] = [],
|
|
422
|
+
): Promise<BlockAttestation[]> {
|
|
548
423
|
const attestations = await this.validationService.attestToProposal(proposal, attestors);
|
|
549
424
|
await this.p2pClient.addAttestations(attestations);
|
|
550
425
|
return attestations;
|
|
@@ -573,5 +448,3 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
573
448
|
return authResponse.toBuffer();
|
|
574
449
|
}
|
|
575
450
|
}
|
|
576
|
-
|
|
577
|
-
// Conversion helpers moved into NodeKeystoreAdapter.
|