@aztec/validator-client 0.0.1-commit.2ed92850 → 0.0.1-commit.43597cc1
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 +10 -9
- package/dest/block_proposal_handler.d.ts +3 -4
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +5 -4
- package/dest/checkpoint_builder.d.ts +10 -11
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +38 -25
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -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/metrics.d.ts +4 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +34 -5
- package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +16 -16
- package/dest/validator.d.ts +12 -8
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +46 -28
- package/package.json +19 -17
- package/src/block_proposal_handler.ts +7 -6
- package/src/checkpoint_builder.ts +55 -25
- package/src/config.ts +2 -3
- package/src/key_store/ha_key_store.ts +2 -2
- package/src/metrics.ts +45 -6
- package/src/tx_validator/tx_validator_factory.ts +51 -32
- package/src/validator.ts +50 -32
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { merge, pick } from '@aztec/foundation/collection';
|
|
3
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
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,18 +37,13 @@ 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
|
-
const log = createLogger('checkpoint-builder');
|
|
40
|
-
|
|
41
|
-
/** Result of building a block within a checkpoint. Extends the base interface with timer. */
|
|
42
|
-
export interface BuildBlockInCheckpointResultWithTimer extends BuildBlockInCheckpointResult {
|
|
43
|
-
blockBuildingTimer: Timer;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
40
|
/**
|
|
47
41
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
48
42
|
* and completing it.
|
|
49
43
|
*/
|
|
50
44
|
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
45
|
+
private log: Logger;
|
|
46
|
+
|
|
51
47
|
constructor(
|
|
52
48
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
53
49
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -55,7 +51,13 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
55
51
|
private contractDataSource: ContractDataSource,
|
|
56
52
|
private dateProvider: DateProvider,
|
|
57
53
|
private telemetryClient: TelemetryClient,
|
|
58
|
-
|
|
54
|
+
bindings?: LoggerBindings,
|
|
55
|
+
) {
|
|
56
|
+
this.log = createLogger('checkpoint-builder', {
|
|
57
|
+
...bindings,
|
|
58
|
+
instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
59
61
|
|
|
60
62
|
getConstantData(): CheckpointGlobalVariables {
|
|
61
63
|
return this.checkpointBuilder.constants;
|
|
@@ -68,12 +70,11 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
68
70
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
69
71
|
blockNumber: BlockNumber,
|
|
70
72
|
timestamp: bigint,
|
|
71
|
-
opts: PublicProcessorLimits & { expectedEndState?: StateReference },
|
|
72
|
-
): Promise<
|
|
73
|
-
const blockBuildingTimer = new Timer();
|
|
73
|
+
opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
|
|
74
|
+
): Promise<BuildBlockInCheckpointResult> {
|
|
74
75
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
75
76
|
|
|
76
|
-
log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
77
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
77
78
|
slot,
|
|
78
79
|
blockNumber,
|
|
79
80
|
...opts,
|
|
@@ -97,6 +98,12 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
97
98
|
processor.process(pendingTxs, opts, validator),
|
|
98
99
|
);
|
|
99
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
|
+
|
|
100
107
|
// Add block to checkpoint
|
|
101
108
|
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
102
109
|
expectedEndState: opts.expectedEndState,
|
|
@@ -105,25 +112,28 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
105
112
|
// How much public gas was processed
|
|
106
113
|
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
107
114
|
|
|
108
|
-
|
|
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 {
|
|
109
122
|
block,
|
|
110
123
|
publicGas,
|
|
111
124
|
publicProcessorDuration,
|
|
112
125
|
numTxs: processedTxs.length,
|
|
113
126
|
failedTxs,
|
|
114
|
-
blockBuildingTimer,
|
|
115
127
|
usedTxs,
|
|
116
128
|
usedTxBlobFields,
|
|
117
129
|
};
|
|
118
|
-
log.debug('Built block within checkpoint', res.block.header);
|
|
119
|
-
return res;
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
/** Completes the checkpoint and returns it. */
|
|
123
133
|
async completeCheckpoint(): Promise<Checkpoint> {
|
|
124
134
|
const checkpoint = await this.checkpointBuilder.completeCheckpoint();
|
|
125
135
|
|
|
126
|
-
log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
136
|
+
this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
127
137
|
checkpointNumber: checkpoint.number,
|
|
128
138
|
numBlocks: checkpoint.blocks.length,
|
|
129
139
|
archiveRoot: checkpoint.archive.root.toString(),
|
|
@@ -139,14 +149,16 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
139
149
|
|
|
140
150
|
protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
|
|
141
151
|
const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
142
|
-
const contractsDB = new PublicContractsDB(this.contractDataSource);
|
|
152
|
+
const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
|
|
143
153
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
144
154
|
|
|
155
|
+
const bindings = this.log.getBindings();
|
|
145
156
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
146
157
|
guardedFork,
|
|
147
158
|
contractsDB,
|
|
148
159
|
globalVariables,
|
|
149
160
|
this.telemetryClient,
|
|
161
|
+
bindings,
|
|
150
162
|
);
|
|
151
163
|
|
|
152
164
|
const processor = new PublicProcessor(
|
|
@@ -156,7 +168,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
156
168
|
publicTxSimulator,
|
|
157
169
|
this.dateProvider,
|
|
158
170
|
this.telemetryClient,
|
|
159
|
-
|
|
171
|
+
createLogger('simulator:public-processor', bindings),
|
|
160
172
|
this.config,
|
|
161
173
|
);
|
|
162
174
|
|
|
@@ -165,6 +177,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
165
177
|
this.contractDataSource,
|
|
166
178
|
globalVariables,
|
|
167
179
|
txPublicSetupAllowList,
|
|
180
|
+
this.log.getBindings(),
|
|
168
181
|
);
|
|
169
182
|
|
|
170
183
|
return {
|
|
@@ -176,13 +189,17 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
176
189
|
|
|
177
190
|
/** Factory for creating checkpoint builders. */
|
|
178
191
|
export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
192
|
+
private log: Logger;
|
|
193
|
+
|
|
179
194
|
constructor(
|
|
180
195
|
private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
|
|
181
196
|
private worldState: WorldStateSynchronizer,
|
|
182
197
|
private contractDataSource: ContractDataSource,
|
|
183
198
|
private dateProvider: DateProvider,
|
|
184
199
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
185
|
-
) {
|
|
200
|
+
) {
|
|
201
|
+
this.log = createLogger('checkpoint-builder');
|
|
202
|
+
}
|
|
186
203
|
|
|
187
204
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
188
205
|
return this.config;
|
|
@@ -201,11 +218,12 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
201
218
|
l1ToL2Messages: Fr[],
|
|
202
219
|
previousCheckpointOutHashes: Fr[],
|
|
203
220
|
fork: MerkleTreeWriteOperations,
|
|
221
|
+
bindings?: LoggerBindings,
|
|
204
222
|
): Promise<CheckpointBuilder> {
|
|
205
223
|
const stateReference = await fork.getStateReference();
|
|
206
224
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
207
225
|
|
|
208
|
-
log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
226
|
+
this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
209
227
|
checkpointNumber,
|
|
210
228
|
msgCount: l1ToL2Messages.length,
|
|
211
229
|
initialStateReference: stateReference.toInspect(),
|
|
@@ -219,6 +237,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
219
237
|
l1ToL2Messages,
|
|
220
238
|
previousCheckpointOutHashes,
|
|
221
239
|
fork,
|
|
240
|
+
bindings,
|
|
222
241
|
);
|
|
223
242
|
|
|
224
243
|
return new CheckpointBuilder(
|
|
@@ -228,6 +247,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
228
247
|
this.contractDataSource,
|
|
229
248
|
this.dateProvider,
|
|
230
249
|
this.telemetryClient,
|
|
250
|
+
bindings,
|
|
231
251
|
);
|
|
232
252
|
}
|
|
233
253
|
|
|
@@ -241,15 +261,23 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
241
261
|
previousCheckpointOutHashes: Fr[],
|
|
242
262
|
fork: MerkleTreeWriteOperations,
|
|
243
263
|
existingBlocks: L2Block[] = [],
|
|
264
|
+
bindings?: LoggerBindings,
|
|
244
265
|
): Promise<CheckpointBuilder> {
|
|
245
266
|
const stateReference = await fork.getStateReference();
|
|
246
267
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
247
268
|
|
|
248
269
|
if (existingBlocks.length === 0) {
|
|
249
|
-
return this.startCheckpoint(
|
|
270
|
+
return this.startCheckpoint(
|
|
271
|
+
checkpointNumber,
|
|
272
|
+
constants,
|
|
273
|
+
l1ToL2Messages,
|
|
274
|
+
previousCheckpointOutHashes,
|
|
275
|
+
fork,
|
|
276
|
+
bindings,
|
|
277
|
+
);
|
|
250
278
|
}
|
|
251
279
|
|
|
252
|
-
log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
280
|
+
this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
253
281
|
checkpointNumber,
|
|
254
282
|
msgCount: l1ToL2Messages.length,
|
|
255
283
|
existingBlockCount: existingBlocks.length,
|
|
@@ -265,6 +293,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
265
293
|
previousCheckpointOutHashes,
|
|
266
294
|
fork,
|
|
267
295
|
existingBlocks,
|
|
296
|
+
bindings,
|
|
268
297
|
);
|
|
269
298
|
|
|
270
299
|
return new CheckpointBuilder(
|
|
@@ -274,6 +303,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
274
303
|
this.contractDataSource,
|
|
275
304
|
this.dateProvider,
|
|
276
305
|
this.telemetryClient,
|
|
306
|
+
bindings,
|
|
277
307
|
);
|
|
278
308
|
}
|
|
279
309
|
|
package/src/config.ts
CHANGED
|
@@ -65,10 +65,9 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
65
65
|
'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
|
|
66
66
|
...booleanConfigHelper(false),
|
|
67
67
|
},
|
|
68
|
-
// TODO(palla/mbps): Change default to false once checkpoint validation is stable
|
|
69
68
|
skipCheckpointProposalValidation: {
|
|
70
|
-
description: 'Skip checkpoint proposal validation and always attest (default:
|
|
71
|
-
defaultValue:
|
|
69
|
+
description: 'Skip checkpoint proposal validation and always attest (default: false)',
|
|
70
|
+
defaultValue: false,
|
|
72
71
|
},
|
|
73
72
|
skipPushProposedBlocksToArchiver: {
|
|
74
73
|
description: 'Skip pushing re-executed blocks to archiver (default: false)',
|
|
@@ -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
|
/**
|
package/src/metrics.ts
CHANGED
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
Metrics,
|
|
7
7
|
type TelemetryClient,
|
|
8
8
|
type UpDownCounter,
|
|
9
|
+
createUpDownCounterWithDefault,
|
|
9
10
|
} from '@aztec/telemetry-client';
|
|
10
11
|
|
|
12
|
+
import type { BlockProposalValidationFailureReason } from './block_proposal_handler.js';
|
|
13
|
+
|
|
11
14
|
export class ValidatorMetrics {
|
|
12
15
|
private failedReexecutionCounter: UpDownCounter;
|
|
13
16
|
private successfulAttestationsCount: UpDownCounter;
|
|
@@ -21,16 +24,44 @@ export class ValidatorMetrics {
|
|
|
21
24
|
constructor(telemetryClient: TelemetryClient) {
|
|
22
25
|
const meter = telemetryClient.getMeter('Validator');
|
|
23
26
|
|
|
24
|
-
this.failedReexecutionCounter = meter
|
|
27
|
+
this.failedReexecutionCounter = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT, {
|
|
28
|
+
[Attributes.STATUS]: ['failed'],
|
|
29
|
+
});
|
|
25
30
|
|
|
26
|
-
this.successfulAttestationsCount =
|
|
31
|
+
this.successfulAttestationsCount = createUpDownCounterWithDefault(
|
|
32
|
+
meter,
|
|
33
|
+
Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT,
|
|
34
|
+
);
|
|
27
35
|
|
|
28
|
-
this.failedAttestationsBadProposalCount =
|
|
36
|
+
this.failedAttestationsBadProposalCount = createUpDownCounterWithDefault(
|
|
37
|
+
meter,
|
|
29
38
|
Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
|
|
39
|
+
{
|
|
40
|
+
[Attributes.ERROR_TYPE]: [
|
|
41
|
+
'invalid_proposal',
|
|
42
|
+
'state_mismatch',
|
|
43
|
+
'failed_txs',
|
|
44
|
+
'in_hash_mismatch',
|
|
45
|
+
'parent_block_wrong_slot',
|
|
46
|
+
],
|
|
47
|
+
[Attributes.IS_COMMITTEE_MEMBER]: [true, false],
|
|
48
|
+
},
|
|
30
49
|
);
|
|
31
50
|
|
|
32
|
-
this.failedAttestationsNodeIssueCount =
|
|
51
|
+
this.failedAttestationsNodeIssueCount = createUpDownCounterWithDefault(
|
|
52
|
+
meter,
|
|
33
53
|
Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
|
|
54
|
+
{
|
|
55
|
+
[Attributes.ERROR_TYPE]: [
|
|
56
|
+
'parent_block_not_found',
|
|
57
|
+
'global_variables_mismatch',
|
|
58
|
+
'block_number_already_exists',
|
|
59
|
+
'txs_not_available',
|
|
60
|
+
'timeout',
|
|
61
|
+
'unknown_error',
|
|
62
|
+
],
|
|
63
|
+
[Attributes.IS_COMMITTEE_MEMBER]: [true, false],
|
|
64
|
+
},
|
|
34
65
|
);
|
|
35
66
|
|
|
36
67
|
this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
|
|
@@ -58,14 +89,22 @@ export class ValidatorMetrics {
|
|
|
58
89
|
this.successfulAttestationsCount.add(num);
|
|
59
90
|
}
|
|
60
91
|
|
|
61
|
-
public incFailedAttestationsBadProposal(
|
|
92
|
+
public incFailedAttestationsBadProposal(
|
|
93
|
+
num: number,
|
|
94
|
+
reason: BlockProposalValidationFailureReason,
|
|
95
|
+
inCommittee: boolean,
|
|
96
|
+
) {
|
|
62
97
|
this.failedAttestationsBadProposalCount.add(num, {
|
|
63
98
|
[Attributes.ERROR_TYPE]: reason,
|
|
64
99
|
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
65
100
|
});
|
|
66
101
|
}
|
|
67
102
|
|
|
68
|
-
public incFailedAttestationsNodeIssue(
|
|
103
|
+
public incFailedAttestationsNodeIssue(
|
|
104
|
+
num: number,
|
|
105
|
+
reason: BlockProposalValidationFailureReason,
|
|
106
|
+
inCommittee: boolean,
|
|
107
|
+
) {
|
|
69
108
|
this.failedAttestationsNodeIssueCount.add(num, {
|
|
70
109
|
[Attributes.ERROR_TYPE]: reason,
|
|
71
110
|
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
+
import type { LoggerBindings } from '@aztec/foundation/log';
|
|
3
4
|
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
|
|
4
5
|
import {
|
|
5
6
|
AggregateTxValidator,
|
|
@@ -53,32 +54,41 @@ export function createValidatorForAcceptingTxs(
|
|
|
53
54
|
blockNumber: BlockNumber;
|
|
54
55
|
txsPermitted: boolean;
|
|
55
56
|
},
|
|
57
|
+
bindings?: LoggerBindings,
|
|
56
58
|
): TxValidator<Tx> {
|
|
57
59
|
const validators: TxValidator<Tx>[] = [
|
|
58
|
-
new TxPermittedValidator(txsPermitted),
|
|
59
|
-
new SizeTxValidator(),
|
|
60
|
-
new DataTxValidator(),
|
|
61
|
-
new MetadataTxValidator(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
new TxPermittedValidator(txsPermitted, bindings),
|
|
61
|
+
new SizeTxValidator(bindings),
|
|
62
|
+
new DataTxValidator(bindings),
|
|
63
|
+
new MetadataTxValidator(
|
|
64
|
+
{
|
|
65
|
+
l1ChainId: new Fr(l1ChainId),
|
|
66
|
+
rollupVersion: new Fr(rollupVersion),
|
|
67
|
+
protocolContractsHash,
|
|
68
|
+
vkTreeRoot: getVKTreeRoot(),
|
|
69
|
+
},
|
|
70
|
+
bindings,
|
|
71
|
+
),
|
|
72
|
+
new TimestampTxValidator(
|
|
73
|
+
{
|
|
74
|
+
timestamp,
|
|
75
|
+
blockNumber,
|
|
76
|
+
},
|
|
77
|
+
bindings,
|
|
78
|
+
),
|
|
79
|
+
new DoubleSpendTxValidator(new NullifierCache(db), bindings),
|
|
80
|
+
new PhasesTxValidator(contractDataSource, setupAllowList, timestamp, bindings),
|
|
81
|
+
new BlockHeaderTxValidator(new ArchiveCache(db), bindings),
|
|
74
82
|
];
|
|
75
83
|
|
|
76
84
|
if (!skipFeeEnforcement) {
|
|
77
|
-
validators.push(
|
|
85
|
+
validators.push(
|
|
86
|
+
new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings),
|
|
87
|
+
);
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
if (verifier) {
|
|
81
|
-
validators.push(new TxProofValidator(verifier));
|
|
91
|
+
validators.push(new TxProofValidator(verifier, bindings));
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
return new AggregateTxValidator(...validators);
|
|
@@ -89,6 +99,7 @@ export function createValidatorForBlockBuilding(
|
|
|
89
99
|
contractDataSource: ContractDataSource,
|
|
90
100
|
globalVariables: GlobalVariables,
|
|
91
101
|
setupAllowList: AllowedElement[],
|
|
102
|
+
bindings?: LoggerBindings,
|
|
92
103
|
): PublicProcessorValidator {
|
|
93
104
|
const nullifierCache = new NullifierCache(db);
|
|
94
105
|
const archiveCache = new ArchiveCache(db);
|
|
@@ -102,6 +113,7 @@ export function createValidatorForBlockBuilding(
|
|
|
102
113
|
contractDataSource,
|
|
103
114
|
globalVariables,
|
|
104
115
|
setupAllowList,
|
|
116
|
+
bindings,
|
|
105
117
|
),
|
|
106
118
|
nullifierCache,
|
|
107
119
|
};
|
|
@@ -114,22 +126,29 @@ function preprocessValidator(
|
|
|
114
126
|
contractDataSource: ContractDataSource,
|
|
115
127
|
globalVariables: GlobalVariables,
|
|
116
128
|
setupAllowList: AllowedElement[],
|
|
129
|
+
bindings?: LoggerBindings,
|
|
117
130
|
): TxValidator<Tx> {
|
|
118
131
|
// We don't include the TxProofValidator nor the DataTxValidator here because they are already checked by the time we get to block building.
|
|
119
132
|
return new AggregateTxValidator(
|
|
120
|
-
new MetadataTxValidator(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
new MetadataTxValidator(
|
|
134
|
+
{
|
|
135
|
+
l1ChainId: globalVariables.chainId,
|
|
136
|
+
rollupVersion: globalVariables.version,
|
|
137
|
+
protocolContractsHash,
|
|
138
|
+
vkTreeRoot: getVKTreeRoot(),
|
|
139
|
+
},
|
|
140
|
+
bindings,
|
|
141
|
+
),
|
|
142
|
+
new TimestampTxValidator(
|
|
143
|
+
{
|
|
144
|
+
timestamp: globalVariables.timestamp,
|
|
145
|
+
blockNumber: globalVariables.blockNumber,
|
|
146
|
+
},
|
|
147
|
+
bindings,
|
|
148
|
+
),
|
|
149
|
+
new DoubleSpendTxValidator(nullifierCache, bindings),
|
|
150
|
+
new PhasesTxValidator(contractDataSource, setupAllowList, globalVariables.timestamp, bindings),
|
|
151
|
+
new GasTxValidator(publicStateSource, ProtocolContractAddress.FeeJuice, globalVariables.gasFees, bindings),
|
|
152
|
+
new BlockHeaderTxValidator(archiveCache, bindings),
|
|
134
153
|
);
|
|
135
154
|
}
|
package/src/validator.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
|
18
18
|
import { sleep } from '@aztec/foundation/sleep';
|
|
19
19
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
20
20
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
21
|
-
import type { P2P, PeerId
|
|
21
|
+
import type { DuplicateProposalInfo, P2P, PeerId } from '@aztec/p2p';
|
|
22
22
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
23
23
|
import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
|
|
24
24
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
@@ -26,11 +26,12 @@ import type { CommitteeAttestationsAndSigners, L2Block, L2BlockSink, L2BlockSour
|
|
|
26
26
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
27
27
|
import type {
|
|
28
28
|
CreateCheckpointProposalLastBlockData,
|
|
29
|
+
ITxProvider,
|
|
29
30
|
Validator,
|
|
30
31
|
ValidatorClientFullConfig,
|
|
31
32
|
WorldStateSynchronizer,
|
|
32
33
|
} from '@aztec/stdlib/interfaces/server';
|
|
33
|
-
import type
|
|
34
|
+
import { type L1ToL2MessageSource, accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging';
|
|
34
35
|
import type {
|
|
35
36
|
BlockProposal,
|
|
36
37
|
BlockProposalOptions,
|
|
@@ -87,11 +88,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
87
88
|
|
|
88
89
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
89
90
|
|
|
90
|
-
// TODO(palla/mbps): Remove this once checkpoint validation is stable and we can validate all blocks properly.
|
|
91
|
-
// Tracks slots for which we have successfully validated a block proposal, so we can attest to checkpoint proposals for those slots.
|
|
92
|
-
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
93
|
-
private validatedBlockSlots: Set<SlotNumber> = new Set();
|
|
94
|
-
|
|
95
91
|
protected constructor(
|
|
96
92
|
private keyStore: ExtendedValidatorKeyStore,
|
|
97
93
|
private epochCache: EpochCache,
|
|
@@ -184,7 +180,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
184
180
|
p2pClient: P2P,
|
|
185
181
|
blockSource: L2BlockSource & L2BlockSink,
|
|
186
182
|
l1ToL2MessageSource: L1ToL2MessageSource,
|
|
187
|
-
txProvider:
|
|
183
|
+
txProvider: ITxProvider,
|
|
188
184
|
keyStoreManager: KeystoreManager,
|
|
189
185
|
blobClient: BlobClientInterface,
|
|
190
186
|
dateProvider: DateProvider = new DateProvider(),
|
|
@@ -313,6 +309,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
313
309
|
): Promise<CheckpointAttestation[] | undefined> => this.attestToCheckpointProposal(checkpoint, proposalSender);
|
|
314
310
|
this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
|
|
315
311
|
|
|
312
|
+
// Duplicate proposal handler - triggers slashing for equivocation
|
|
313
|
+
this.p2pClient.registerDuplicateProposalCallback((info: DuplicateProposalInfo) => {
|
|
314
|
+
this.handleDuplicateProposal(info);
|
|
315
|
+
});
|
|
316
|
+
|
|
316
317
|
const myAddresses = this.getValidatorAddresses();
|
|
317
318
|
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
318
319
|
|
|
@@ -413,10 +414,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
413
414
|
return false;
|
|
414
415
|
}
|
|
415
416
|
|
|
416
|
-
// TODO(palla/mbps): Remove this once checkpoint validation is stable.
|
|
417
|
-
// Track that we successfully validated a block for this slot, so we can attest to checkpoint proposals for it.
|
|
418
|
-
this.validatedBlockSlots.add(slotNumber);
|
|
419
|
-
|
|
420
417
|
return true;
|
|
421
418
|
}
|
|
422
419
|
|
|
@@ -461,17 +458,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
461
458
|
fishermanMode: this.config.fishermanMode || false,
|
|
462
459
|
});
|
|
463
460
|
|
|
464
|
-
// TODO(palla/mbps): Remove this once checkpoint validation is stable.
|
|
465
|
-
// Check that we have successfully validated a block for this slot before attesting to the checkpoint.
|
|
466
|
-
if (!this.validatedBlockSlots.has(slotNumber)) {
|
|
467
|
-
this.log.warn(`No validated block found for slot ${slotNumber}, refusing to attest to checkpoint`, proposalInfo);
|
|
468
|
-
return undefined;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
461
|
// Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
this.log.verbose(`Skipping checkpoint proposal validation for slot ${slotNumber}`, proposalInfo);
|
|
462
|
+
if (this.config.skipCheckpointProposalValidation) {
|
|
463
|
+
this.log.warn(`Skipping checkpoint proposal validation for slot ${slotNumber}`, proposalInfo);
|
|
475
464
|
} else {
|
|
476
465
|
const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
|
|
477
466
|
if (!validationResult.isValid) {
|
|
@@ -534,7 +523,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
534
523
|
attestors: EthAddress[] = [],
|
|
535
524
|
): Promise<CheckpointAttestation[]> {
|
|
536
525
|
const attestations = await this.validationService.attestToCheckpointProposal(proposal, attestors);
|
|
537
|
-
await this.p2pClient.
|
|
526
|
+
await this.p2pClient.addOwnCheckpointAttestations(attestations);
|
|
538
527
|
return attestations;
|
|
539
528
|
}
|
|
540
529
|
|
|
@@ -547,7 +536,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
547
536
|
proposalInfo: LogData,
|
|
548
537
|
): Promise<{ isValid: true } | { isValid: false; reason: string }> {
|
|
549
538
|
const slot = proposal.slotNumber;
|
|
550
|
-
const timeoutSeconds = 10;
|
|
539
|
+
const timeoutSeconds = 10; // TODO(palla/mbps): This should map to the timetable settings
|
|
551
540
|
|
|
552
541
|
// Wait for last block to sync by archive
|
|
553
542
|
let lastBlockHeader: BlockHeader | undefined;
|
|
@@ -617,6 +606,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
617
606
|
previousCheckpointOutHashes,
|
|
618
607
|
fork,
|
|
619
608
|
blocks,
|
|
609
|
+
this.log.getBindings(),
|
|
620
610
|
);
|
|
621
611
|
|
|
622
612
|
// Complete the checkpoint to get computed values
|
|
@@ -642,13 +632,17 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
642
632
|
return { isValid: false, reason: 'archive_mismatch' };
|
|
643
633
|
}
|
|
644
634
|
|
|
645
|
-
// Check that the accumulated out hash matches the value in the proposal.
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
|
|
635
|
+
// Check that the accumulated epoch out hash matches the value in the proposal.
|
|
636
|
+
// The epoch out hash is the accumulated hash of all checkpoint out hashes in the epoch.
|
|
637
|
+
const checkpointOutHash = computedCheckpoint.getCheckpointOutHash();
|
|
638
|
+
const computedEpochOutHash = accumulateCheckpointOutHashes([...previousCheckpointOutHashes, checkpointOutHash]);
|
|
639
|
+
const proposalEpochOutHash = proposal.checkpointHeader.epochOutHash;
|
|
640
|
+
if (!computedEpochOutHash.equals(proposalEpochOutHash)) {
|
|
649
641
|
this.log.warn(`Epoch out hash mismatch`, {
|
|
650
|
-
|
|
651
|
-
|
|
642
|
+
proposalEpochOutHash: proposalEpochOutHash.toString(),
|
|
643
|
+
computedEpochOutHash: computedEpochOutHash.toString(),
|
|
644
|
+
checkpointOutHash: checkpointOutHash.toString(),
|
|
645
|
+
previousCheckpointOutHashes: previousCheckpointOutHashes.map(h => h.toString()),
|
|
652
646
|
...proposalInfo,
|
|
653
647
|
});
|
|
654
648
|
return { isValid: false, reason: 'out_hash_mismatch' };
|
|
@@ -732,6 +726,30 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
732
726
|
]);
|
|
733
727
|
}
|
|
734
728
|
|
|
729
|
+
/**
|
|
730
|
+
* Handle detection of a duplicate proposal (equivocation).
|
|
731
|
+
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
732
|
+
*/
|
|
733
|
+
private handleDuplicateProposal(info: DuplicateProposalInfo): void {
|
|
734
|
+
const { slot, proposer, type } = info;
|
|
735
|
+
|
|
736
|
+
this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
|
|
737
|
+
proposer: proposer.toString(),
|
|
738
|
+
slot,
|
|
739
|
+
type,
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Emit slash event
|
|
743
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
744
|
+
{
|
|
745
|
+
validator: proposer,
|
|
746
|
+
amount: this.config.slashDuplicateProposalPenalty,
|
|
747
|
+
offenseType: OffenseType.DUPLICATE_PROPOSAL,
|
|
748
|
+
epochOrSlot: BigInt(slot),
|
|
749
|
+
},
|
|
750
|
+
]);
|
|
751
|
+
}
|
|
752
|
+
|
|
735
753
|
async createBlockProposal(
|
|
736
754
|
blockHeader: BlockHeader,
|
|
737
755
|
indexWithinCheckpoint: IndexWithinCheckpoint,
|
|
@@ -739,7 +757,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
739
757
|
archive: Fr,
|
|
740
758
|
txs: Tx[],
|
|
741
759
|
proposerAddress: EthAddress | undefined,
|
|
742
|
-
options: BlockProposalOptions,
|
|
760
|
+
options: BlockProposalOptions = {},
|
|
743
761
|
): Promise<BlockProposal> {
|
|
744
762
|
// TODO(palla/mbps): Prevent double proposals properly
|
|
745
763
|
// if (this.previousProposal?.slotNumber === blockHeader.globalVariables.slotNumber) {
|
|
@@ -771,7 +789,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
771
789
|
archive: Fr,
|
|
772
790
|
lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
|
|
773
791
|
proposerAddress: EthAddress | undefined,
|
|
774
|
-
options: CheckpointProposalOptions,
|
|
792
|
+
options: CheckpointProposalOptions = {},
|
|
775
793
|
): Promise<CheckpointProposal> {
|
|
776
794
|
this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
|
|
777
795
|
return await this.validationService.createCheckpointProposal(
|