@aztec/p2p 0.55.0 → 0.55.1

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.
Files changed (53) hide show
  1. package/dest/client/index.d.ts +2 -2
  2. package/dest/client/index.d.ts.map +1 -1
  3. package/dest/client/index.js +43 -38
  4. package/dest/config.d.ts +49 -0
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +66 -2
  7. package/dest/index.d.ts +1 -0
  8. package/dest/index.d.ts.map +1 -1
  9. package/dest/index.js +2 -1
  10. package/dest/service/libp2p_service.d.ts +10 -11
  11. package/dest/service/libp2p_service.d.ts.map +1 -1
  12. package/dest/service/libp2p_service.js +103 -18
  13. package/dest/service/peer_manager.d.ts +9 -13
  14. package/dest/service/peer_manager.d.ts.map +1 -1
  15. package/dest/service/peer_manager.js +15 -1
  16. package/dest/service/peer_scoring.d.ts +32 -0
  17. package/dest/service/peer_scoring.d.ts.map +1 -0
  18. package/dest/service/peer_scoring.js +67 -0
  19. package/dest/tx_validator/aggregate_tx_validator.d.ts +7 -0
  20. package/dest/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
  21. package/dest/tx_validator/aggregate_tx_validator.js +23 -0
  22. package/dest/tx_validator/data_validator.d.ts +6 -0
  23. package/dest/tx_validator/data_validator.d.ts.map +1 -0
  24. package/dest/tx_validator/data_validator.js +47 -0
  25. package/dest/tx_validator/double_spend_validator.d.ts +12 -0
  26. package/dest/tx_validator/double_spend_validator.d.ts.map +1 -0
  27. package/dest/tx_validator/double_spend_validator.js +53 -0
  28. package/dest/tx_validator/index.d.ts +6 -0
  29. package/dest/tx_validator/index.d.ts.map +1 -0
  30. package/dest/tx_validator/index.js +6 -0
  31. package/dest/tx_validator/metadata_validator.d.ts +10 -0
  32. package/dest/tx_validator/metadata_validator.d.ts.map +1 -0
  33. package/dest/tx_validator/metadata_validator.js +50 -0
  34. package/dest/tx_validator/tx_proof_validator.d.ts +9 -0
  35. package/dest/tx_validator/tx_proof_validator.d.ts.map +1 -0
  36. package/dest/tx_validator/tx_proof_validator.js +29 -0
  37. package/dest/util.d.ts +7 -0
  38. package/dest/util.d.ts.map +1 -1
  39. package/dest/util.js +1 -1
  40. package/package.json +6 -6
  41. package/src/client/index.ts +65 -47
  42. package/src/config.ts +127 -0
  43. package/src/index.ts +1 -0
  44. package/src/service/libp2p_service.ts +137 -24
  45. package/src/service/peer_manager.ts +23 -4
  46. package/src/service/peer_scoring.ts +81 -0
  47. package/src/tx_validator/aggregate_tx_validator.ts +24 -0
  48. package/src/tx_validator/data_validator.ts +61 -0
  49. package/src/tx_validator/double_spend_validator.ts +65 -0
  50. package/src/tx_validator/index.ts +5 -0
  51. package/src/tx_validator/metadata_validator.ts +61 -0
  52. package/src/tx_validator/tx_proof_validator.ts +28 -0
  53. package/src/util.ts +8 -0
@@ -1,36 +1,49 @@
1
1
  import {
2
2
  BlockAttestation,
3
3
  BlockProposal,
4
+ type ClientProtocolCircuitVerifier,
4
5
  type Gossipable,
6
+ type L2BlockSource,
7
+ MerkleTreeId,
5
8
  type RawGossipMessage,
6
9
  TopicType,
7
10
  TopicTypeMap,
8
11
  Tx,
9
12
  TxHash,
13
+ type WorldStateSynchronizer,
10
14
  } from '@aztec/circuit-types';
15
+ import { Fr } from '@aztec/circuits.js';
11
16
  import { createDebugLogger } from '@aztec/foundation/log';
12
17
  import { SerialQueue } from '@aztec/foundation/queue';
13
18
  import { RunningPromise } from '@aztec/foundation/running-promise';
14
19
  import type { AztecKVStore } from '@aztec/kv-store';
15
20
 
16
21
  import { type ENR } from '@chainsafe/enr';
17
- import { type GossipsubEvents, gossipsub } from '@chainsafe/libp2p-gossipsub';
22
+ import { type GossipSub, type GossipSubComponents, gossipsub } from '@chainsafe/libp2p-gossipsub';
23
+ import { createPeerScoreParams, createTopicScoreParams } from '@chainsafe/libp2p-gossipsub/score';
18
24
  import { noise } from '@chainsafe/libp2p-noise';
19
25
  import { yamux } from '@chainsafe/libp2p-yamux';
20
26
  import { identify } from '@libp2p/identify';
21
- import type { PeerId, PubSub } from '@libp2p/interface';
27
+ import type { PeerId } from '@libp2p/interface';
22
28
  import '@libp2p/kad-dht';
23
29
  import { mplex } from '@libp2p/mplex';
24
30
  import { createFromJSON, createSecp256k1PeerId } from '@libp2p/peer-id-factory';
25
31
  import { tcp } from '@libp2p/tcp';
26
- import { type Libp2p, createLibp2p } from 'libp2p';
32
+ import { createLibp2p } from 'libp2p';
27
33
 
28
34
  import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
29
35
  import { type P2PConfig } from '../config.js';
30
36
  import { type TxPool } from '../tx_pool/index.js';
31
- import { convertToMultiaddr } from '../util.js';
37
+ import {
38
+ DataTxValidator,
39
+ DoubleSpendTxValidator,
40
+ MetadataTxValidator,
41
+ TxProofValidator,
42
+ } from '../tx_validator/index.js';
43
+ import { type PubSubLibp2p, convertToMultiaddr } from '../util.js';
32
44
  import { AztecDatastore } from './data_store.js';
33
45
  import { PeerManager } from './peer_manager.js';
46
+ import { PeerErrorSeverity } from './peer_scoring.js';
34
47
  import { pingHandler, statusHandler } from './reqresp/handlers.js';
35
48
  import {
36
49
  DEFAULT_SUB_PROTOCOL_HANDLERS,
@@ -45,11 +58,6 @@ import {
45
58
  import { ReqResp } from './reqresp/reqresp.js';
46
59
  import type { P2PService, PeerDiscoveryService } from './service.js';
47
60
 
48
- export interface PubSubLibp2p extends Libp2p {
49
- services: {
50
- pubsub: PubSub<GossipsubEvents>;
51
- };
52
- }
53
61
  /**
54
62
  * Create a libp2p peer ID from the private key if provided, otherwise creates a new random ID.
55
63
  * @param privateKey - Optional peer ID private key as hex string
@@ -90,10 +98,17 @@ export class LibP2PService implements P2PService {
90
98
  private peerDiscoveryService: PeerDiscoveryService,
91
99
  private txPool: TxPool,
92
100
  private attestationPool: AttestationPool,
101
+ private l2BlockSource: L2BlockSource,
102
+ private proofVerifier: ClientProtocolCircuitVerifier,
103
+ private worldStateSynchronizer: WorldStateSynchronizer,
93
104
  private requestResponseHandlers: ReqRespSubProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS,
94
105
  private logger = createDebugLogger('aztec:libp2p_service'),
95
106
  ) {
96
107
  this.peerManager = new PeerManager(node, peerDiscoveryService, config, logger);
108
+ this.node.services.pubsub.score.params.appSpecificScore = (peerId: string) => {
109
+ return this.peerManager.getPeerScore(peerId);
110
+ };
111
+ this.node.services.pubsub.score.params.appSpecificWeight = 10;
97
112
  this.reqresp = new ReqResp(config, node);
98
113
 
99
114
  this.blockReceivedCallback = (block: BlockProposal): Promise<BlockAttestation | undefined> => {
@@ -136,15 +151,15 @@ export class LibP2PService implements P2PService {
136
151
 
137
152
  // add GossipSub listener
138
153
  this.node.services.pubsub.addEventListener('gossipsub:message', async e => {
139
- const { msg } = e.detail;
154
+ const { msg, propagationSource: peerId } = e.detail;
140
155
  this.logger.debug(`Received PUBSUB message.`);
141
156
 
142
- await this.jobQueue.put(() => this.handleNewGossipMessage(msg));
157
+ await this.jobQueue.put(() => this.handleNewGossipMessage(msg, peerId));
143
158
  });
144
159
 
145
160
  // Start running promise for peer discovery
146
161
  this.discoveryRunningPromise = new RunningPromise(() => {
147
- this.peerManager.discover();
162
+ this.peerManager.heartbeat();
148
163
  }, this.config.peerCheckIntervalMS);
149
164
  this.discoveryRunningPromise.start();
150
165
  await this.reqresp.start(this.requestResponseHandlers);
@@ -181,6 +196,9 @@ export class LibP2PService implements P2PService {
181
196
  peerId: PeerId,
182
197
  txPool: TxPool,
183
198
  attestationPool: AttestationPool,
199
+ l2BlockSource: L2BlockSource,
200
+ proofVerifier: ClientProtocolCircuitVerifier,
201
+ worldStateSynchronizer: WorldStateSynchronizer,
184
202
  store: AztecKVStore,
185
203
  ) {
186
204
  const { tcpListenAddress, tcpAnnounceAddress, minPeerCount, maxPeerCount } = config;
@@ -223,13 +241,22 @@ export class LibP2PService implements P2PService {
223
241
  }),
224
242
  pubsub: gossipsub({
225
243
  allowPublishToZeroTopicPeers: true,
226
- D: 6,
227
- Dlo: 4,
228
- Dhi: 12,
229
- heartbeatInterval: 1_000,
230
- mcacheLength: 5,
231
- mcacheGossip: 3,
232
- }),
244
+ D: config.gossipsubD,
245
+ Dlo: config.gossipsubDlo,
246
+ Dhi: config.gossipsubDhi,
247
+ heartbeatInterval: config.gossipsubInterval,
248
+ mcacheLength: config.gossipsubMcacheLength,
249
+ mcacheGossip: config.gossipsubMcacheGossip,
250
+ scoreParams: createPeerScoreParams({
251
+ topics: {
252
+ [Tx.p2pTopic]: createTopicScoreParams({
253
+ topicWeight: 1,
254
+ invalidMessageDeliveriesWeight: -20,
255
+ invalidMessageDeliveriesDecay: 0.5,
256
+ }),
257
+ },
258
+ }),
259
+ }) as (components: GossipSubComponents) => GossipSub,
233
260
  },
234
261
  });
235
262
 
@@ -252,7 +279,17 @@ export class LibP2PService implements P2PService {
252
279
  [TX_REQ_PROTOCOL]: txHandler,
253
280
  };
254
281
 
255
- return new LibP2PService(config, node, peerDiscoveryService, txPool, attestationPool, requestResponseHandlers);
282
+ return new LibP2PService(
283
+ config,
284
+ node,
285
+ peerDiscoveryService,
286
+ txPool,
287
+ attestationPool,
288
+ l2BlockSource,
289
+ proofVerifier,
290
+ worldStateSynchronizer,
291
+ requestResponseHandlers,
292
+ );
256
293
  }
257
294
 
258
295
  /**
@@ -323,10 +360,10 @@ export class LibP2PService implements P2PService {
323
360
  * @param topic - The message's topic.
324
361
  * @param data - The message data
325
362
  */
326
- private async handleNewGossipMessage(message: RawGossipMessage) {
363
+ private async handleNewGossipMessage(message: RawGossipMessage, peerId: PeerId) {
327
364
  if (message.topic === Tx.p2pTopic) {
328
365
  const tx = Tx.fromBuffer(Buffer.from(message.data));
329
- await this.processTxFromPeer(tx);
366
+ await this.processTxFromPeer(tx, peerId);
330
367
  }
331
368
  if (message.topic === BlockAttestation.p2pTopic) {
332
369
  const attestation = BlockAttestation.fromBuffer(Buffer.from(message.data));
@@ -376,11 +413,87 @@ export class LibP2PService implements P2PService {
376
413
  void this.jobQueue.put(() => Promise.resolve(this.sendToPeers(message)));
377
414
  }
378
415
 
379
- private async processTxFromPeer(tx: Tx): Promise<void> {
416
+ private async processTxFromPeer(tx: Tx, peerId: PeerId): Promise<void> {
380
417
  const txHash = tx.getTxHash();
381
418
  const txHashString = txHash.toString();
382
419
  this.logger.verbose(`Received tx ${txHashString} from external peer.`);
383
- await this.txPool.addTxs([tx]);
420
+
421
+ const isValidTx = await this.validateTx(tx, peerId);
422
+
423
+ if (isValidTx) {
424
+ await this.txPool.addTxs([tx]);
425
+ }
426
+ }
427
+
428
+ private async validateTx(tx: Tx, peerId: PeerId): Promise<boolean> {
429
+ const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1;
430
+ // basic data validation
431
+ const dataValidator = new DataTxValidator();
432
+ const [_, dataInvalidTxs] = await dataValidator.validateTxs([tx]);
433
+ if (dataInvalidTxs.length > 0) {
434
+ // penalize
435
+ this.node.services.pubsub.score.markInvalidMessageDelivery(peerId.toString(), Tx.p2pTopic);
436
+ return false;
437
+ }
438
+
439
+ // metadata validation
440
+ const metadataValidator = new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(blockNumber));
441
+ const [__, metaInvalidTxs] = await metadataValidator.validateTxs([tx]);
442
+ if (metaInvalidTxs.length > 0) {
443
+ // penalize
444
+ this.node.services.pubsub.score.markInvalidMessageDelivery(peerId.toString(), Tx.p2pTopic);
445
+ return false;
446
+ }
447
+
448
+ // double spend validation
449
+ const doubleSpendValidator = new DoubleSpendTxValidator({
450
+ getNullifierIndex: async (nullifier: Fr) => {
451
+ const merkleTree = this.worldStateSynchronizer.getLatest();
452
+ const index = await merkleTree.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
453
+ return index;
454
+ },
455
+ });
456
+ const [___, doubleSpendInvalidTxs] = await doubleSpendValidator.validateTxs([tx]);
457
+ if (doubleSpendInvalidTxs.length > 0) {
458
+ // check if nullifier is older than 20 blocks
459
+ if (blockNumber - this.config.severePeerPenaltyBlockLength > 0) {
460
+ const snapshotValidator = new DoubleSpendTxValidator({
461
+ getNullifierIndex: async (nullifier: Fr) => {
462
+ const merkleTree = this.worldStateSynchronizer.getSnapshot(
463
+ blockNumber - this.config.severePeerPenaltyBlockLength,
464
+ );
465
+ const index = await merkleTree.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
466
+ return index;
467
+ },
468
+ });
469
+
470
+ const [____, snapshotInvalidTxs] = await snapshotValidator.validateTxs([tx]);
471
+ // High penalty if nullifier is older than 20 blocks
472
+ if (snapshotInvalidTxs.length > 0) {
473
+ // penalize
474
+ this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
475
+ return false;
476
+ }
477
+ }
478
+ // penalize
479
+ this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
480
+ return false;
481
+ }
482
+
483
+ // proof validation
484
+ const proofValidator = new TxProofValidator(this.proofVerifier);
485
+ const [_____, proofInvalidTxs] = await proofValidator.validateTxs([tx]);
486
+ if (proofInvalidTxs.length > 0) {
487
+ // penalize
488
+ this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
489
+ return false;
490
+ }
491
+
492
+ return true;
493
+ }
494
+
495
+ public getPeerScore(peerId: PeerId): number {
496
+ return this.node.services.pubsub.score.score(peerId.toString());
384
497
  }
385
498
 
386
499
  private async sendToPeers<T extends Gossipable>(message: T) {
@@ -3,9 +3,10 @@ import { createDebugLogger } from '@aztec/foundation/log';
3
3
  import { type ENR } from '@chainsafe/enr';
4
4
  import { type PeerId } from '@libp2p/interface';
5
5
  import { type Multiaddr } from '@multiformats/multiaddr';
6
- import { type Libp2p } from 'libp2p';
7
6
 
8
7
  import { type P2PConfig } from '../config.js';
8
+ import { type PubSubLibp2p } from '../util.js';
9
+ import { type PeerErrorSeverity, PeerScoring } from './peer_scoring.js';
9
10
  import { type PeerDiscoveryService } from './service.js';
10
11
 
11
12
  const MAX_DIAL_ATTEMPTS = 3;
@@ -20,12 +21,15 @@ type CachedPeer = {
20
21
 
21
22
  export class PeerManager {
22
23
  private cachedPeers: Map<string, CachedPeer> = new Map();
24
+ private peerScoring: PeerScoring;
25
+
23
26
  constructor(
24
- private libP2PNode: Libp2p,
27
+ private libP2PNode: PubSubLibp2p,
25
28
  private peerDiscoveryService: PeerDiscoveryService,
26
29
  private config: P2PConfig,
27
30
  private logger = createDebugLogger('aztec:p2p:peer_manager'),
28
31
  ) {
32
+ this.peerScoring = new PeerScoring(config);
29
33
  // Handle new established connections
30
34
  this.libP2PNode.addEventListener('peer:connect', evt => {
31
35
  const peerId = evt.detail;
@@ -52,10 +56,25 @@ export class PeerManager {
52
56
  });
53
57
  }
54
58
 
59
+ public heartbeat() {
60
+ this.discover();
61
+ this.peerScoring.decayAllScores();
62
+ }
63
+
64
+ public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
65
+ const id = peerId.toString();
66
+ const penaltyValue = this.peerScoring.peerPenalties[penalty];
67
+ this.peerScoring.updateScore(id, -penaltyValue);
68
+ }
69
+
70
+ public getPeerScore(peerId: string): number {
71
+ return this.peerScoring.getScore(peerId);
72
+ }
73
+
55
74
  /**
56
75
  * Discovers peers.
57
76
  */
58
- public discover() {
77
+ private discover() {
59
78
  // Get current connections
60
79
  const connections = this.libP2PNode.getConnections();
61
80
 
@@ -155,7 +174,7 @@ export class PeerManager {
155
174
  }
156
175
  }
157
176
 
158
- async dialPeer(peer: CachedPeer) {
177
+ private async dialPeer(peer: CachedPeer) {
159
178
  const id = peer.peerId.toString();
160
179
  await this.libP2PNode.peerStore.merge(peer.peerId, { multiaddrs: [peer.multiaddrTcp] });
161
180
 
@@ -0,0 +1,81 @@
1
+ import { type P2PConfig } from '../config.js';
2
+
3
+ export enum PeerErrorSeverity {
4
+ /**
5
+ * Not malicious action, but it must not be tolerated
6
+ * ~2 occurrences will get the peer banned
7
+ */
8
+ LowToleranceError = 'LowToleranceError',
9
+ /**
10
+ * Negative action that can be tolerated only sometimes
11
+ * ~10 occurrences will get the peer banned
12
+ */
13
+ MidToleranceError = 'MidToleranceError',
14
+ /**
15
+ * Some error that can be tolerated multiple times
16
+ * ~50 occurrences will get the peer banned
17
+ */
18
+ HighToleranceError = 'HighToleranceError',
19
+ }
20
+
21
+ const DefaultPeerPenalties = {
22
+ [PeerErrorSeverity.LowToleranceError]: 2,
23
+ [PeerErrorSeverity.MidToleranceError]: 10,
24
+ [PeerErrorSeverity.HighToleranceError]: 50,
25
+ };
26
+
27
+ export class PeerScoring {
28
+ private scores: Map<string, number> = new Map();
29
+ private lastUpdateTime: Map<string, number> = new Map();
30
+ private decayInterval = 1000 * 60; // 1 minute
31
+ private decayFactor = 0.9;
32
+ peerPenalties: { [key in PeerErrorSeverity]: number };
33
+
34
+ constructor(config: P2PConfig) {
35
+ const orderedValues = config.peerPenaltyValues.sort((a, b) => a - b);
36
+ this.peerPenalties = {
37
+ [PeerErrorSeverity.HighToleranceError]:
38
+ orderedValues[0] ?? DefaultPeerPenalties[PeerErrorSeverity.LowToleranceError],
39
+ [PeerErrorSeverity.MidToleranceError]:
40
+ orderedValues[1] ?? DefaultPeerPenalties[PeerErrorSeverity.MidToleranceError],
41
+ [PeerErrorSeverity.LowToleranceError]:
42
+ orderedValues[2] ?? DefaultPeerPenalties[PeerErrorSeverity.HighToleranceError],
43
+ };
44
+ }
45
+
46
+ updateScore(peerId: string, scoreDelta: number): void {
47
+ const currentTime = Date.now();
48
+ const lastUpdate = this.lastUpdateTime.get(peerId) || currentTime;
49
+ const timePassed = currentTime - lastUpdate;
50
+ const decayPeriods = Math.floor(timePassed / this.decayInterval);
51
+
52
+ let currentScore = this.scores.get(peerId) || 0;
53
+
54
+ // Apply decay
55
+ currentScore *= Math.pow(this.decayFactor, decayPeriods);
56
+
57
+ // Apply new score delta
58
+ currentScore += scoreDelta;
59
+
60
+ this.scores.set(peerId, currentScore);
61
+ this.lastUpdateTime.set(peerId, currentTime);
62
+ }
63
+
64
+ decayAllScores(): void {
65
+ const currentTime = Date.now();
66
+ for (const [peerId, lastUpdate] of this.lastUpdateTime.entries()) {
67
+ const timePassed = currentTime - lastUpdate;
68
+ const decayPeriods = Math.floor(timePassed / this.decayInterval);
69
+ if (decayPeriods > 0) {
70
+ let score = this.scores.get(peerId) || 0;
71
+ score *= Math.pow(this.decayFactor, decayPeriods);
72
+ this.scores.set(peerId, score);
73
+ this.lastUpdateTime.set(peerId, currentTime);
74
+ }
75
+ }
76
+ }
77
+
78
+ getScore(peerId: string): number {
79
+ return this.scores.get(peerId) || 0;
80
+ }
81
+ }
@@ -0,0 +1,24 @@
1
+ import { type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
2
+
3
+ export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValidator<T> {
4
+ #validators: TxValidator<T>[];
5
+ constructor(...validators: TxValidator<T>[]) {
6
+ if (validators.length === 0) {
7
+ throw new Error('At least one validator must be provided');
8
+ }
9
+
10
+ this.#validators = validators;
11
+ }
12
+
13
+ async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
14
+ const invalidTxs: T[] = [];
15
+ let txPool = txs;
16
+ for (const validator of this.#validators) {
17
+ const [valid, invalid] = await validator.validateTxs(txPool);
18
+ invalidTxs.push(...invalid);
19
+ txPool = valid;
20
+ }
21
+
22
+ return [txPool, invalidTxs];
23
+ }
24
+ }
@@ -0,0 +1,61 @@
1
+ import { Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { createDebugLogger } from '@aztec/foundation/log';
3
+
4
+ export class DataTxValidator implements TxValidator<Tx> {
5
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_data');
6
+
7
+ validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
8
+ const validTxs: Tx[] = [];
9
+ const invalidTxs: Tx[] = [];
10
+ for (const tx of txs) {
11
+ if (!this.#hasCorrectExecutionRequests(tx)) {
12
+ invalidTxs.push(tx);
13
+ continue;
14
+ }
15
+
16
+ validTxs.push(tx);
17
+ }
18
+
19
+ return Promise.resolve([validTxs, invalidTxs]);
20
+ }
21
+
22
+ #hasCorrectExecutionRequests(tx: Tx): boolean {
23
+ const callRequests = [
24
+ ...tx.data.getRevertiblePublicCallRequests(),
25
+ ...tx.data.getNonRevertiblePublicCallRequests(),
26
+ ];
27
+ if (callRequests.length !== tx.enqueuedPublicFunctionCalls.length) {
28
+ this.#log.warn(
29
+ `Rejecting tx ${Tx.getHash(tx)} because of mismatch number of execution requests for public calls. Expected ${
30
+ callRequests.length
31
+ }. Got ${tx.enqueuedPublicFunctionCalls.length}.`,
32
+ );
33
+ return false;
34
+ }
35
+
36
+ const invalidExecutionRequestIndex = tx.enqueuedPublicFunctionCalls.findIndex(
37
+ (execRequest, i) => !execRequest.isForCallRequest(callRequests[i]),
38
+ );
39
+ if (invalidExecutionRequestIndex !== -1) {
40
+ this.#log.warn(
41
+ `Rejecting tx ${Tx.getHash(
42
+ tx,
43
+ )} because of incorrect execution requests for public call at index ${invalidExecutionRequestIndex}.`,
44
+ );
45
+ return false;
46
+ }
47
+
48
+ const teardownCallRequest = tx.data.getTeardownPublicCallRequest();
49
+ const isInvalidTeardownExecutionRequest =
50
+ (!teardownCallRequest && !tx.publicTeardownFunctionCall.isEmpty()) ||
51
+ (teardownCallRequest && !tx.publicTeardownFunctionCall.isForCallRequest(teardownCallRequest));
52
+ if (isInvalidTeardownExecutionRequest) {
53
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} because of incorrect teardown execution requests.`);
54
+ return false;
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ // TODO: Check logs.
61
+ }
@@ -0,0 +1,65 @@
1
+ import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { Fr } from '@aztec/circuits.js';
3
+ import { createDebugLogger } from '@aztec/foundation/log';
4
+
5
+ export interface NullifierSource {
6
+ getNullifierIndex: (nullifier: Fr) => Promise<bigint | undefined>;
7
+ }
8
+
9
+ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
10
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_double_spend');
11
+ #nullifierSource: NullifierSource;
12
+
13
+ constructor(nullifierSource: NullifierSource, private readonly isValidatingBlock: boolean = true) {
14
+ this.#nullifierSource = nullifierSource;
15
+ }
16
+
17
+ async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
18
+ const validTxs: T[] = [];
19
+ const invalidTxs: T[] = [];
20
+ const thisBlockNullifiers = new Set<bigint>();
21
+
22
+ for (const tx of txs) {
23
+ if (!(await this.#uniqueNullifiers(tx, thisBlockNullifiers))) {
24
+ invalidTxs.push(tx);
25
+ continue;
26
+ }
27
+
28
+ validTxs.push(tx);
29
+ }
30
+
31
+ return [validTxs, invalidTxs];
32
+ }
33
+
34
+ async #uniqueNullifiers(tx: AnyTx, thisBlockNullifiers: Set<bigint>): Promise<boolean> {
35
+ const nullifiers = tx.data.getNonEmptyNullifiers().map(x => x.toBigInt());
36
+
37
+ // Ditch this tx if it has repeated nullifiers
38
+ const uniqueNullifiers = new Set(nullifiers);
39
+ if (uniqueNullifiers.size !== nullifiers.length) {
40
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for emitting duplicate nullifiers`);
41
+ return false;
42
+ }
43
+
44
+ if (this.isValidatingBlock) {
45
+ for (const nullifier of nullifiers) {
46
+ if (thisBlockNullifiers.has(nullifier)) {
47
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier in the same block`);
48
+ return false;
49
+ }
50
+
51
+ thisBlockNullifiers.add(nullifier);
52
+ }
53
+ }
54
+
55
+ const nullifierIndexes = await Promise.all(nullifiers.map(n => this.#nullifierSource.getNullifierIndex(new Fr(n))));
56
+
57
+ const hasDuplicates = nullifierIndexes.some(index => index !== undefined);
58
+ if (hasDuplicates) {
59
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating nullifiers present in state trees`);
60
+ return false;
61
+ }
62
+
63
+ return true;
64
+ }
65
+ }
@@ -0,0 +1,5 @@
1
+ export * from './aggregate_tx_validator.js';
2
+ export * from './data_validator.js';
3
+ export * from './double_spend_validator.js';
4
+ export * from './metadata_validator.js';
5
+ export * from './tx_proof_validator.js';
@@ -0,0 +1,61 @@
1
+ import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { type Fr } from '@aztec/circuits.js';
3
+ import { createDebugLogger } from '@aztec/foundation/log';
4
+
5
+ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
6
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_metadata');
7
+
8
+ constructor(private chainId: Fr, private blockNumber: Fr) {}
9
+
10
+ validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
11
+ const validTxs: T[] = [];
12
+ const invalidTxs: T[] = [];
13
+ for (const tx of txs) {
14
+ if (!this.#hasCorrectChainId(tx)) {
15
+ invalidTxs.push(tx);
16
+ continue;
17
+ }
18
+
19
+ if (!this.#isValidForBlockNumber(tx)) {
20
+ invalidTxs.push(tx);
21
+ continue;
22
+ }
23
+
24
+ validTxs.push(tx);
25
+ }
26
+
27
+ return Promise.resolve([validTxs, invalidTxs]);
28
+ }
29
+
30
+ #hasCorrectChainId(tx: T): boolean {
31
+ if (!tx.data.constants.txContext.chainId.equals(this.chainId)) {
32
+ this.#log.warn(
33
+ `Rejecting tx ${Tx.getHash(
34
+ tx,
35
+ )} because of incorrect chain ${tx.data.constants.txContext.chainId.toNumber()} != ${this.chainId.toNumber()}`,
36
+ );
37
+ return false;
38
+ } else {
39
+ return true;
40
+ }
41
+ }
42
+
43
+ #isValidForBlockNumber(tx: T): boolean {
44
+ const target =
45
+ tx instanceof Tx
46
+ ? tx.data.forRollup?.rollupValidationRequests || tx.data.forPublic!.validationRequests.forRollup
47
+ : tx.data.rollupValidationRequests;
48
+ const maxBlockNumber = target.maxBlockNumber;
49
+
50
+ if (maxBlockNumber.isSome && maxBlockNumber.value < this.blockNumber) {
51
+ this.#log.warn(
52
+ `Rejecting tx ${Tx.getHash(tx)} for low max block number. Tx max block number: ${
53
+ maxBlockNumber.value
54
+ }, current block number: ${this.blockNumber}.`,
55
+ );
56
+ return false;
57
+ } else {
58
+ return true;
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,28 @@
1
+ import { type ClientProtocolCircuitVerifier, Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { createDebugLogger } from '@aztec/foundation/log';
3
+
4
+ export class TxProofValidator implements TxValidator<Tx> {
5
+ #log = createDebugLogger('aztec:sequencer:tx_validator:private_proof');
6
+
7
+ constructor(private verifier: ClientProtocolCircuitVerifier) {}
8
+
9
+ async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
10
+ const validTxs: Tx[] = [];
11
+ const invalidTxs: Tx[] = [];
12
+
13
+ for (const tx of txs) {
14
+ if (await this.verifier.verifyProof(tx)) {
15
+ validTxs.push(tx);
16
+ } else {
17
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for invalid proof`);
18
+ invalidTxs.push(tx);
19
+ }
20
+ }
21
+
22
+ return [validTxs, invalidTxs];
23
+ }
24
+
25
+ validateTx(tx: Tx): Promise<boolean> {
26
+ return this.verifier.verifyProof(tx);
27
+ }
28
+ }
package/src/util.ts CHANGED
@@ -1,4 +1,12 @@
1
+ import type { GossipSub } from '@chainsafe/libp2p-gossipsub';
1
2
  import { resolve } from 'dns/promises';
3
+ import type { Libp2p } from 'libp2p';
4
+
5
+ export interface PubSubLibp2p extends Libp2p {
6
+ services: {
7
+ pubsub: GossipSub;
8
+ };
9
+ }
2
10
 
3
11
  /**
4
12
  * Converts an address string to a multiaddr string.