@aztec/validator-client 3.0.0-devnet.2 → 3.0.0-devnet.2-patch.1
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 +7 -6
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +17 -13
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -0
- package/dest/duties/validation_service.d.ts +4 -4
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +7 -8
- package/dest/factory.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/key_store/index.d.ts +1 -1
- package/dest/key_store/interface.d.ts +1 -1
- package/dest/key_store/local_key_store.d.ts +1 -1
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +1 -1
- package/dest/key_store/node_keystore_adapter.d.ts +1 -1
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.d.ts +1 -7
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +7 -8
- package/dest/metrics.d.ts +3 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +6 -4
- package/dest/validator.d.ts +7 -6
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +72 -38
- package/package.json +14 -14
- package/src/block_proposal_handler.ts +26 -23
- package/src/config.ts +6 -0
- package/src/duties/validation_service.ts +7 -10
- package/src/key_store/local_key_store.ts +1 -1
- package/src/key_store/node_keystore_adapter.ts +1 -1
- package/src/key_store/web3signer_key_store.ts +7 -10
- package/src/metrics.ts +4 -2
- package/src/validator.ts +87 -52
package/dest/validator.js
CHANGED
|
@@ -28,10 +28,10 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
28
28
|
blockProposalHandler;
|
|
29
29
|
config;
|
|
30
30
|
dateProvider;
|
|
31
|
-
log;
|
|
32
31
|
tracer;
|
|
33
32
|
validationService;
|
|
34
33
|
metrics;
|
|
34
|
+
log;
|
|
35
35
|
// Whether it has already registered handlers on the p2p client
|
|
36
36
|
hasRegisteredHandlers;
|
|
37
37
|
// Used to check if we are sending the same proposal twice
|
|
@@ -40,12 +40,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
40
40
|
epochCacheUpdateLoop;
|
|
41
41
|
proposersOfInvalidBlocks;
|
|
42
42
|
constructor(keyStore, epochCache, p2pClient, blockProposalHandler, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
43
|
-
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.dateProvider = dateProvider, this.
|
|
43
|
+
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
|
|
44
|
+
// Create child logger with fisherman prefix if in fisherman mode
|
|
45
|
+
this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
|
|
44
46
|
this.tracer = telemetry.getTracer('Validator');
|
|
45
47
|
this.metrics = new ValidatorMetrics(telemetry);
|
|
46
|
-
this.validationService = new ValidationService(keyStore, log.createChild('validation-service'));
|
|
48
|
+
this.validationService = new ValidationService(keyStore, this.log.createChild('validation-service'));
|
|
47
49
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
48
|
-
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
50
|
+
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), this.log, 1000);
|
|
49
51
|
const myAddresses = this.getValidatorAddresses();
|
|
50
52
|
this.log.verbose(`Initialized validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
51
53
|
}
|
|
@@ -137,10 +139,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
137
139
|
await this.registerHandlers();
|
|
138
140
|
const myAddresses = this.getValidatorAddresses();
|
|
139
141
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
142
|
+
this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
140
143
|
if (inCommittee.length > 0) {
|
|
141
|
-
this.log.info(`
|
|
142
|
-
} else {
|
|
143
|
-
this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
144
|
+
this.log.info(`Addresses in current validator committee: ${inCommittee.map((a)=>a.toString()).join(', ')}`);
|
|
144
145
|
}
|
|
145
146
|
this.epochCacheUpdateLoop.start();
|
|
146
147
|
return Promise.resolve();
|
|
@@ -160,7 +161,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
async attestToProposal(proposal, proposalSender) {
|
|
163
|
-
const slotNumber = proposal.slotNumber
|
|
164
|
+
const slotNumber = proposal.slotNumber;
|
|
164
165
|
const proposer = proposal.getSender();
|
|
165
166
|
// Reject proposals with invalid signatures
|
|
166
167
|
if (!proposer) {
|
|
@@ -176,32 +177,31 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
176
177
|
};
|
|
177
178
|
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
178
179
|
...proposalInfo,
|
|
179
|
-
txHashes: proposal.txHashes.map((t)=>t.toString())
|
|
180
|
+
txHashes: proposal.txHashes.map((t)=>t.toString()),
|
|
181
|
+
fishermanMode: this.config.fishermanMode || false
|
|
180
182
|
});
|
|
181
183
|
// Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
|
|
182
184
|
// invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
+
// In fisherman mode, we always reexecute to validate proposals.
|
|
186
|
+
const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
|
|
187
|
+
const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals;
|
|
185
188
|
const validationResult = await this.blockProposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute);
|
|
186
189
|
if (!validationResult.isValid) {
|
|
187
190
|
this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// Node issues: parent_block_not_found, block_number_already_exists, txs_not_available, timeout, unknown_error
|
|
203
|
-
this.metrics.incFailedAttestationsNodeIssue(1, reason);
|
|
204
|
-
}
|
|
191
|
+
const reason = validationResult.reason || 'unknown';
|
|
192
|
+
// Classify failure reason: bad proposal vs node issue
|
|
193
|
+
const badProposalReasons = [
|
|
194
|
+
'invalid_proposal',
|
|
195
|
+
'state_mismatch',
|
|
196
|
+
'failed_txs',
|
|
197
|
+
'in_hash_mismatch',
|
|
198
|
+
'parent_block_wrong_slot'
|
|
199
|
+
];
|
|
200
|
+
if (badProposalReasons.includes(reason)) {
|
|
201
|
+
this.metrics.incFailedAttestationsBadProposal(1, reason, partOfCommittee);
|
|
202
|
+
} else {
|
|
203
|
+
// Node issues so we can't attest
|
|
204
|
+
this.metrics.incFailedAttestationsNodeIssue(1, reason, partOfCommittee);
|
|
205
205
|
}
|
|
206
206
|
// Slash invalid block proposals (can happen even when not in committee)
|
|
207
207
|
if (validationResult.reason && SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason) && slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
@@ -211,15 +211,42 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
211
211
|
return undefined;
|
|
212
212
|
}
|
|
213
213
|
// Check that I have any address in current committee before attesting
|
|
214
|
-
if
|
|
214
|
+
// In fisherman mode, we still create attestations for validation even if not in committee
|
|
215
|
+
if (!partOfCommittee && !this.config.fishermanMode) {
|
|
215
216
|
this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
|
|
216
217
|
return undefined;
|
|
217
218
|
}
|
|
218
219
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
219
|
-
this.log.info(
|
|
220
|
+
this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} proposal for slot ${slotNumber}`, {
|
|
221
|
+
...proposalInfo,
|
|
222
|
+
inCommittee: partOfCommittee,
|
|
223
|
+
fishermanMode: this.config.fishermanMode || false
|
|
224
|
+
});
|
|
220
225
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
221
226
|
// If the above function does not throw an error, then we can attest to the proposal
|
|
222
|
-
|
|
227
|
+
// Determine which validators should attest
|
|
228
|
+
let attestors;
|
|
229
|
+
if (partOfCommittee) {
|
|
230
|
+
attestors = inCommittee;
|
|
231
|
+
} else if (this.config.fishermanMode) {
|
|
232
|
+
// In fisherman mode, create attestations for validation purposes even if not in committee. These won't be broadcast.
|
|
233
|
+
attestors = this.getValidatorAddresses();
|
|
234
|
+
} else {
|
|
235
|
+
attestors = [];
|
|
236
|
+
}
|
|
237
|
+
// Only create attestations if we have attestors
|
|
238
|
+
if (attestors.length === 0) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
if (this.config.fishermanMode) {
|
|
242
|
+
// bail out early and don't save attestations to the pool in fisherman mode
|
|
243
|
+
this.log.info(`Creating attestations for proposal for slot ${slotNumber}`, {
|
|
244
|
+
...proposalInfo,
|
|
245
|
+
attestors: attestors.map((a)=>a.toString())
|
|
246
|
+
});
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
return this.createBlockAttestationsFromProposal(proposal, attestors);
|
|
223
250
|
}
|
|
224
251
|
slashInvalidBlock(proposal) {
|
|
225
252
|
const proposer = proposal.getSender();
|
|
@@ -239,16 +266,16 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
239
266
|
validator: proposer,
|
|
240
267
|
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
241
268
|
offenseType: OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL,
|
|
242
|
-
epochOrSlot: proposal.slotNumber
|
|
269
|
+
epochOrSlot: BigInt(proposal.slotNumber)
|
|
243
270
|
}
|
|
244
271
|
]);
|
|
245
272
|
}
|
|
246
|
-
async createBlockProposal(blockNumber, header, archive,
|
|
247
|
-
if (this.previousProposal?.slotNumber
|
|
273
|
+
async createBlockProposal(blockNumber, header, archive, txs, proposerAddress, options) {
|
|
274
|
+
if (this.previousProposal?.slotNumber === header.slotNumber) {
|
|
248
275
|
this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
|
249
276
|
return Promise.resolve(undefined);
|
|
250
277
|
}
|
|
251
|
-
const newProposal = await this.validationService.createBlockProposal(header, archive,
|
|
278
|
+
const newProposal = await this.validationService.createBlockProposal(header, archive, txs, proposerAddress, {
|
|
252
279
|
...options,
|
|
253
280
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
|
|
254
281
|
});
|
|
@@ -262,16 +289,23 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
262
289
|
return await this.validationService.signAttestationsAndSigners(attestationsAndSigners, proposer);
|
|
263
290
|
}
|
|
264
291
|
async collectOwnAttestations(proposal) {
|
|
265
|
-
const slot = proposal.payload.header.slotNumber
|
|
292
|
+
const slot = proposal.payload.header.slotNumber;
|
|
266
293
|
const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
|
|
267
294
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, {
|
|
268
295
|
inCommittee
|
|
269
296
|
});
|
|
270
|
-
|
|
297
|
+
const attestations = await this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
298
|
+
// We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
|
|
299
|
+
// other nodes can see that our validators did attest to this block proposal, and do not slash us
|
|
300
|
+
// due to inactivity for missed attestations.
|
|
301
|
+
void this.p2pClient.broadcastAttestations(attestations).catch((err)=>{
|
|
302
|
+
this.log.error(`Failed to broadcast self-attestations for slot ${slot}`, err);
|
|
303
|
+
});
|
|
304
|
+
return attestations;
|
|
271
305
|
}
|
|
272
306
|
async collectAttestations(proposal, required, deadline) {
|
|
273
307
|
// Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
|
|
274
|
-
const slot = proposal.payload.header.slotNumber
|
|
308
|
+
const slot = proposal.payload.header.slotNumber;
|
|
275
309
|
this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
|
|
276
310
|
if (+deadline < this.dateProvider.now()) {
|
|
277
311
|
this.log.error(`Deadline ${deadline.toISOString()} for collecting ${required} attestations for slot ${slot} is in the past`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "3.0.0-devnet.2",
|
|
3
|
+
"version": "3.0.0-devnet.2-patch.1",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"start": "node --no-warnings ./dest/bin",
|
|
21
|
-
"build": "yarn clean && tsc
|
|
22
|
-
"build:dev": "tsc
|
|
21
|
+
"build": "yarn clean && ../scripts/tsc.sh",
|
|
22
|
+
"build:dev": "../scripts/tsc.sh --watch",
|
|
23
23
|
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
24
24
|
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
25
25
|
},
|
|
@@ -64,25 +64,25 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/constants": "3.0.0-devnet.2",
|
|
68
|
-
"@aztec/epoch-cache": "3.0.0-devnet.2",
|
|
69
|
-
"@aztec/ethereum": "3.0.0-devnet.2",
|
|
70
|
-
"@aztec/foundation": "3.0.0-devnet.2",
|
|
71
|
-
"@aztec/node-keystore": "3.0.0-devnet.2",
|
|
72
|
-
"@aztec/p2p": "3.0.0-devnet.2",
|
|
73
|
-
"@aztec/
|
|
74
|
-
"@aztec/
|
|
75
|
-
"@aztec/
|
|
76
|
-
"@aztec/telemetry-client": "3.0.0-devnet.2",
|
|
67
|
+
"@aztec/constants": "3.0.0-devnet.2-patch.1",
|
|
68
|
+
"@aztec/epoch-cache": "3.0.0-devnet.2-patch.1",
|
|
69
|
+
"@aztec/ethereum": "3.0.0-devnet.2-patch.1",
|
|
70
|
+
"@aztec/foundation": "3.0.0-devnet.2-patch.1",
|
|
71
|
+
"@aztec/node-keystore": "3.0.0-devnet.2-patch.1",
|
|
72
|
+
"@aztec/p2p": "3.0.0-devnet.2-patch.1",
|
|
73
|
+
"@aztec/slasher": "3.0.0-devnet.2-patch.1",
|
|
74
|
+
"@aztec/stdlib": "3.0.0-devnet.2-patch.1",
|
|
75
|
+
"@aztec/telemetry-client": "3.0.0-devnet.2-patch.1",
|
|
77
76
|
"koa": "^2.16.1",
|
|
78
77
|
"koa-router": "^13.1.1",
|
|
79
78
|
"tslib": "^2.4.0",
|
|
80
|
-
"viem": "npm:@
|
|
79
|
+
"viem": "npm:@aztec/viem@2.38.2"
|
|
81
80
|
},
|
|
82
81
|
"devDependencies": {
|
|
83
82
|
"@jest/globals": "^30.0.0",
|
|
84
83
|
"@types/jest": "^30.0.0",
|
|
85
84
|
"@types/node": "^22.15.17",
|
|
85
|
+
"@typescript/native-preview": "7.0.0-dev.20251126.1",
|
|
86
86
|
"jest": "^30.0.0",
|
|
87
87
|
"jest-mock-extended": "^4.0.0",
|
|
88
88
|
"ts-node": "^10.9.1",
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
|
+
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
4
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
3
|
-
import { Fr } from '@aztec/foundation/fields';
|
|
4
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
5
6
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
6
7
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
7
8
|
import type { P2P, PeerId } from '@aztec/p2p';
|
|
8
9
|
import { TxProvider } from '@aztec/p2p';
|
|
9
10
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
10
|
-
import { computeInHashFromL1ToL2Messages } from '@aztec/prover-client/helpers';
|
|
11
11
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
12
12
|
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
13
|
import type { IFullNodeBlockBuilder, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
|
|
14
|
-
import type
|
|
14
|
+
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
15
15
|
import { type BlockProposal, ConsensusPayload } from '@aztec/stdlib/p2p';
|
|
16
16
|
import { BlockHeader, type FailedTx, GlobalVariables, type Tx } from '@aztec/stdlib/tx';
|
|
17
17
|
import {
|
|
@@ -45,14 +45,14 @@ type ReexecuteTransactionsResult = {
|
|
|
45
45
|
|
|
46
46
|
export type BlockProposalValidationSuccessResult = {
|
|
47
47
|
isValid: true;
|
|
48
|
-
blockNumber:
|
|
48
|
+
blockNumber: BlockNumber;
|
|
49
49
|
reexecutionResult?: ReexecuteTransactionsResult;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export type BlockProposalValidationFailureResult = {
|
|
53
53
|
isValid: false;
|
|
54
54
|
reason: BlockProposalValidationFailureReason;
|
|
55
|
-
blockNumber?:
|
|
55
|
+
blockNumber?: BlockNumber;
|
|
56
56
|
reexecutionResult?: ReexecuteTransactionsResult;
|
|
57
57
|
};
|
|
58
58
|
|
|
@@ -73,6 +73,9 @@ export class BlockProposalHandler {
|
|
|
73
73
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
74
74
|
private log = createLogger('validator:block-proposal-handler'),
|
|
75
75
|
) {
|
|
76
|
+
if (config.fishermanMode) {
|
|
77
|
+
this.log = this.log.createChild('[FISHERMAN]');
|
|
78
|
+
}
|
|
76
79
|
this.tracer = telemetry.getTracer('BlockProposalHandler');
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -81,14 +84,14 @@ export class BlockProposalHandler {
|
|
|
81
84
|
try {
|
|
82
85
|
const result = await this.handleBlockProposal(proposal, proposalSender, true);
|
|
83
86
|
if (result.isValid) {
|
|
84
|
-
this.log.info(`Non-validator reexecution completed for slot ${proposal.slotNumber
|
|
87
|
+
this.log.info(`Non-validator reexecution completed for slot ${proposal.slotNumber}`, {
|
|
85
88
|
blockNumber: result.blockNumber,
|
|
86
89
|
reexecutionTimeMs: result.reexecutionResult?.reexecutionTimeMs,
|
|
87
90
|
totalManaUsed: result.reexecutionResult?.totalManaUsed,
|
|
88
91
|
numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0,
|
|
89
92
|
});
|
|
90
93
|
} else {
|
|
91
|
-
this.log.warn(`Non-validator reexecution failed for slot ${proposal.slotNumber
|
|
94
|
+
this.log.warn(`Non-validator reexecution failed for slot ${proposal.slotNumber}`, {
|
|
92
95
|
blockNumber: result.blockNumber,
|
|
93
96
|
reason: result.reason,
|
|
94
97
|
});
|
|
@@ -108,7 +111,7 @@ export class BlockProposalHandler {
|
|
|
108
111
|
proposalSender: PeerId,
|
|
109
112
|
shouldReexecute: boolean,
|
|
110
113
|
): Promise<BlockProposalValidationResult> {
|
|
111
|
-
const slotNumber = proposal.slotNumber
|
|
114
|
+
const slotNumber = proposal.slotNumber;
|
|
112
115
|
const proposer = proposal.getSender();
|
|
113
116
|
const config = this.blockBuilder.getConfig();
|
|
114
117
|
|
|
@@ -150,7 +153,10 @@ export class BlockProposalHandler {
|
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
// Compute the block number based on the parent block
|
|
153
|
-
const blockNumber =
|
|
156
|
+
const blockNumber =
|
|
157
|
+
parentBlockHeader === 'genesis'
|
|
158
|
+
? BlockNumber(INITIAL_L2_BLOCK_NUM)
|
|
159
|
+
: BlockNumber(parentBlockHeader.getBlockNumber() + 1);
|
|
154
160
|
|
|
155
161
|
// Check that this block number does not exist already
|
|
156
162
|
const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
|
|
@@ -167,8 +173,10 @@ export class BlockProposalHandler {
|
|
|
167
173
|
});
|
|
168
174
|
|
|
169
175
|
// Check that I have the same set of l1ToL2Messages as the proposal
|
|
170
|
-
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(
|
|
171
|
-
|
|
176
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(
|
|
177
|
+
CheckpointNumber.fromBlockNumber(blockNumber),
|
|
178
|
+
);
|
|
179
|
+
const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
172
180
|
const proposalInHash = proposal.payload.header.contentCommitment.inHash;
|
|
173
181
|
if (!computedInHash.equals(proposalInHash)) {
|
|
174
182
|
this.log.warn(`L1 to L2 messages in hash mismatch, skipping processing`, {
|
|
@@ -204,7 +212,7 @@ export class BlockProposalHandler {
|
|
|
204
212
|
|
|
205
213
|
private async getParentBlock(proposal: BlockProposal): Promise<'genesis' | BlockHeader | undefined> {
|
|
206
214
|
const parentArchive = proposal.payload.header.lastArchiveRoot;
|
|
207
|
-
const slot = proposal.slotNumber
|
|
215
|
+
const slot = proposal.slotNumber;
|
|
208
216
|
const config = this.blockBuilder.getConfig();
|
|
209
217
|
const { genesisArchiveRoot } = await this.blockSource.getGenesisValues();
|
|
210
218
|
|
|
@@ -239,8 +247,8 @@ export class BlockProposalHandler {
|
|
|
239
247
|
}
|
|
240
248
|
}
|
|
241
249
|
|
|
242
|
-
private getReexecutionDeadline(slot:
|
|
243
|
-
const nextSlotTimestampSeconds = Number(getTimestampForSlot(slot +
|
|
250
|
+
private getReexecutionDeadline(slot: SlotNumber, config: { l1GenesisTime: bigint; slotDuration: number }): Date {
|
|
251
|
+
const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
|
|
244
252
|
const msNeededForPropagationAndPublishing = this.config.validatorReexecuteDeadlineMs;
|
|
245
253
|
return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
|
|
246
254
|
}
|
|
@@ -259,7 +267,7 @@ export class BlockProposalHandler {
|
|
|
259
267
|
|
|
260
268
|
async reexecuteTransactions(
|
|
261
269
|
proposal: BlockProposal,
|
|
262
|
-
blockNumber:
|
|
270
|
+
blockNumber: BlockNumber,
|
|
263
271
|
txs: Tx[],
|
|
264
272
|
l1ToL2Messages: Fr[],
|
|
265
273
|
): Promise<ReexecuteTransactionsResult> {
|
|
@@ -290,11 +298,11 @@ export class BlockProposalHandler {
|
|
|
290
298
|
});
|
|
291
299
|
|
|
292
300
|
const { block, failedTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, globalVariables, {
|
|
293
|
-
deadline: this.getReexecutionDeadline(proposal.payload.header.slotNumber
|
|
301
|
+
deadline: this.getReexecutionDeadline(proposal.payload.header.slotNumber, config),
|
|
294
302
|
});
|
|
295
303
|
|
|
296
304
|
const numFailedTxs = failedTxs.length;
|
|
297
|
-
const slot = proposal.slotNumber
|
|
305
|
+
const slot = proposal.slotNumber;
|
|
298
306
|
this.log.verbose(`Transaction re-execution complete for slot ${slot}`, {
|
|
299
307
|
numFailedTxs,
|
|
300
308
|
numProposalTxs: txHashes.length,
|
|
@@ -320,12 +328,7 @@ export class BlockProposalHandler {
|
|
|
320
328
|
actual: proposal.payload.toInspect(),
|
|
321
329
|
});
|
|
322
330
|
this.metrics?.recordFailedReexecution(proposal);
|
|
323
|
-
throw new ReExStateMismatchError(
|
|
324
|
-
proposal.archive,
|
|
325
|
-
block.archive.root,
|
|
326
|
-
proposal.payload.stateReference,
|
|
327
|
-
block.header.state,
|
|
328
|
-
);
|
|
331
|
+
throw new ReExStateMismatchError(proposal.archive, block.archive.root);
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
const reexecutionTimeMs = timer.ms();
|
package/src/config.ts
CHANGED
|
@@ -64,6 +64,12 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
64
64
|
'Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status).',
|
|
65
65
|
...booleanConfigHelper(false),
|
|
66
66
|
},
|
|
67
|
+
fishermanMode: {
|
|
68
|
+
env: 'FISHERMAN_MODE',
|
|
69
|
+
description:
|
|
70
|
+
'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
|
|
71
|
+
...booleanConfigHelper(false),
|
|
72
|
+
},
|
|
67
73
|
};
|
|
68
74
|
|
|
69
75
|
/**
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
|
-
import { keccak256 } from '@aztec/foundation/crypto';
|
|
2
|
+
import { keccak256 } from '@aztec/foundation/crypto/keccak';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
4
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
5
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
5
|
-
import { Fr } from '@aztec/foundation/fields';
|
|
6
6
|
import { createLogger } from '@aztec/foundation/log';
|
|
7
|
-
import { unfreeze } from '@aztec/foundation/types';
|
|
8
7
|
import type { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
9
8
|
import {
|
|
10
9
|
BlockAttestation,
|
|
@@ -14,8 +13,7 @@ import {
|
|
|
14
13
|
SignatureDomainSeparator,
|
|
15
14
|
} from '@aztec/stdlib/p2p';
|
|
16
15
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
17
|
-
import {
|
|
18
|
-
import { StateReference, type Tx } from '@aztec/stdlib/tx';
|
|
16
|
+
import type { Tx } from '@aztec/stdlib/tx';
|
|
19
17
|
|
|
20
18
|
import type { ValidatorKeyStore } from '../key_store/interface.js';
|
|
21
19
|
|
|
@@ -38,7 +36,6 @@ export class ValidationService {
|
|
|
38
36
|
async createBlockProposal(
|
|
39
37
|
header: CheckpointHeader,
|
|
40
38
|
archive: Fr,
|
|
41
|
-
stateReference: StateReference,
|
|
42
39
|
txs: Tx[],
|
|
43
40
|
proposerAttesterAddress: EthAddress | undefined,
|
|
44
41
|
options: BlockProposalOptions,
|
|
@@ -54,14 +51,14 @@ export class ValidationService {
|
|
|
54
51
|
// TODO: check if this is calculated earlier / can not be recomputed
|
|
55
52
|
const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
|
|
56
53
|
|
|
57
|
-
// For testing:
|
|
54
|
+
// For testing: change the new archive to trigger state_mismatch validation failure
|
|
58
55
|
if (options.broadcastInvalidBlockProposal) {
|
|
59
|
-
|
|
60
|
-
this.log.warn(`Creating INVALID block proposal for slot ${header.slotNumber
|
|
56
|
+
archive = Fr.random();
|
|
57
|
+
this.log.warn(`Creating INVALID block proposal for slot ${header.slotNumber}`);
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
return BlockProposal.createProposalFromSigner(
|
|
64
|
-
new ConsensusPayload(header, archive
|
|
61
|
+
new ConsensusPayload(header, archive),
|
|
65
62
|
txHashes,
|
|
66
63
|
options.publishFullTxs ? txs : undefined,
|
|
67
64
|
payloadSigner,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
|
-
import { Secp256k1Signer } from '@aztec/foundation/crypto';
|
|
2
|
+
import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
3
3
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EthSigner } from '@aztec/ethereum';
|
|
1
|
+
import type { EthSigner } from '@aztec/ethereum/eth-signer';
|
|
2
2
|
import type { Buffer32 } from '@aztec/foundation/buffer';
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
|
+
import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
2
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
4
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
4
5
|
|
|
@@ -45,11 +46,8 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
|
45
46
|
* @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
|
|
46
47
|
* @return signatures
|
|
47
48
|
*/
|
|
48
|
-
public
|
|
49
|
-
|
|
50
|
-
this.addresses.map(address => this.makeJsonRpcSignTypedDataRequest(address, typedData)),
|
|
51
|
-
);
|
|
52
|
-
return signatures;
|
|
49
|
+
public signTypedData(typedData: TypedDataDefinition): Promise<Signature[]> {
|
|
50
|
+
return Promise.all(this.addresses.map(address => this.makeJsonRpcSignTypedDataRequest(address, typedData)));
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
/**
|
|
@@ -73,9 +71,8 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
|
73
71
|
* @param message - The message to sign
|
|
74
72
|
* @return signatures
|
|
75
73
|
*/
|
|
76
|
-
public
|
|
77
|
-
|
|
78
|
-
return signatures;
|
|
74
|
+
public signMessage(message: Buffer32): Promise<Signature[]> {
|
|
75
|
+
return Promise.all(this.addresses.map(address => this.makeJsonRpcSignRequest(address, message)));
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
/**
|
|
@@ -144,7 +141,7 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
|
144
141
|
}
|
|
145
142
|
|
|
146
143
|
// Parse the signature from the hex string
|
|
147
|
-
return Signature.fromString(signatureHex as `0x${string}`);
|
|
144
|
+
return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
|
|
148
145
|
}
|
|
149
146
|
|
|
150
147
|
private async makeJsonRpcSignTypedDataRequest(
|
|
@@ -190,6 +187,6 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
|
190
187
|
signatureHex = '0x' + signatureHex;
|
|
191
188
|
}
|
|
192
189
|
|
|
193
|
-
return Signature.fromString(signatureHex as `0x${string}`);
|
|
190
|
+
return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
|
|
194
191
|
}
|
|
195
192
|
}
|
package/src/metrics.ts
CHANGED
|
@@ -85,15 +85,17 @@ export class ValidatorMetrics {
|
|
|
85
85
|
this.successfulAttestationsCount.add(num);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
public incFailedAttestationsBadProposal(num: number, reason: string) {
|
|
88
|
+
public incFailedAttestationsBadProposal(num: number, reason: string, inCommittee: boolean) {
|
|
89
89
|
this.failedAttestationsBadProposalCount.add(num, {
|
|
90
90
|
[Attributes.ERROR_TYPE]: reason,
|
|
91
|
+
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
public incFailedAttestationsNodeIssue(num: number, reason: string) {
|
|
95
|
+
public incFailedAttestationsNodeIssue(num: number, reason: string, inCommittee: boolean) {
|
|
95
96
|
this.failedAttestationsNodeIssueCount.add(num, {
|
|
96
97
|
[Attributes.ERROR_TYPE]: reason,
|
|
98
|
+
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
101
|
}
|