@aztec/validator-client 0.0.1-commit.3469e52 → 0.0.1-commit.381b1a9
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 +64 -19
- package/dest/block_proposal_handler.d.ts +9 -10
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +129 -82
- package/dest/checkpoint_builder.d.ts +23 -13
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +127 -46
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +28 -6
- 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 +6 -12
- package/dest/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +2 -1
- package/dest/index.d.ts +1 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +0 -1
- 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 +3 -3
- package/dest/metrics.d.ts +12 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +46 -5
- package/dest/validator.d.ts +40 -14
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +211 -55
- package/package.json +19 -17
- package/src/block_proposal_handler.ts +159 -109
- package/src/checkpoint_builder.ts +172 -52
- package/src/config.ts +28 -6
- package/src/duties/validation_service.ts +12 -11
- package/src/factory.ts +1 -0
- package/src/index.ts +0 -1
- package/src/key_store/ha_key_store.ts +3 -3
- package/src/metrics.ts +63 -6
- package/src/validator.ts +270 -68
- package/dest/tx_validator/index.d.ts +0 -3
- package/dest/tx_validator/index.d.ts.map +0 -1
- package/dest/tx_validator/index.js +0 -2
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -54
- package/src/tx_validator/index.ts +0 -2
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -135
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
|
|
2
|
+
import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB, MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT } from '@aztec/constants';
|
|
1
3
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
|
-
import { merge, pick } from '@aztec/foundation/collection';
|
|
4
|
+
import { merge, pick, sum } from '@aztec/foundation/collection';
|
|
3
5
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
6
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
7
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
6
|
-
import { DateProvider,
|
|
7
|
-
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
8
|
+
import { DateProvider, elapsed } from '@aztec/foundation/timer';
|
|
9
|
+
import { createTxValidatorForBlockBuilding, getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
8
10
|
import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
|
|
9
11
|
import {
|
|
10
12
|
GuardedMerkleTreeOperations,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
PublicProcessor,
|
|
13
15
|
createPublicTxSimulatorForBlockBuilding,
|
|
14
16
|
} from '@aztec/simulator/server';
|
|
15
|
-
import {
|
|
17
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
16
18
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
19
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
18
20
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
@@ -23,31 +25,27 @@ import {
|
|
|
23
25
|
FullNodeBlockBuilderConfigKeys,
|
|
24
26
|
type ICheckpointBlockBuilder,
|
|
25
27
|
type ICheckpointsBuilder,
|
|
28
|
+
InsufficientValidTxsError,
|
|
26
29
|
type MerkleTreeWriteOperations,
|
|
27
30
|
type PublicProcessorLimits,
|
|
28
31
|
type WorldStateSynchronizer,
|
|
29
32
|
} from '@aztec/stdlib/interfaces/server';
|
|
33
|
+
import { type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
30
34
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
31
35
|
import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
32
36
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
33
|
-
|
|
34
|
-
import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_factory.js';
|
|
37
|
+
import { ForkCheckpoint } from '@aztec/world-state';
|
|
35
38
|
|
|
36
39
|
// Re-export for backward compatibility
|
|
37
40
|
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
38
41
|
|
|
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
42
|
/**
|
|
47
43
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
48
44
|
* and completing it.
|
|
49
45
|
*/
|
|
50
46
|
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
47
|
+
private log: Logger;
|
|
48
|
+
|
|
51
49
|
constructor(
|
|
52
50
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
53
51
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -55,7 +53,14 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
55
53
|
private contractDataSource: ContractDataSource,
|
|
56
54
|
private dateProvider: DateProvider,
|
|
57
55
|
private telemetryClient: TelemetryClient,
|
|
58
|
-
|
|
56
|
+
bindings?: LoggerBindings,
|
|
57
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
58
|
+
) {
|
|
59
|
+
this.log = createLogger('checkpoint-builder', {
|
|
60
|
+
...bindings,
|
|
61
|
+
instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
getConstantData(): CheckpointGlobalVariables {
|
|
61
66
|
return this.checkpointBuilder.constants;
|
|
@@ -63,17 +68,17 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
63
68
|
|
|
64
69
|
/**
|
|
65
70
|
* Builds a single block within this checkpoint.
|
|
71
|
+
* Automatically caps gas and blob field limits based on checkpoint-level budgets and prior blocks.
|
|
66
72
|
*/
|
|
67
73
|
async buildBlock(
|
|
68
74
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
69
75
|
blockNumber: BlockNumber,
|
|
70
76
|
timestamp: bigint,
|
|
71
|
-
opts: PublicProcessorLimits & { expectedEndState?: StateReference },
|
|
72
|
-
): Promise<
|
|
73
|
-
const blockBuildingTimer = new Timer();
|
|
77
|
+
opts: PublicProcessorLimits & { expectedEndState?: StateReference; minValidTxs?: number } = {},
|
|
78
|
+
): Promise<BuildBlockInCheckpointResult> {
|
|
74
79
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
75
80
|
|
|
76
|
-
log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
81
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
77
82
|
slot,
|
|
78
83
|
blockNumber,
|
|
79
84
|
...opts,
|
|
@@ -93,37 +98,60 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
93
98
|
});
|
|
94
99
|
const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, this.fork);
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Add block to checkpoint
|
|
101
|
-
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
102
|
-
expectedEndState: opts.expectedEndState,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// How much public gas was processed
|
|
106
|
-
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
107
|
-
|
|
108
|
-
const res = {
|
|
109
|
-
block,
|
|
110
|
-
publicGas,
|
|
111
|
-
publicProcessorDuration,
|
|
112
|
-
numTxs: processedTxs.length,
|
|
113
|
-
failedTxs,
|
|
114
|
-
blockBuildingTimer,
|
|
115
|
-
usedTxs,
|
|
116
|
-
usedTxBlobFields,
|
|
101
|
+
// Cap gas limits amd available blob fields by remaining checkpoint-level budgets
|
|
102
|
+
const cappedOpts: PublicProcessorLimits & { expectedEndState?: StateReference } = {
|
|
103
|
+
...opts,
|
|
104
|
+
...this.capLimitsByCheckpointBudgets(opts),
|
|
117
105
|
};
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
|
|
107
|
+
// We execute all merkle tree operations on a world state fork checkpoint
|
|
108
|
+
// This enables us to discard all modifications in the event that we fail to successfully process sufficient transactions
|
|
109
|
+
const forkCheckpoint = await ForkCheckpoint.new(this.fork);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
113
|
+
processor.process(pendingTxs, cappedOpts, validator),
|
|
114
|
+
);
|
|
115
|
+
// Throw before updating state if we don't have enough valid txs
|
|
116
|
+
const minValidTxs = opts.minValidTxs ?? 0;
|
|
117
|
+
if (processedTxs.length < minValidTxs) {
|
|
118
|
+
throw new InsufficientValidTxsError(processedTxs.length, minValidTxs, failedTxs);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Commit the fork checkpoint
|
|
122
|
+
await forkCheckpoint.commit();
|
|
123
|
+
|
|
124
|
+
// Add block to checkpoint
|
|
125
|
+
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
126
|
+
expectedEndState: opts.expectedEndState,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.log.debug('Built block within checkpoint', {
|
|
130
|
+
header: block.header.toInspect(),
|
|
131
|
+
processedTxs: processedTxs.map(tx => tx.hash.toString()),
|
|
132
|
+
failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
block,
|
|
137
|
+
publicProcessorDuration,
|
|
138
|
+
numTxs: processedTxs.length,
|
|
139
|
+
failedTxs,
|
|
140
|
+
usedTxs,
|
|
141
|
+
};
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// If we reached the point of committing the checkpoint, this does nothing
|
|
144
|
+
// Otherwise it reverts any changes made to the fork for this failed block
|
|
145
|
+
await forkCheckpoint.revert();
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
120
148
|
}
|
|
121
149
|
|
|
122
150
|
/** Completes the checkpoint and returns it. */
|
|
123
151
|
async completeCheckpoint(): Promise<Checkpoint> {
|
|
124
152
|
const checkpoint = await this.checkpointBuilder.completeCheckpoint();
|
|
125
153
|
|
|
126
|
-
log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
154
|
+
this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
127
155
|
checkpointNumber: checkpoint.number,
|
|
128
156
|
numBlocks: checkpoint.blocks.length,
|
|
129
157
|
archiveRoot: checkpoint.archive.root.toString(),
|
|
@@ -137,16 +165,79 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
137
165
|
return this.checkpointBuilder.clone().completeCheckpoint();
|
|
138
166
|
}
|
|
139
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Caps per-block gas and blob field limits by remaining checkpoint-level budgets.
|
|
170
|
+
* Computes remaining L2 gas (mana), DA gas, and blob fields from blocks already added to the checkpoint,
|
|
171
|
+
* then returns opts with maxBlockGas and maxBlobFields capped accordingly.
|
|
172
|
+
*/
|
|
173
|
+
protected capLimitsByCheckpointBudgets(
|
|
174
|
+
opts: PublicProcessorLimits,
|
|
175
|
+
): Pick<PublicProcessorLimits, 'maxBlockGas' | 'maxBlobFields' | 'maxTransactions'> {
|
|
176
|
+
const existingBlocks = this.checkpointBuilder.getBlocks();
|
|
177
|
+
|
|
178
|
+
// Remaining L2 gas (mana)
|
|
179
|
+
// IMPORTANT: This assumes mana is computed solely based on L2 gas used in transactions.
|
|
180
|
+
// This may change in the future.
|
|
181
|
+
const usedMana = sum(existingBlocks.map(b => b.header.totalManaUsed.toNumber()));
|
|
182
|
+
const remainingMana = this.config.rollupManaLimit - usedMana;
|
|
183
|
+
|
|
184
|
+
// Remaining DA gas
|
|
185
|
+
const usedDAGas = sum(existingBlocks.map(b => b.computeDAGasUsed())) ?? 0;
|
|
186
|
+
const remainingDAGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT - usedDAGas;
|
|
187
|
+
|
|
188
|
+
// Remaining blob fields (block blob fields include both tx data and block-end overhead)
|
|
189
|
+
const usedBlobFields = sum(existingBlocks.map(b => b.toBlobFields().length));
|
|
190
|
+
const totalBlobCapacity = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
191
|
+
const isFirstBlock = existingBlocks.length === 0;
|
|
192
|
+
const blockEndOverhead = getNumBlockEndBlobFields(isFirstBlock);
|
|
193
|
+
const maxBlobFieldsForTxs = totalBlobCapacity - usedBlobFields - blockEndOverhead;
|
|
194
|
+
|
|
195
|
+
// Cap L2 gas by remaining checkpoint mana
|
|
196
|
+
const cappedL2Gas = Math.min(opts.maxBlockGas?.l2Gas ?? remainingMana, remainingMana);
|
|
197
|
+
|
|
198
|
+
// Cap DA gas by remaining checkpoint DA gas budget
|
|
199
|
+
const cappedDAGas = Math.min(opts.maxBlockGas?.daGas ?? remainingDAGas, remainingDAGas);
|
|
200
|
+
|
|
201
|
+
// Cap blob fields by remaining checkpoint blob capacity
|
|
202
|
+
const cappedBlobFields =
|
|
203
|
+
opts.maxBlobFields !== undefined ? Math.min(opts.maxBlobFields, maxBlobFieldsForTxs) : maxBlobFieldsForTxs;
|
|
204
|
+
|
|
205
|
+
// Cap transaction count by remaining checkpoint tx budget
|
|
206
|
+
let cappedMaxTransactions: number | undefined;
|
|
207
|
+
if (this.config.maxTxsPerCheckpoint !== undefined) {
|
|
208
|
+
const usedTxs = sum(existingBlocks.map(b => b.body.txEffects.length));
|
|
209
|
+
const remainingTxs = Math.max(0, this.config.maxTxsPerCheckpoint - usedTxs);
|
|
210
|
+
cappedMaxTransactions =
|
|
211
|
+
opts.maxTransactions !== undefined ? Math.min(opts.maxTransactions, remainingTxs) : remainingTxs;
|
|
212
|
+
} else {
|
|
213
|
+
cappedMaxTransactions = opts.maxTransactions;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
maxBlockGas: new Gas(cappedDAGas, cappedL2Gas),
|
|
218
|
+
maxBlobFields: cappedBlobFields,
|
|
219
|
+
maxTransactions: cappedMaxTransactions,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
140
223
|
protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
|
|
141
|
-
const txPublicSetupAllowList =
|
|
142
|
-
|
|
224
|
+
const txPublicSetupAllowList = [
|
|
225
|
+
...(await getDefaultAllowedSetupFunctions()),
|
|
226
|
+
...(this.config.txPublicSetupAllowListExtend ?? []),
|
|
227
|
+
];
|
|
228
|
+
const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
|
|
143
229
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
144
230
|
|
|
231
|
+
const collectDebugLogs = this.debugLogStore.isEnabled;
|
|
232
|
+
|
|
233
|
+
const bindings = this.log.getBindings();
|
|
145
234
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
146
235
|
guardedFork,
|
|
147
236
|
contractsDB,
|
|
148
237
|
globalVariables,
|
|
149
238
|
this.telemetryClient,
|
|
239
|
+
bindings,
|
|
240
|
+
collectDebugLogs,
|
|
150
241
|
);
|
|
151
242
|
|
|
152
243
|
const processor = new PublicProcessor(
|
|
@@ -156,15 +247,17 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
156
247
|
publicTxSimulator,
|
|
157
248
|
this.dateProvider,
|
|
158
249
|
this.telemetryClient,
|
|
159
|
-
|
|
250
|
+
createLogger('simulator:public-processor', bindings),
|
|
160
251
|
this.config,
|
|
252
|
+
this.debugLogStore,
|
|
161
253
|
);
|
|
162
254
|
|
|
163
|
-
const validator =
|
|
255
|
+
const validator = createTxValidatorForBlockBuilding(
|
|
164
256
|
fork,
|
|
165
257
|
this.contractDataSource,
|
|
166
258
|
globalVariables,
|
|
167
259
|
txPublicSetupAllowList,
|
|
260
|
+
this.log.getBindings(),
|
|
168
261
|
);
|
|
169
262
|
|
|
170
263
|
return {
|
|
@@ -176,13 +269,18 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
176
269
|
|
|
177
270
|
/** Factory for creating checkpoint builders. */
|
|
178
271
|
export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
272
|
+
private log: Logger;
|
|
273
|
+
|
|
179
274
|
constructor(
|
|
180
275
|
private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
|
|
181
276
|
private worldState: WorldStateSynchronizer,
|
|
182
277
|
private contractDataSource: ContractDataSource,
|
|
183
278
|
private dateProvider: DateProvider,
|
|
184
279
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
185
|
-
|
|
280
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
281
|
+
) {
|
|
282
|
+
this.log = createLogger('checkpoint-builder');
|
|
283
|
+
}
|
|
186
284
|
|
|
187
285
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
188
286
|
return this.config;
|
|
@@ -198,19 +296,22 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
198
296
|
async startCheckpoint(
|
|
199
297
|
checkpointNumber: CheckpointNumber,
|
|
200
298
|
constants: CheckpointGlobalVariables,
|
|
299
|
+
feeAssetPriceModifier: bigint,
|
|
201
300
|
l1ToL2Messages: Fr[],
|
|
202
301
|
previousCheckpointOutHashes: Fr[],
|
|
203
302
|
fork: MerkleTreeWriteOperations,
|
|
303
|
+
bindings?: LoggerBindings,
|
|
204
304
|
): Promise<CheckpointBuilder> {
|
|
205
305
|
const stateReference = await fork.getStateReference();
|
|
206
306
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
207
307
|
|
|
208
|
-
log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
308
|
+
this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
209
309
|
checkpointNumber,
|
|
210
310
|
msgCount: l1ToL2Messages.length,
|
|
211
311
|
initialStateReference: stateReference.toInspect(),
|
|
212
312
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
213
313
|
constants,
|
|
314
|
+
feeAssetPriceModifier,
|
|
214
315
|
});
|
|
215
316
|
|
|
216
317
|
const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
|
|
@@ -219,6 +320,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
219
320
|
l1ToL2Messages,
|
|
220
321
|
previousCheckpointOutHashes,
|
|
221
322
|
fork,
|
|
323
|
+
bindings,
|
|
324
|
+
feeAssetPriceModifier,
|
|
222
325
|
);
|
|
223
326
|
|
|
224
327
|
return new CheckpointBuilder(
|
|
@@ -228,6 +331,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
228
331
|
this.contractDataSource,
|
|
229
332
|
this.dateProvider,
|
|
230
333
|
this.telemetryClient,
|
|
334
|
+
bindings,
|
|
335
|
+
this.debugLogStore,
|
|
231
336
|
);
|
|
232
337
|
}
|
|
233
338
|
|
|
@@ -237,34 +342,47 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
237
342
|
async openCheckpoint(
|
|
238
343
|
checkpointNumber: CheckpointNumber,
|
|
239
344
|
constants: CheckpointGlobalVariables,
|
|
345
|
+
feeAssetPriceModifier: bigint,
|
|
240
346
|
l1ToL2Messages: Fr[],
|
|
241
347
|
previousCheckpointOutHashes: Fr[],
|
|
242
348
|
fork: MerkleTreeWriteOperations,
|
|
243
|
-
existingBlocks:
|
|
349
|
+
existingBlocks: L2Block[] = [],
|
|
350
|
+
bindings?: LoggerBindings,
|
|
244
351
|
): Promise<CheckpointBuilder> {
|
|
245
352
|
const stateReference = await fork.getStateReference();
|
|
246
353
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
247
354
|
|
|
248
355
|
if (existingBlocks.length === 0) {
|
|
249
|
-
return this.startCheckpoint(
|
|
356
|
+
return this.startCheckpoint(
|
|
357
|
+
checkpointNumber,
|
|
358
|
+
constants,
|
|
359
|
+
feeAssetPriceModifier,
|
|
360
|
+
l1ToL2Messages,
|
|
361
|
+
previousCheckpointOutHashes,
|
|
362
|
+
fork,
|
|
363
|
+
bindings,
|
|
364
|
+
);
|
|
250
365
|
}
|
|
251
366
|
|
|
252
|
-
log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
367
|
+
this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
253
368
|
checkpointNumber,
|
|
254
369
|
msgCount: l1ToL2Messages.length,
|
|
255
370
|
existingBlockCount: existingBlocks.length,
|
|
256
371
|
initialStateReference: stateReference.toInspect(),
|
|
257
372
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
258
373
|
constants,
|
|
374
|
+
feeAssetPriceModifier,
|
|
259
375
|
});
|
|
260
376
|
|
|
261
377
|
const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
|
|
262
378
|
checkpointNumber,
|
|
263
379
|
constants,
|
|
380
|
+
feeAssetPriceModifier,
|
|
264
381
|
l1ToL2Messages,
|
|
265
382
|
previousCheckpointOutHashes,
|
|
266
383
|
fork,
|
|
267
384
|
existingBlocks,
|
|
385
|
+
bindings,
|
|
268
386
|
);
|
|
269
387
|
|
|
270
388
|
return new CheckpointBuilder(
|
|
@@ -274,6 +392,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
274
392
|
this.contractDataSource,
|
|
275
393
|
this.dateProvider,
|
|
276
394
|
this.telemetryClient,
|
|
395
|
+
bindings,
|
|
396
|
+
this.debugLogStore,
|
|
277
397
|
);
|
|
278
398
|
}
|
|
279
399
|
|
package/src/config.ts
CHANGED
|
@@ -65,15 +65,37 @@ 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
|
-
// TODO(palla/mbps): Change default to false once block sync is stable
|
|
74
72
|
skipPushProposedBlocksToArchiver: {
|
|
75
|
-
description: 'Skip pushing re-executed blocks to archiver (default:
|
|
76
|
-
defaultValue:
|
|
73
|
+
description: 'Skip pushing re-executed blocks to archiver (default: false)',
|
|
74
|
+
defaultValue: false,
|
|
75
|
+
},
|
|
76
|
+
attestToEquivocatedProposals: {
|
|
77
|
+
description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
|
|
78
|
+
...booleanConfigHelper(false),
|
|
79
|
+
},
|
|
80
|
+
validateMaxL2BlockGas: {
|
|
81
|
+
env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
|
|
82
|
+
description: 'Maximum L2 block gas for validation. Proposals exceeding this limit are rejected.',
|
|
83
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
84
|
+
},
|
|
85
|
+
validateMaxDABlockGas: {
|
|
86
|
+
env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
|
|
87
|
+
description: 'Maximum DA block gas for validation. Proposals exceeding this limit are rejected.',
|
|
88
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
89
|
+
},
|
|
90
|
+
validateMaxTxsPerBlock: {
|
|
91
|
+
env: 'VALIDATOR_MAX_TX_PER_BLOCK',
|
|
92
|
+
description: 'Maximum transactions per block for validation. Proposals exceeding this limit are rejected.',
|
|
93
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
94
|
+
},
|
|
95
|
+
validateMaxTxsPerCheckpoint: {
|
|
96
|
+
env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
|
|
97
|
+
description: 'Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected.',
|
|
98
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
77
99
|
},
|
|
78
100
|
...validatorHASignerConfigMappings,
|
|
79
101
|
};
|
|
@@ -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,22 +144,16 @@ 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
|
);
|
|
144
151
|
|
|
145
152
|
// TODO(spy/ha): Use checkpointNumber instead of blockNumber once CheckpointHeader includes it.
|
|
146
|
-
//
|
|
153
|
+
// CheckpointProposalCore doesn't have lastBlock info, so use 0 as a proxy.
|
|
147
154
|
// blockNumber is NOT used for the primary key so it's safe to use here.
|
|
148
155
|
// See CheckpointHeader TODO and SigningContext types documentation.
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
blockNumber = proposal.blockNumber;
|
|
152
|
-
} catch {
|
|
153
|
-
// Checkpoint proposal may not have lastBlock, use 0 as fallback
|
|
154
|
-
blockNumber = BlockNumber(0);
|
|
155
|
-
}
|
|
156
|
+
const blockNumber = BlockNumber(0);
|
|
156
157
|
const context: SigningContext = {
|
|
157
158
|
slot: proposal.slotNumber,
|
|
158
159
|
blockNumber,
|
|
@@ -176,7 +177,7 @@ export class ValidationService {
|
|
|
176
177
|
} else {
|
|
177
178
|
const error = result.reason;
|
|
178
179
|
if (error instanceof DutyAlreadySignedError || error instanceof SlashingProtectionError) {
|
|
179
|
-
this.log.
|
|
180
|
+
this.log.verbose(
|
|
180
181
|
`Attestation for slot ${proposal.slotNumber} by ${attestors[i]} already signed by another High-Availability node`,
|
|
181
182
|
);
|
|
182
183
|
// Continue with remaining attestors
|
package/src/factory.ts
CHANGED
|
@@ -29,6 +29,7 @@ export function createBlockProposalHandler(
|
|
|
29
29
|
const metrics = new ValidatorMetrics(deps.telemetry);
|
|
30
30
|
const blockProposalValidator = new BlockProposalValidator(deps.epochCache, {
|
|
31
31
|
txsPermitted: !config.disableTransactions,
|
|
32
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
|
|
32
33
|
});
|
|
33
34
|
return new BlockProposalHandler(
|
|
34
35
|
deps.checkpointsBuilder,
|
package/src/index.ts
CHANGED
|
@@ -240,7 +240,7 @@ export class HAKeyStore implements ExtendedValidatorKeyStore {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
if (error instanceof SlashingProtectionError) {
|
|
243
|
-
this.log.
|
|
243
|
+
this.log.info(`Duty already signed by another node with different payload`, {
|
|
244
244
|
dutyType: context.dutyType,
|
|
245
245
|
slot: context.slot,
|
|
246
246
|
existingMessageHash: error.existingMessageHash,
|
|
@@ -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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EpochNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
1
3
|
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
2
4
|
import {
|
|
3
5
|
Attributes,
|
|
@@ -6,13 +8,18 @@ import {
|
|
|
6
8
|
Metrics,
|
|
7
9
|
type TelemetryClient,
|
|
8
10
|
type UpDownCounter,
|
|
11
|
+
createUpDownCounterWithDefault,
|
|
9
12
|
} from '@aztec/telemetry-client';
|
|
10
13
|
|
|
14
|
+
import type { BlockProposalValidationFailureReason } from './block_proposal_handler.js';
|
|
15
|
+
|
|
11
16
|
export class ValidatorMetrics {
|
|
12
17
|
private failedReexecutionCounter: UpDownCounter;
|
|
13
18
|
private successfulAttestationsCount: UpDownCounter;
|
|
14
19
|
private failedAttestationsBadProposalCount: UpDownCounter;
|
|
15
20
|
private failedAttestationsNodeIssueCount: UpDownCounter;
|
|
21
|
+
private currentEpoch: Gauge;
|
|
22
|
+
private attestedEpochCount: UpDownCounter;
|
|
16
23
|
|
|
17
24
|
private reexMana: Histogram;
|
|
18
25
|
private reexTx: Histogram;
|
|
@@ -21,18 +28,50 @@ export class ValidatorMetrics {
|
|
|
21
28
|
constructor(telemetryClient: TelemetryClient) {
|
|
22
29
|
const meter = telemetryClient.getMeter('Validator');
|
|
23
30
|
|
|
24
|
-
this.failedReexecutionCounter = meter
|
|
31
|
+
this.failedReexecutionCounter = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT, {
|
|
32
|
+
[Attributes.STATUS]: ['failed'],
|
|
33
|
+
});
|
|
25
34
|
|
|
26
|
-
this.successfulAttestationsCount =
|
|
35
|
+
this.successfulAttestationsCount = createUpDownCounterWithDefault(
|
|
36
|
+
meter,
|
|
37
|
+
Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT,
|
|
38
|
+
);
|
|
27
39
|
|
|
28
|
-
this.failedAttestationsBadProposalCount =
|
|
40
|
+
this.failedAttestationsBadProposalCount = createUpDownCounterWithDefault(
|
|
41
|
+
meter,
|
|
29
42
|
Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
|
|
43
|
+
{
|
|
44
|
+
[Attributes.ERROR_TYPE]: [
|
|
45
|
+
'invalid_proposal',
|
|
46
|
+
'state_mismatch',
|
|
47
|
+
'failed_txs',
|
|
48
|
+
'in_hash_mismatch',
|
|
49
|
+
'parent_block_wrong_slot',
|
|
50
|
+
],
|
|
51
|
+
[Attributes.IS_COMMITTEE_MEMBER]: [true, false],
|
|
52
|
+
},
|
|
30
53
|
);
|
|
31
54
|
|
|
32
|
-
this.failedAttestationsNodeIssueCount =
|
|
55
|
+
this.failedAttestationsNodeIssueCount = createUpDownCounterWithDefault(
|
|
56
|
+
meter,
|
|
33
57
|
Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
|
|
58
|
+
{
|
|
59
|
+
[Attributes.ERROR_TYPE]: [
|
|
60
|
+
'parent_block_not_found',
|
|
61
|
+
'global_variables_mismatch',
|
|
62
|
+
'block_number_already_exists',
|
|
63
|
+
'txs_not_available',
|
|
64
|
+
'timeout',
|
|
65
|
+
'unknown_error',
|
|
66
|
+
],
|
|
67
|
+
[Attributes.IS_COMMITTEE_MEMBER]: [true, false],
|
|
68
|
+
},
|
|
34
69
|
);
|
|
35
70
|
|
|
71
|
+
this.currentEpoch = meter.createGauge(Metrics.VALIDATOR_CURRENT_EPOCH);
|
|
72
|
+
|
|
73
|
+
this.attestedEpochCount = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_ATTESTED_EPOCH_COUNT);
|
|
74
|
+
|
|
36
75
|
this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
|
|
37
76
|
|
|
38
77
|
this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT);
|
|
@@ -58,17 +97,35 @@ export class ValidatorMetrics {
|
|
|
58
97
|
this.successfulAttestationsCount.add(num);
|
|
59
98
|
}
|
|
60
99
|
|
|
61
|
-
public incFailedAttestationsBadProposal(
|
|
100
|
+
public incFailedAttestationsBadProposal(
|
|
101
|
+
num: number,
|
|
102
|
+
reason: BlockProposalValidationFailureReason,
|
|
103
|
+
inCommittee: boolean,
|
|
104
|
+
) {
|
|
62
105
|
this.failedAttestationsBadProposalCount.add(num, {
|
|
63
106
|
[Attributes.ERROR_TYPE]: reason,
|
|
64
107
|
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
65
108
|
});
|
|
66
109
|
}
|
|
67
110
|
|
|
68
|
-
public incFailedAttestationsNodeIssue(
|
|
111
|
+
public incFailedAttestationsNodeIssue(
|
|
112
|
+
num: number,
|
|
113
|
+
reason: BlockProposalValidationFailureReason,
|
|
114
|
+
inCommittee: boolean,
|
|
115
|
+
) {
|
|
69
116
|
this.failedAttestationsNodeIssueCount.add(num, {
|
|
70
117
|
[Attributes.ERROR_TYPE]: reason,
|
|
71
118
|
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
72
119
|
});
|
|
73
120
|
}
|
|
121
|
+
|
|
122
|
+
/** Update the gauge tracking the current epoch number (proxy for total epochs elapsed). */
|
|
123
|
+
public setCurrentEpoch(epoch: EpochNumber) {
|
|
124
|
+
this.currentEpoch.record(Number(epoch));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Increment the count of epochs in which the given attester submitted at least one attestation. */
|
|
128
|
+
public incAttestedEpochCount(attester: EthAddress) {
|
|
129
|
+
this.attestedEpochCount.add(1, { [Attributes.ATTESTER_ADDRESS]: attester.toString() });
|
|
130
|
+
}
|
|
74
131
|
}
|