@aztec/validator-client 0.0.1-commit.7d4e6cd → 0.0.1-commit.7ffbba4
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 +95 -24
- package/dest/block_proposal_handler.d.ts +10 -10
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +76 -76
- package/dest/checkpoint_builder.d.ts +31 -25
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +114 -41
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +33 -14
- package/dest/duties/validation_service.d.ts +20 -7
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +69 -22
- package/dest/factory.d.ts +2 -2
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +3 -2
- 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 +99 -0
- package/dest/key_store/ha_key_store.d.ts.map +1 -0
- package/dest/key_store/ha_key_store.js +208 -0
- package/dest/key_store/index.d.ts +2 -1
- package/dest/key_store/index.d.ts.map +1 -1
- package/dest/key_store/index.js +1 -0
- package/dest/key_store/interface.d.ts +36 -6
- package/dest/key_store/interface.d.ts.map +1 -1
- package/dest/key_store/local_key_store.d.ts +10 -5
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +8 -4
- package/dest/key_store/node_keystore_adapter.d.ts +18 -5
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
- package/dest/key_store/node_keystore_adapter.js +18 -4
- package/dest/key_store/web3signer_key_store.d.ts +10 -5
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +8 -4
- 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 +45 -18
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +262 -98
- package/package.json +21 -17
- package/src/block_proposal_handler.ts +93 -95
- package/src/checkpoint_builder.ts +171 -48
- package/src/config.ts +32 -13
- package/src/duties/validation_service.ts +94 -25
- package/src/factory.ts +2 -0
- package/src/index.ts +0 -1
- package/src/key_store/ha_key_store.ts +269 -0
- package/src/key_store/index.ts +1 -0
- package/src/key_store/interface.ts +44 -5
- package/src/key_store/local_key_store.ts +13 -4
- package/src/key_store/node_keystore_adapter.ts +27 -4
- package/src/key_store/web3signer_key_store.ts +17 -4
- package/src/metrics.ts +63 -6
- package/src/validator.ts +326 -116
- 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 -53
- 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 -133
|
@@ -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,39 +14,37 @@ 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';
|
|
20
|
+
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
18
21
|
import { Gas } from '@aztec/stdlib/gas';
|
|
19
22
|
import {
|
|
23
|
+
type BuildBlockInCheckpointResult,
|
|
20
24
|
type FullNodeBlockBuilderConfig,
|
|
21
25
|
FullNodeBlockBuilderConfigKeys,
|
|
26
|
+
type ICheckpointBlockBuilder,
|
|
27
|
+
type ICheckpointsBuilder,
|
|
22
28
|
type MerkleTreeWriteOperations,
|
|
29
|
+
NoValidTxsError,
|
|
23
30
|
type PublicProcessorLimits,
|
|
31
|
+
type WorldStateSynchronizer,
|
|
24
32
|
} from '@aztec/stdlib/interfaces/server';
|
|
33
|
+
import { type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
25
34
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
26
|
-
import { type CheckpointGlobalVariables,
|
|
35
|
+
import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
27
36
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const log = createLogger('checkpoint-builder');
|
|
32
|
-
|
|
33
|
-
export interface BuildBlockInCheckpointResult {
|
|
34
|
-
block: L2BlockNew;
|
|
35
|
-
publicGas: Gas;
|
|
36
|
-
publicProcessorDuration: number;
|
|
37
|
-
numTxs: number;
|
|
38
|
-
failedTxs: FailedTx[];
|
|
39
|
-
blockBuildingTimer: Timer;
|
|
40
|
-
usedTxs: Tx[];
|
|
41
|
-
}
|
|
38
|
+
// Re-export for backward compatibility
|
|
39
|
+
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
45
43
|
* and completing it.
|
|
46
44
|
*/
|
|
47
|
-
export class CheckpointBuilder {
|
|
45
|
+
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
46
|
+
private log: Logger;
|
|
47
|
+
|
|
48
48
|
constructor(
|
|
49
49
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
50
50
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -52,7 +52,14 @@ export class CheckpointBuilder {
|
|
|
52
52
|
private contractDataSource: ContractDataSource,
|
|
53
53
|
private dateProvider: DateProvider,
|
|
54
54
|
private telemetryClient: TelemetryClient,
|
|
55
|
-
|
|
55
|
+
bindings?: LoggerBindings,
|
|
56
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
57
|
+
) {
|
|
58
|
+
this.log = createLogger('checkpoint-builder', {
|
|
59
|
+
...bindings,
|
|
60
|
+
instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
56
63
|
|
|
57
64
|
getConstantData(): CheckpointGlobalVariables {
|
|
58
65
|
return this.checkpointBuilder.constants;
|
|
@@ -60,17 +67,22 @@ export class CheckpointBuilder {
|
|
|
60
67
|
|
|
61
68
|
/**
|
|
62
69
|
* Builds a single block within this checkpoint.
|
|
70
|
+
* Automatically caps gas and blob field limits based on checkpoint-level budgets and prior blocks.
|
|
63
71
|
*/
|
|
64
72
|
async buildBlock(
|
|
65
73
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
66
74
|
blockNumber: BlockNumber,
|
|
67
75
|
timestamp: bigint,
|
|
68
|
-
opts: PublicProcessorLimits & { expectedEndState?: StateReference },
|
|
76
|
+
opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
|
|
69
77
|
): Promise<BuildBlockInCheckpointResult> {
|
|
70
|
-
const blockBuildingTimer = new Timer();
|
|
71
78
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
72
79
|
|
|
73
|
-
log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
80
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
81
|
+
slot,
|
|
82
|
+
blockNumber,
|
|
83
|
+
...opts,
|
|
84
|
+
currentTime: new Date(this.dateProvider.now()),
|
|
85
|
+
});
|
|
74
86
|
|
|
75
87
|
const constants = this.checkpointBuilder.constants;
|
|
76
88
|
const globalVariables = GlobalVariables.from({
|
|
@@ -85,36 +97,47 @@ export class CheckpointBuilder {
|
|
|
85
97
|
});
|
|
86
98
|
const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, this.fork);
|
|
87
99
|
|
|
100
|
+
// Cap gas limits amd available blob fields by remaining checkpoint-level budgets
|
|
101
|
+
const cappedOpts: PublicProcessorLimits & { expectedEndState?: StateReference } = {
|
|
102
|
+
...opts,
|
|
103
|
+
...this.capLimitsByCheckpointBudgets(opts),
|
|
104
|
+
};
|
|
105
|
+
|
|
88
106
|
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
89
|
-
processor.process(pendingTxs,
|
|
107
|
+
processor.process(pendingTxs, cappedOpts, validator),
|
|
90
108
|
);
|
|
91
109
|
|
|
110
|
+
// Throw if we didn't collect a single valid tx and we're not allowed to build empty blocks
|
|
111
|
+
// (only the first block in a checkpoint can be empty)
|
|
112
|
+
if (processedTxs.length === 0 && this.checkpointBuilder.getBlockCount() > 0) {
|
|
113
|
+
throw new NoValidTxsError(failedTxs);
|
|
114
|
+
}
|
|
115
|
+
|
|
92
116
|
// Add block to checkpoint
|
|
93
|
-
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
117
|
+
const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
94
118
|
expectedEndState: opts.expectedEndState,
|
|
95
119
|
});
|
|
96
120
|
|
|
97
|
-
|
|
98
|
-
|
|
121
|
+
this.log.debug('Built block within checkpoint', {
|
|
122
|
+
header: block.header.toInspect(),
|
|
123
|
+
processedTxs: processedTxs.map(tx => tx.hash.toString()),
|
|
124
|
+
failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
|
|
125
|
+
});
|
|
99
126
|
|
|
100
|
-
|
|
127
|
+
return {
|
|
101
128
|
block,
|
|
102
|
-
publicGas,
|
|
103
129
|
publicProcessorDuration,
|
|
104
130
|
numTxs: processedTxs.length,
|
|
105
131
|
failedTxs,
|
|
106
|
-
blockBuildingTimer,
|
|
107
132
|
usedTxs,
|
|
108
133
|
};
|
|
109
|
-
log.debug('Built block within checkpoint', res.block.header);
|
|
110
|
-
return res;
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
/** Completes the checkpoint and returns it. */
|
|
114
137
|
async completeCheckpoint(): Promise<Checkpoint> {
|
|
115
138
|
const checkpoint = await this.checkpointBuilder.completeCheckpoint();
|
|
116
139
|
|
|
117
|
-
log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
140
|
+
this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
118
141
|
checkpointNumber: checkpoint.number,
|
|
119
142
|
numBlocks: checkpoint.blocks.length,
|
|
120
143
|
archiveRoot: checkpoint.archive.root.toString(),
|
|
@@ -128,16 +151,79 @@ export class CheckpointBuilder {
|
|
|
128
151
|
return this.checkpointBuilder.clone().completeCheckpoint();
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Caps per-block gas and blob field limits by remaining checkpoint-level budgets.
|
|
156
|
+
* Computes remaining L2 gas (mana), DA gas, and blob fields from blocks already added to the checkpoint,
|
|
157
|
+
* then returns opts with maxBlockGas and maxBlobFields capped accordingly.
|
|
158
|
+
*/
|
|
159
|
+
protected capLimitsByCheckpointBudgets(
|
|
160
|
+
opts: PublicProcessorLimits,
|
|
161
|
+
): Pick<PublicProcessorLimits, 'maxBlockGas' | 'maxBlobFields' | 'maxTransactions'> {
|
|
162
|
+
const existingBlocks = this.checkpointBuilder.getBlocks();
|
|
163
|
+
|
|
164
|
+
// Remaining L2 gas (mana)
|
|
165
|
+
// IMPORTANT: This assumes mana is computed solely based on L2 gas used in transactions.
|
|
166
|
+
// This may change in the future.
|
|
167
|
+
const usedMana = sum(existingBlocks.map(b => b.header.totalManaUsed.toNumber()));
|
|
168
|
+
const remainingMana = this.config.rollupManaLimit - usedMana;
|
|
169
|
+
|
|
170
|
+
// Remaining DA gas
|
|
171
|
+
const usedDAGas = sum(existingBlocks.map(b => b.computeDAGasUsed())) ?? 0;
|
|
172
|
+
const remainingDAGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT - usedDAGas;
|
|
173
|
+
|
|
174
|
+
// Remaining blob fields (block blob fields include both tx data and block-end overhead)
|
|
175
|
+
const usedBlobFields = sum(existingBlocks.map(b => b.toBlobFields().length));
|
|
176
|
+
const totalBlobCapacity = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
177
|
+
const isFirstBlock = existingBlocks.length === 0;
|
|
178
|
+
const blockEndOverhead = getNumBlockEndBlobFields(isFirstBlock);
|
|
179
|
+
const maxBlobFieldsForTxs = totalBlobCapacity - usedBlobFields - blockEndOverhead;
|
|
180
|
+
|
|
181
|
+
// Cap L2 gas by remaining checkpoint mana
|
|
182
|
+
const cappedL2Gas = Math.min(opts.maxBlockGas?.l2Gas ?? remainingMana, remainingMana);
|
|
183
|
+
|
|
184
|
+
// Cap DA gas by remaining checkpoint DA gas budget
|
|
185
|
+
const cappedDAGas = Math.min(opts.maxBlockGas?.daGas ?? remainingDAGas, remainingDAGas);
|
|
186
|
+
|
|
187
|
+
// Cap blob fields by remaining checkpoint blob capacity
|
|
188
|
+
const cappedBlobFields =
|
|
189
|
+
opts.maxBlobFields !== undefined ? Math.min(opts.maxBlobFields, maxBlobFieldsForTxs) : maxBlobFieldsForTxs;
|
|
190
|
+
|
|
191
|
+
// Cap transaction count by remaining checkpoint tx budget
|
|
192
|
+
let cappedMaxTransactions: number | undefined;
|
|
193
|
+
if (this.config.maxTxsPerCheckpoint !== undefined) {
|
|
194
|
+
const usedTxs = sum(existingBlocks.map(b => b.body.txEffects.length));
|
|
195
|
+
const remainingTxs = Math.max(0, this.config.maxTxsPerCheckpoint - usedTxs);
|
|
196
|
+
cappedMaxTransactions =
|
|
197
|
+
opts.maxTransactions !== undefined ? Math.min(opts.maxTransactions, remainingTxs) : remainingTxs;
|
|
198
|
+
} else {
|
|
199
|
+
cappedMaxTransactions = opts.maxTransactions;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
maxBlockGas: new Gas(cappedDAGas, cappedL2Gas),
|
|
204
|
+
maxBlobFields: cappedBlobFields,
|
|
205
|
+
maxTransactions: cappedMaxTransactions,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
131
209
|
protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
|
|
132
|
-
const txPublicSetupAllowList =
|
|
133
|
-
|
|
210
|
+
const txPublicSetupAllowList = [
|
|
211
|
+
...(await getDefaultAllowedSetupFunctions()),
|
|
212
|
+
...(this.config.txPublicSetupAllowListExtend ?? []),
|
|
213
|
+
];
|
|
214
|
+
const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
|
|
134
215
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
135
216
|
|
|
217
|
+
const collectDebugLogs = this.debugLogStore.isEnabled;
|
|
218
|
+
|
|
219
|
+
const bindings = this.log.getBindings();
|
|
136
220
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
137
221
|
guardedFork,
|
|
138
222
|
contractsDB,
|
|
139
223
|
globalVariables,
|
|
140
224
|
this.telemetryClient,
|
|
225
|
+
bindings,
|
|
226
|
+
collectDebugLogs,
|
|
141
227
|
);
|
|
142
228
|
|
|
143
229
|
const processor = new PublicProcessor(
|
|
@@ -147,15 +233,17 @@ export class CheckpointBuilder {
|
|
|
147
233
|
publicTxSimulator,
|
|
148
234
|
this.dateProvider,
|
|
149
235
|
this.telemetryClient,
|
|
150
|
-
|
|
236
|
+
createLogger('simulator:public-processor', bindings),
|
|
151
237
|
this.config,
|
|
238
|
+
this.debugLogStore,
|
|
152
239
|
);
|
|
153
240
|
|
|
154
|
-
const validator =
|
|
241
|
+
const validator = createTxValidatorForBlockBuilding(
|
|
155
242
|
fork,
|
|
156
243
|
this.contractDataSource,
|
|
157
244
|
globalVariables,
|
|
158
245
|
txPublicSetupAllowList,
|
|
246
|
+
this.log.getBindings(),
|
|
159
247
|
);
|
|
160
248
|
|
|
161
249
|
return {
|
|
@@ -165,16 +253,20 @@ export class CheckpointBuilder {
|
|
|
165
253
|
}
|
|
166
254
|
}
|
|
167
255
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
256
|
+
/** Factory for creating checkpoint builders. */
|
|
257
|
+
export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
258
|
+
private log: Logger;
|
|
259
|
+
|
|
172
260
|
constructor(
|
|
173
|
-
private config: FullNodeBlockBuilderConfig,
|
|
261
|
+
private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
|
|
262
|
+
private worldState: WorldStateSynchronizer,
|
|
174
263
|
private contractDataSource: ContractDataSource,
|
|
175
264
|
private dateProvider: DateProvider,
|
|
176
265
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
177
|
-
|
|
266
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
267
|
+
) {
|
|
268
|
+
this.log = createLogger('checkpoint-builder');
|
|
269
|
+
}
|
|
178
270
|
|
|
179
271
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
180
272
|
return this.config;
|
|
@@ -190,25 +282,32 @@ export class FullNodeCheckpointsBuilder {
|
|
|
190
282
|
async startCheckpoint(
|
|
191
283
|
checkpointNumber: CheckpointNumber,
|
|
192
284
|
constants: CheckpointGlobalVariables,
|
|
285
|
+
feeAssetPriceModifier: bigint,
|
|
193
286
|
l1ToL2Messages: Fr[],
|
|
287
|
+
previousCheckpointOutHashes: Fr[],
|
|
194
288
|
fork: MerkleTreeWriteOperations,
|
|
289
|
+
bindings?: LoggerBindings,
|
|
195
290
|
): Promise<CheckpointBuilder> {
|
|
196
291
|
const stateReference = await fork.getStateReference();
|
|
197
292
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
198
293
|
|
|
199
|
-
log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
294
|
+
this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
200
295
|
checkpointNumber,
|
|
201
296
|
msgCount: l1ToL2Messages.length,
|
|
202
297
|
initialStateReference: stateReference.toInspect(),
|
|
203
298
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
204
299
|
constants,
|
|
300
|
+
feeAssetPriceModifier,
|
|
205
301
|
});
|
|
206
302
|
|
|
207
303
|
const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
|
|
208
304
|
checkpointNumber,
|
|
209
305
|
constants,
|
|
210
306
|
l1ToL2Messages,
|
|
307
|
+
previousCheckpointOutHashes,
|
|
211
308
|
fork,
|
|
309
|
+
bindings,
|
|
310
|
+
feeAssetPriceModifier,
|
|
212
311
|
);
|
|
213
312
|
|
|
214
313
|
return new CheckpointBuilder(
|
|
@@ -218,6 +317,8 @@ export class FullNodeCheckpointsBuilder {
|
|
|
218
317
|
this.contractDataSource,
|
|
219
318
|
this.dateProvider,
|
|
220
319
|
this.telemetryClient,
|
|
320
|
+
bindings,
|
|
321
|
+
this.debugLogStore,
|
|
221
322
|
);
|
|
222
323
|
}
|
|
223
324
|
|
|
@@ -227,32 +328,47 @@ export class FullNodeCheckpointsBuilder {
|
|
|
227
328
|
async openCheckpoint(
|
|
228
329
|
checkpointNumber: CheckpointNumber,
|
|
229
330
|
constants: CheckpointGlobalVariables,
|
|
331
|
+
feeAssetPriceModifier: bigint,
|
|
230
332
|
l1ToL2Messages: Fr[],
|
|
333
|
+
previousCheckpointOutHashes: Fr[],
|
|
231
334
|
fork: MerkleTreeWriteOperations,
|
|
232
|
-
existingBlocks:
|
|
335
|
+
existingBlocks: L2Block[] = [],
|
|
336
|
+
bindings?: LoggerBindings,
|
|
233
337
|
): Promise<CheckpointBuilder> {
|
|
234
338
|
const stateReference = await fork.getStateReference();
|
|
235
339
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
236
340
|
|
|
237
341
|
if (existingBlocks.length === 0) {
|
|
238
|
-
return this.startCheckpoint(
|
|
342
|
+
return this.startCheckpoint(
|
|
343
|
+
checkpointNumber,
|
|
344
|
+
constants,
|
|
345
|
+
feeAssetPriceModifier,
|
|
346
|
+
l1ToL2Messages,
|
|
347
|
+
previousCheckpointOutHashes,
|
|
348
|
+
fork,
|
|
349
|
+
bindings,
|
|
350
|
+
);
|
|
239
351
|
}
|
|
240
352
|
|
|
241
|
-
log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
353
|
+
this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
242
354
|
checkpointNumber,
|
|
243
355
|
msgCount: l1ToL2Messages.length,
|
|
244
356
|
existingBlockCount: existingBlocks.length,
|
|
245
357
|
initialStateReference: stateReference.toInspect(),
|
|
246
358
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
247
359
|
constants,
|
|
360
|
+
feeAssetPriceModifier,
|
|
248
361
|
});
|
|
249
362
|
|
|
250
363
|
const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
|
|
251
364
|
checkpointNumber,
|
|
252
365
|
constants,
|
|
366
|
+
feeAssetPriceModifier,
|
|
253
367
|
l1ToL2Messages,
|
|
368
|
+
previousCheckpointOutHashes,
|
|
254
369
|
fork,
|
|
255
370
|
existingBlocks,
|
|
371
|
+
bindings,
|
|
256
372
|
);
|
|
257
373
|
|
|
258
374
|
return new CheckpointBuilder(
|
|
@@ -262,6 +378,13 @@ export class FullNodeCheckpointsBuilder {
|
|
|
262
378
|
this.contractDataSource,
|
|
263
379
|
this.dateProvider,
|
|
264
380
|
this.telemetryClient,
|
|
381
|
+
bindings,
|
|
382
|
+
this.debugLogStore,
|
|
265
383
|
);
|
|
266
384
|
}
|
|
385
|
+
|
|
386
|
+
/** Returns a fork of the world state at the given block number. */
|
|
387
|
+
getFork(blockNumber: BlockNumber): Promise<MerkleTreeWriteOperations> {
|
|
388
|
+
return this.worldState.fork(blockNumber);
|
|
389
|
+
}
|
|
267
390
|
}
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
secretValueConfigHelper,
|
|
7
7
|
} from '@aztec/foundation/config';
|
|
8
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
|
+
import { localSignerConfigMappings, validatorHASignerConfigMappings } from '@aztec/stdlib/ha-signing';
|
|
9
10
|
import type { ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
|
|
10
11
|
|
|
11
12
|
export type { ValidatorClientConfig };
|
|
@@ -53,16 +54,10 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
53
54
|
description: 'Re-execute transactions before attesting',
|
|
54
55
|
...booleanConfigHelper(true),
|
|
55
56
|
},
|
|
56
|
-
validatorReexecuteDeadlineMs: {
|
|
57
|
-
env: 'VALIDATOR_REEXECUTE_DEADLINE_MS',
|
|
58
|
-
description: 'Will re-execute until this many milliseconds are left in the slot',
|
|
59
|
-
...numberConfigHelper(6000),
|
|
60
|
-
},
|
|
61
57
|
alwaysReexecuteBlockProposals: {
|
|
62
|
-
env: 'ALWAYS_REEXECUTE_BLOCK_PROPOSALS',
|
|
63
58
|
description:
|
|
64
59
|
'Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status).',
|
|
65
|
-
|
|
60
|
+
defaultValue: true,
|
|
66
61
|
},
|
|
67
62
|
fishermanMode: {
|
|
68
63
|
env: 'FISHERMAN_MODE',
|
|
@@ -70,16 +65,40 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
70
65
|
'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
|
|
71
66
|
...booleanConfigHelper(false),
|
|
72
67
|
},
|
|
73
|
-
// TODO(palla/mbps): Change default to false once checkpoint validation is stable
|
|
74
68
|
skipCheckpointProposalValidation: {
|
|
75
|
-
description: 'Skip checkpoint proposal validation and always attest (default:
|
|
76
|
-
defaultValue:
|
|
69
|
+
description: 'Skip checkpoint proposal validation and always attest (default: false)',
|
|
70
|
+
defaultValue: false,
|
|
77
71
|
},
|
|
78
|
-
// TODO(palla/mbps): Change default to false once block sync is stable
|
|
79
72
|
skipPushProposedBlocksToArchiver: {
|
|
80
|
-
description: 'Skip pushing re-executed blocks to archiver (default:
|
|
81
|
-
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),
|
|
82
99
|
},
|
|
100
|
+
...localSignerConfigMappings,
|
|
101
|
+
...validatorHASignerConfigMappings,
|
|
83
102
|
};
|
|
84
103
|
|
|
85
104
|
/**
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlockNumber,
|
|
3
|
+
type CheckpointNumber,
|
|
4
|
+
IndexWithinCheckpoint,
|
|
5
|
+
type SlotNumber,
|
|
6
|
+
} from '@aztec/foundation/branded-types';
|
|
1
7
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
8
|
import { keccak256 } from '@aztec/foundation/crypto/keccak';
|
|
3
9
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
@@ -18,6 +24,8 @@ import {
|
|
|
18
24
|
} from '@aztec/stdlib/p2p';
|
|
19
25
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
20
26
|
import type { BlockHeader, Tx } from '@aztec/stdlib/tx';
|
|
27
|
+
import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validator-ha-signer/errors';
|
|
28
|
+
import { DutyType, type SigningContext } from '@aztec/validator-ha-signer/types';
|
|
21
29
|
|
|
22
30
|
import type { ValidatorKeyStore } from '../key_store/interface.js';
|
|
23
31
|
|
|
@@ -31,34 +39,40 @@ export class ValidationService {
|
|
|
31
39
|
* Create a block proposal with the given header, archive, and transactions
|
|
32
40
|
*
|
|
33
41
|
* @param blockHeader - The block header
|
|
34
|
-
* @param
|
|
42
|
+
* @param blockIndexWithinCheckpoint - The block index within checkpoint for HA signing context
|
|
35
43
|
* @param inHash - Hash of L1 to L2 messages for this checkpoint
|
|
36
44
|
* @param archive - The archive of the current block
|
|
37
|
-
* @param txs -
|
|
45
|
+
* @param txs - Ordered list of transactions (Tx[])
|
|
46
|
+
* @param proposerAttesterAddress - The address of the proposer/attester, or undefined
|
|
38
47
|
* @param options - Block proposal options (including broadcastInvalidBlockProposal for testing)
|
|
39
48
|
*
|
|
40
49
|
* @returns A block proposal signing the above information
|
|
50
|
+
* @throws DutyAlreadySignedError if HA signer indicates duty already signed by another node
|
|
51
|
+
* @throws SlashingProtectionError if attempting to sign different data for same slot
|
|
41
52
|
*/
|
|
42
53
|
public createBlockProposal(
|
|
43
54
|
blockHeader: BlockHeader,
|
|
44
|
-
|
|
55
|
+
blockIndexWithinCheckpoint: IndexWithinCheckpoint,
|
|
45
56
|
inHash: Fr,
|
|
46
57
|
archive: Fr,
|
|
47
58
|
txs: Tx[],
|
|
48
59
|
proposerAttesterAddress: EthAddress | undefined,
|
|
49
60
|
options: BlockProposalOptions,
|
|
50
61
|
): Promise<BlockProposal> {
|
|
51
|
-
const payloadSigner = this.getPayloadSigner(proposerAttesterAddress);
|
|
52
|
-
|
|
53
62
|
// For testing: change the new archive to trigger state_mismatch validation failure
|
|
54
63
|
if (options.broadcastInvalidBlockProposal) {
|
|
55
64
|
archive = Fr.random();
|
|
56
65
|
this.log.warn(`Creating INVALID block proposal for slot ${blockHeader.globalVariables.slotNumber}`);
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
// Create a signer that uses the appropriate address
|
|
69
|
+
const address = proposerAttesterAddress ?? this.keyStore.getAddress(0);
|
|
70
|
+
const payloadSigner = (payload: Buffer32, context: SigningContext) =>
|
|
71
|
+
this.keyStore.signMessageWithAddress(address, payload, context);
|
|
72
|
+
|
|
59
73
|
return BlockProposal.createProposalFromSigner(
|
|
60
74
|
blockHeader,
|
|
61
|
-
|
|
75
|
+
blockIndexWithinCheckpoint,
|
|
62
76
|
inHash,
|
|
63
77
|
archive,
|
|
64
78
|
txs.map(tx => tx.getTxHash()),
|
|
@@ -81,18 +95,23 @@ export class ValidationService {
|
|
|
81
95
|
public createCheckpointProposal(
|
|
82
96
|
checkpointHeader: CheckpointHeader,
|
|
83
97
|
archive: Fr,
|
|
98
|
+
feeAssetPriceModifier: bigint,
|
|
84
99
|
lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
|
|
85
100
|
proposerAttesterAddress: EthAddress | undefined,
|
|
86
101
|
options: CheckpointProposalOptions,
|
|
87
102
|
): Promise<CheckpointProposal> {
|
|
88
|
-
const payloadSigner = this.getPayloadSigner(proposerAttesterAddress);
|
|
89
|
-
|
|
90
103
|
// For testing: change the archive to trigger state_mismatch validation failure
|
|
91
104
|
if (options.broadcastInvalidCheckpointProposal) {
|
|
92
105
|
archive = Fr.random();
|
|
93
106
|
this.log.warn(`Creating INVALID checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
|
|
94
107
|
}
|
|
95
108
|
|
|
109
|
+
// Create a signer that takes payload and context, and uses the appropriate address
|
|
110
|
+
const payloadSigner = (payload: Buffer32, context: SigningContext) => {
|
|
111
|
+
const address = proposerAttesterAddress ?? this.keyStore.getAddress(0);
|
|
112
|
+
return this.keyStore.signMessageWithAddress(address, payload, context);
|
|
113
|
+
};
|
|
114
|
+
|
|
96
115
|
// Last block to include in the proposal
|
|
97
116
|
const lastBlock = lastBlockInfo && {
|
|
98
117
|
blockHeader: lastBlockInfo.blockHeader,
|
|
@@ -101,17 +120,13 @@ export class ValidationService {
|
|
|
101
120
|
txs: options.publishFullTxs ? lastBlockInfo.txs : undefined,
|
|
102
121
|
};
|
|
103
122
|
|
|
104
|
-
return CheckpointProposal.createProposalFromSigner(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// if there is no proposer attester address, just use the first signer
|
|
112
|
-
const signer = this.keyStore.getAddress(0);
|
|
113
|
-
return (payload: Buffer32) => this.keyStore.signMessageWithAddress(signer, payload);
|
|
114
|
-
}
|
|
123
|
+
return CheckpointProposal.createProposalFromSigner(
|
|
124
|
+
checkpointHeader,
|
|
125
|
+
archive,
|
|
126
|
+
feeAssetPriceModifier,
|
|
127
|
+
lastBlock,
|
|
128
|
+
payloadSigner,
|
|
129
|
+
);
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
/**
|
|
@@ -129,23 +144,77 @@ export class ValidationService {
|
|
|
129
144
|
attestors: EthAddress[],
|
|
130
145
|
): Promise<CheckpointAttestation[]> {
|
|
131
146
|
// Create the attestation payload from the checkpoint proposal
|
|
132
|
-
const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive);
|
|
147
|
+
const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive, proposal.feeAssetPriceModifier);
|
|
133
148
|
const buf = Buffer32.fromBuffer(
|
|
134
149
|
keccak256(payload.getPayloadToSign(SignatureDomainSeparator.checkpointAttestation)),
|
|
135
150
|
);
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
|
|
152
|
+
// TODO(spy/ha): Use checkpointNumber instead of blockNumber once CheckpointHeader includes it.
|
|
153
|
+
// CheckpointProposalCore doesn't have lastBlock info, so use 0 as a proxy.
|
|
154
|
+
// blockNumber is NOT used for the primary key so it's safe to use here.
|
|
155
|
+
// See CheckpointHeader TODO and SigningContext types documentation.
|
|
156
|
+
const blockNumber = BlockNumber(0);
|
|
157
|
+
const context: SigningContext = {
|
|
158
|
+
slot: proposal.slotNumber,
|
|
159
|
+
blockNumber,
|
|
160
|
+
dutyType: DutyType.ATTESTATION,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Sign each attestor in parallel, catching HA errors per-attestor
|
|
164
|
+
const results = await Promise.allSettled(
|
|
165
|
+
attestors.map(async attestor => {
|
|
166
|
+
const sig = await this.keyStore.signMessageWithAddress(attestor, buf, context);
|
|
167
|
+
// return new BlockAttestation(proposal.payload, sig, proposal.signature);
|
|
168
|
+
return new CheckpointAttestation(payload, sig, proposal.signature);
|
|
169
|
+
}),
|
|
138
170
|
);
|
|
139
|
-
|
|
171
|
+
|
|
172
|
+
const attestations: CheckpointAttestation[] = [];
|
|
173
|
+
for (let i = 0; i < results.length; i++) {
|
|
174
|
+
const result = results[i];
|
|
175
|
+
if (result.status === 'fulfilled') {
|
|
176
|
+
attestations.push(result.value);
|
|
177
|
+
} else {
|
|
178
|
+
const error = result.reason;
|
|
179
|
+
if (error instanceof DutyAlreadySignedError || error instanceof SlashingProtectionError) {
|
|
180
|
+
this.log.info(
|
|
181
|
+
`Attestation for slot ${proposal.slotNumber} by ${attestors[i]} already signed by another High-Availability node`,
|
|
182
|
+
);
|
|
183
|
+
// Continue with remaining attestors
|
|
184
|
+
} else {
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return attestations;
|
|
140
191
|
}
|
|
141
192
|
|
|
142
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Sign attestations and signers payload
|
|
195
|
+
* @param attestationsAndSigners - The attestations and signers to sign
|
|
196
|
+
* @param proposer - The proposer address to sign with
|
|
197
|
+
* @param slot - The slot number for HA signing context
|
|
198
|
+
* @param blockNumber - The block or checkpoint number for HA signing context
|
|
199
|
+
* @returns signature
|
|
200
|
+
* @throws DutyAlreadySignedError if already signed by another HA node
|
|
201
|
+
* @throws SlashingProtectionError if attempting to sign different data for same slot
|
|
202
|
+
*/
|
|
203
|
+
signAttestationsAndSigners(
|
|
143
204
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
144
205
|
proposer: EthAddress,
|
|
206
|
+
slot: SlotNumber,
|
|
207
|
+
blockNumber: BlockNumber | CheckpointNumber,
|
|
145
208
|
): Promise<Signature> {
|
|
209
|
+
const context: SigningContext = {
|
|
210
|
+
slot,
|
|
211
|
+
blockNumber,
|
|
212
|
+
dutyType: DutyType.ATTESTATIONS_AND_SIGNERS,
|
|
213
|
+
};
|
|
214
|
+
|
|
146
215
|
const buf = Buffer32.fromBuffer(
|
|
147
216
|
keccak256(attestationsAndSigners.getPayloadToSign(SignatureDomainSeparator.attestationsAndSigners)),
|
|
148
217
|
);
|
|
149
|
-
return
|
|
218
|
+
return this.keyStore.signMessageWithAddress(proposer, buf, context);
|
|
150
219
|
}
|
|
151
220
|
}
|