@aztec/validator-client 0.0.1-commit.18ccd8f0 → 0.0.1-commit.1bb068fb5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -18
- package/dest/block_proposal_handler.d.ts +1 -1
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +1 -1
- package/dest/checkpoint_builder.d.ts +5 -9
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +22 -14
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +4 -0
- package/dest/duties/validation_service.d.ts +2 -2
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +3 -3
- package/dest/key_store/ha_key_store.d.ts +1 -1
- package/dest/key_store/ha_key_store.d.ts.map +1 -1
- package/dest/key_store/ha_key_store.js +2 -2
- package/dest/validator.d.ts +24 -5
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +126 -16
- package/package.json +19 -19
- package/src/block_proposal_handler.ts +1 -0
- package/src/checkpoint_builder.ts +23 -12
- package/src/config.ts +4 -0
- package/src/duties/validation_service.ts +9 -2
- package/src/key_store/ha_key_store.ts +2 -2
- package/src/validator.ts +175 -22
package/dest/validator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getBlobsPerL1Block } from '@aztec/blob-lib';
|
|
2
|
-
import {
|
|
2
|
+
import { validateFeeAssetPriceModifier } from '@aztec/ethereum/contracts';
|
|
3
|
+
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
4
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
4
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
5
6
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
@@ -8,7 +9,7 @@ import { sleep } from '@aztec/foundation/sleep';
|
|
|
8
9
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
9
10
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
10
11
|
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
11
|
-
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
12
|
+
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
12
13
|
import { accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging';
|
|
13
14
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
14
15
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
@@ -48,11 +49,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
48
49
|
log;
|
|
49
50
|
// Whether it has already registered handlers on the p2p client
|
|
50
51
|
hasRegisteredHandlers;
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
/** Tracks the last block proposal we created, to detect duplicate proposal attempts. */ lastProposedBlock;
|
|
53
|
+
/** Tracks the last checkpoint proposal we created. */ lastProposedCheckpoint;
|
|
53
54
|
lastEpochForCommitteeUpdateLoop;
|
|
54
55
|
epochCacheUpdateLoop;
|
|
55
56
|
proposersOfInvalidBlocks;
|
|
57
|
+
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
|
|
56
58
|
constructor(keyStore, epochCache, p2pClient, blockProposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
57
59
|
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
|
|
58
60
|
// Create child logger with fisherman prefix if in fisherman mode
|
|
@@ -183,6 +185,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
183
185
|
// and processed separately via the block handler above.
|
|
184
186
|
const checkpointHandler = (checkpoint, proposalSender)=>this.attestToCheckpointProposal(checkpoint, proposalSender);
|
|
185
187
|
this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
|
|
188
|
+
// Duplicate proposal handler - triggers slashing for equivocation
|
|
189
|
+
this.p2pClient.registerDuplicateProposalCallback((info)=>{
|
|
190
|
+
this.handleDuplicateProposal(info);
|
|
191
|
+
});
|
|
192
|
+
// Duplicate attestation handler - triggers slashing for attestation equivocation
|
|
193
|
+
this.p2pClient.registerDuplicateAttestationCallback((info)=>{
|
|
194
|
+
this.handleDuplicateAttestation(info);
|
|
195
|
+
});
|
|
186
196
|
const myAddresses = this.getValidatorAddresses();
|
|
187
197
|
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
188
198
|
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
@@ -203,6 +213,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
203
213
|
this.log.warn(`Received block proposal with invalid signature for slot ${slotNumber}`);
|
|
204
214
|
return false;
|
|
205
215
|
}
|
|
216
|
+
// Ignore proposals from ourselves (may happen in HA setups)
|
|
217
|
+
if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
|
|
218
|
+
this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
|
|
219
|
+
proposer: proposer.toString(),
|
|
220
|
+
slotNumber
|
|
221
|
+
});
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
206
224
|
// Check if we're in the committee (for metrics purposes)
|
|
207
225
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
208
226
|
const partOfCommittee = inCommittee.length > 0;
|
|
@@ -274,6 +292,19 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
274
292
|
this.log.warn(`Received checkpoint proposal with invalid signature for slot ${slotNumber}`);
|
|
275
293
|
return undefined;
|
|
276
294
|
}
|
|
295
|
+
// Ignore proposals from ourselves (may happen in HA setups)
|
|
296
|
+
if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
|
|
297
|
+
this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
|
|
298
|
+
proposer: proposer.toString(),
|
|
299
|
+
slotNumber
|
|
300
|
+
});
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
// Validate fee asset price modifier is within allowed range
|
|
304
|
+
if (!validateFeeAssetPriceModifier(proposal.feeAssetPriceModifier)) {
|
|
305
|
+
this.log.warn(`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${slotNumber}`);
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
277
308
|
// Check that I have any address in current committee before attesting
|
|
278
309
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
279
310
|
const partOfCommittee = inCommittee.length > 0;
|
|
@@ -337,11 +368,32 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
337
368
|
});
|
|
338
369
|
return undefined;
|
|
339
370
|
}
|
|
340
|
-
return this.createCheckpointAttestationsFromProposal(proposal, attestors);
|
|
371
|
+
return await this.createCheckpointAttestationsFromProposal(proposal, attestors);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Checks if we should attest to a slot based on equivocation prevention rules.
|
|
375
|
+
* @returns true if we should attest, false if we should skip
|
|
376
|
+
*/ shouldAttestToSlot(slotNumber) {
|
|
377
|
+
// If attestToEquivocatedProposals is true, always allow
|
|
378
|
+
if (this.config.attestToEquivocatedProposals) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
// Check if incoming slot is strictly greater than last attested
|
|
382
|
+
if (this.lastAttestedProposal && slotNumber <= this.lastAttestedProposal.slotNumber) {
|
|
383
|
+
this.log.warn(`Refusing to process a proposal for slot ${slotNumber} given we already attested to a proposal for slot ${this.lastAttestedProposal.slotNumber}`);
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
341
387
|
}
|
|
342
388
|
async createCheckpointAttestationsFromProposal(proposal, attestors = []) {
|
|
389
|
+
// Equivocation check: must happen right before signing to minimize the race window
|
|
390
|
+
if (!this.shouldAttestToSlot(proposal.slotNumber)) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
343
393
|
const attestations = await this.validationService.attestToCheckpointProposal(proposal, attestors);
|
|
344
|
-
|
|
394
|
+
// Track the proposal we attested to (to prevent equivocation)
|
|
395
|
+
this.lastAttestedProposal = proposal;
|
|
396
|
+
await this.p2pClient.addOwnCheckpointAttestations(attestations);
|
|
345
397
|
return attestations;
|
|
346
398
|
}
|
|
347
399
|
/**
|
|
@@ -349,7 +401,10 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
349
401
|
* @returns Validation result with isValid flag and reason if invalid.
|
|
350
402
|
*/ async validateCheckpointProposal(proposal, proposalInfo) {
|
|
351
403
|
const slot = proposal.slotNumber;
|
|
352
|
-
|
|
404
|
+
// Timeout block syncing at the start of the next slot
|
|
405
|
+
const config = this.checkpointsBuilder.getConfig();
|
|
406
|
+
const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
|
|
407
|
+
const timeoutSeconds = Math.max(1, nextSlotTimestampSeconds - Math.floor(this.dateProvider.now() / 1000));
|
|
353
408
|
// Wait for last block to sync by archive
|
|
354
409
|
let lastBlockHeader;
|
|
355
410
|
try {
|
|
@@ -408,7 +463,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
408
463
|
const fork = await this.worldState.fork(parentBlockNumber);
|
|
409
464
|
try {
|
|
410
465
|
// Create checkpoint builder with all existing blocks
|
|
411
|
-
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork, blocks, this.log.getBindings());
|
|
466
|
+
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, proposal.feeAssetPriceModifier, l1ToL2Messages, previousCheckpointOutHashes, fork, blocks, this.log.getBindings());
|
|
412
467
|
// Complete the checkpoint to get computed values
|
|
413
468
|
const computedCheckpoint = await checkpointBuilder.completeCheckpoint();
|
|
414
469
|
// Compare checkpoint header with proposal
|
|
@@ -524,23 +579,75 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
524
579
|
}
|
|
525
580
|
]);
|
|
526
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Handle detection of a duplicate proposal (equivocation).
|
|
584
|
+
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
585
|
+
*/ handleDuplicateProposal(info) {
|
|
586
|
+
const { slot, proposer, type } = info;
|
|
587
|
+
this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
|
|
588
|
+
proposer: proposer.toString(),
|
|
589
|
+
slot,
|
|
590
|
+
type
|
|
591
|
+
});
|
|
592
|
+
// Emit slash event
|
|
593
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
594
|
+
{
|
|
595
|
+
validator: proposer,
|
|
596
|
+
amount: this.config.slashDuplicateProposalPenalty,
|
|
597
|
+
offenseType: OffenseType.DUPLICATE_PROPOSAL,
|
|
598
|
+
epochOrSlot: BigInt(slot)
|
|
599
|
+
}
|
|
600
|
+
]);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Handle detection of a duplicate attestation (equivocation).
|
|
604
|
+
* Emits a slash event when an attester signs attestations for different proposals at the same slot.
|
|
605
|
+
*/ handleDuplicateAttestation(info) {
|
|
606
|
+
const { slot, attester } = info;
|
|
607
|
+
this.log.warn(`Triggering slash event for duplicate attestation from ${attester.toString()} at slot ${slot}`, {
|
|
608
|
+
attester: attester.toString(),
|
|
609
|
+
slot
|
|
610
|
+
});
|
|
611
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
612
|
+
{
|
|
613
|
+
validator: attester,
|
|
614
|
+
amount: this.config.slashDuplicateAttestationPenalty,
|
|
615
|
+
offenseType: OffenseType.DUPLICATE_ATTESTATION,
|
|
616
|
+
epochOrSlot: BigInt(slot)
|
|
617
|
+
}
|
|
618
|
+
]);
|
|
619
|
+
}
|
|
527
620
|
async createBlockProposal(blockHeader, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, options = {}) {
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
621
|
+
// Validate that we're not creating a proposal for an older or equal position
|
|
622
|
+
if (this.lastProposedBlock) {
|
|
623
|
+
const lastSlot = this.lastProposedBlock.slotNumber;
|
|
624
|
+
const lastIndex = this.lastProposedBlock.indexWithinCheckpoint;
|
|
625
|
+
const newSlot = blockHeader.globalVariables.slotNumber;
|
|
626
|
+
if (newSlot < lastSlot || newSlot === lastSlot && indexWithinCheckpoint <= lastIndex) {
|
|
627
|
+
throw new Error(`Cannot create block proposal for slot ${newSlot} index ${indexWithinCheckpoint}: ` + `already proposed block for slot ${lastSlot} index ${lastIndex}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
533
630
|
this.log.info(`Assembling block proposal for block ${blockHeader.globalVariables.blockNumber} slot ${blockHeader.globalVariables.slotNumber}`);
|
|
534
631
|
const newProposal = await this.validationService.createBlockProposal(blockHeader, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, {
|
|
535
632
|
...options,
|
|
536
633
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
|
|
537
634
|
});
|
|
538
|
-
this.
|
|
635
|
+
this.lastProposedBlock = newProposal;
|
|
539
636
|
return newProposal;
|
|
540
637
|
}
|
|
541
|
-
async createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options = {}) {
|
|
638
|
+
async createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockInfo, proposerAddress, options = {}) {
|
|
639
|
+
// Validate that we're not creating a proposal for an older or equal slot
|
|
640
|
+
if (this.lastProposedCheckpoint) {
|
|
641
|
+
const lastSlot = this.lastProposedCheckpoint.slotNumber;
|
|
642
|
+
const newSlot = checkpointHeader.slotNumber;
|
|
643
|
+
if (newSlot <= lastSlot) {
|
|
644
|
+
throw new Error(`Cannot create checkpoint proposal for slot ${newSlot}: ` + `already proposed checkpoint for slot ${lastSlot}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
542
647
|
this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
|
|
543
|
-
|
|
648
|
+
const newProposal = await this.validationService.createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockInfo, proposerAddress, options);
|
|
649
|
+
this.lastProposedCheckpoint = newProposal;
|
|
650
|
+
return newProposal;
|
|
544
651
|
}
|
|
545
652
|
async broadcastBlockProposal(proposal) {
|
|
546
653
|
await this.p2pClient.broadcastProposal(proposal);
|
|
@@ -555,6 +662,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
555
662
|
inCommittee
|
|
556
663
|
});
|
|
557
664
|
const attestations = await this.createCheckpointAttestationsFromProposal(proposal, inCommittee);
|
|
665
|
+
if (!attestations) {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
558
668
|
// We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
|
|
559
669
|
// other nodes can see that our validators did attest to this block proposal, and do not slash us
|
|
560
670
|
// due to inactivity for missed attestations.
|
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.1bb068fb5",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,30 +64,30 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/blob-client": "0.0.1-commit.
|
|
68
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
69
|
-
"@aztec/constants": "0.0.1-commit.
|
|
70
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
71
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
72
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
73
|
-
"@aztec/node-keystore": "0.0.1-commit.
|
|
74
|
-
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.
|
|
75
|
-
"@aztec/p2p": "0.0.1-commit.
|
|
76
|
-
"@aztec/protocol-contracts": "0.0.1-commit.
|
|
77
|
-
"@aztec/prover-client": "0.0.1-commit.
|
|
78
|
-
"@aztec/simulator": "0.0.1-commit.
|
|
79
|
-
"@aztec/slasher": "0.0.1-commit.
|
|
80
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
81
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
82
|
-
"@aztec/validator-ha-signer": "0.0.1-commit.
|
|
67
|
+
"@aztec/blob-client": "0.0.1-commit.1bb068fb5",
|
|
68
|
+
"@aztec/blob-lib": "0.0.1-commit.1bb068fb5",
|
|
69
|
+
"@aztec/constants": "0.0.1-commit.1bb068fb5",
|
|
70
|
+
"@aztec/epoch-cache": "0.0.1-commit.1bb068fb5",
|
|
71
|
+
"@aztec/ethereum": "0.0.1-commit.1bb068fb5",
|
|
72
|
+
"@aztec/foundation": "0.0.1-commit.1bb068fb5",
|
|
73
|
+
"@aztec/node-keystore": "0.0.1-commit.1bb068fb5",
|
|
74
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.1bb068fb5",
|
|
75
|
+
"@aztec/p2p": "0.0.1-commit.1bb068fb5",
|
|
76
|
+
"@aztec/protocol-contracts": "0.0.1-commit.1bb068fb5",
|
|
77
|
+
"@aztec/prover-client": "0.0.1-commit.1bb068fb5",
|
|
78
|
+
"@aztec/simulator": "0.0.1-commit.1bb068fb5",
|
|
79
|
+
"@aztec/slasher": "0.0.1-commit.1bb068fb5",
|
|
80
|
+
"@aztec/stdlib": "0.0.1-commit.1bb068fb5",
|
|
81
|
+
"@aztec/telemetry-client": "0.0.1-commit.1bb068fb5",
|
|
82
|
+
"@aztec/validator-ha-signer": "0.0.1-commit.1bb068fb5",
|
|
83
83
|
"koa": "^2.16.1",
|
|
84
84
|
"koa-router": "^13.1.1",
|
|
85
85
|
"tslib": "^2.4.0",
|
|
86
86
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@aztec/archiver": "0.0.1-commit.
|
|
90
|
-
"@aztec/world-state": "0.0.1-commit.
|
|
89
|
+
"@aztec/archiver": "0.0.1-commit.1bb068fb5",
|
|
90
|
+
"@aztec/world-state": "0.0.1-commit.1bb068fb5",
|
|
91
91
|
"@electric-sql/pglite": "^0.3.14",
|
|
92
92
|
"@jest/globals": "^30.0.0",
|
|
93
93
|
"@types/jest": "^30.0.0",
|
|
@@ -491,6 +491,7 @@ export class BlockProposalHandler {
|
|
|
491
491
|
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(
|
|
492
492
|
checkpointNumber,
|
|
493
493
|
constants,
|
|
494
|
+
0n, // only takes effect in the following checkpoint.
|
|
494
495
|
l1ToL2Messages,
|
|
495
496
|
previousCheckpointOutHashes,
|
|
496
497
|
fork,
|
|
@@ -3,7 +3,7 @@ import { merge, pick } from '@aztec/foundation/collection';
|
|
|
3
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
4
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
6
|
-
import { DateProvider,
|
|
6
|
+
import { DateProvider, elapsed } from '@aztec/foundation/timer';
|
|
7
7
|
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
8
8
|
import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
|
|
9
9
|
import {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type ICheckpointBlockBuilder,
|
|
25
25
|
type ICheckpointsBuilder,
|
|
26
26
|
type MerkleTreeWriteOperations,
|
|
27
|
+
NoValidTxsError,
|
|
27
28
|
type PublicProcessorLimits,
|
|
28
29
|
type WorldStateSynchronizer,
|
|
29
30
|
} from '@aztec/stdlib/interfaces/server';
|
|
@@ -36,11 +37,6 @@ import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_fac
|
|
|
36
37
|
// Re-export for backward compatibility
|
|
37
38
|
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
38
39
|
|
|
39
|
-
/** Result of building a block within a checkpoint. Extends the base interface with timer. */
|
|
40
|
-
export interface BuildBlockInCheckpointResultWithTimer extends BuildBlockInCheckpointResult {
|
|
41
|
-
blockBuildingTimer: Timer;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
40
|
/**
|
|
45
41
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
46
42
|
* and completing it.
|
|
@@ -75,8 +71,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
75
71
|
blockNumber: BlockNumber,
|
|
76
72
|
timestamp: bigint,
|
|
77
73
|
opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
|
|
78
|
-
): Promise<
|
|
79
|
-
const blockBuildingTimer = new Timer();
|
|
74
|
+
): Promise<BuildBlockInCheckpointResult> {
|
|
80
75
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
81
76
|
|
|
82
77
|
this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
@@ -103,6 +98,12 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
103
98
|
processor.process(pendingTxs, opts, validator),
|
|
104
99
|
);
|
|
105
100
|
|
|
101
|
+
// Throw if we didn't collect a single valid tx and we're not allowed to build empty blocks
|
|
102
|
+
// (only the first block in a checkpoint can be empty)
|
|
103
|
+
if (processedTxs.length === 0 && this.checkpointBuilder.getBlockCount() > 0) {
|
|
104
|
+
throw new NoValidTxsError(failedTxs);
|
|
105
|
+
}
|
|
106
|
+
|
|
106
107
|
// Add block to checkpoint
|
|
107
108
|
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
108
109
|
expectedEndState: opts.expectedEndState,
|
|
@@ -111,18 +112,21 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
111
112
|
// How much public gas was processed
|
|
112
113
|
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
this.log.debug('Built block within checkpoint', {
|
|
116
|
+
header: block.header.toInspect(),
|
|
117
|
+
processedTxs: processedTxs.map(tx => tx.hash.toString()),
|
|
118
|
+
failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
115
122
|
block,
|
|
116
123
|
publicGas,
|
|
117
124
|
publicProcessorDuration,
|
|
118
125
|
numTxs: processedTxs.length,
|
|
119
126
|
failedTxs,
|
|
120
|
-
blockBuildingTimer,
|
|
121
127
|
usedTxs,
|
|
122
128
|
usedTxBlobFields,
|
|
123
129
|
};
|
|
124
|
-
this.log.debug('Built block within checkpoint', res.block.header);
|
|
125
|
-
return res;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
/** Completes the checkpoint and returns it. */
|
|
@@ -211,6 +215,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
211
215
|
async startCheckpoint(
|
|
212
216
|
checkpointNumber: CheckpointNumber,
|
|
213
217
|
constants: CheckpointGlobalVariables,
|
|
218
|
+
feeAssetPriceModifier: bigint,
|
|
214
219
|
l1ToL2Messages: Fr[],
|
|
215
220
|
previousCheckpointOutHashes: Fr[],
|
|
216
221
|
fork: MerkleTreeWriteOperations,
|
|
@@ -225,6 +230,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
225
230
|
initialStateReference: stateReference.toInspect(),
|
|
226
231
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
227
232
|
constants,
|
|
233
|
+
feeAssetPriceModifier,
|
|
228
234
|
});
|
|
229
235
|
|
|
230
236
|
const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
|
|
@@ -234,6 +240,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
234
240
|
previousCheckpointOutHashes,
|
|
235
241
|
fork,
|
|
236
242
|
bindings,
|
|
243
|
+
feeAssetPriceModifier,
|
|
237
244
|
);
|
|
238
245
|
|
|
239
246
|
return new CheckpointBuilder(
|
|
@@ -253,6 +260,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
253
260
|
async openCheckpoint(
|
|
254
261
|
checkpointNumber: CheckpointNumber,
|
|
255
262
|
constants: CheckpointGlobalVariables,
|
|
263
|
+
feeAssetPriceModifier: bigint,
|
|
256
264
|
l1ToL2Messages: Fr[],
|
|
257
265
|
previousCheckpointOutHashes: Fr[],
|
|
258
266
|
fork: MerkleTreeWriteOperations,
|
|
@@ -266,6 +274,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
266
274
|
return this.startCheckpoint(
|
|
267
275
|
checkpointNumber,
|
|
268
276
|
constants,
|
|
277
|
+
feeAssetPriceModifier,
|
|
269
278
|
l1ToL2Messages,
|
|
270
279
|
previousCheckpointOutHashes,
|
|
271
280
|
fork,
|
|
@@ -280,11 +289,13 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
280
289
|
initialStateReference: stateReference.toInspect(),
|
|
281
290
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
282
291
|
constants,
|
|
292
|
+
feeAssetPriceModifier,
|
|
283
293
|
});
|
|
284
294
|
|
|
285
295
|
const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
|
|
286
296
|
checkpointNumber,
|
|
287
297
|
constants,
|
|
298
|
+
feeAssetPriceModifier,
|
|
288
299
|
l1ToL2Messages,
|
|
289
300
|
previousCheckpointOutHashes,
|
|
290
301
|
fork,
|
package/src/config.ts
CHANGED
|
@@ -73,6 +73,10 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
73
73
|
description: 'Skip pushing re-executed blocks to archiver (default: false)',
|
|
74
74
|
defaultValue: false,
|
|
75
75
|
},
|
|
76
|
+
attestToEquivocatedProposals: {
|
|
77
|
+
description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
|
|
78
|
+
...booleanConfigHelper(false),
|
|
79
|
+
},
|
|
76
80
|
...validatorHASignerConfigMappings,
|
|
77
81
|
};
|
|
78
82
|
|
|
@@ -95,6 +95,7 @@ export class ValidationService {
|
|
|
95
95
|
public createCheckpointProposal(
|
|
96
96
|
checkpointHeader: CheckpointHeader,
|
|
97
97
|
archive: Fr,
|
|
98
|
+
feeAssetPriceModifier: bigint,
|
|
98
99
|
lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
|
|
99
100
|
proposerAttesterAddress: EthAddress | undefined,
|
|
100
101
|
options: CheckpointProposalOptions,
|
|
@@ -119,7 +120,13 @@ export class ValidationService {
|
|
|
119
120
|
txs: options.publishFullTxs ? lastBlockInfo.txs : undefined,
|
|
120
121
|
};
|
|
121
122
|
|
|
122
|
-
return CheckpointProposal.createProposalFromSigner(
|
|
123
|
+
return CheckpointProposal.createProposalFromSigner(
|
|
124
|
+
checkpointHeader,
|
|
125
|
+
archive,
|
|
126
|
+
feeAssetPriceModifier,
|
|
127
|
+
lastBlock,
|
|
128
|
+
payloadSigner,
|
|
129
|
+
);
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
/**
|
|
@@ -137,7 +144,7 @@ export class ValidationService {
|
|
|
137
144
|
attestors: EthAddress[],
|
|
138
145
|
): Promise<CheckpointAttestation[]> {
|
|
139
146
|
// Create the attestation payload from the checkpoint proposal
|
|
140
|
-
const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive);
|
|
147
|
+
const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive, proposal.feeAssetPriceModifier);
|
|
141
148
|
const buf = Buffer32.fromBuffer(
|
|
142
149
|
keccak256(payload.getPayloadToSign(SignatureDomainSeparator.checkpointAttestation)),
|
|
143
150
|
);
|
|
@@ -256,8 +256,8 @@ export class HAKeyStore implements ExtendedValidatorKeyStore {
|
|
|
256
256
|
/**
|
|
257
257
|
* Start the high-availability key store
|
|
258
258
|
*/
|
|
259
|
-
public start()
|
|
260
|
-
|
|
259
|
+
public async start() {
|
|
260
|
+
await this.haSigner.start();
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
/**
|