@aztec/validator-client 0.0.1-commit.b655e406 → 0.0.1-commit.c7c42ec
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 +3 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +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 +1 -1
- package/dest/metrics.d.ts +1 -1
- package/dest/metrics.d.ts.map +1 -1
- package/dest/validator.d.ts +12 -8
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +100 -28
- package/package.json +16 -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/factory.ts +3 -0
- 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 +1 -1
- package/src/validator.ts +118 -41
package/dest/validator.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { getBlobsPerL1Block } from '@aztec/blob-lib';
|
|
1
8
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
9
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
3
10
|
import { sleep } from '@aztec/foundation/sleep';
|
|
@@ -5,7 +12,7 @@ import { DateProvider } from '@aztec/foundation/timer';
|
|
|
5
12
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
6
13
|
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
7
14
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
8
|
-
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
15
|
+
import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
9
16
|
import { EventEmitter } from 'events';
|
|
10
17
|
import { BlockProposalHandler } from './block_proposal_handler.js';
|
|
11
18
|
import { ValidationService } from './duties/validation_service.js';
|
|
@@ -27,11 +34,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
27
34
|
p2pClient;
|
|
28
35
|
blockProposalHandler;
|
|
29
36
|
config;
|
|
37
|
+
fileStoreBlobUploadClient;
|
|
30
38
|
dateProvider;
|
|
31
|
-
log;
|
|
32
39
|
tracer;
|
|
33
40
|
validationService;
|
|
34
41
|
metrics;
|
|
42
|
+
log;
|
|
35
43
|
// Whether it has already registered handlers on the p2p client
|
|
36
44
|
hasRegisteredHandlers;
|
|
37
45
|
// Used to check if we are sending the same proposal twice
|
|
@@ -39,13 +47,15 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
39
47
|
lastEpochForCommitteeUpdateLoop;
|
|
40
48
|
epochCacheUpdateLoop;
|
|
41
49
|
proposersOfInvalidBlocks;
|
|
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.
|
|
50
|
+
constructor(keyStore, epochCache, p2pClient, blockProposalHandler, config, fileStoreBlobUploadClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
51
|
+
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.fileStoreBlobUploadClient = fileStoreBlobUploadClient, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
|
|
52
|
+
// Create child logger with fisherman prefix if in fisherman mode
|
|
53
|
+
this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
|
|
44
54
|
this.tracer = telemetry.getTracer('Validator');
|
|
45
55
|
this.metrics = new ValidatorMetrics(telemetry);
|
|
46
|
-
this.validationService = new ValidationService(keyStore, log.createChild('validation-service'));
|
|
56
|
+
this.validationService = new ValidationService(keyStore, this.log.createChild('validation-service'));
|
|
47
57
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
48
|
-
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
58
|
+
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), this.log, 1000);
|
|
49
59
|
const myAddresses = this.getValidatorAddresses();
|
|
50
60
|
this.log.verbose(`Initialized validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
51
61
|
}
|
|
@@ -92,13 +102,13 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
92
102
|
this.log.error(`Error updating epoch committee`, err);
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
|
-
static new(config, blockBuilder, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
|
|
105
|
+
static new(config, blockBuilder, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, fileStoreBlobUploadClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
|
|
96
106
|
const metrics = new ValidatorMetrics(telemetry);
|
|
97
107
|
const blockProposalValidator = new BlockProposalValidator(epochCache, {
|
|
98
108
|
txsPermitted: !config.disableTransactions
|
|
99
109
|
});
|
|
100
110
|
const blockProposalHandler = new BlockProposalHandler(blockBuilder, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, config, metrics, dateProvider, telemetry);
|
|
101
|
-
const validator = new ValidatorClient(NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager), epochCache, p2pClient, blockProposalHandler, config, dateProvider, telemetry);
|
|
111
|
+
const validator = new ValidatorClient(NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager), epochCache, p2pClient, blockProposalHandler, config, fileStoreBlobUploadClient, dateProvider, telemetry);
|
|
102
112
|
return validator;
|
|
103
113
|
}
|
|
104
114
|
getValidatorAddresses() {
|
|
@@ -137,10 +147,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
137
147
|
await this.registerHandlers();
|
|
138
148
|
const myAddresses = this.getValidatorAddresses();
|
|
139
149
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
150
|
+
this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
140
151
|
if (inCommittee.length > 0) {
|
|
141
|
-
this.log.info(`
|
|
142
|
-
} else {
|
|
143
|
-
this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
152
|
+
this.log.info(`Addresses in current validator committee: ${inCommittee.map((a)=>a.toString()).join(', ')}`);
|
|
144
153
|
}
|
|
145
154
|
this.epochCacheUpdateLoop.start();
|
|
146
155
|
return Promise.resolve();
|
|
@@ -160,7 +169,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
async attestToProposal(proposal, proposalSender) {
|
|
163
|
-
const slotNumber = proposal.slotNumber
|
|
172
|
+
const slotNumber = proposal.slotNumber;
|
|
164
173
|
const proposer = proposal.getSender();
|
|
165
174
|
// Reject proposals with invalid signatures
|
|
166
175
|
if (!proposer) {
|
|
@@ -176,12 +185,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
176
185
|
};
|
|
177
186
|
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
178
187
|
...proposalInfo,
|
|
179
|
-
txHashes: proposal.txHashes.map((t)=>t.toString())
|
|
188
|
+
txHashes: proposal.txHashes.map((t)=>t.toString()),
|
|
189
|
+
fishermanMode: this.config.fishermanMode || false
|
|
180
190
|
});
|
|
181
191
|
// Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
|
|
182
192
|
// invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
|
|
183
|
-
|
|
184
|
-
const
|
|
193
|
+
// In fisherman mode, we always reexecute to validate proposals.
|
|
194
|
+
const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
|
|
195
|
+
const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals || this.fileStoreBlobUploadClient;
|
|
185
196
|
const validationResult = await this.blockProposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute);
|
|
186
197
|
if (!validationResult.isValid) {
|
|
187
198
|
this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
@@ -208,15 +219,55 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
208
219
|
return undefined;
|
|
209
220
|
}
|
|
210
221
|
// Check that I have any address in current committee before attesting
|
|
211
|
-
if
|
|
222
|
+
// In fisherman mode, we still create attestations for validation even if not in committee
|
|
223
|
+
if (!partOfCommittee && !this.config.fishermanMode) {
|
|
212
224
|
this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
|
|
213
225
|
return undefined;
|
|
214
226
|
}
|
|
215
227
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
216
|
-
this.log.info(
|
|
228
|
+
this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} proposal for slot ${slotNumber}`, {
|
|
229
|
+
...proposalInfo,
|
|
230
|
+
inCommittee: partOfCommittee,
|
|
231
|
+
fishermanMode: this.config.fishermanMode || false
|
|
232
|
+
});
|
|
217
233
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
234
|
+
// Upload blobs to filestore after successful re-execution (fire-and-forget)
|
|
235
|
+
if (validationResult.reexecutionResult?.block && this.fileStoreBlobUploadClient) {
|
|
236
|
+
void Promise.resolve().then(async ()=>{
|
|
237
|
+
try {
|
|
238
|
+
const blobFields = validationResult.reexecutionResult.block.getCheckpointBlobFields();
|
|
239
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
240
|
+
await this.fileStoreBlobUploadClient.saveBlobs(blobs, true);
|
|
241
|
+
this.log.debug(`Uploaded ${blobs.length} blobs to filestore from re-execution`, proposalInfo);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
this.log.warn(`Failed to upload blobs from re-execution`, err);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
218
247
|
// If the above function does not throw an error, then we can attest to the proposal
|
|
219
|
-
|
|
248
|
+
// Determine which validators should attest
|
|
249
|
+
let attestors;
|
|
250
|
+
if (partOfCommittee) {
|
|
251
|
+
attestors = inCommittee;
|
|
252
|
+
} else if (this.config.fishermanMode) {
|
|
253
|
+
// In fisherman mode, create attestations for validation purposes even if not in committee. These won't be broadcast.
|
|
254
|
+
attestors = this.getValidatorAddresses();
|
|
255
|
+
} else {
|
|
256
|
+
attestors = [];
|
|
257
|
+
}
|
|
258
|
+
// Only create attestations if we have attestors
|
|
259
|
+
if (attestors.length === 0) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
if (this.config.fishermanMode) {
|
|
263
|
+
// bail out early and don't save attestations to the pool in fisherman mode
|
|
264
|
+
this.log.info(`Creating attestations for proposal for slot ${slotNumber}`, {
|
|
265
|
+
...proposalInfo,
|
|
266
|
+
attestors: attestors.map((a)=>a.toString())
|
|
267
|
+
});
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
return this.createBlockAttestationsFromProposal(proposal, attestors);
|
|
220
271
|
}
|
|
221
272
|
slashInvalidBlock(proposal) {
|
|
222
273
|
const proposer = proposal.getSender();
|
|
@@ -236,22 +287,30 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
236
287
|
validator: proposer,
|
|
237
288
|
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
238
289
|
offenseType: OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL,
|
|
239
|
-
epochOrSlot: proposal.slotNumber
|
|
290
|
+
epochOrSlot: BigInt(proposal.slotNumber)
|
|
240
291
|
}
|
|
241
292
|
]);
|
|
242
293
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
294
|
+
// TODO(palla/mbps): Block proposal should not require a checkpoint proposal
|
|
295
|
+
async createBlockProposal(blockNumber, header, archive, txs, proposerAddress, options) {
|
|
296
|
+
// TODO(palla/mbps): Prevent double proposals properly
|
|
297
|
+
// if (this.previousProposal?.slotNumber === header.slotNumber) {
|
|
298
|
+
// this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
|
299
|
+
// return Promise.resolve(undefined);
|
|
300
|
+
// }
|
|
301
|
+
this.log.info(`Assembling block proposal for block ${blockNumber} slot ${header.slotNumber}`);
|
|
302
|
+
const newProposal = await this.validationService.createBlockProposal(header, archive, txs, proposerAddress, {
|
|
249
303
|
...options,
|
|
250
304
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
|
|
251
305
|
});
|
|
252
306
|
this.previousProposal = newProposal;
|
|
253
307
|
return newProposal;
|
|
254
308
|
}
|
|
309
|
+
// TODO(palla/mbps): Effectively create a checkpoint proposal different from a block proposal
|
|
310
|
+
createCheckpointProposal(header, archive, txs, proposerAddress, options) {
|
|
311
|
+
this.log.info(`Assembling checkpoint proposal for slot ${header.slotNumber}`);
|
|
312
|
+
return this.createBlockProposal(0, header, archive, txs, proposerAddress, options);
|
|
313
|
+
}
|
|
255
314
|
async broadcastBlockProposal(proposal) {
|
|
256
315
|
await this.p2pClient.broadcastProposal(proposal);
|
|
257
316
|
}
|
|
@@ -259,16 +318,23 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
259
318
|
return await this.validationService.signAttestationsAndSigners(attestationsAndSigners, proposer);
|
|
260
319
|
}
|
|
261
320
|
async collectOwnAttestations(proposal) {
|
|
262
|
-
const slot = proposal.payload.header.slotNumber
|
|
321
|
+
const slot = proposal.payload.header.slotNumber;
|
|
263
322
|
const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
|
|
264
323
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, {
|
|
265
324
|
inCommittee
|
|
266
325
|
});
|
|
267
|
-
|
|
326
|
+
const attestations = await this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
327
|
+
// We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
|
|
328
|
+
// other nodes can see that our validators did attest to this block proposal, and do not slash us
|
|
329
|
+
// due to inactivity for missed attestations.
|
|
330
|
+
void this.p2pClient.broadcastAttestations(attestations).catch((err)=>{
|
|
331
|
+
this.log.error(`Failed to broadcast self-attestations for slot ${slot}`, err);
|
|
332
|
+
});
|
|
333
|
+
return attestations;
|
|
268
334
|
}
|
|
269
335
|
async collectAttestations(proposal, required, deadline) {
|
|
270
336
|
// Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
|
|
271
|
-
const slot = proposal.payload.header.slotNumber
|
|
337
|
+
const slot = proposal.payload.header.slotNumber;
|
|
272
338
|
this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
|
|
273
339
|
if (+deadline < this.dateProvider.now()) {
|
|
274
340
|
this.log.error(`Deadline ${deadline.toISOString()} for collecting ${required} attestations for slot ${slot} is in the past`);
|
|
@@ -341,3 +407,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
341
407
|
return authResponse.toBuffer();
|
|
342
408
|
}
|
|
343
409
|
}
|
|
410
|
+
_ts_decorate([
|
|
411
|
+
trackSpan('validator.attestToProposal', (proposal, proposalSender)=>({
|
|
412
|
+
[Attributes.BLOCK_HASH]: proposal.payload.header.hash.toString(),
|
|
413
|
+
[Attributes.PEER_ID]: proposalSender.toString()
|
|
414
|
+
}))
|
|
415
|
+
], ValidatorClient.prototype, "attestToProposal", null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.c7c42ec",
|
|
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,27 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/
|
|
68
|
-
"@aztec/
|
|
69
|
-
"@aztec/
|
|
70
|
-
"@aztec/
|
|
71
|
-
"@aztec/
|
|
72
|
-
"@aztec/
|
|
73
|
-
"@aztec/
|
|
74
|
-
"@aztec/
|
|
75
|
-
"@aztec/
|
|
76
|
-
"@aztec/
|
|
67
|
+
"@aztec/blob-client": "0.0.1-commit.c7c42ec",
|
|
68
|
+
"@aztec/blob-lib": "0.0.1-commit.c7c42ec",
|
|
69
|
+
"@aztec/constants": "0.0.1-commit.c7c42ec",
|
|
70
|
+
"@aztec/epoch-cache": "0.0.1-commit.c7c42ec",
|
|
71
|
+
"@aztec/ethereum": "0.0.1-commit.c7c42ec",
|
|
72
|
+
"@aztec/foundation": "0.0.1-commit.c7c42ec",
|
|
73
|
+
"@aztec/node-keystore": "0.0.1-commit.c7c42ec",
|
|
74
|
+
"@aztec/p2p": "0.0.1-commit.c7c42ec",
|
|
75
|
+
"@aztec/slasher": "0.0.1-commit.c7c42ec",
|
|
76
|
+
"@aztec/stdlib": "0.0.1-commit.c7c42ec",
|
|
77
|
+
"@aztec/telemetry-client": "0.0.1-commit.c7c42ec",
|
|
77
78
|
"koa": "^2.16.1",
|
|
78
79
|
"koa-router": "^13.1.1",
|
|
79
80
|
"tslib": "^2.4.0",
|
|
80
|
-
"viem": "npm:@
|
|
81
|
+
"viem": "npm:@aztec/viem@2.38.2"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
|
83
84
|
"@jest/globals": "^30.0.0",
|
|
84
85
|
"@types/jest": "^30.0.0",
|
|
85
86
|
"@types/node": "^22.15.17",
|
|
87
|
+
"@typescript/native-preview": "7.0.0-dev.20251126.1",
|
|
86
88
|
"jest": "^30.0.0",
|
|
87
89
|
"jest-mock-extended": "^4.0.0",
|
|
88
90
|
"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,
|
package/src/factory.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FileStoreBlobClient } from '@aztec/blob-client/filestore';
|
|
1
2
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
3
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
3
4
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
@@ -51,6 +52,7 @@ export function createValidatorClient(
|
|
|
51
52
|
dateProvider: DateProvider;
|
|
52
53
|
epochCache: EpochCache;
|
|
53
54
|
keyStoreManager: KeystoreManager | undefined;
|
|
55
|
+
fileStoreBlobUploadClient?: FileStoreBlobClient;
|
|
54
56
|
},
|
|
55
57
|
) {
|
|
56
58
|
if (config.disableValidator || !deps.keyStoreManager) {
|
|
@@ -67,6 +69,7 @@ export function createValidatorClient(
|
|
|
67
69
|
deps.l1ToL2MessageSource,
|
|
68
70
|
txProvider,
|
|
69
71
|
deps.keyStoreManager,
|
|
72
|
+
deps.fileStoreBlobUploadClient,
|
|
70
73
|
deps.dateProvider,
|
|
71
74
|
deps.telemetry,
|
|
72
75
|
);
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import type { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
|
-
import { normalizeSignature } from '@aztec/foundation/crypto';
|
|
2
|
+
import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
5
5
|
|