@aztec/validator-client 0.0.1-commit.cb6bed7c2 → 0.0.1-commit.cbf2c2d5d
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 +9 -10
- package/dest/block_proposal_handler.d.ts +3 -2
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +68 -5
- package/dest/checkpoint_builder.d.ts +10 -7
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +64 -41
- package/dest/duties/validation_service.js +1 -1
- package/dest/factory.d.ts +3 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +2 -2
- package/dest/key_store/ha_key_store.js +1 -1
- package/dest/validator.d.ts +3 -3
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +27 -22
- package/package.json +19 -19
- package/src/block_proposal_handler.ts +83 -5
- package/src/checkpoint_builder.ts +79 -52
- package/src/duties/validation_service.ts +1 -1
- package/src/factory.ts +4 -1
- package/src/key_store/ha_key_store.ts +1 -1
- package/src/validator.ts +41 -27
|
@@ -20,13 +20,14 @@ import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
|
20
20
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
21
21
|
import { Gas } from '@aztec/stdlib/gas';
|
|
22
22
|
import {
|
|
23
|
+
type BlockBuilderOptions,
|
|
23
24
|
type BuildBlockInCheckpointResult,
|
|
24
25
|
type FullNodeBlockBuilderConfig,
|
|
25
26
|
FullNodeBlockBuilderConfigKeys,
|
|
26
27
|
type ICheckpointBlockBuilder,
|
|
27
28
|
type ICheckpointsBuilder,
|
|
29
|
+
InsufficientValidTxsError,
|
|
28
30
|
type MerkleTreeWriteOperations,
|
|
29
|
-
NoValidTxsError,
|
|
30
31
|
type PublicProcessorLimits,
|
|
31
32
|
type WorldStateSynchronizer,
|
|
32
33
|
} from '@aztec/stdlib/interfaces/server';
|
|
@@ -34,6 +35,7 @@ import { type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
|
34
35
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
35
36
|
import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
36
37
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
38
|
+
import { ForkCheckpoint } from '@aztec/world-state';
|
|
37
39
|
|
|
38
40
|
// Re-export for backward compatibility
|
|
39
41
|
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
@@ -45,6 +47,9 @@ export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/serv
|
|
|
45
47
|
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
46
48
|
private log: Logger;
|
|
47
49
|
|
|
50
|
+
/** Persistent contracts DB shared across all blocks in this checkpoint. */
|
|
51
|
+
protected contractsDB: PublicContractsDB;
|
|
52
|
+
|
|
48
53
|
constructor(
|
|
49
54
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
50
55
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -59,6 +64,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
59
64
|
...bindings,
|
|
60
65
|
instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
|
|
61
66
|
});
|
|
67
|
+
this.contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
getConstantData(): CheckpointGlobalVariables {
|
|
@@ -73,7 +79,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
73
79
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
74
80
|
blockNumber: BlockNumber,
|
|
75
81
|
timestamp: bigint,
|
|
76
|
-
opts:
|
|
82
|
+
opts: BlockBuilderOptions & { expectedEndState?: StateReference },
|
|
77
83
|
): Promise<BuildBlockInCheckpointResult> {
|
|
78
84
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
79
85
|
|
|
@@ -103,34 +109,54 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
103
109
|
...this.capLimitsByCheckpointBudgets(opts),
|
|
104
110
|
};
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
// Add block to checkpoint
|
|
117
|
-
const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
118
|
-
expectedEndState: opts.expectedEndState,
|
|
119
|
-
});
|
|
112
|
+
// Create a block-level checkpoint on the contracts DB so we can roll back on failure
|
|
113
|
+
this.contractsDB.createCheckpoint();
|
|
114
|
+
// We execute all merkle tree operations on a world state fork checkpoint
|
|
115
|
+
// This enables us to discard all modifications in the event that we fail to successfully process sufficient transactions
|
|
116
|
+
const forkCheckpoint = await ForkCheckpoint.new(this.fork);
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
});
|
|
118
|
+
try {
|
|
119
|
+
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
120
|
+
processor.process(pendingTxs, cappedOpts, validator),
|
|
121
|
+
);
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
123
|
+
// Throw before updating state if we don't have enough valid txs
|
|
124
|
+
const minValidTxs = opts.minValidTxs ?? 0;
|
|
125
|
+
if (processedTxs.length < minValidTxs) {
|
|
126
|
+
throw new InsufficientValidTxsError(processedTxs.length, minValidTxs, failedTxs);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Commit the fork checkpoint
|
|
130
|
+
await forkCheckpoint.commit();
|
|
131
|
+
|
|
132
|
+
// Add block to checkpoint
|
|
133
|
+
const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
134
|
+
expectedEndState: opts.expectedEndState,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this.contractsDB.commitCheckpoint();
|
|
138
|
+
|
|
139
|
+
this.log.debug('Built block within checkpoint', {
|
|
140
|
+
header: block.header.toInspect(),
|
|
141
|
+
processedTxs: processedTxs.map(tx => tx.hash.toString()),
|
|
142
|
+
failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
block,
|
|
147
|
+
publicProcessorDuration,
|
|
148
|
+
numTxs: processedTxs.length,
|
|
149
|
+
failedTxs,
|
|
150
|
+
usedTxs,
|
|
151
|
+
};
|
|
152
|
+
} catch (err) {
|
|
153
|
+
// Revert all changes to contracts db
|
|
154
|
+
this.contractsDB.revertCheckpoint();
|
|
155
|
+
// If we reached the point of committing the checkpoint, this does nothing
|
|
156
|
+
// Otherwise it reverts any changes made to the fork for this failed block
|
|
157
|
+
await forkCheckpoint.revert();
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
134
160
|
}
|
|
135
161
|
|
|
136
162
|
/** Completes the checkpoint and returns it. */
|
|
@@ -153,11 +179,12 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
153
179
|
|
|
154
180
|
/**
|
|
155
181
|
* Caps per-block gas and blob field limits by remaining checkpoint-level budgets.
|
|
156
|
-
*
|
|
157
|
-
*
|
|
182
|
+
* When building a proposal (isBuildingProposal=true), computes a fair share of remaining budget
|
|
183
|
+
* across remaining blocks scaled by the multiplier. When validating, only caps by per-block limit
|
|
184
|
+
* and remaining checkpoint budget (no redistribution or multiplier).
|
|
158
185
|
*/
|
|
159
186
|
protected capLimitsByCheckpointBudgets(
|
|
160
|
-
opts:
|
|
187
|
+
opts: BlockBuilderOptions,
|
|
161
188
|
): Pick<PublicProcessorLimits, 'maxBlockGas' | 'maxBlobFields' | 'maxTransactions'> {
|
|
162
189
|
const existingBlocks = this.checkpointBuilder.getBlocks();
|
|
163
190
|
|
|
@@ -178,31 +205,31 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
178
205
|
const blockEndOverhead = getNumBlockEndBlobFields(isFirstBlock);
|
|
179
206
|
const maxBlobFieldsForTxs = totalBlobCapacity - usedBlobFields - blockEndOverhead;
|
|
180
207
|
|
|
181
|
-
//
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
cappedMaxTransactions =
|
|
208
|
+
// Remaining txs
|
|
209
|
+
const usedTxs = sum(existingBlocks.map(b => b.body.txEffects.length));
|
|
210
|
+
const remainingTxs = Math.max(0, (this.config.maxTxsPerCheckpoint ?? Infinity) - usedTxs);
|
|
211
|
+
|
|
212
|
+
// Cap by per-block limit + remaining checkpoint budget
|
|
213
|
+
let cappedL2Gas = Math.min(opts.maxBlockGas?.l2Gas ?? Infinity, remainingMana);
|
|
214
|
+
let cappedDAGas = Math.min(opts.maxBlockGas?.daGas ?? Infinity, remainingDAGas);
|
|
215
|
+
let cappedBlobFields = Math.min(opts.maxBlobFields ?? Infinity, maxBlobFieldsForTxs);
|
|
216
|
+
let cappedMaxTransactions = Math.min(opts.maxTransactions ?? Infinity, remainingTxs);
|
|
217
|
+
|
|
218
|
+
// Proposer mode: further cap by fair share of remaining budget across remaining blocks
|
|
219
|
+
if (opts.isBuildingProposal) {
|
|
220
|
+
const remainingBlocks = Math.max(1, opts.maxBlocksPerCheckpoint - existingBlocks.length);
|
|
221
|
+
const multiplier = opts.perBlockAllocationMultiplier;
|
|
222
|
+
|
|
223
|
+
cappedL2Gas = Math.min(cappedL2Gas, Math.ceil((remainingMana / remainingBlocks) * multiplier));
|
|
224
|
+
cappedDAGas = Math.min(cappedDAGas, Math.ceil((remainingDAGas / remainingBlocks) * multiplier));
|
|
225
|
+
cappedBlobFields = Math.min(cappedBlobFields, Math.ceil((maxBlobFieldsForTxs / remainingBlocks) * multiplier));
|
|
226
|
+
cappedMaxTransactions = Math.min(cappedMaxTransactions, Math.ceil((remainingTxs / remainingBlocks) * multiplier));
|
|
200
227
|
}
|
|
201
228
|
|
|
202
229
|
return {
|
|
203
230
|
maxBlockGas: new Gas(cappedDAGas, cappedL2Gas),
|
|
204
231
|
maxBlobFields: cappedBlobFields,
|
|
205
|
-
maxTransactions: cappedMaxTransactions,
|
|
232
|
+
maxTransactions: Number.isFinite(cappedMaxTransactions) ? cappedMaxTransactions : undefined,
|
|
206
233
|
};
|
|
207
234
|
}
|
|
208
235
|
|
|
@@ -211,7 +238,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
211
238
|
...(await getDefaultAllowedSetupFunctions()),
|
|
212
239
|
...(this.config.txPublicSetupAllowListExtend ?? []),
|
|
213
240
|
];
|
|
214
|
-
const contractsDB =
|
|
241
|
+
const contractsDB = this.contractsDB;
|
|
215
242
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
216
243
|
|
|
217
244
|
const collectDebugLogs = this.debugLogStore.isEnabled;
|
|
@@ -177,7 +177,7 @@ export class ValidationService {
|
|
|
177
177
|
} else {
|
|
178
178
|
const error = result.reason;
|
|
179
179
|
if (error instanceof DutyAlreadySignedError || error instanceof SlashingProtectionError) {
|
|
180
|
-
this.log.
|
|
180
|
+
this.log.verbose(
|
|
181
181
|
`Attestation for slot ${proposal.slotNumber} by ${attestors[i]} already signed by another High-Availability node`,
|
|
182
182
|
);
|
|
183
183
|
// Continue with remaining attestors
|
package/src/factory.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
|
7
7
|
import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
8
8
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
9
9
|
import type { TelemetryClient } from '@aztec/telemetry-client';
|
|
10
|
+
import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
|
|
10
11
|
|
|
11
12
|
import { BlockProposalHandler } from './block_proposal_handler.js';
|
|
12
13
|
import type { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
@@ -29,7 +30,7 @@ export function createBlockProposalHandler(
|
|
|
29
30
|
const metrics = new ValidatorMetrics(deps.telemetry);
|
|
30
31
|
const blockProposalValidator = new BlockProposalValidator(deps.epochCache, {
|
|
31
32
|
txsPermitted: !config.disableTransactions,
|
|
32
|
-
maxTxsPerBlock: config.validateMaxTxsPerBlock,
|
|
33
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
|
|
33
34
|
});
|
|
34
35
|
return new BlockProposalHandler(
|
|
35
36
|
deps.checkpointsBuilder,
|
|
@@ -59,6 +60,7 @@ export function createValidatorClient(
|
|
|
59
60
|
epochCache: EpochCache;
|
|
60
61
|
keyStoreManager: KeystoreManager | undefined;
|
|
61
62
|
blobClient: BlobClientInterface;
|
|
63
|
+
slashingProtectionDb?: SlashingProtectionDatabase;
|
|
62
64
|
},
|
|
63
65
|
) {
|
|
64
66
|
if (config.disableValidator || !deps.keyStoreManager) {
|
|
@@ -79,5 +81,6 @@ export function createValidatorClient(
|
|
|
79
81
|
deps.blobClient,
|
|
80
82
|
deps.dateProvider,
|
|
81
83
|
deps.telemetry,
|
|
84
|
+
deps.slashingProtectionDb,
|
|
82
85
|
);
|
|
83
86
|
}
|
|
@@ -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,
|
package/src/validator.ts
CHANGED
|
@@ -46,8 +46,12 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
46
46
|
import type { BlockHeader, CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
47
47
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
48
48
|
import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
49
|
-
import {
|
|
50
|
-
|
|
49
|
+
import {
|
|
50
|
+
createHASigner,
|
|
51
|
+
createLocalSignerWithProtection,
|
|
52
|
+
createSignerFromSharedDb,
|
|
53
|
+
} from '@aztec/validator-ha-signer/factory';
|
|
54
|
+
import { DutyType, type SigningContext, type SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
|
|
51
55
|
import type { ValidatorHASigner } from '@aztec/validator-ha-signer/validator-ha-signer';
|
|
52
56
|
|
|
53
57
|
import { EventEmitter } from 'events';
|
|
@@ -197,6 +201,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
197
201
|
blobClient: BlobClientInterface,
|
|
198
202
|
dateProvider: DateProvider = new DateProvider(),
|
|
199
203
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
204
|
+
slashingProtectionDb?: SlashingProtectionDatabase,
|
|
200
205
|
) {
|
|
201
206
|
const metrics = new ValidatorMetrics(telemetry);
|
|
202
207
|
const blockProposalValidator = new BlockProposalValidator(epochCache, {
|
|
@@ -219,7 +224,13 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
219
224
|
|
|
220
225
|
const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
|
|
221
226
|
let slashingProtectionSigner: ValidatorHASigner;
|
|
222
|
-
if (
|
|
227
|
+
if (slashingProtectionDb) {
|
|
228
|
+
// Shared database mode: use a pre-existing database (e.g. for testing HA setups).
|
|
229
|
+
({ signer: slashingProtectionSigner } = createSignerFromSharedDb(slashingProtectionDb, config, {
|
|
230
|
+
telemetryClient: telemetry,
|
|
231
|
+
dateProvider,
|
|
232
|
+
}));
|
|
233
|
+
} else if (config.haSigningEnabled) {
|
|
223
234
|
// Multi-node HA mode: use PostgreSQL-backed distributed locking.
|
|
224
235
|
// If maxStuckDutiesAgeMs is not explicitly set, compute it from Aztec slot duration
|
|
225
236
|
const haConfig = {
|
|
@@ -378,13 +389,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
378
389
|
return false;
|
|
379
390
|
}
|
|
380
391
|
|
|
381
|
-
//
|
|
392
|
+
// Log self-proposals from HA peers (same validator key on different nodes)
|
|
382
393
|
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
383
|
-
this.log.
|
|
394
|
+
this.log.verbose(`Processing block proposal from HA peer for slot ${slotNumber}`, {
|
|
384
395
|
proposer: proposer.toString(),
|
|
385
396
|
slotNumber,
|
|
386
397
|
});
|
|
387
|
-
return false;
|
|
388
398
|
}
|
|
389
399
|
|
|
390
400
|
// Check if we're in the committee (for metrics purposes)
|
|
@@ -416,9 +426,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
416
426
|
);
|
|
417
427
|
|
|
418
428
|
if (!validationResult.isValid) {
|
|
419
|
-
this.log.warn(`Block proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
420
|
-
|
|
421
429
|
const reason = validationResult.reason || 'unknown';
|
|
430
|
+
|
|
431
|
+
this.log.warn(`Block proposal validation failed: ${reason}`, proposalInfo);
|
|
432
|
+
|
|
422
433
|
// Classify failure reason: bad proposal vs node issue
|
|
423
434
|
const badProposalReasons: BlockProposalValidationFailureReason[] = [
|
|
424
435
|
'invalid_proposal',
|
|
@@ -473,26 +484,26 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
473
484
|
proposal: CheckpointProposalCore,
|
|
474
485
|
_proposalSender: PeerId,
|
|
475
486
|
): Promise<CheckpointAttestation[] | undefined> {
|
|
476
|
-
const
|
|
487
|
+
const proposalSlotNumber = proposal.slotNumber;
|
|
477
488
|
const proposer = proposal.getSender();
|
|
478
489
|
|
|
479
490
|
// If escape hatch is open for this slot's epoch, do not attest.
|
|
480
|
-
if (await this.epochCache.isEscapeHatchOpenAtSlot(
|
|
481
|
-
this.log.warn(`Escape hatch open for slot ${
|
|
491
|
+
if (await this.epochCache.isEscapeHatchOpenAtSlot(proposalSlotNumber)) {
|
|
492
|
+
this.log.warn(`Escape hatch open for slot ${proposalSlotNumber}, skipping checkpoint attestation handling`);
|
|
482
493
|
return undefined;
|
|
483
494
|
}
|
|
484
495
|
|
|
485
496
|
// Reject proposals with invalid signatures
|
|
486
497
|
if (!proposer) {
|
|
487
|
-
this.log.warn(`Received checkpoint proposal with invalid signature for slot ${
|
|
498
|
+
this.log.warn(`Received checkpoint proposal with invalid signature for proposal slot ${proposalSlotNumber}`);
|
|
488
499
|
return undefined;
|
|
489
500
|
}
|
|
490
501
|
|
|
491
502
|
// Ignore proposals from ourselves (may happen in HA setups)
|
|
492
503
|
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
493
|
-
this.log.
|
|
504
|
+
this.log.debug(`Ignoring block proposal from self for slot ${proposalSlotNumber}`, {
|
|
494
505
|
proposer: proposer.toString(),
|
|
495
|
-
|
|
506
|
+
proposalSlotNumber,
|
|
496
507
|
});
|
|
497
508
|
return undefined;
|
|
498
509
|
}
|
|
@@ -500,28 +511,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
500
511
|
// Validate fee asset price modifier is within allowed range
|
|
501
512
|
if (!validateFeeAssetPriceModifier(proposal.feeAssetPriceModifier)) {
|
|
502
513
|
this.log.warn(
|
|
503
|
-
`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${
|
|
514
|
+
`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${proposalSlotNumber}`,
|
|
504
515
|
);
|
|
505
516
|
return undefined;
|
|
506
517
|
}
|
|
507
518
|
|
|
508
|
-
// Check that I have any address in
|
|
509
|
-
const inCommittee = await this.epochCache.filterInCommittee(
|
|
519
|
+
// Check that I have any address in the committee where this checkpoint will land before attesting
|
|
520
|
+
const inCommittee = await this.epochCache.filterInCommittee(proposalSlotNumber, this.getValidatorAddresses());
|
|
510
521
|
const partOfCommittee = inCommittee.length > 0;
|
|
511
522
|
|
|
512
523
|
const proposalInfo = {
|
|
513
|
-
|
|
524
|
+
proposalSlotNumber,
|
|
514
525
|
archive: proposal.archive.toString(),
|
|
515
526
|
proposer: proposer.toString(),
|
|
516
527
|
};
|
|
517
|
-
this.log.info(`Received checkpoint proposal for slot ${
|
|
528
|
+
this.log.info(`Received checkpoint proposal for slot ${proposalSlotNumber}`, {
|
|
518
529
|
...proposalInfo,
|
|
519
530
|
fishermanMode: this.config.fishermanMode || false,
|
|
520
531
|
});
|
|
521
532
|
|
|
522
533
|
// Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
|
|
523
534
|
if (this.config.skipCheckpointProposalValidation) {
|
|
524
|
-
this.log.warn(`Skipping checkpoint proposal validation for slot ${
|
|
535
|
+
this.log.warn(`Skipping checkpoint proposal validation for slot ${proposalSlotNumber}`, proposalInfo);
|
|
525
536
|
} else {
|
|
526
537
|
const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
|
|
527
538
|
if (!validationResult.isValid) {
|
|
@@ -543,16 +554,19 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
543
554
|
}
|
|
544
555
|
|
|
545
556
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
546
|
-
this.log.info(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
557
|
+
this.log.info(
|
|
558
|
+
`${partOfCommittee ? 'Attesting to' : 'Validated'} checkpoint proposal for slot ${proposalSlotNumber}`,
|
|
559
|
+
{
|
|
560
|
+
...proposalInfo,
|
|
561
|
+
inCommittee: partOfCommittee,
|
|
562
|
+
fishermanMode: this.config.fishermanMode || false,
|
|
563
|
+
},
|
|
564
|
+
);
|
|
551
565
|
|
|
552
566
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
553
567
|
|
|
554
568
|
// Track epoch participation per attester: count each (attester, epoch) pair at most once
|
|
555
|
-
const proposalEpoch = getEpochAtSlot(
|
|
569
|
+
const proposalEpoch = getEpochAtSlot(proposalSlotNumber, this.epochCache.getL1Constants());
|
|
556
570
|
for (const attester of inCommittee) {
|
|
557
571
|
const key = attester.toString();
|
|
558
572
|
const lastEpoch = this.lastAttestedEpochByAttester.get(key);
|
|
@@ -580,7 +594,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
580
594
|
|
|
581
595
|
if (this.config.fishermanMode) {
|
|
582
596
|
// bail out early and don't save attestations to the pool in fisherman mode
|
|
583
|
-
this.log.info(`Creating checkpoint attestations for slot ${
|
|
597
|
+
this.log.info(`Creating checkpoint attestations for slot ${proposalSlotNumber}`, {
|
|
584
598
|
...proposalInfo,
|
|
585
599
|
attestors: attestors.map(a => a.toString()),
|
|
586
600
|
});
|