@aztec/p2p 0.82.2 → 0.82.3-nightly.20250403

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 (40) hide show
  1. package/dest/client/p2p_client.d.ts +5 -1
  2. package/dest/client/p2p_client.d.ts.map +1 -1
  3. package/dest/client/p2p_client.js +17 -6
  4. package/dest/config.d.ts +1 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +1 -0
  7. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +11 -2
  8. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  9. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  10. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +40 -19
  11. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +2 -1
  12. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +14 -2
  14. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +2 -1
  15. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  16. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +8 -2
  17. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
  18. package/dest/msg_validators/tx_validator/double_spend_validator.js +21 -27
  19. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +2 -1
  20. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  21. package/dest/msg_validators/tx_validator/metadata_validator.js +14 -1
  22. package/dest/services/libp2p/libp2p_service.d.ts +3 -1
  23. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  24. package/dest/services/libp2p/libp2p_service.js +7 -4
  25. package/dest/test-helpers/reqresp-nodes.js +3 -3
  26. package/dest/testbench/p2p_client_testbench_worker.js +7 -2
  27. package/dest/testbench/worker_client_manager.js +1 -1
  28. package/package.json +10 -10
  29. package/src/client/p2p_client.ts +23 -7
  30. package/src/config.ts +1 -0
  31. package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
  32. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -20
  33. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +16 -1
  34. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +12 -1
  35. package/src/msg_validators/tx_validator/double_spend_validator.ts +12 -17
  36. package/src/msg_validators/tx_validator/metadata_validator.ts +17 -1
  37. package/src/services/libp2p/libp2p_service.ts +13 -4
  38. package/src/test-helpers/reqresp-nodes.ts +3 -3
  39. package/src/testbench/p2p_client_testbench_worker.ts +2 -1
  40. package/src/testbench/worker_client_manager.ts +1 -1
@@ -64,6 +64,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
64
64
  * @param block - The block received from the peer.
65
65
  * @returns The attestation for the block, if any.
66
66
  */ blockReceivedCallback;
67
+ gossipSubEventHandler;
67
68
  constructor(clientType, config, node, peerDiscoveryService, mempools, l2BlockSource, epochCache, proofVerifier, worldStateSynchronizer, telemetry, logger = createLogger('p2p:libp2p_service')){
68
69
  super(telemetry, 'LibP2PService'), this.clientType = clientType, this.config = config, this.node = node, this.peerDiscoveryService = peerDiscoveryService, this.mempools = mempools, this.l2BlockSource = l2BlockSource, this.proofVerifier = proofVerifier, this.worldStateSynchronizer = worldStateSynchronizer, this.logger = logger, this.jobQueue = new SerialQueue(), this.trustedPeersIds = [];
69
70
  const peerScoring = new PeerScoring(config);
@@ -76,6 +77,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
76
77
  this.node.services.pubsub.score.params.appSpecificWeight = 10;
77
78
  this.attestationValidator = new AttestationValidator(epochCache);
78
79
  this.blockProposalValidator = new BlockProposalValidator(epochCache);
80
+ this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
79
81
  this.blockReceivedCallback = async (block)=>{
80
82
  this.logger.debug(`Handler not yet registered: Block received callback not set. Received block for slot ${block.slotNumber.toNumber()} from peer.`, {
81
83
  p2pMessageIdentifier: await block.p2pMessageIdentifier()
@@ -230,7 +232,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
230
232
  [ReqRespSubProtocol.BLOCK]: blockHandler.bind(this)
231
233
  };
232
234
  // add GossipSub listener
233
- this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.handleGossipSubEvent.bind(this));
235
+ this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
234
236
  // Start running promise for peer discovery
235
237
  this.discoveryRunningPromise = new RunningPromise(()=>this.peerManager.heartbeat(), this.logger, this.config.peerCheckIntervalMS);
236
238
  this.discoveryRunningPromise.start();
@@ -253,7 +255,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
253
255
  * @returns An empty promise.
254
256
  */ async stop() {
255
257
  // Remove gossip sub listener
256
- this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.handleGossipSubEvent.bind(this));
258
+ this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
257
259
  // Stop peer manager
258
260
  this.logger.debug('Stopping peer manager...');
259
261
  await this.peerManager.stop();
@@ -383,7 +385,8 @@ import { ReqResp } from '../reqresp/reqresp.js';
383
385
  tx
384
386
  ]);
385
387
  }
386
- /**Process Attestation From Peer
388
+ /**
389
+ * Process Attestation From Peer
387
390
  * When a proposal is received from a peer, we add it to the attestation pool, so it can be accessed by other services.
388
391
  *
389
392
  * @param attestation - The attestation to process.
@@ -536,7 +539,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
536
539
  severity: PeerErrorSeverity.HighToleranceError
537
540
  },
538
541
  metadataValidator: {
539
- validator: new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(blockNumber)),
542
+ validator: new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(this.config.rollupVersion), new Fr(blockNumber)),
540
543
  severity: PeerErrorSeverity.HighToleranceError
541
544
  },
542
545
  proofValidator: {
@@ -30,10 +30,10 @@ import { convertToMultiaddr, createLibP2PPeerIdFromPrivateKey } from '../util.js
30
30
  start,
31
31
  addresses: {
32
32
  listen: [
33
- `/ip4/0.0.0.0/tcp/${port}`
33
+ `/ip4/127.0.0.1/tcp/${port}`
34
34
  ],
35
35
  announce: [
36
- `/ip4/0.0.0.0/tcp/${port}`
36
+ `/ip4/127.0.0.1/tcp/${port}`
37
37
  ]
38
38
  },
39
39
  connectionEncryption: [
@@ -173,7 +173,7 @@ export function createBootstrapNodeConfig(privateKey, port, chainConfig) {
173
173
  dataDirectory: undefined,
174
174
  dataStoreMapSizeKB: 0,
175
175
  bootstrapNodes: [],
176
- listenAddress: '0.0.0.0'
176
+ listenAddress: '127.0.0.1'
177
177
  };
178
178
  }
179
179
  export function createBootstrapNodeFromPrivateKey(privateKey, port, telemetry = getTelemetryClient(), chainConfig = emptyChainConfig) {
@@ -35,12 +35,17 @@ function mockAttestationPool() {
35
35
  deleteAttestationsOlderThan: ()=>Promise.resolve(),
36
36
  deleteAttestationsForSlot: ()=>Promise.resolve(),
37
37
  deleteAttestationsForSlotAndProposal: ()=>Promise.resolve(),
38
- getAttestationsForSlot: ()=>Promise.resolve([])
38
+ getAttestationsForSlot: ()=>Promise.resolve([]),
39
+ getAttestationsForSlotAndProposal: ()=>Promise.resolve([])
39
40
  };
40
41
  }
41
42
  function mockEpochCache() {
42
43
  return {
43
- getCommittee: ()=>Promise.resolve([]),
44
+ getCommittee: ()=>Promise.resolve({
45
+ committee: [],
46
+ seed: 1n,
47
+ epoch: 0n
48
+ }),
44
49
  getProposerIndexEncoding: ()=>'0x',
45
50
  getEpochAndSlotNow: ()=>({
46
51
  epoch: 0n,
@@ -11,7 +11,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
11
  const workerPath = path.join(__dirname, '../../dest/testbench/p2p_client_testbench_worker.js');
12
12
  const testChainConfig = {
13
13
  l1ChainId: 31337,
14
- version: 1,
14
+ rollupVersion: 1,
15
15
  l1Contracts: {
16
16
  rollupAddress: EthAddress.random()
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/p2p",
3
- "version": "0.82.2",
3
+ "version": "0.82.3-nightly.20250403",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -65,14 +65,14 @@
65
65
  ]
66
66
  },
67
67
  "dependencies": {
68
- "@aztec/constants": "0.82.2",
69
- "@aztec/epoch-cache": "0.82.2",
70
- "@aztec/foundation": "0.82.2",
71
- "@aztec/kv-store": "0.82.2",
72
- "@aztec/noir-protocol-circuits-types": "0.82.2",
73
- "@aztec/protocol-contracts": "0.82.2",
74
- "@aztec/stdlib": "0.82.2",
75
- "@aztec/telemetry-client": "0.82.2",
68
+ "@aztec/constants": "0.82.3-nightly.20250403",
69
+ "@aztec/epoch-cache": "0.82.3-nightly.20250403",
70
+ "@aztec/foundation": "0.82.3-nightly.20250403",
71
+ "@aztec/kv-store": "0.82.3-nightly.20250403",
72
+ "@aztec/noir-protocol-circuits-types": "0.82.3-nightly.20250403",
73
+ "@aztec/protocol-contracts": "0.82.3-nightly.20250403",
74
+ "@aztec/stdlib": "0.82.3-nightly.20250403",
75
+ "@aztec/telemetry-client": "0.82.3-nightly.20250403",
76
76
  "@chainsafe/discv5": "9.0.0",
77
77
  "@chainsafe/enr": "3.0.0",
78
78
  "@chainsafe/libp2p-gossipsub": "13.0.0",
@@ -101,7 +101,7 @@
101
101
  "xxhash-wasm": "^1.1.0"
102
102
  },
103
103
  "devDependencies": {
104
- "@aztec/archiver": "0.82.2",
104
+ "@aztec/archiver": "0.82.3-nightly.20250403",
105
105
  "@jest/globals": "^29.5.0",
106
106
  "@types/jest": "^29.5.0",
107
107
  "@types/node": "^18.14.6",
@@ -187,6 +187,7 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
187
187
  private synchedBlockHashes: AztecAsyncMap<number, string>;
188
188
  private synchedLatestBlockNumber: AztecAsyncSingleton<number>;
189
189
  private synchedProvenBlockNumber: AztecAsyncSingleton<number>;
190
+ private synchedLatestSlot: AztecAsyncSingleton<bigint>;
190
191
 
191
192
  private txPool: TxPool;
192
193
  private attestationPool: T extends P2PClientType.Full ? AttestationPool : undefined;
@@ -236,6 +237,7 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
236
237
  this.synchedBlockHashes = store.openMap('p2p_pool_block_hashes');
237
238
  this.synchedLatestBlockNumber = store.openSingleton('p2p_pool_last_l2_block');
238
239
  this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block');
240
+ this.synchedLatestSlot = store.openSingleton('p2p_pool_last_l2_slot');
239
241
 
240
242
  this.txPool = mempools.txPool;
241
243
  this.attestationPool = mempools.attestationPool!;
@@ -293,12 +295,12 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
293
295
  break;
294
296
  case 'chain-proven': {
295
297
  const from = (await this.getSyncedProvenBlockNum()) + 1;
296
- const limit = event.blockNumber - from + 1;
298
+ const limit = event.block.number - from + 1;
297
299
  await this.handleProvenL2Blocks(await this.l2BlockSource.getBlocks(from, limit));
298
300
  break;
299
301
  }
300
302
  case 'chain-pruned':
301
- await this.handlePruneL2Blocks(event.blockNumber);
303
+ await this.handlePruneL2Blocks(event.block.number);
302
304
  break;
303
305
  default: {
304
306
  const _: never = event;
@@ -380,8 +382,16 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
380
382
  return this.p2pService.propagate(proposal);
381
383
  }
382
384
 
383
- public async getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
384
- return (await this.attestationPool?.getAttestationsForSlot(slot, proposalId)) ?? [];
385
+ public async getAttestationsForSlot(slot: bigint, proposalId?: string): Promise<BlockAttestation[]> {
386
+ return (
387
+ (await (proposalId
388
+ ? this.attestationPool?.getAttestationsForSlotAndProposal(slot, proposalId)
389
+ : this.attestationPool?.getAttestationsForSlot(slot))) ?? []
390
+ );
391
+ }
392
+
393
+ public addAttestation(attestation: BlockAttestation): Promise<void> {
394
+ return this.attestationPool?.addAttestations([attestation]) ?? Promise.resolve();
385
395
  }
386
396
 
387
397
  // REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963
@@ -592,6 +602,11 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
592
602
  return (await this.synchedProvenBlockNumber.getAsync()) ?? INITIAL_L2_BLOCK_NUM - 1;
593
603
  }
594
604
 
605
+ /** Returns latest L2 slot for which we have seen an L2 block. */
606
+ public async getSyncedLatestSlot(): Promise<bigint> {
607
+ return (await this.synchedLatestSlot.getAsync()) ?? BigInt(0);
608
+ }
609
+
595
610
  /**
596
611
  * Method to check the status the p2p client.
597
612
  * @returns Information about p2p client status: state & syncedToBlockNum.
@@ -659,12 +674,13 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
659
674
 
660
675
  await this.markTxsAsMinedFromBlocks(blocks.map(b => b.block));
661
676
  await this.addAttestationsToPool(blocks);
662
- const lastBlockNum = blocks.at(-1)!.block.number;
677
+ const lastBlock = blocks.at(-1)!.block;
663
678
  await Promise.all(
664
679
  blocks.map(async block => this.synchedBlockHashes.set(block.block.number, (await block.block.hash()).toString())),
665
680
  );
666
- await this.synchedLatestBlockNumber.set(lastBlockNum);
667
- this.log.verbose(`Synched to latest block ${lastBlockNum}`);
681
+ await this.synchedLatestBlockNumber.set(lastBlock.number);
682
+ await this.synchedLatestSlot.set(lastBlock.header.getSlot());
683
+ this.log.verbose(`Synched to latest block ${lastBlock.number}`);
668
684
  await this.startServiceIfSynched();
669
685
  }
670
686
 
package/src/config.ts CHANGED
@@ -371,6 +371,7 @@ export type BootnodeConfig = Pick<
371
371
  const bootnodeConfigKeys: (keyof BootnodeConfig)[] = [
372
372
  'p2pIp',
373
373
  'p2pPort',
374
+ 'listenAddress',
374
375
  'peerIdPrivateKey',
375
376
  'dataDirectory',
376
377
  'dataStoreMapSizeKB',
@@ -50,7 +50,17 @@ export interface AttestationPool {
50
50
  deleteAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<void>;
51
51
 
52
52
  /**
53
- * Get Attestations for slot
53
+ * Get all Attestations for all proposals for a given slot
54
+ *
55
+ * Retrieve all of the attestations observed pertaining to a given slot
56
+ *
57
+ * @param slot - The slot to query
58
+ * @return BlockAttestations
59
+ */
60
+ getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]>;
61
+
62
+ /**
63
+ * Get Attestations for slot and given proposal
54
64
  *
55
65
  * Retrieve all of the attestations observed pertaining to a given slot
56
66
  *
@@ -58,5 +68,5 @@ export interface AttestationPool {
58
68
  * @param proposalId - The proposal to query
59
69
  * @return BlockAttestations
60
70
  */
61
- getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]>;
71
+ getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]>;
62
72
  }
@@ -45,25 +45,45 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
45
45
  it('should add attestations to pool', async () => {
46
46
  const slotNumber = 420;
47
47
  const archive = Fr.random();
48
- const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber, archive)));
48
+ const attestations = await Promise.all(
49
+ signers.slice(0, -1).map(signer => mockAttestation(signer, slotNumber, archive)),
50
+ );
49
51
 
50
52
  await ap.addAttestations(attestations);
51
53
 
52
54
  // Check metrics have been updated.
53
55
  expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
54
56
 
55
- const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString());
57
+ const retrievedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString());
58
+ expect(retrievedAttestations.length).toBe(attestations.length);
59
+ compareAttestations(retrievedAttestations, attestations);
56
60
 
57
- expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
61
+ const retrievedAttestationsForSlot = await ap.getAttestationsForSlot(BigInt(slotNumber));
62
+ expect(retrievedAttestationsForSlot.length).toBe(attestations.length);
63
+ compareAttestations(retrievedAttestationsForSlot, attestations);
58
64
 
59
- compareAttestations(retreivedAttestations, attestations);
65
+ // Add another one
66
+ const newAttestation = await mockAttestation(signers[NUMBER_OF_SIGNERS_PER_TEST - 1], slotNumber, archive);
67
+ await ap.addAttestations([newAttestation]);
68
+ expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(1);
69
+ const retrievedAttestationsAfterAdd = await ap.getAttestationsForSlotAndProposal(
70
+ BigInt(slotNumber),
71
+ archive.toString(),
72
+ );
73
+ expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1);
74
+ compareAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]);
75
+ const retrievedAttestationsForSlotAfterAdd = await ap.getAttestationsForSlot(BigInt(slotNumber));
76
+ expect(retrievedAttestationsForSlotAfterAdd.length).toBe(attestations.length + 1);
77
+ compareAttestations(retrievedAttestationsForSlotAfterAdd, [...attestations, newAttestation]);
60
78
 
61
79
  // Delete by slot
62
80
  await ap.deleteAttestationsForSlot(BigInt(slotNumber));
81
+ expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length + 1);
63
82
 
64
- expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
65
-
66
- const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString());
83
+ const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(
84
+ BigInt(slotNumber),
85
+ archive.toString(),
86
+ );
67
87
  expect(retreivedAttestationsAfterDelete.length).toBe(0);
68
88
  });
69
89
 
@@ -82,7 +102,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
82
102
  // Add them to store and check we end up with only one
83
103
  await ap.addAttestations(attestations);
84
104
 
85
- const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString());
105
+ const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString());
86
106
  expect(retreivedAttestations.length).toBe(1);
87
107
  expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer());
88
108
  expect(retreivedAttestations[0].payload.txHashes).toEqual(txs);
@@ -90,7 +110,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
90
110
 
91
111
  // Try adding them on another operation and check they are still not duplicated
92
112
  await ap.addAttestations([attestations[0]]);
93
- expect(await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString())).toHaveLength(1);
113
+ expect(await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString())).toHaveLength(1);
94
114
  });
95
115
 
96
116
  it('should store attestations by differing slot', async () => {
@@ -103,7 +123,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
103
123
  const slot = attestation.payload.header.globalVariables.slotNumber;
104
124
  const archive = attestation.archive.toString();
105
125
 
106
- const retreivedAttestations = await ap.getAttestationsForSlot(slot.toBigInt(), archive);
126
+ const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(slot.toBigInt(), archive);
107
127
  expect(retreivedAttestations.length).toBe(1);
108
128
  expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
109
129
  expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
@@ -123,7 +143,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
123
143
  const slot = attestation.payload.header.globalVariables.slotNumber;
124
144
  const proposalId = attestation.archive.toString();
125
145
 
126
- const retreivedAttestations = await ap.getAttestationsForSlot(slot.toBigInt(), proposalId);
146
+ const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(slot.toBigInt(), proposalId);
127
147
  expect(retreivedAttestations.length).toBe(1);
128
148
  expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
129
149
  expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
@@ -140,7 +160,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
140
160
 
141
161
  expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
142
162
 
143
- const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
163
+ const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
144
164
  expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
145
165
  compareAttestations(retreivedAttestations, attestations);
146
166
 
@@ -148,7 +168,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
148
168
 
149
169
  expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
150
170
 
151
- const gottenAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
171
+ const gottenAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
152
172
  expect(gottenAfterDelete.length).toBe(0);
153
173
  });
154
174
 
@@ -160,13 +180,13 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
160
180
 
161
181
  await ap.addAttestations(attestations);
162
182
 
163
- const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
183
+ const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
164
184
  expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
165
185
  compareAttestations(retreivedAttestations, attestations);
166
186
 
167
187
  await ap.deleteAttestationsForSlot(BigInt(slotNumber));
168
188
 
169
- const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
189
+ const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
170
190
  expect(retreivedAttestationsAfterDelete.length).toBe(0);
171
191
  });
172
192
 
@@ -187,7 +207,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
187
207
  expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
188
208
  expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations2.length);
189
209
 
190
- const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
210
+ const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
191
211
  expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
192
212
  compareAttestations(retreivedAttestations, attestations);
193
213
 
@@ -195,10 +215,10 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
195
215
 
196
216
  expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
197
217
 
198
- const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
218
+ const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
199
219
  expect(retreivedAttestationsAfterDelete.length).toBe(0);
200
220
 
201
- const retreivedAttestationsAfterDeleteForOtherProposal = await ap.getAttestationsForSlot(
221
+ const retreivedAttestationsAfterDeleteForOtherProposal = await ap.getAttestationsForSlotAndProposal(
202
222
  BigInt(slotNumber),
203
223
  proposalId2,
204
224
  );
@@ -215,14 +235,14 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
215
235
 
216
236
  await ap.addAttestations(attestations);
217
237
 
218
- const attestationsForSlot1 = await ap.getAttestationsForSlot(BigInt(1), proposalId);
238
+ const attestationsForSlot1 = await ap.getAttestationsForSlotAndProposal(BigInt(1), proposalId);
219
239
  expect(attestationsForSlot1.length).toBe(signers.length);
220
240
 
221
241
  const deleteAttestationsSpy = jest.spyOn(ap, 'deleteAttestationsForSlot');
222
242
 
223
243
  await ap.deleteAttestationsOlderThan(BigInt(73));
224
244
 
225
- const attestationsForSlot1AfterDelete = await ap.getAttestationsForSlot(BigInt(1), proposalId);
245
+ const attestationsForSlot1AfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(1), proposalId);
226
246
  expect(attestationsForSlot1AfterDelete.length).toBe(0);
227
247
 
228
248
  expect(deleteAttestationsSpy).toHaveBeenCalledTimes(5);
@@ -60,6 +60,9 @@ export class KvAttestationPool implements AttestationPool {
60
60
 
61
61
  this.log.verbose(`Added attestation for slot ${slotNumber.toBigInt()} from ${address}`, {
62
62
  signature: attestation.signature.toString(),
63
+ slotNumber,
64
+ address,
65
+ proposalId,
63
66
  });
64
67
  }
65
68
  });
@@ -67,7 +70,19 @@ export class KvAttestationPool implements AttestationPool {
67
70
  this.metrics.recordAddedObjects(attestations.length);
68
71
  }
69
72
 
70
- public async getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
73
+ public async getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
74
+ const slotFr = new Fr(slot);
75
+ const proposalIds = await toArray(this.proposalsForSlot.getValuesAsync(slotFr.toString()));
76
+ const attestations: BlockAttestation[] = [];
77
+
78
+ for (const proposalId of proposalIds) {
79
+ attestations.push(...(await this.getAttestationsForSlotAndProposal(slot, proposalId)));
80
+ }
81
+
82
+ return attestations;
83
+ }
84
+
85
+ public async getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
71
86
  const attestationIds = await toArray(
72
87
  this.attestationsForProposal.getValuesAsync(this.getProposalKey(slot, proposalId)),
73
88
  );
@@ -15,7 +15,15 @@ export class InMemoryAttestationPool implements AttestationPool {
15
15
  this.metrics = new PoolInstrumentation(telemetry, PoolName.ATTESTATION_POOL);
16
16
  }
17
17
 
18
- public getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
18
+ public getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
19
+ return Promise.resolve(
20
+ Array.from(this.attestations.get(slot)?.values() ?? []).flatMap(proposalAttestationMap =>
21
+ Array.from(proposalAttestationMap.values()),
22
+ ),
23
+ );
24
+ }
25
+
26
+ public getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
19
27
  const slotAttestationMap = this.attestations.get(slot);
20
28
  if (slotAttestationMap) {
21
29
  const proposalAttestationMap = slotAttestationMap.get(proposalId);
@@ -40,6 +48,9 @@ export class InMemoryAttestationPool implements AttestationPool {
40
48
 
41
49
  this.log.verbose(`Added attestation for slot ${slotNumber.toBigInt()} from ${address}`, {
42
50
  signature: attestation.signature.toString(),
51
+ slotNumber,
52
+ address,
53
+ proposalId,
43
54
  });
44
55
  }
45
56
 
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from '@aztec/foundation/log';
2
- import { type AnyTx, Tx, type TxValidationResult, type TxValidator, hasPublicCalls } from '@aztec/stdlib/tx';
2
+ import { type AnyTx, Tx, type TxValidationResult, type TxValidator } from '@aztec/stdlib/tx';
3
3
 
4
4
  export interface NullifierSource {
5
5
  nullifiersExist: (nullifiers: Buffer[]) => Promise<boolean[]>;
@@ -14,25 +14,20 @@ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
14
14
  }
15
15
 
16
16
  async validateTx(tx: T): Promise<TxValidationResult> {
17
- // Don't need to check for duplicate nullifiers if the tx has public calls
18
- // because the AVM will perform merkle insertions as it goes and will fail on
19
- // duplicate nullifier. In fact we CANNOT check here because the nullifiers
20
- // have already been inserted, and so they will exist in nullifierSource.
21
- if (!hasPublicCalls(tx)) {
22
- const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;
17
+ const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;
23
18
 
24
- // Ditch this tx if it has repeated nullifiers
25
- const uniqueNullifiers = new Set(nullifiers);
26
- if (uniqueNullifiers.size !== nullifiers.length) {
27
- this.#log.warn(`Rejecting tx ${await Tx.getHash(tx)} for emitting duplicate nullifiers`);
28
- return { result: 'invalid', reason: ['Duplicate nullifier in tx'] };
29
- }
19
+ // Ditch this tx if it has repeated nullifiers
20
+ const uniqueNullifiers = new Set(nullifiers);
21
+ if (uniqueNullifiers.size !== nullifiers.length) {
22
+ this.#log.warn(`Rejecting tx ${await Tx.getHash(tx)} for emitting duplicate nullifiers`);
23
+ return { result: 'invalid', reason: ['Duplicate nullifier in tx'] };
24
+ }
30
25
 
31
- if ((await this.#nullifierSource.nullifiersExist(nullifiers.map(n => n.toBuffer()))).some(Boolean)) {
32
- this.#log.warn(`Rejecting tx ${await Tx.getHash(tx)} for repeating a nullifier`);
33
- return { result: 'invalid', reason: ['Existing nullifier'] };
34
- }
26
+ if ((await this.#nullifierSource.nullifiersExist(nullifiers.map(n => n.toBuffer()))).some(Boolean)) {
27
+ this.#log.warn(`Rejecting tx ${await Tx.getHash(tx)} for repeating a nullifier`);
28
+ return { result: 'invalid', reason: ['Existing nullifier'] };
35
29
  }
30
+
36
31
  return { result: 'valid' };
37
32
  }
38
33
  }
@@ -5,13 +5,16 @@ import { type AnyTx, Tx, type TxValidationResult, type TxValidator } from '@azte
5
5
  export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
6
6
  #log = createLogger('p2p:tx_validator:tx_metadata');
7
7
 
8
- constructor(private chainId: Fr, private blockNumber: Fr) {}
8
+ constructor(private chainId: Fr, private rollupVersion: Fr, private blockNumber: Fr) {}
9
9
 
10
10
  async validateTx(tx: T): Promise<TxValidationResult> {
11
11
  const errors = [];
12
12
  if (!(await this.#hasCorrectChainId(tx))) {
13
13
  errors.push('Incorrect chain id');
14
14
  }
15
+ if (!(await this.#hasCorrectRollupVersion(tx))) {
16
+ errors.push('Incorrect rollup version');
17
+ }
15
18
  if (!(await this.#isValidForBlockNumber(tx))) {
16
19
  errors.push('Invalid block number');
17
20
  }
@@ -45,4 +48,17 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
45
48
  return true;
46
49
  }
47
50
  }
51
+
52
+ async #hasCorrectRollupVersion(tx: T): Promise<boolean> {
53
+ if (!tx.data.constants.txContext.version.equals(this.rollupVersion)) {
54
+ this.#log.warn(
55
+ `Rejecting tx ${await Tx.getHash(
56
+ tx,
57
+ )} because of incorrect rollup version ${tx.data.constants.txContext.version.toNumber()} != ${this.rollupVersion.toNumber()}`,
58
+ );
59
+ return false;
60
+ } else {
61
+ return true;
62
+ }
63
+ }
48
64
  }
@@ -102,6 +102,8 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
102
102
  */
103
103
  private blockReceivedCallback: (block: BlockProposal) => Promise<BlockAttestation | undefined>;
104
104
 
105
+ private gossipSubEventHandler: (e: CustomEvent<GossipsubMessage>) => void;
106
+
105
107
  constructor(
106
108
  private clientType: T,
107
109
  private config: P2PConfig,
@@ -139,6 +141,8 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
139
141
  this.attestationValidator = new AttestationValidator(epochCache);
140
142
  this.blockProposalValidator = new BlockProposalValidator(epochCache);
141
143
 
144
+ this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
145
+
142
146
  this.blockReceivedCallback = async (block: BlockProposal): Promise<BlockAttestation | undefined> => {
143
147
  this.logger.debug(
144
148
  `Handler not yet registered: Block received callback not set. Received block for slot ${block.slotNumber.toNumber()} from peer.`,
@@ -329,7 +333,7 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
329
333
  };
330
334
 
331
335
  // add GossipSub listener
332
- this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.handleGossipSubEvent.bind(this));
336
+ this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
333
337
 
334
338
  // Start running promise for peer discovery
335
339
  this.discoveryRunningPromise = new RunningPromise(
@@ -360,7 +364,7 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
360
364
  */
361
365
  public async stop() {
362
366
  // Remove gossip sub listener
363
- this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.handleGossipSubEvent.bind(this));
367
+ this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
364
368
 
365
369
  // Stop peer manager
366
370
  this.logger.debug('Stopping peer manager...');
@@ -521,7 +525,8 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
521
525
  await this.mempools.txPool.addTxs([tx]);
522
526
  }
523
527
 
524
- /**Process Attestation From Peer
528
+ /**
529
+ * Process Attestation From Peer
525
530
  * When a proposal is received from a peer, we add it to the attestation pool, so it can be accessed by other services.
526
531
  *
527
532
  * @param attestation - The attestation to process.
@@ -716,7 +721,11 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
716
721
  severity: PeerErrorSeverity.HighToleranceError,
717
722
  },
718
723
  metadataValidator: {
719
- validator: new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(blockNumber)),
724
+ validator: new MetadataTxValidator(
725
+ new Fr(this.config.l1ChainId),
726
+ new Fr(this.config.rollupVersion),
727
+ new Fr(blockNumber),
728
+ ),
720
729
  severity: PeerErrorSeverity.HighToleranceError,
721
730
  },
722
731
  proofValidator: {
@@ -55,8 +55,8 @@ export async function createLibp2pNode(
55
55
  const options: Libp2pOptions = {
56
56
  start,
57
57
  addresses: {
58
- listen: [`/ip4/0.0.0.0/tcp/${port}`],
59
- announce: [`/ip4/0.0.0.0/tcp/${port}`],
58
+ listen: [`/ip4/127.0.0.1/tcp/${port}`],
59
+ announce: [`/ip4/127.0.0.1/tcp/${port}`],
60
60
  },
61
61
  connectionEncryption: [noise()],
62
62
  streamMuxers: [yamux()],
@@ -239,7 +239,7 @@ export function createBootstrapNodeConfig(privateKey: string, port: number, chai
239
239
  dataDirectory: undefined,
240
240
  dataStoreMapSizeKB: 0,
241
241
  bootstrapNodes: [],
242
- listenAddress: '0.0.0.0',
242
+ listenAddress: '127.0.0.1',
243
243
  };
244
244
  }
245
245
 
@@ -48,12 +48,13 @@ function mockAttestationPool(): AttestationPool {
48
48
  deleteAttestationsForSlot: () => Promise.resolve(),
49
49
  deleteAttestationsForSlotAndProposal: () => Promise.resolve(),
50
50
  getAttestationsForSlot: () => Promise.resolve([]),
51
+ getAttestationsForSlotAndProposal: () => Promise.resolve([]),
51
52
  };
52
53
  }
53
54
 
54
55
  function mockEpochCache(): EpochCacheInterface {
55
56
  return {
56
- getCommittee: () => Promise.resolve([] as EthAddress[]),
57
+ getCommittee: () => Promise.resolve({ committee: [], seed: 1n, epoch: 0n }),
57
58
  getProposerIndexEncoding: () => '0x' as `0x${string}`,
58
59
  getEpochAndSlotNow: () => ({ epoch: 0n, slot: 0n, ts: 0n }),
59
60
  computeProposerIndex: () => 0n,