@aztec/validator-client 0.0.1-commit.2f68f620 → 0.0.1-commit.321f6a9
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 +2 -0
- package/dest/checkpoint_builder.d.ts +17 -7
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +26 -9
- package/dest/config.d.ts +9 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +9 -1
- package/dest/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +10 -3
- package/dest/key_store/web3signer_key_store.d.ts +10 -2
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +32 -41
- package/dest/proposal_handler.d.ts +38 -7
- package/dest/proposal_handler.d.ts.map +1 -1
- package/dest/proposal_handler.js +230 -49
- package/dest/validator.d.ts +11 -5
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +51 -42
- package/package.json +19 -19
- package/src/checkpoint_builder.ts +28 -10
- package/src/config.ts +16 -3
- package/src/factory.ts +9 -1
- package/src/key_store/web3signer_key_store.ts +43 -59
- package/src/proposal_handler.ts +250 -53
- package/src/validator.ts +61 -46
package/dest/validator.js
CHANGED
|
@@ -8,49 +8,23 @@ import { DateProvider } from '@aztec/foundation/timer';
|
|
|
8
8
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
9
9
|
import { OffenseType, WANT_TO_CLEAR_SLASH_EVENT, WANT_TO_SLASH_EVENT, getOffenseTypeName } from '@aztec/slasher';
|
|
10
10
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
11
|
+
import { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
11
12
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
12
13
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
13
14
|
import { createHASigner, createLocalSignerWithProtection, createSignerFromSharedDb } from '@aztec/validator-ha-signer/factory';
|
|
14
15
|
import { DutyType } from '@aztec/validator-ha-signer/types';
|
|
15
16
|
import { EventEmitter } from 'events';
|
|
17
|
+
import { DEFAULT_MAX_GOSSIP_CLOCK_DISPARITY_MS } from './config.js';
|
|
16
18
|
import { ValidationService } from './duties/validation_service.js';
|
|
17
19
|
import { HAKeyStore } from './key_store/ha_key_store.js';
|
|
18
20
|
import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
19
21
|
import { ValidatorMetrics } from './metrics.js';
|
|
20
|
-
import { ProposalHandler } from './proposal_handler.js';
|
|
22
|
+
import { ProposalHandler, SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT, SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT } from './proposal_handler.js';
|
|
21
23
|
// We maintain a set of proposers who have proposed invalid blocks.
|
|
22
24
|
// Just cap the set to avoid unbounded growth.
|
|
23
25
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
24
|
-
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
25
26
|
const MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS = 1000;
|
|
26
27
|
const MAX_TRACKED_BAD_ATTESTATIONS = 10_000;
|
|
27
|
-
// What errors from the block proposal handler result in slashing
|
|
28
|
-
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
29
|
-
'state_mismatch',
|
|
30
|
-
'failed_txs',
|
|
31
|
-
'global_variables_mismatch',
|
|
32
|
-
'invalid_proposal',
|
|
33
|
-
'parent_block_wrong_slot',
|
|
34
|
-
'in_hash_mismatch'
|
|
35
|
-
];
|
|
36
|
-
const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
37
|
-
// enabled
|
|
38
|
-
['invalid_fee_asset_price_modifier']: true,
|
|
39
|
-
['checkpoint_header_mismatch']: true,
|
|
40
|
-
// These late mismatches should normally be caught by earlier checks, but if reached after validating the local
|
|
41
|
-
// checkpoint inputs, the proposer-signed payload disagrees with deterministic recomputation.
|
|
42
|
-
['archive_mismatch']: true,
|
|
43
|
-
['out_hash_mismatch']: true,
|
|
44
|
-
['no_blocks_for_slot']: true,
|
|
45
|
-
['too_many_blocks_in_checkpoint']: true,
|
|
46
|
-
['checkpoint_validation_failed']: true,
|
|
47
|
-
['last_block_archive_mismatch']: true,
|
|
48
|
-
// disabled
|
|
49
|
-
['invalid_signature']: false,
|
|
50
|
-
['last_block_not_found']: false,
|
|
51
|
-
['block_fetch_error']: false,
|
|
52
|
-
['checkpoint_already_published']: false
|
|
53
|
-
};
|
|
54
28
|
/**
|
|
55
29
|
* Validator Client
|
|
56
30
|
*/ export class ValidatorClient extends EventEmitter {
|
|
@@ -78,13 +52,12 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
78
52
|
epochCacheUpdateLoop;
|
|
79
53
|
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */ lastAttestedEpochByAttester;
|
|
80
54
|
proposersOfInvalidBlocks;
|
|
81
|
-
slotsWithInvalidProposals;
|
|
82
55
|
invalidCheckpointProposalOffenseKeys;
|
|
56
|
+
oversizedProposalOffenseKeys;
|
|
83
57
|
badAttestationOffenseKeys;
|
|
84
|
-
slotsWithProposalEquivocation;
|
|
85
58
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
|
|
86
59
|
constructor(keyStore, epochCache, p2pClient, proposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, slashingProtectionSigner, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
87
|
-
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.proposalHandler = proposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.slashingProtectionSigner = slashingProtectionSigner, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.lastAttestedEpochByAttester = new Map(), this.proposersOfInvalidBlocks = FifoSet.withLimit(MAX_PROPOSERS_OF_INVALID_BLOCKS), this.
|
|
60
|
+
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.proposalHandler = proposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.slashingProtectionSigner = slashingProtectionSigner, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.lastAttestedEpochByAttester = new Map(), this.proposersOfInvalidBlocks = FifoSet.withLimit(MAX_PROPOSERS_OF_INVALID_BLOCKS), this.invalidCheckpointProposalOffenseKeys = FifoSet.withLimit(MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS), this.oversizedProposalOffenseKeys = FifoSet.withLimit(MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS), this.badAttestationOffenseKeys = FifoSet.withLimit(MAX_TRACKED_BAD_ATTESTATIONS);
|
|
88
61
|
// Create child logger with fisherman prefix if in fisherman mode
|
|
89
62
|
this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
|
|
90
63
|
this.tracer = telemetry.getTracer('Validator');
|
|
@@ -142,7 +115,11 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
142
115
|
}
|
|
143
116
|
static async new(config, checkpointsBuilder, worldState, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, blobClient, reexecutionTracker, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), slashingProtectionDb) {
|
|
144
117
|
const metrics = new ValidatorMetrics(telemetry);
|
|
145
|
-
const
|
|
118
|
+
const consensusTimetable = new ConsensusTimetable({
|
|
119
|
+
l1Constants: epochCache.getL1Constants(),
|
|
120
|
+
blockDuration: config.blockDurationMs / 1000
|
|
121
|
+
});
|
|
122
|
+
const blockProposalValidator = new BlockProposalValidator(epochCache, consensusTimetable, {
|
|
146
123
|
txsPermitted: !config.disableTransactions,
|
|
147
124
|
maxTxsPerBlock: config.validateMaxTxsPerBlock,
|
|
148
125
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
@@ -150,9 +127,10 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
150
127
|
signatureContext: {
|
|
151
128
|
chainId: config.l1ChainId,
|
|
152
129
|
rollupAddress: config.rollupAddress
|
|
153
|
-
}
|
|
130
|
+
},
|
|
131
|
+
clockDisparityMs: config.maxGossipClockDisparityMs ?? DEFAULT_MAX_GOSSIP_CLOCK_DISPARITY_MS
|
|
154
132
|
});
|
|
155
|
-
const proposalHandler = new ProposalHandler(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, config, blobClient, reexecutionTracker, metrics, dateProvider, telemetry, undefined);
|
|
133
|
+
const proposalHandler = new ProposalHandler(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, consensusTimetable, config, blobClient, reexecutionTracker, metrics, dateProvider, telemetry, undefined);
|
|
156
134
|
const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
|
|
157
135
|
let slashingProtectionSigner;
|
|
158
136
|
if (slashingProtectionDb) {
|
|
@@ -209,10 +187,10 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
209
187
|
return this.config;
|
|
210
188
|
}
|
|
211
189
|
hasProposalEquivocation(slotNumber) {
|
|
212
|
-
return this.
|
|
190
|
+
return this.proposalHandler.hasProposalEquivocation(slotNumber);
|
|
213
191
|
}
|
|
214
192
|
hasInvalidProposals(slotNumber) {
|
|
215
|
-
return this.
|
|
193
|
+
return this.proposalHandler.hasInvalidProposals(slotNumber);
|
|
216
194
|
}
|
|
217
195
|
updateConfig(config) {
|
|
218
196
|
this.config = {
|
|
@@ -262,6 +240,10 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
262
240
|
this.p2pClient.registerDuplicateProposalCallback((info)=>{
|
|
263
241
|
this.handleDuplicateProposal(info);
|
|
264
242
|
});
|
|
243
|
+
// Oversized proposal handler - triggers slashing for proposals beyond the per-checkpoint block limit
|
|
244
|
+
this.p2pClient.registerOversizedProposalCallback((info)=>{
|
|
245
|
+
this.handleOversizedProposal(info);
|
|
246
|
+
});
|
|
265
247
|
// Duplicate attestation handler - triggers slashing for attestation equivocation
|
|
266
248
|
this.p2pClient.registerDuplicateAttestationCallback((info)=>{
|
|
267
249
|
this.handleDuplicateAttestation(info);
|
|
@@ -369,7 +351,7 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
369
351
|
}
|
|
370
352
|
// Ignore proposals from ourselves (may happen in HA setups)
|
|
371
353
|
if (proposer && this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
|
|
372
|
-
this.log.debug(`
|
|
354
|
+
this.log.debug(`Not attesting to block proposal from self for slot ${proposalSlotNumber}`, {
|
|
373
355
|
proposer: proposer.toString(),
|
|
374
356
|
proposalSlotNumber
|
|
375
357
|
});
|
|
@@ -522,7 +504,8 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
522
504
|
if (!SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT[result.reason]) {
|
|
523
505
|
return;
|
|
524
506
|
}
|
|
525
|
-
this
|
|
507
|
+
// The slot is already marked invalid by the all-nodes checkpoint handler that invokes this callback,
|
|
508
|
+
// so we only emit the proposer slash event here.
|
|
526
509
|
if (this.slashInvalidCheckpointProposal(proposal)) {
|
|
527
510
|
this.log.info(`Detected invalid checkpoint proposal offense`, {
|
|
528
511
|
...proposalInfo,
|
|
@@ -557,11 +540,11 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
557
540
|
return true;
|
|
558
541
|
}
|
|
559
542
|
markInvalidProposalSlot(slotNumber) {
|
|
560
|
-
this.
|
|
543
|
+
this.proposalHandler.markInvalidProposalSlot(slotNumber);
|
|
561
544
|
}
|
|
562
545
|
handleCheckpointAttestation(attestation) {
|
|
563
546
|
const slotNumber = attestation.slotNumber;
|
|
564
|
-
if (!this.
|
|
547
|
+
if (!this.proposalHandler.hasInvalidProposals(slotNumber) || this.proposalHandler.hasProposalEquivocation(slotNumber)) {
|
|
565
548
|
return;
|
|
566
549
|
}
|
|
567
550
|
const attester = attestation.getSender();
|
|
@@ -595,11 +578,37 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
595
578
|
]);
|
|
596
579
|
}
|
|
597
580
|
/**
|
|
581
|
+
* Handle detection of an oversized block proposal: one whose index within its checkpoint lands at or
|
|
582
|
+
* beyond the consensus per-checkpoint block limit. A single signed proposal at an illegal index is
|
|
583
|
+
* self-contained evidence, so emit an invalid-block-proposal slash event for the proposer, deduped per
|
|
584
|
+
* (proposer, slot) since the p2p layer reports every oversized proposal it stores.
|
|
585
|
+
*/ handleOversizedProposal(info) {
|
|
586
|
+
const { slot, proposer } = info;
|
|
587
|
+
const offenseType = OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL;
|
|
588
|
+
if (!this.oversizedProposalOffenseKeys.addIfAbsent(`${proposer.toString()}:${offenseType}:${slot}`)) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.log.info(`Detected oversized block proposal offense from ${proposer.toString()} at slot ${slot}`, {
|
|
592
|
+
proposer: proposer.toString(),
|
|
593
|
+
slot,
|
|
594
|
+
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
595
|
+
offenseType: getOffenseTypeName(offenseType)
|
|
596
|
+
});
|
|
597
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
598
|
+
{
|
|
599
|
+
validator: proposer,
|
|
600
|
+
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
601
|
+
offenseType,
|
|
602
|
+
epochOrSlot: BigInt(slot)
|
|
603
|
+
}
|
|
604
|
+
]);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
598
607
|
* Handle detection of a duplicate proposal (equivocation).
|
|
599
608
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
600
609
|
*/ handleDuplicateProposal(info) {
|
|
601
610
|
const { slot, proposer, type } = info;
|
|
602
|
-
this.
|
|
611
|
+
this.proposalHandler.markProposalEquivocation(slot);
|
|
603
612
|
this.log.info(`Detected duplicate ${type} proposal offense from ${proposer.toString()} at slot ${slot}`, {
|
|
604
613
|
proposer: proposer.toString(),
|
|
605
614
|
slot,
|
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.321f6a9",
|
|
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.321f6a9",
|
|
68
|
+
"@aztec/blob-lib": "0.0.1-commit.321f6a9",
|
|
69
|
+
"@aztec/constants": "0.0.1-commit.321f6a9",
|
|
70
|
+
"@aztec/epoch-cache": "0.0.1-commit.321f6a9",
|
|
71
|
+
"@aztec/ethereum": "0.0.1-commit.321f6a9",
|
|
72
|
+
"@aztec/foundation": "0.0.1-commit.321f6a9",
|
|
73
|
+
"@aztec/node-keystore": "0.0.1-commit.321f6a9",
|
|
74
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.321f6a9",
|
|
75
|
+
"@aztec/p2p": "0.0.1-commit.321f6a9",
|
|
76
|
+
"@aztec/protocol-contracts": "0.0.1-commit.321f6a9",
|
|
77
|
+
"@aztec/prover-client": "0.0.1-commit.321f6a9",
|
|
78
|
+
"@aztec/simulator": "0.0.1-commit.321f6a9",
|
|
79
|
+
"@aztec/slasher": "0.0.1-commit.321f6a9",
|
|
80
|
+
"@aztec/stdlib": "0.0.1-commit.321f6a9",
|
|
81
|
+
"@aztec/telemetry-client": "0.0.1-commit.321f6a9",
|
|
82
|
+
"@aztec/validator-ha-signer": "0.0.1-commit.321f6a9",
|
|
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.321f6a9",
|
|
90
|
+
"@aztec/world-state": "0.0.1-commit.321f6a9",
|
|
91
91
|
"@electric-sql/pglite": "^0.3.14",
|
|
92
92
|
"@jest/globals": "^30.0.0",
|
|
93
93
|
"@types/jest": "^30.0.0",
|
|
@@ -9,12 +9,13 @@ import { DateProvider, elapsed } from '@aztec/foundation/timer';
|
|
|
9
9
|
import { createTxValidatorForBlockBuilding, getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
10
10
|
import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
|
|
11
11
|
import {
|
|
12
|
+
type AvmSimulator,
|
|
12
13
|
GuardedMerkleTreeOperations,
|
|
13
14
|
PublicContractsDB,
|
|
14
15
|
PublicProcessor,
|
|
15
16
|
createPublicTxSimulatorForBlockBuilding,
|
|
16
17
|
} from '@aztec/simulator/server';
|
|
17
|
-
import { L2Block } from '@aztec/stdlib/block';
|
|
18
|
+
import { type BlockHash, L2Block } from '@aztec/stdlib/block';
|
|
18
19
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
19
20
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
20
21
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
@@ -57,6 +58,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
57
58
|
private contractDataSource: ContractDataSource,
|
|
58
59
|
private dateProvider: DateProvider,
|
|
59
60
|
private telemetryClient: TelemetryClient,
|
|
61
|
+
private avmSimulator: AvmSimulator,
|
|
60
62
|
bindings?: LoggerBindings,
|
|
61
63
|
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
62
64
|
) {
|
|
@@ -219,10 +221,12 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
219
221
|
if (opts.isBuildingProposal) {
|
|
220
222
|
const remainingBlocks = Math.max(1, opts.maxBlocksPerCheckpoint - existingBlocks.length);
|
|
221
223
|
const multiplier = opts.perBlockAllocationMultiplier;
|
|
224
|
+
// DA gas and blob fields use a higher multiplier so the largest contract class deploy fits a block.
|
|
225
|
+
const daMultiplier = opts.perBlockDAAllocationMultiplier ?? multiplier;
|
|
222
226
|
|
|
223
227
|
cappedL2Gas = Math.min(cappedL2Gas, Math.ceil((remainingMana / remainingBlocks) * multiplier));
|
|
224
|
-
cappedDAGas = Math.min(cappedDAGas, Math.ceil((remainingDAGas / remainingBlocks) *
|
|
225
|
-
cappedBlobFields = Math.min(cappedBlobFields, Math.ceil((maxBlobFieldsForTxs / remainingBlocks) *
|
|
228
|
+
cappedDAGas = Math.min(cappedDAGas, Math.ceil((remainingDAGas / remainingBlocks) * daMultiplier));
|
|
229
|
+
cappedBlobFields = Math.min(cappedBlobFields, Math.ceil((maxBlobFieldsForTxs / remainingBlocks) * daMultiplier));
|
|
226
230
|
cappedMaxTransactions = Math.min(cappedMaxTransactions, Math.ceil((remainingTxs / remainingBlocks) * multiplier));
|
|
227
231
|
}
|
|
228
232
|
|
|
@@ -241,16 +245,18 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
241
245
|
const contractsDB = this.contractsDB;
|
|
242
246
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
243
247
|
|
|
244
|
-
const collectDebugLogs = this.debugLogStore.isEnabled;
|
|
245
|
-
|
|
246
248
|
const bindings = this.log.getBindings();
|
|
249
|
+
// Extract the WSDB fork ID so the C++ AVM can modify the same fork in-place; the simulator reads
|
|
250
|
+
// contract data from `contractsDB`, scoped to this fork for the duration of each simulation.
|
|
251
|
+
const wsdbForkId = fork.getRevision().forkId;
|
|
247
252
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
248
|
-
|
|
249
|
-
contractsDB,
|
|
253
|
+
this.avmSimulator,
|
|
250
254
|
globalVariables,
|
|
255
|
+
contractsDB,
|
|
256
|
+
wsdbForkId,
|
|
251
257
|
this.telemetryClient,
|
|
252
258
|
bindings,
|
|
253
|
-
|
|
259
|
+
this.debugLogStore?.isEnabled ?? false,
|
|
254
260
|
);
|
|
255
261
|
|
|
256
262
|
const processor = new PublicProcessor(
|
|
@@ -289,6 +295,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
289
295
|
private worldState: WorldStateSynchronizer,
|
|
290
296
|
private contractDataSource: ContractDataSource,
|
|
291
297
|
private dateProvider: DateProvider,
|
|
298
|
+
private avmSimulator: AvmSimulator,
|
|
292
299
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
293
300
|
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
294
301
|
) {
|
|
@@ -344,6 +351,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
344
351
|
this.contractDataSource,
|
|
345
352
|
this.dateProvider,
|
|
346
353
|
this.telemetryClient,
|
|
354
|
+
this.avmSimulator,
|
|
347
355
|
bindings,
|
|
348
356
|
this.debugLogStore,
|
|
349
357
|
);
|
|
@@ -405,13 +413,23 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
405
413
|
this.contractDataSource,
|
|
406
414
|
this.dateProvider,
|
|
407
415
|
this.telemetryClient,
|
|
416
|
+
this.avmSimulator,
|
|
408
417
|
bindings,
|
|
409
418
|
this.debugLogStore,
|
|
410
419
|
);
|
|
411
420
|
}
|
|
412
421
|
|
|
413
|
-
/**
|
|
414
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Syncs world state to the given block number and returns a fork of it at that block.
|
|
424
|
+
*
|
|
425
|
+
* Syncing first is required: the block source (archiver) can already hold a block while world state
|
|
426
|
+
* still trails it, and forking a not-yet-applied block throws a raw "initialize from future block"
|
|
427
|
+
* tree error. syncImmediate blocks until world state reaches the block, or throws a typed error if it
|
|
428
|
+
* genuinely cannot. When `blockHash` is provided it is verified against the synced block, triggering a
|
|
429
|
+
* resync on mismatch (reorg detection).
|
|
430
|
+
*/
|
|
431
|
+
async getFork(blockNumber: BlockNumber, blockHash?: BlockHash): Promise<MerkleTreeWriteOperations> {
|
|
432
|
+
await this.worldState.syncImmediate(blockNumber, blockHash);
|
|
415
433
|
return this.worldState.fork(blockNumber);
|
|
416
434
|
}
|
|
417
435
|
}
|
package/src/config.ts
CHANGED
|
@@ -4,15 +4,26 @@ import {
|
|
|
4
4
|
getConfigFromMappings,
|
|
5
5
|
numberConfigHelper,
|
|
6
6
|
optionalNumberConfigHelper,
|
|
7
|
+
pickConfigMappings,
|
|
7
8
|
secretValueConfigHelper,
|
|
8
9
|
} from '@aztec/foundation/config';
|
|
9
10
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
11
|
+
import { type SequencerConfig, sharedSequencerConfigMappings } from '@aztec/stdlib/config';
|
|
10
12
|
import { localSignerConfigMappings, validatorHASignerConfigMappings } from '@aztec/stdlib/ha-signing';
|
|
11
13
|
import type { ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
|
|
12
14
|
|
|
13
15
|
export type { ValidatorClientConfig };
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Default clock-disparity tolerance (ms) for proposal/attestation receive windows, mirroring the p2p config
|
|
19
|
+
* default. Used by the validator-client validators when the merged node config does not carry the value.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_MAX_GOSSIP_CLOCK_DISPARITY_MS = 500;
|
|
22
|
+
|
|
23
|
+
export const validatorClientConfigMappings: ConfigMappingsType<
|
|
24
|
+
ValidatorClientConfig & Pick<SequencerConfig, 'blockDurationMs'>
|
|
25
|
+
> = {
|
|
26
|
+
...pickConfigMappings(sharedSequencerConfigMappings, ['blockDurationMs']),
|
|
16
27
|
validatorPrivateKeys: {
|
|
17
28
|
env: 'VALIDATOR_PRIVATE_KEYS',
|
|
18
29
|
description: 'List of private keys of the validators participating in attestation duties',
|
|
@@ -112,6 +123,8 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
112
123
|
* Note: If an environment variable is not set, the default value is used.
|
|
113
124
|
* @returns The validator configuration.
|
|
114
125
|
*/
|
|
115
|
-
export function getProverEnvVars(): ValidatorClientConfig {
|
|
116
|
-
return getConfigFromMappings<ValidatorClientConfig
|
|
126
|
+
export function getProverEnvVars(): ValidatorClientConfig & Pick<SequencerConfig, 'blockDurationMs'> {
|
|
127
|
+
return getConfigFromMappings<ValidatorClientConfig & Pick<SequencerConfig, 'blockDurationMs'>>(
|
|
128
|
+
validatorClientConfigMappings,
|
|
129
|
+
);
|
|
117
130
|
}
|
package/src/factory.ts
CHANGED
|
@@ -7,10 +7,12 @@ import type { L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
|
7
7
|
import type { CheckpointReexecutionTracker } from '@aztec/stdlib/checkpoint';
|
|
8
8
|
import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
9
9
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
10
|
+
import { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
10
11
|
import type { TelemetryClient } from '@aztec/telemetry-client';
|
|
11
12
|
import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
|
|
12
13
|
|
|
13
14
|
import type { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
15
|
+
import { DEFAULT_MAX_GOSSIP_CLOCK_DISPARITY_MS } from './config.js';
|
|
14
16
|
import { ValidatorMetrics } from './metrics.js';
|
|
15
17
|
import { ProposalHandler } from './proposal_handler.js';
|
|
16
18
|
import { ValidatorClient } from './validator.js';
|
|
@@ -31,7 +33,11 @@ export function createProposalHandler(
|
|
|
31
33
|
},
|
|
32
34
|
) {
|
|
33
35
|
const metrics = new ValidatorMetrics(deps.telemetry);
|
|
34
|
-
const
|
|
36
|
+
const consensusTimetable = new ConsensusTimetable({
|
|
37
|
+
l1Constants: deps.epochCache.getL1Constants(),
|
|
38
|
+
blockDuration: config.blockDurationMs / 1000,
|
|
39
|
+
});
|
|
40
|
+
const blockProposalValidator = new BlockProposalValidator(deps.epochCache, consensusTimetable, {
|
|
35
41
|
txsPermitted: !config.disableTransactions,
|
|
36
42
|
maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
|
|
37
43
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
@@ -39,6 +45,7 @@ export function createProposalHandler(
|
|
|
39
45
|
chainId: config.l1ChainId,
|
|
40
46
|
rollupAddress: config.rollupAddress,
|
|
41
47
|
},
|
|
48
|
+
clockDisparityMs: config.maxGossipClockDisparityMs ?? DEFAULT_MAX_GOSSIP_CLOCK_DISPARITY_MS,
|
|
42
49
|
});
|
|
43
50
|
return new ProposalHandler(
|
|
44
51
|
deps.checkpointsBuilder,
|
|
@@ -48,6 +55,7 @@ export function createProposalHandler(
|
|
|
48
55
|
deps.p2pClient.getTxProvider(),
|
|
49
56
|
blockProposalValidator,
|
|
50
57
|
deps.epochCache,
|
|
58
|
+
consensusTimetable,
|
|
51
59
|
config,
|
|
52
60
|
deps.blobClient,
|
|
53
61
|
deps.reexecutionTracker,
|
|
@@ -8,6 +8,9 @@ import type { TypedDataDefinition } from 'viem';
|
|
|
8
8
|
|
|
9
9
|
import type { ValidatorKeyStore } from './interface.js';
|
|
10
10
|
|
|
11
|
+
/** Default hard timeout (ms) applied to each Web3Signer HTTP request. */
|
|
12
|
+
const DEFAULT_WEB3SIGNER_REQUEST_TIMEOUT_MS = 30_000;
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* Web3Signer Key Store
|
|
13
16
|
*
|
|
@@ -15,10 +18,15 @@ import type { ValidatorKeyStore } from './interface.js';
|
|
|
15
18
|
* This implementation uses the Web3Signer JSON-RPC API for secp256k1 signatures.
|
|
16
19
|
*/
|
|
17
20
|
export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
21
|
+
private readonly requestTimeoutMs: number;
|
|
22
|
+
|
|
18
23
|
constructor(
|
|
19
24
|
private addresses: EthAddress[],
|
|
20
25
|
private baseUrl: string,
|
|
21
|
-
|
|
26
|
+
requestTimeoutMs: number = DEFAULT_WEB3SIGNER_REQUEST_TIMEOUT_MS,
|
|
27
|
+
) {
|
|
28
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
29
|
+
}
|
|
22
30
|
|
|
23
31
|
/**
|
|
24
32
|
* Get the address of a signer by index
|
|
@@ -108,75 +116,50 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
|
108
116
|
* @param data - The data to sign
|
|
109
117
|
* @returns The signature
|
|
110
118
|
*/
|
|
111
|
-
private
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Use JSON-RPC eth_sign method which automatically applies Ethereum message prefixing
|
|
115
|
-
const body = {
|
|
119
|
+
private makeJsonRpcSignRequest(address: EthAddress, data: Buffer32): Promise<Signature> {
|
|
120
|
+
// eth_sign automatically applies Ethereum message prefixing to the raw data.
|
|
121
|
+
return this.sendSignRequest({
|
|
116
122
|
jsonrpc: '2.0',
|
|
117
123
|
method: 'eth_sign',
|
|
118
|
-
params: [
|
|
119
|
-
address.toString(), // Ethereum address as identifier
|
|
120
|
-
data.toString(), // Raw data to sign (eth_sign will apply Ethereum message prefix)
|
|
121
|
-
],
|
|
124
|
+
params: [address.toString(), data.toString()],
|
|
122
125
|
id: 1,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const response = await fetch(url, {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
headers: {
|
|
128
|
-
'Content-Type': 'application/json',
|
|
129
|
-
},
|
|
130
|
-
body: JSON.stringify(body),
|
|
131
126
|
});
|
|
132
|
-
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
const errorText = await response.text();
|
|
135
|
-
throw new Error(`Web3Signer request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const result = await response.json();
|
|
139
|
-
|
|
140
|
-
// Handle JSON-RPC response format
|
|
141
|
-
if (result.error) {
|
|
142
|
-
throw new Error(`Web3Signer JSON-RPC error: ${result.error.code} - ${result.error.message}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!result.result) {
|
|
146
|
-
throw new Error('Invalid response from Web3Signer: no result found');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
let signatureHex = result.result;
|
|
150
|
-
|
|
151
|
-
// Ensure the signature has the 0x prefix
|
|
152
|
-
if (!signatureHex.startsWith('0x')) {
|
|
153
|
-
signatureHex = '0x' + signatureHex;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Parse the signature from the hex string
|
|
157
|
-
return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
|
|
158
127
|
}
|
|
159
128
|
|
|
160
|
-
private
|
|
161
|
-
|
|
162
|
-
typedData: TypedDataDefinition,
|
|
163
|
-
): Promise<Signature> {
|
|
164
|
-
const url = this.baseUrl;
|
|
165
|
-
|
|
166
|
-
const body = {
|
|
129
|
+
private makeJsonRpcSignTypedDataRequest(address: EthAddress, typedData: TypedDataDefinition): Promise<Signature> {
|
|
130
|
+
return this.sendSignRequest({
|
|
167
131
|
jsonrpc: '2.0',
|
|
168
132
|
method: 'eth_signTypedData',
|
|
169
133
|
params: [address.toString(), JSON.stringify(typedData)],
|
|
170
134
|
id: 1,
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const response = await fetch(url, {
|
|
174
|
-
method: 'POST',
|
|
175
|
-
headers: {
|
|
176
|
-
'Content-Type': 'application/json',
|
|
177
|
-
},
|
|
178
|
-
body: JSON.stringify(body),
|
|
179
135
|
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Send a JSON-RPC request to Web3Signer under a hard request timeout and parse the signature.
|
|
140
|
+
* A timed-out or aborted request is surfaced as a clear timeout error rather than hanging, so a
|
|
141
|
+
* slow or unreachable signer cannot stall an HA signing operation past its own timeout budget.
|
|
142
|
+
*/
|
|
143
|
+
private async sendSignRequest(body: object): Promise<Signature> {
|
|
144
|
+
let response: Response;
|
|
145
|
+
try {
|
|
146
|
+
response = await fetch(this.baseUrl, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify(body),
|
|
152
|
+
signal: AbortSignal.timeout(this.requestTimeoutMs),
|
|
153
|
+
});
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (
|
|
156
|
+
(err instanceof Error || err instanceof DOMException) &&
|
|
157
|
+
(err.name === 'TimeoutError' || err.name === 'AbortError')
|
|
158
|
+
) {
|
|
159
|
+
throw new Error(`Web3Signer request timed out after ${this.requestTimeoutMs}ms`);
|
|
160
|
+
}
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
180
163
|
|
|
181
164
|
if (!response.ok) {
|
|
182
165
|
const errorText = await response.text();
|
|
@@ -185,6 +168,7 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
|
185
168
|
|
|
186
169
|
const result = await response.json();
|
|
187
170
|
|
|
171
|
+
// Handle JSON-RPC response format
|
|
188
172
|
if (result.error) {
|
|
189
173
|
throw new Error(`Web3Signer JSON-RPC error: ${result.error.code} - ${result.error.message}`);
|
|
190
174
|
}
|