@aztec/validator-client 0.0.1-commit.3d8f95d → 0.0.1-commit.3fd054f6
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 +41 -0
- package/dest/block_proposal_handler.d.ts +5 -4
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +130 -62
- package/dest/checkpoint_builder.d.ts +21 -8
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +124 -46
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +22 -1
- 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 +3 -1
- 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.js +1 -1
- package/dest/metrics.d.ts +9 -1
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +12 -0
- package/dest/validator.d.ts +15 -7
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +109 -43
- package/package.json +19 -19
- package/src/block_proposal_handler.ts +157 -80
- package/src/checkpoint_builder.ts +142 -39
- package/src/config.ts +22 -1
- package/src/duties/validation_service.ts +12 -11
- package/src/factory.ts +4 -0
- package/src/index.ts +0 -1
- package/src/key_store/ha_key_store.ts +1 -1
- package/src/metrics.ts +18 -0
- package/src/validator.ts +124 -42
- 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 -19
- 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 -154
package/src/validator.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { type Blob, getBlobsPerL1Block } from '@aztec/blob-lib';
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
+
import { validateFeeAssetPriceModifier } from '@aztec/ethereum/contracts';
|
|
4
5
|
import {
|
|
5
6
|
BlockNumber,
|
|
6
7
|
CheckpointNumber,
|
|
@@ -23,7 +24,8 @@ import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol }
|
|
|
23
24
|
import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
|
|
24
25
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
25
26
|
import type { CommitteeAttestationsAndSigners, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
26
|
-
import {
|
|
27
|
+
import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
28
|
+
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
27
29
|
import type {
|
|
28
30
|
CreateCheckpointProposalLastBlockData,
|
|
29
31
|
ITxProvider,
|
|
@@ -44,8 +46,13 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
44
46
|
import type { BlockHeader, CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
45
47
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
46
48
|
import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
47
|
-
import {
|
|
48
|
-
|
|
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';
|
|
55
|
+
import type { ValidatorHASigner } from '@aztec/validator-ha-signer/validator-ha-signer';
|
|
49
56
|
|
|
50
57
|
import { EventEmitter } from 'events';
|
|
51
58
|
import type { TypedDataDefinition } from 'viem';
|
|
@@ -76,7 +83,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
76
83
|
private validationService: ValidationService;
|
|
77
84
|
private metrics: ValidatorMetrics;
|
|
78
85
|
private log: Logger;
|
|
79
|
-
|
|
80
86
|
// Whether it has already registered handlers on the p2p client
|
|
81
87
|
private hasRegisteredHandlers = false;
|
|
82
88
|
|
|
@@ -88,6 +94,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
88
94
|
|
|
89
95
|
private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
|
|
90
96
|
private epochCacheUpdateLoop: RunningPromise;
|
|
97
|
+
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
|
|
98
|
+
private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
|
|
91
99
|
|
|
92
100
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
93
101
|
|
|
@@ -105,6 +113,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
105
113
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
106
114
|
private config: ValidatorClientFullConfig,
|
|
107
115
|
private blobClient: BlobClientInterface,
|
|
116
|
+
private slashingProtectionSigner: ValidatorHASigner,
|
|
108
117
|
private dateProvider: DateProvider = new DateProvider(),
|
|
109
118
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
110
119
|
log = createLogger('validator'),
|
|
@@ -158,6 +167,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
158
167
|
this.log.trace(`No committee found for slot`);
|
|
159
168
|
return;
|
|
160
169
|
}
|
|
170
|
+
this.metrics.setCurrentEpoch(epoch);
|
|
161
171
|
if (epoch !== this.lastEpochForCommitteeUpdateLoop) {
|
|
162
172
|
const me = this.getValidatorAddresses();
|
|
163
173
|
const committeeSet = new Set(committee.map(v => v.toString()));
|
|
@@ -191,10 +201,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
191
201
|
blobClient: BlobClientInterface,
|
|
192
202
|
dateProvider: DateProvider = new DateProvider(),
|
|
193
203
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
204
|
+
slashingProtectionDb?: SlashingProtectionDatabase,
|
|
194
205
|
) {
|
|
195
206
|
const metrics = new ValidatorMetrics(telemetry);
|
|
196
207
|
const blockProposalValidator = new BlockProposalValidator(epochCache, {
|
|
197
208
|
txsPermitted: !config.disableTransactions,
|
|
209
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock,
|
|
198
210
|
});
|
|
199
211
|
const blockProposalHandler = new BlockProposalHandler(
|
|
200
212
|
checkpointsBuilder,
|
|
@@ -210,16 +222,34 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
210
222
|
telemetry,
|
|
211
223
|
);
|
|
212
224
|
|
|
213
|
-
|
|
214
|
-
|
|
225
|
+
const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
|
|
226
|
+
let slashingProtectionSigner: ValidatorHASigner;
|
|
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) {
|
|
234
|
+
// Multi-node HA mode: use PostgreSQL-backed distributed locking.
|
|
215
235
|
// If maxStuckDutiesAgeMs is not explicitly set, compute it from Aztec slot duration
|
|
216
236
|
const haConfig = {
|
|
217
237
|
...config,
|
|
218
238
|
maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs ?? epochCache.getL1Constants().slotDuration * 2 * 1000,
|
|
219
239
|
};
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
({ signer: slashingProtectionSigner } = await createHASigner(haConfig, {
|
|
241
|
+
telemetryClient: telemetry,
|
|
242
|
+
dateProvider,
|
|
243
|
+
}));
|
|
244
|
+
} else {
|
|
245
|
+
// Single-node mode: use LMDB-backed local signing protection.
|
|
246
|
+
// This prevents double-signing if the node crashes and restarts mid-proposal.
|
|
247
|
+
({ signer: slashingProtectionSigner } = await createLocalSignerWithProtection(config, {
|
|
248
|
+
telemetryClient: telemetry,
|
|
249
|
+
dateProvider,
|
|
250
|
+
}));
|
|
222
251
|
}
|
|
252
|
+
const validatorKeyStore: ExtendedValidatorKeyStore = new HAKeyStore(nodeKeystoreAdapter, slashingProtectionSigner);
|
|
223
253
|
|
|
224
254
|
const validator = new ValidatorClient(
|
|
225
255
|
validatorKeyStore,
|
|
@@ -232,6 +262,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
232
262
|
l1ToL2MessageSource,
|
|
233
263
|
config,
|
|
234
264
|
blobClient,
|
|
265
|
+
slashingProtectionSigner,
|
|
235
266
|
dateProvider,
|
|
236
267
|
telemetry,
|
|
237
268
|
);
|
|
@@ -269,6 +300,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
269
300
|
this.config = { ...this.config, ...config };
|
|
270
301
|
}
|
|
271
302
|
|
|
303
|
+
public reloadKeystore(newManager: KeystoreManager): void {
|
|
304
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
305
|
+
this.keyStore = new HAKeyStore(newAdapter, this.slashingProtectionSigner);
|
|
306
|
+
this.validationService = new ValidationService(this.keyStore, this.log.createChild('validation-service'));
|
|
307
|
+
}
|
|
308
|
+
|
|
272
309
|
public async start() {
|
|
273
310
|
if (this.epochCacheUpdateLoop.isRunning()) {
|
|
274
311
|
this.log.warn(`Validator client already started`);
|
|
@@ -352,13 +389,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
352
389
|
return false;
|
|
353
390
|
}
|
|
354
391
|
|
|
355
|
-
//
|
|
392
|
+
// Log self-proposals from HA peers (same validator key on different nodes)
|
|
356
393
|
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
357
|
-
this.log.
|
|
394
|
+
this.log.verbose(`Processing block proposal from HA peer for slot ${slotNumber}`, {
|
|
358
395
|
proposer: proposer.toString(),
|
|
359
396
|
slotNumber,
|
|
360
397
|
});
|
|
361
|
-
return false;
|
|
362
398
|
}
|
|
363
399
|
|
|
364
400
|
// Check if we're in the committee (for metrics purposes)
|
|
@@ -390,9 +426,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
390
426
|
);
|
|
391
427
|
|
|
392
428
|
if (!validationResult.isValid) {
|
|
393
|
-
this.log.warn(`Block proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
394
|
-
|
|
395
429
|
const reason = validationResult.reason || 'unknown';
|
|
430
|
+
|
|
431
|
+
this.log.warn(`Block proposal validation failed: ${reason}`, proposalInfo);
|
|
432
|
+
|
|
396
433
|
// Classify failure reason: bad proposal vs node issue
|
|
397
434
|
const badProposalReasons: BlockProposalValidationFailureReason[] = [
|
|
398
435
|
'invalid_proposal',
|
|
@@ -447,49 +484,55 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
447
484
|
proposal: CheckpointProposalCore,
|
|
448
485
|
_proposalSender: PeerId,
|
|
449
486
|
): Promise<CheckpointAttestation[] | undefined> {
|
|
450
|
-
const
|
|
487
|
+
const proposalSlotNumber = proposal.slotNumber;
|
|
451
488
|
const proposer = proposal.getSender();
|
|
452
489
|
|
|
453
490
|
// If escape hatch is open for this slot's epoch, do not attest.
|
|
454
|
-
if (await this.epochCache.isEscapeHatchOpenAtSlot(
|
|
455
|
-
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`);
|
|
456
493
|
return undefined;
|
|
457
494
|
}
|
|
458
495
|
|
|
459
496
|
// Reject proposals with invalid signatures
|
|
460
497
|
if (!proposer) {
|
|
461
|
-
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}`);
|
|
462
499
|
return undefined;
|
|
463
500
|
}
|
|
464
501
|
|
|
465
502
|
// Ignore proposals from ourselves (may happen in HA setups)
|
|
466
503
|
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
467
|
-
this.log.
|
|
504
|
+
this.log.debug(`Ignoring block proposal from self for slot ${proposalSlotNumber}`, {
|
|
468
505
|
proposer: proposer.toString(),
|
|
469
|
-
|
|
506
|
+
proposalSlotNumber,
|
|
470
507
|
});
|
|
471
508
|
return undefined;
|
|
472
509
|
}
|
|
473
510
|
|
|
474
|
-
//
|
|
475
|
-
|
|
511
|
+
// Validate fee asset price modifier is within allowed range
|
|
512
|
+
if (!validateFeeAssetPriceModifier(proposal.feeAssetPriceModifier)) {
|
|
513
|
+
this.log.warn(
|
|
514
|
+
`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${proposalSlotNumber}`,
|
|
515
|
+
);
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
|
|
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());
|
|
476
521
|
const partOfCommittee = inCommittee.length > 0;
|
|
477
522
|
|
|
478
523
|
const proposalInfo = {
|
|
479
|
-
|
|
524
|
+
proposalSlotNumber,
|
|
480
525
|
archive: proposal.archive.toString(),
|
|
481
526
|
proposer: proposer.toString(),
|
|
482
|
-
txCount: proposal.txHashes.length,
|
|
483
527
|
};
|
|
484
|
-
this.log.info(`Received checkpoint proposal for slot ${
|
|
528
|
+
this.log.info(`Received checkpoint proposal for slot ${proposalSlotNumber}`, {
|
|
485
529
|
...proposalInfo,
|
|
486
|
-
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
487
530
|
fishermanMode: this.config.fishermanMode || false,
|
|
488
531
|
});
|
|
489
532
|
|
|
490
533
|
// Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
|
|
491
534
|
if (this.config.skipCheckpointProposalValidation) {
|
|
492
|
-
this.log.warn(`Skipping checkpoint proposal validation for slot ${
|
|
535
|
+
this.log.warn(`Skipping checkpoint proposal validation for slot ${proposalSlotNumber}`, proposalInfo);
|
|
493
536
|
} else {
|
|
494
537
|
const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
|
|
495
538
|
if (!validationResult.isValid) {
|
|
@@ -511,14 +554,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
511
554
|
}
|
|
512
555
|
|
|
513
556
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
514
|
-
this.log.info(
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
+
);
|
|
519
565
|
|
|
520
566
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
521
567
|
|
|
568
|
+
// Track epoch participation per attester: count each (attester, epoch) pair at most once
|
|
569
|
+
const proposalEpoch = getEpochAtSlot(proposalSlotNumber, this.epochCache.getL1Constants());
|
|
570
|
+
for (const attester of inCommittee) {
|
|
571
|
+
const key = attester.toString();
|
|
572
|
+
const lastEpoch = this.lastAttestedEpochByAttester.get(key);
|
|
573
|
+
if (lastEpoch === undefined || proposalEpoch > lastEpoch) {
|
|
574
|
+
this.lastAttestedEpochByAttester.set(key, proposalEpoch);
|
|
575
|
+
this.metrics.incAttestedEpochCount(attester);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
522
579
|
// Determine which validators should attest
|
|
523
580
|
let attestors: EthAddress[];
|
|
524
581
|
if (partOfCommittee) {
|
|
@@ -537,7 +594,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
537
594
|
|
|
538
595
|
if (this.config.fishermanMode) {
|
|
539
596
|
// bail out early and don't save attestations to the pool in fisherman mode
|
|
540
|
-
this.log.info(`Creating checkpoint attestations for slot ${
|
|
597
|
+
this.log.info(`Creating checkpoint attestations for slot ${proposalSlotNumber}`, {
|
|
541
598
|
...proposalInfo,
|
|
542
599
|
attestors: attestors.map(a => a.toString()),
|
|
543
600
|
});
|
|
@@ -595,7 +652,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
595
652
|
proposalInfo: LogData,
|
|
596
653
|
): Promise<{ isValid: true } | { isValid: false; reason: string }> {
|
|
597
654
|
const slot = proposal.slotNumber;
|
|
598
|
-
|
|
655
|
+
|
|
656
|
+
// Timeout block syncing at the start of the next slot
|
|
657
|
+
const config = this.checkpointsBuilder.getConfig();
|
|
658
|
+
const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
|
|
659
|
+
const timeoutSeconds = Math.max(1, nextSlotTimestampSeconds - Math.floor(this.dateProvider.now() / 1000));
|
|
599
660
|
|
|
600
661
|
// Wait for last block to sync by archive
|
|
601
662
|
let lastBlockHeader: BlockHeader | undefined;
|
|
@@ -630,6 +691,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
630
691
|
return { isValid: false, reason: 'no_blocks_for_slot' };
|
|
631
692
|
}
|
|
632
693
|
|
|
694
|
+
// Ensure the last block for this slot matches the archive in the checkpoint proposal
|
|
695
|
+
if (!blocks.at(-1)?.archive.root.equals(proposal.archive)) {
|
|
696
|
+
this.log.warn(`Last block archive mismatch for checkpoint proposal`, proposalInfo);
|
|
697
|
+
return { isValid: false, reason: 'last_block_archive_mismatch' };
|
|
698
|
+
}
|
|
699
|
+
|
|
633
700
|
this.log.debug(`Found ${blocks.length} blocks for slot ${slot}`, {
|
|
634
701
|
...proposalInfo,
|
|
635
702
|
blockNumbers: blocks.map(b => b.number),
|
|
@@ -643,14 +710,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
643
710
|
// Get L1-to-L2 messages for this checkpoint
|
|
644
711
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
645
712
|
|
|
646
|
-
//
|
|
647
|
-
// TODO: There can be a more efficient way to get the previous checkpoint out hashes without having to fetch the
|
|
648
|
-
// actual checkpoints and the blocks/txs in them.
|
|
713
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
649
714
|
const epoch = getEpochAtSlot(slot, this.epochCache.getL1Constants());
|
|
650
|
-
const
|
|
651
|
-
.filter(
|
|
652
|
-
.
|
|
653
|
-
const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
|
|
715
|
+
const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch))
|
|
716
|
+
.filter(c => c.checkpointNumber < checkpointNumber)
|
|
717
|
+
.map(c => c.checkpointOutHash);
|
|
654
718
|
|
|
655
719
|
// Fork world state at the block before the first block
|
|
656
720
|
const parentBlockNumber = BlockNumber(firstBlock.number - 1);
|
|
@@ -661,6 +725,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
661
725
|
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(
|
|
662
726
|
checkpointNumber,
|
|
663
727
|
constants,
|
|
728
|
+
proposal.feeAssetPriceModifier,
|
|
664
729
|
l1ToL2Messages,
|
|
665
730
|
previousCheckpointOutHashes,
|
|
666
731
|
fork,
|
|
@@ -707,6 +772,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
707
772
|
return { isValid: false, reason: 'out_hash_mismatch' };
|
|
708
773
|
}
|
|
709
774
|
|
|
775
|
+
// Final round of validations on the checkpoint, just in case.
|
|
776
|
+
try {
|
|
777
|
+
validateCheckpoint(computedCheckpoint, {
|
|
778
|
+
rollupManaLimit: this.checkpointsBuilder.getConfig().rollupManaLimit,
|
|
779
|
+
maxDABlockGas: this.config.validateMaxDABlockGas,
|
|
780
|
+
maxL2BlockGas: this.config.validateMaxL2BlockGas,
|
|
781
|
+
maxTxsPerBlock: this.config.validateMaxTxsPerBlock,
|
|
782
|
+
maxTxsPerCheckpoint: this.config.validateMaxTxsPerCheckpoint,
|
|
783
|
+
});
|
|
784
|
+
} catch (err) {
|
|
785
|
+
this.log.warn(`Checkpoint validation failed: ${err}`, proposalInfo);
|
|
786
|
+
return { isValid: false, reason: 'checkpoint_validation_failed' };
|
|
787
|
+
}
|
|
788
|
+
|
|
710
789
|
this.log.verbose(`Checkpoint proposal validation successful for slot ${slot}`, proposalInfo);
|
|
711
790
|
return { isValid: true };
|
|
712
791
|
} finally {
|
|
@@ -723,6 +802,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
723
802
|
chainId: gv.chainId,
|
|
724
803
|
version: gv.version,
|
|
725
804
|
slotNumber: gv.slotNumber,
|
|
805
|
+
timestamp: gv.timestamp,
|
|
726
806
|
coinbase: gv.coinbase,
|
|
727
807
|
feeRecipient: gv.feeRecipient,
|
|
728
808
|
gasFees: gv.gasFees,
|
|
@@ -732,7 +812,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
732
812
|
/**
|
|
733
813
|
* Uploads blobs for a checkpoint to the filestore (fire and forget).
|
|
734
814
|
*/
|
|
735
|
-
|
|
815
|
+
protected async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void> {
|
|
736
816
|
try {
|
|
737
817
|
const lastBlockHeader = await this.blockSource.getBlockHeaderByArchive(proposal.archive);
|
|
738
818
|
if (!lastBlockHeader) {
|
|
@@ -747,7 +827,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
747
827
|
}
|
|
748
828
|
|
|
749
829
|
const blobFields = blocks.flatMap(b => b.toBlobFields());
|
|
750
|
-
const blobs: Blob[] = getBlobsPerL1Block(blobFields);
|
|
830
|
+
const blobs: Blob[] = await getBlobsPerL1Block(blobFields);
|
|
751
831
|
await this.blobClient.sendBlobsToFilestore(blobs);
|
|
752
832
|
this.log.debug(`Uploaded ${blobs.length} blobs to filestore for checkpoint at slot ${proposal.slotNumber}`, {
|
|
753
833
|
...proposalInfo,
|
|
@@ -876,6 +956,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
876
956
|
async createCheckpointProposal(
|
|
877
957
|
checkpointHeader: CheckpointHeader,
|
|
878
958
|
archive: Fr,
|
|
959
|
+
feeAssetPriceModifier: bigint,
|
|
879
960
|
lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
|
|
880
961
|
proposerAddress: EthAddress | undefined,
|
|
881
962
|
options: CheckpointProposalOptions = {},
|
|
@@ -897,6 +978,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
897
978
|
const newProposal = await this.validationService.createCheckpointProposal(
|
|
898
979
|
checkpointHeader,
|
|
899
980
|
archive,
|
|
981
|
+
feeAssetPriceModifier,
|
|
900
982
|
lastBlockInfo,
|
|
901
983
|
proposerAddress,
|
|
902
984
|
options,
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export * from './nullifier_cache.js';
|
|
2
|
-
export * from './tx_validator_factory.js';
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eF92YWxpZGF0b3IvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDJCQUEyQixDQUFDIn0=
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tx_validator/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { NullifierSource } from '@aztec/p2p';
|
|
2
|
-
import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
|
|
3
|
-
/**
|
|
4
|
-
* Implements a nullifier source by checking a DB and an in-memory collection.
|
|
5
|
-
* Intended for validating transactions as they are added to a block.
|
|
6
|
-
*/
|
|
7
|
-
export declare class NullifierCache implements NullifierSource {
|
|
8
|
-
private db;
|
|
9
|
-
nullifiers: Set<string>;
|
|
10
|
-
constructor(db: MerkleTreeReadOperations);
|
|
11
|
-
nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]>;
|
|
12
|
-
addNullifiers(nullifiers: Buffer[]): void;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVsbGlmaWVyX2NhY2hlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHhfdmFsaWRhdG9yL251bGxpZmllcl9jYWNoZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDbEQsT0FBTyxLQUFLLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUdoRjs7O0dBR0c7QUFDSCxxQkFBYSxjQUFlLFlBQVcsZUFBZTtJQUd4QyxPQUFPLENBQUMsRUFBRTtJQUZ0QixVQUFVLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBRXhCLFlBQW9CLEVBQUUsRUFBRSx3QkFBd0IsRUFFL0M7SUFFWSxlQUFlLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQU9yRTtJQUVNLGFBQWEsQ0FBQyxVQUFVLEVBQUUsTUFBTSxFQUFFLFFBSXhDO0NBQ0YifQ==
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nullifier_cache.d.ts","sourceRoot":"","sources":["../../src/tx_validator/nullifier_cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAGxC,OAAO,CAAC,EAAE;IAFtB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAExB,YAAoB,EAAE,EAAE,wBAAwB,EAE/C;IAEY,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAOrE;IAEM,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAIxC;CACF"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
2
|
-
/**
|
|
3
|
-
* Implements a nullifier source by checking a DB and an in-memory collection.
|
|
4
|
-
* Intended for validating transactions as they are added to a block.
|
|
5
|
-
*/ export class NullifierCache {
|
|
6
|
-
db;
|
|
7
|
-
nullifiers;
|
|
8
|
-
constructor(db){
|
|
9
|
-
this.db = db;
|
|
10
|
-
this.nullifiers = new Set();
|
|
11
|
-
}
|
|
12
|
-
async nullifiersExist(nullifiers) {
|
|
13
|
-
const cacheResults = nullifiers.map((n)=>this.nullifiers.has(n.toString()));
|
|
14
|
-
const toCheckDb = nullifiers.filter((_n, index)=>!cacheResults[index]);
|
|
15
|
-
const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
|
|
16
|
-
let dbIndex = 0;
|
|
17
|
-
return nullifiers.map((_n, index)=>cacheResults[index] || dbHits[dbIndex++] !== undefined);
|
|
18
|
-
}
|
|
19
|
-
addNullifiers(nullifiers) {
|
|
20
|
-
for (const nullifier of nullifiers){
|
|
21
|
-
this.nullifiers.add(nullifier.toString());
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
|
-
import type { LoggerBindings } from '@aztec/foundation/log';
|
|
3
|
-
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
4
|
-
import type { GasFees } from '@aztec/stdlib/gas';
|
|
5
|
-
import type { AllowedElement, ClientProtocolCircuitVerifier, MerkleTreeReadOperations, PublicProcessorValidator } from '@aztec/stdlib/interfaces/server';
|
|
6
|
-
import { GlobalVariables, type Tx, type TxValidator } from '@aztec/stdlib/tx';
|
|
7
|
-
import type { UInt64 } from '@aztec/stdlib/types';
|
|
8
|
-
export declare function createValidatorForAcceptingTxs(db: MerkleTreeReadOperations, contractDataSource: ContractDataSource, verifier: ClientProtocolCircuitVerifier | undefined, { l1ChainId, rollupVersion, setupAllowList, gasFees, skipFeeEnforcement, timestamp, blockNumber, txsPermitted }: {
|
|
9
|
-
l1ChainId: number;
|
|
10
|
-
rollupVersion: number;
|
|
11
|
-
setupAllowList: AllowedElement[];
|
|
12
|
-
gasFees: GasFees;
|
|
13
|
-
skipFeeEnforcement?: boolean;
|
|
14
|
-
timestamp: UInt64;
|
|
15
|
-
blockNumber: BlockNumber;
|
|
16
|
-
txsPermitted: boolean;
|
|
17
|
-
}, bindings?: LoggerBindings): TxValidator<Tx>;
|
|
18
|
-
export declare function createValidatorForBlockBuilding(db: MerkleTreeReadOperations, contractDataSource: ContractDataSource, globalVariables: GlobalVariables, setupAllowList: AllowedElement[], bindings?: LoggerBindings): PublicProcessorValidator;
|
|
19
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHhfdmFsaWRhdG9yX2ZhY3RvcnkuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eF92YWxpZGF0b3IvdHhfdmFsaWRhdG9yX2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRTlELE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBaUI1RCxPQUFPLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ2pFLE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxFQUNWLGNBQWMsRUFDZCw2QkFBNkIsRUFDN0Isd0JBQXdCLEVBQ3hCLHdCQUF3QixFQUN6QixNQUFNLGlDQUFpQyxDQUFDO0FBRXpDLE9BQU8sRUFBRSxlQUFlLEVBQUUsS0FBSyxFQUFFLEVBQUUsS0FBSyxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUM5RSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUlsRCx3QkFBZ0IsOEJBQThCLENBQzVDLEVBQUUsRUFBRSx3QkFBd0IsRUFDNUIsa0JBQWtCLEVBQUUsa0JBQWtCLEVBQ3RDLFFBQVEsRUFBRSw2QkFBNkIsR0FBRyxTQUFTLEVBQ25ELEVBQ0UsU0FBUyxFQUNULGFBQWEsRUFDYixjQUFjLEVBQ2QsT0FBTyxFQUNQLGtCQUFrQixFQUNsQixTQUFTLEVBQ1QsV0FBVyxFQUNYLFlBQVksRUFDYixFQUFFO0lBQ0QsU0FBUyxFQUFFLE1BQU0sQ0FBQztJQUNsQixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLGNBQWMsRUFBRSxjQUFjLEVBQUUsQ0FBQztJQUNqQyxPQUFPLEVBQUUsT0FBTyxDQUFDO0lBQ2pCLGtCQUFrQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBQzdCLFNBQVMsRUFBRSxNQUFNLENBQUM7SUFDbEIsV0FBVyxFQUFFLFdBQVcsQ0FBQztJQUN6QixZQUFZLEVBQUUsT0FBTyxDQUFDO0NBQ3ZCLEVBQ0QsUUFBUSxDQUFDLEVBQUUsY0FBYyxHQUN4QixXQUFXLENBQUMsRUFBRSxDQUFDLENBcUNqQjtBQUVELHdCQUFnQiwrQkFBK0IsQ0FDN0MsRUFBRSxFQUFFLHdCQUF3QixFQUM1QixrQkFBa0IsRUFBRSxrQkFBa0IsRUFDdEMsZUFBZSxFQUFFLGVBQWUsRUFDaEMsY0FBYyxFQUFFLGNBQWMsRUFBRSxFQUNoQyxRQUFRLENBQUMsRUFBRSxjQUFjLEdBQ3hCLHdCQUF3QixDQWlCMUIifQ==
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tx_validator_factory.d.ts","sourceRoot":"","sources":["../../src/tx_validator/tx_validator_factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiB5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIlD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,wBAAwB,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,QAAQ,EAAE,6BAA6B,GAAG,SAAS,EACnD,EACE,SAAS,EACT,aAAa,EACb,cAAc,EACd,OAAO,EACP,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,YAAY,EACb,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;CACvB,EACD,QAAQ,CAAC,EAAE,cAAc,GACxB,WAAW,CAAC,EAAE,CAAC,CAqCjB;AAED,wBAAgB,+BAA+B,CAC7C,EAAE,EAAE,wBAAwB,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,eAAe,EAAE,eAAe,EAChC,cAAc,EAAE,cAAc,EAAE,EAChC,QAAQ,CAAC,EAAE,cAAc,GACxB,wBAAwB,CAiB1B"}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
|
-
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
|
|
3
|
-
import { AggregateTxValidator, ArchiveCache, BlockHeaderTxValidator, DataTxValidator, DoubleSpendTxValidator, GasTxValidator, MetadataTxValidator, PhasesTxValidator, SizeTxValidator, TimestampTxValidator, TxPermittedValidator, TxProofValidator } from '@aztec/p2p';
|
|
4
|
-
import { ProtocolContractAddress, protocolContractsHash } from '@aztec/protocol-contracts';
|
|
5
|
-
import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
|
|
6
|
-
import { NullifierCache } from './nullifier_cache.js';
|
|
7
|
-
export function createValidatorForAcceptingTxs(db, contractDataSource, verifier, { l1ChainId, rollupVersion, setupAllowList, gasFees, skipFeeEnforcement, timestamp, blockNumber, txsPermitted }, bindings) {
|
|
8
|
-
const validators = [
|
|
9
|
-
new TxPermittedValidator(txsPermitted, bindings),
|
|
10
|
-
new SizeTxValidator(bindings),
|
|
11
|
-
new DataTxValidator(bindings),
|
|
12
|
-
new MetadataTxValidator({
|
|
13
|
-
l1ChainId: new Fr(l1ChainId),
|
|
14
|
-
rollupVersion: new Fr(rollupVersion),
|
|
15
|
-
protocolContractsHash,
|
|
16
|
-
vkTreeRoot: getVKTreeRoot()
|
|
17
|
-
}, bindings),
|
|
18
|
-
new TimestampTxValidator({
|
|
19
|
-
timestamp,
|
|
20
|
-
blockNumber
|
|
21
|
-
}, bindings),
|
|
22
|
-
new DoubleSpendTxValidator(new NullifierCache(db), bindings),
|
|
23
|
-
new PhasesTxValidator(contractDataSource, setupAllowList, timestamp, bindings),
|
|
24
|
-
new BlockHeaderTxValidator(new ArchiveCache(db), bindings)
|
|
25
|
-
];
|
|
26
|
-
if (!skipFeeEnforcement) {
|
|
27
|
-
validators.push(new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings));
|
|
28
|
-
}
|
|
29
|
-
if (verifier) {
|
|
30
|
-
validators.push(new TxProofValidator(verifier, bindings));
|
|
31
|
-
}
|
|
32
|
-
return new AggregateTxValidator(...validators);
|
|
33
|
-
}
|
|
34
|
-
export function createValidatorForBlockBuilding(db, contractDataSource, globalVariables, setupAllowList, bindings) {
|
|
35
|
-
const nullifierCache = new NullifierCache(db);
|
|
36
|
-
const archiveCache = new ArchiveCache(db);
|
|
37
|
-
const publicStateSource = new DatabasePublicStateSource(db);
|
|
38
|
-
return {
|
|
39
|
-
preprocessValidator: preprocessValidator(nullifierCache, archiveCache, publicStateSource, contractDataSource, globalVariables, setupAllowList, bindings),
|
|
40
|
-
nullifierCache
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function preprocessValidator(nullifierCache, archiveCache, publicStateSource, contractDataSource, globalVariables, setupAllowList, bindings) {
|
|
44
|
-
// We don't include the TxProofValidator nor the DataTxValidator here because they are already checked by the time we get to block building.
|
|
45
|
-
return new AggregateTxValidator(new MetadataTxValidator({
|
|
46
|
-
l1ChainId: globalVariables.chainId,
|
|
47
|
-
rollupVersion: globalVariables.version,
|
|
48
|
-
protocolContractsHash,
|
|
49
|
-
vkTreeRoot: getVKTreeRoot()
|
|
50
|
-
}, bindings), new TimestampTxValidator({
|
|
51
|
-
timestamp: globalVariables.timestamp,
|
|
52
|
-
blockNumber: globalVariables.blockNumber
|
|
53
|
-
}, bindings), new DoubleSpendTxValidator(nullifierCache, bindings), new PhasesTxValidator(contractDataSource, setupAllowList, globalVariables.timestamp, bindings), new GasTxValidator(publicStateSource, ProtocolContractAddress.FeeJuice, globalVariables.gasFees, bindings), new BlockHeaderTxValidator(archiveCache, bindings));
|
|
54
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { NullifierSource } from '@aztec/p2p';
|
|
2
|
-
import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
|
|
3
|
-
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Implements a nullifier source by checking a DB and an in-memory collection.
|
|
7
|
-
* Intended for validating transactions as they are added to a block.
|
|
8
|
-
*/
|
|
9
|
-
export class NullifierCache implements NullifierSource {
|
|
10
|
-
nullifiers: Set<string>;
|
|
11
|
-
|
|
12
|
-
constructor(private db: MerkleTreeReadOperations) {
|
|
13
|
-
this.nullifiers = new Set();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public async nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]> {
|
|
17
|
-
const cacheResults = nullifiers.map(n => this.nullifiers.has(n.toString()));
|
|
18
|
-
const toCheckDb = nullifiers.filter((_n, index) => !cacheResults[index]);
|
|
19
|
-
const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
|
|
20
|
-
|
|
21
|
-
let dbIndex = 0;
|
|
22
|
-
return nullifiers.map((_n, index) => cacheResults[index] || dbHits[dbIndex++] !== undefined);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public addNullifiers(nullifiers: Buffer[]) {
|
|
26
|
-
for (const nullifier of nullifiers) {
|
|
27
|
-
this.nullifiers.add(nullifier.toString());
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|