@aztec/p2p 2.1.0-rc.9 → 2.1.2

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 (104) hide show
  1. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  2. package/dest/bootstrap/bootstrap.js +14 -4
  3. package/dest/client/factory.d.ts +1 -0
  4. package/dest/client/factory.d.ts.map +1 -1
  5. package/dest/client/factory.js +5 -3
  6. package/dest/client/interface.d.ts +1 -1
  7. package/dest/client/interface.d.ts.map +1 -1
  8. package/dest/client/p2p_client.d.ts +2 -1
  9. package/dest/client/p2p_client.d.ts.map +1 -1
  10. package/dest/client/p2p_client.js +12 -2
  11. package/dest/config.d.ts +4 -4
  12. package/dest/config.d.ts.map +1 -1
  13. package/dest/config.js +4 -3
  14. package/dest/enr/generate-enr.d.ts +1 -1
  15. package/dest/enr/generate-enr.d.ts.map +1 -1
  16. package/dest/enr/generate-enr.js +1 -1
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +15 -0
  18. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  19. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  20. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +35 -4
  21. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +2 -0
  22. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  23. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +34 -2
  24. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +2 -0
  25. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  26. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +43 -7
  27. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  28. package/dest/mem_pools/attestation_pool/mocks.js +5 -3
  29. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +1 -0
  30. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +6 -0
  32. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +1 -0
  33. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool/memory_tx_pool.js +6 -0
  35. package/dest/mem_pools/tx_pool/tx_pool.d.ts +6 -0
  36. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
  37. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -0
  38. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  39. package/dest/msg_validators/attestation_validator/attestation_validator.js +29 -2
  40. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +4 -1
  41. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -1
  42. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +16 -4
  43. package/dest/services/discv5/discV5_service.d.ts +2 -2
  44. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  45. package/dest/services/discv5/discV5_service.js +2 -2
  46. package/dest/services/dummy_service.d.ts +1 -1
  47. package/dest/services/dummy_service.d.ts.map +1 -1
  48. package/dest/services/encoding.d.ts +24 -3
  49. package/dest/services/encoding.d.ts.map +1 -1
  50. package/dest/services/encoding.js +73 -5
  51. package/dest/services/libp2p/libp2p_service.d.ts +16 -9
  52. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  53. package/dest/services/libp2p/libp2p_service.js +89 -39
  54. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  55. package/dest/services/peer-manager/peer_manager.js +9 -3
  56. package/dest/services/reqresp/reqresp.js +2 -2
  57. package/dest/services/service.d.ts +1 -1
  58. package/dest/services/service.d.ts.map +1 -1
  59. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  60. package/dest/services/tx_collection/fast_tx_collection.js +6 -1
  61. package/dest/services/tx_collection/tx_collection.d.ts +2 -1
  62. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  63. package/dest/services/tx_collection/tx_collection.js +3 -2
  64. package/dest/services/tx_provider.d.ts +1 -1
  65. package/dest/services/tx_provider.d.ts.map +1 -1
  66. package/dest/services/tx_provider.js +7 -3
  67. package/dest/test-helpers/make-enrs.js +1 -1
  68. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  69. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  70. package/dest/test-helpers/reqresp-nodes.js +4 -3
  71. package/dest/testbench/p2p_client_testbench_worker.js +4 -1
  72. package/dest/versioning.d.ts +1 -1
  73. package/dest/versioning.d.ts.map +1 -1
  74. package/package.json +17 -17
  75. package/src/bootstrap/bootstrap.ts +15 -4
  76. package/src/client/factory.ts +10 -3
  77. package/src/client/interface.ts +1 -1
  78. package/src/client/p2p_client.ts +12 -3
  79. package/src/config.ts +6 -4
  80. package/src/enr/generate-enr.ts +1 -1
  81. package/src/mem_pools/attestation_pool/attestation_pool.ts +17 -0
  82. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -4
  83. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +43 -2
  84. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +52 -7
  85. package/src/mem_pools/attestation_pool/mocks.ts +6 -3
  86. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +5 -0
  87. package/src/mem_pools/tx_pool/memory_tx_pool.ts +5 -0
  88. package/src/mem_pools/tx_pool/tx_pool.ts +7 -0
  89. package/src/msg_validators/attestation_validator/attestation_validator.ts +39 -2
  90. package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +20 -4
  91. package/src/services/discv5/discV5_service.ts +2 -2
  92. package/src/services/dummy_service.ts +1 -1
  93. package/src/services/encoding.ts +80 -5
  94. package/src/services/libp2p/libp2p_service.ts +93 -51
  95. package/src/services/peer-manager/peer_manager.ts +10 -3
  96. package/src/services/reqresp/reqresp.ts +2 -2
  97. package/src/services/service.ts +1 -1
  98. package/src/services/tx_collection/fast_tx_collection.ts +3 -1
  99. package/src/services/tx_collection/tx_collection.ts +3 -2
  100. package/src/services/tx_provider.ts +3 -2
  101. package/src/test-helpers/make-enrs.ts +1 -1
  102. package/src/test-helpers/reqresp-nodes.ts +3 -2
  103. package/src/testbench/p2p_client_testbench_worker.ts +3 -0
  104. package/src/versioning.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/p2p",
3
- "version": "2.1.0-rc.9",
3
+ "version": "2.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -67,19 +67,17 @@
67
67
  ]
68
68
  },
69
69
  "dependencies": {
70
- "@aztec/constants": "2.1.0-rc.9",
71
- "@aztec/epoch-cache": "2.1.0-rc.9",
72
- "@aztec/ethereum": "2.1.0-rc.9",
73
- "@aztec/foundation": "2.1.0-rc.9",
74
- "@aztec/kv-store": "2.1.0-rc.9",
75
- "@aztec/noir-contracts.js": "2.1.0-rc.9",
76
- "@aztec/noir-protocol-circuits-types": "2.1.0-rc.9",
77
- "@aztec/protocol-contracts": "2.1.0-rc.9",
78
- "@aztec/simulator": "2.1.0-rc.9",
79
- "@aztec/stdlib": "2.1.0-rc.9",
80
- "@aztec/telemetry-client": "2.1.0-rc.9",
81
- "@chainsafe/discv5": "9.0.0",
82
- "@chainsafe/enr": "3.0.0",
70
+ "@aztec/constants": "2.1.2",
71
+ "@aztec/epoch-cache": "2.1.2",
72
+ "@aztec/ethereum": "2.1.2",
73
+ "@aztec/foundation": "2.1.2",
74
+ "@aztec/kv-store": "2.1.2",
75
+ "@aztec/noir-contracts.js": "2.1.2",
76
+ "@aztec/noir-protocol-circuits-types": "2.1.2",
77
+ "@aztec/protocol-contracts": "2.1.2",
78
+ "@aztec/simulator": "2.1.2",
79
+ "@aztec/stdlib": "2.1.2",
80
+ "@aztec/telemetry-client": "2.1.2",
83
81
  "@chainsafe/libp2p-gossipsub": "13.0.0",
84
82
  "@chainsafe/libp2p-noise": "^15.0.0",
85
83
  "@chainsafe/libp2p-yamux": "^6.0.2",
@@ -94,6 +92,8 @@
94
92
  "@libp2p/prometheus-metrics": "^4.2.4",
95
93
  "@libp2p/tcp": "9.0.24",
96
94
  "@multiformats/multiaddr": "12.1.14",
95
+ "@nethermindeth/discv5": "9.0.0-backport-306-v4",
96
+ "@nethermindeth/enr": "3.0.0-backport-306-v4",
97
97
  "interface-datastore": "^8.2.11",
98
98
  "interface-store": "^5.1.8",
99
99
  "libp2p": "1.5.0",
@@ -104,8 +104,8 @@
104
104
  "xxhash-wasm": "^1.1.0"
105
105
  },
106
106
  "devDependencies": {
107
- "@aztec/archiver": "2.1.0-rc.9",
108
- "@aztec/world-state": "2.1.0-rc.9",
107
+ "@aztec/archiver": "2.1.2",
108
+ "@aztec/world-state": "2.1.2",
109
109
  "@jest/globals": "^30.0.0",
110
110
  "@types/jest": "^30.0.0",
111
111
  "@types/node": "^22.15.17",
@@ -117,7 +117,7 @@
117
117
  "ts-node": "^10.9.1",
118
118
  "typescript": "^5.3.3",
119
119
  "uint8arrays": "^5.0.3",
120
- "viem": "2.23.7"
120
+ "viem": "npm:@spalladino/viem@2.38.2-eip7594.0"
121
121
  },
122
122
  "files": [
123
123
  "dest",
@@ -3,14 +3,14 @@ import type { AztecAsyncKVStore } from '@aztec/kv-store';
3
3
  import type { P2PBootstrapApi } from '@aztec/stdlib/interfaces/server';
4
4
  import { OtelMetricsAdapter, type TelemetryClient } from '@aztec/telemetry-client';
5
5
 
6
- import { Discv5, type Discv5EventEmitter } from '@chainsafe/discv5';
7
- import { ENR, type SignableENR } from '@chainsafe/enr';
8
6
  import type { PeerId } from '@libp2p/interface';
9
7
  import { type Multiaddr, multiaddr } from '@multiformats/multiaddr';
8
+ import { Discv5, type Discv5EventEmitter } from '@nethermindeth/discv5';
9
+ import { ENR, type SignableENR } from '@nethermindeth/enr';
10
10
 
11
11
  import type { BootnodeConfig } from '../config.js';
12
12
  import { createBootnodeENRandPeerId } from '../enr/generate-enr.js';
13
- import { convertToMultiaddr, getPeerIdPrivateKey } from '../util.js';
13
+ import { convertToMultiaddr, getPeerIdPrivateKey, getPublicIp } from '../util.js';
14
14
 
15
15
  /**
16
16
  * Encapsulates a 'Bootstrap' node, used for the purpose of assisting new joiners in acquiring peers.
@@ -31,7 +31,18 @@ export class BootstrapNode implements P2PBootstrapApi {
31
31
  * @returns An empty promise.
32
32
  */
33
33
  public async start(config: BootnodeConfig) {
34
- const { p2pIp, p2pPort, listenAddress, p2pBroadcastPort } = config;
34
+ const { p2pPort, listenAddress, p2pBroadcastPort, queryForIp } = config;
35
+ let p2pIp = config.p2pIp;
36
+ this.logger.info(`Starting bootstrap node with config: ${JSON.stringify(config)}`);
37
+ if (!p2pIp) {
38
+ if (queryForIp) {
39
+ this.logger.info('Querying for public IP address...');
40
+ const publicIp = await getPublicIp();
41
+ p2pIp = publicIp;
42
+ this.logger.info(`Found public IP address: ${publicIp}`);
43
+ }
44
+ }
45
+
35
46
  if (!p2pIp) {
36
47
  throw new Error('You need to provide a P2P IP address.');
37
48
  }
@@ -14,7 +14,7 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
14
14
  import { P2PClient } from '../client/p2p_client.js';
15
15
  import type { P2PConfig } from '../config.js';
16
16
  import type { AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js';
17
- import { InMemoryAttestationPool } from '../mem_pools/attestation_pool/memory_attestation_pool.js';
17
+ import { KvAttestationPool } from '../mem_pools/attestation_pool/kv_attestation_pool.js';
18
18
  import type { MemPools } from '../mem_pools/interface.js';
19
19
  import { AztecKVTxPool, type TxPool } from '../mem_pools/tx_pool/index.js';
20
20
  import { DummyP2PService } from '../services/dummy_service.js';
@@ -35,6 +35,7 @@ export type P2PClientDeps<T extends P2PClientType> = {
35
35
  export const P2P_STORE_NAME = 'p2p';
36
36
  export const P2P_ARCHIVE_STORE_NAME = 'p2p-archive';
37
37
  export const P2P_PEER_STORE_NAME = 'p2p-peers';
38
+ export const P2P_ATTESTATION_STORE_NAME = 'p2p-attestation';
38
39
 
39
40
  export async function createP2PClient<T extends P2PClientType>(
40
41
  clientType: T,
@@ -50,7 +51,7 @@ export async function createP2PClient<T extends P2PClientType>(
50
51
  ) {
51
52
  const config = await configureP2PClientAddresses({
52
53
  ...inputConfig,
53
- dataStoreMapSizeKB: inputConfig.p2pStoreMapSizeKb ?? inputConfig.dataStoreMapSizeKB,
54
+ dataStoreMapSizeKb: inputConfig.p2pStoreMapSizeKb ?? inputConfig.dataStoreMapSizeKb,
54
55
  });
55
56
 
56
57
  const logger = deps.logger ?? createLogger('p2p');
@@ -64,6 +65,12 @@ export async function createP2PClient<T extends P2PClientType>(
64
65
  const store = deps.store ?? (await createStore(P2P_STORE_NAME, 2, config, createLogger('p2p:lmdb-v2')));
65
66
  const archive = await createStore(P2P_ARCHIVE_STORE_NAME, 1, config, createLogger('p2p-archive:lmdb-v2'));
66
67
  const peerStore = await createStore(P2P_PEER_STORE_NAME, 1, config, createLogger('p2p-peer:lmdb-v2'));
68
+ const attestationStore = await createStore(
69
+ P2P_ATTESTATION_STORE_NAME,
70
+ 1,
71
+ config,
72
+ createLogger('p2p-attestation:lmdb-v2'),
73
+ );
67
74
  const l1Constants = await archiver.getL1Constants();
68
75
 
69
76
  const mempools: MemPools<T> = {
@@ -75,7 +82,7 @@ export async function createP2PClient<T extends P2PClientType>(
75
82
  }),
76
83
  attestationPool:
77
84
  clientType === P2PClientType.Full
78
- ? ((deps.attestationPool ?? new InMemoryAttestationPool(telemetry)) as T extends P2PClientType.Full
85
+ ? ((deps.attestationPool ?? new KvAttestationPool(attestationStore, telemetry)) as T extends P2PClientType.Full
79
86
  ? AttestationPool
80
87
  : undefined)
81
88
  : undefined,
@@ -3,8 +3,8 @@ import type { P2PApiFull } from '@aztec/stdlib/interfaces/server';
3
3
  import type { BlockProposal, P2PClientType } from '@aztec/stdlib/p2p';
4
4
  import type { Tx, TxHash } from '@aztec/stdlib/tx';
5
5
 
6
- import type { ENR } from '@chainsafe/enr';
7
6
  import type { PeerId } from '@libp2p/interface';
7
+ import type { ENR } from '@nethermindeth/enr';
8
8
 
9
9
  import type { P2PConfig } from '../config.js';
10
10
  import type { AuthRequest, StatusMessage } from '../services/index.js';
@@ -26,8 +26,8 @@ import {
26
26
  trackSpan,
27
27
  } from '@aztec/telemetry-client';
28
28
 
29
- import type { ENR } from '@chainsafe/enr';
30
29
  import type { PeerId } from '@libp2p/interface';
30
+ import type { ENR } from '@nethermindeth/enr';
31
31
 
32
32
  import { type P2PConfig, getP2PDefaultConfig } from '../config.js';
33
33
  import type { AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js';
@@ -123,7 +123,13 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
123
123
  const constants = this.txCollection.getConstants();
124
124
  const nextSlotTimestampSeconds = Number(getTimestampForSlot(block.slotNumber.toBigInt() + 1n, constants));
125
125
  const deadline = new Date(nextSlotTimestampSeconds * 1000);
126
- await this.txProvider.getTxsForBlockProposal(block, { pinnedPeer: sender, deadline });
126
+ const parentBlock = await this.l2BlockSource.getBlockHeaderByArchive(block.payload.header.lastArchiveRoot);
127
+ if (!parentBlock) {
128
+ this.log.debug(`Cannot collect txs for proposal as parent block not found`);
129
+ return;
130
+ }
131
+ const blockNumber = parentBlock.getBlockNumber() + 1;
132
+ await this.txProvider.getTxsForBlockProposal(block, blockNumber, { pinnedPeer: sender, deadline });
127
133
  return undefined;
128
134
  });
129
135
 
@@ -365,7 +371,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
365
371
  }
366
372
 
367
373
  @trackSpan('p2pClient.broadcastProposal', async proposal => ({
368
- [Attributes.BLOCK_NUMBER]: proposal.blockNumber,
369
374
  [Attributes.SLOT_NUMBER]: proposal.slotNumber.toNumber(),
370
375
  [Attributes.BLOCK_ARCHIVE]: proposal.archive.toString(),
371
376
  [Attributes.P2P_ID]: (await proposal.p2pMessageIdentifier()).toString(),
@@ -387,6 +392,10 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
387
392
  return this.attestationPool?.addAttestations(attestations) ?? Promise.resolve();
388
393
  }
389
394
 
395
+ public deleteAttestation(attestation: BlockAttestation): Promise<void> {
396
+ return this.attestationPool?.deleteAttestations([attestation]) ?? Promise.resolve();
397
+ }
398
+
390
399
  // REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963
391
400
  // ^ This pattern is not my favorite (md)
392
401
  public registerBlockProposalHandler(handler: P2PBlockReceivedCallback): void {
package/src/config.ts CHANGED
@@ -127,7 +127,7 @@ export interface P2PConfig extends P2PReqRespConfig, ChainConfig, TxCollectionCo
127
127
  /** A list of preferred peers. */
128
128
  preferredPeers: string[];
129
129
 
130
- /** The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKB. */
130
+ /** The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. */
131
131
  p2pStoreMapSizeKb?: number;
132
132
 
133
133
  /** Which calls are allowed in the public setup phase of a tx. */
@@ -360,7 +360,7 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
360
360
  p2pStoreMapSizeKb: {
361
361
  env: 'P2P_STORE_MAP_SIZE_KB',
362
362
  parseEnv: (val: string | undefined) => (val ? +val : undefined),
363
- description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKB.',
363
+ description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb.',
364
364
  },
365
365
  txPublicSetupAllowList: {
366
366
  env: 'TX_PUBLIC_SETUP_ALLOWLIST',
@@ -449,9 +449,10 @@ export type BootnodeConfig = Pick<
449
449
  | 'peerIdPrivateKeyPath'
450
450
  | 'bootstrapNodes'
451
451
  | 'listenAddress'
452
+ | 'queryForIp'
452
453
  > &
453
454
  Required<Pick<P2PConfig, 'p2pIp' | 'p2pPort'>> &
454
- Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKB'> &
455
+ Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb'> &
455
456
  Pick<ChainConfig, 'l1ChainId'>;
456
457
 
457
458
  const bootnodeConfigKeys: (keyof BootnodeConfig)[] = [
@@ -462,9 +463,10 @@ const bootnodeConfigKeys: (keyof BootnodeConfig)[] = [
462
463
  'peerIdPrivateKey',
463
464
  'peerIdPrivateKeyPath',
464
465
  'dataDirectory',
465
- 'dataStoreMapSizeKB',
466
+ 'dataStoreMapSizeKb',
466
467
  'bootstrapNodes',
467
468
  'l1ChainId',
469
+ 'queryForIp',
468
470
  ];
469
471
 
470
472
  export const bootnodeConfigMappings = pickConfigMappings(
@@ -2,9 +2,9 @@ import type { LogFn } from '@aztec/foundation/log';
2
2
  import { type ChainConfig, emptyChainConfig } from '@aztec/stdlib/config';
3
3
  import type { ComponentsVersions } from '@aztec/stdlib/versioning';
4
4
 
5
- import { ENR, SignableENR } from '@chainsafe/enr';
6
5
  import type { PeerId } from '@libp2p/interface';
7
6
  import { type Multiaddr, multiaddr } from '@multiformats/multiaddr';
7
+ import { ENR, SignableENR } from '@nethermindeth/enr';
8
8
 
9
9
  import { AZTEC_ENR_CLIENT_VERSION_KEY, AZTEC_ENR_KEY } from '../types/index.js';
10
10
  import { convertToMultiaddr, createLibP2PPeerIdFromPrivateKey } from '../util.js';
@@ -21,6 +21,15 @@ export interface AttestationPool {
21
21
  */
22
22
  getBlockProposal(id: string): Promise<BlockProposal | undefined>;
23
23
 
24
+ /**
25
+ * Check if a block proposal exists in the pool
26
+ *
27
+ * @param idOrProposal - The ID of the block proposal or the block proposal itself to check. The ID is proposal.payload.archive
28
+ *
29
+ * @return True if the block proposal exists, false otherwise.
30
+ */
31
+ hasBlockProposal(idOrProposal: string | BlockProposal): Promise<boolean>;
32
+
24
33
  /**
25
34
  * AddAttestations
26
35
  *
@@ -84,6 +93,14 @@ export interface AttestationPool {
84
93
  */
85
94
  getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]>;
86
95
 
96
+ /**
97
+ * Check if a specific attestation exists in the pool
98
+ *
99
+ * @param attestation - The attestation to check
100
+ * @return True if the attestation exists, false otherwise
101
+ */
102
+ hasAttestation(attestation: BlockAttestation): Promise<boolean>;
103
+
87
104
  /** Returns whether the pool is empty. */
88
105
  isEmpty(): Promise<boolean>;
89
106
  }
@@ -41,7 +41,6 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
41
41
  };
42
42
 
43
43
  const mockBlockProposal = (signer: Secp256k1Signer, slotNumber: number, archive: Fr = Fr.random()): BlockProposal => {
44
- const blockNumber = 1;
45
44
  const header = makeHeader(1, 2, slotNumber);
46
45
  const payload = new ConsensusPayload(header.toPropose(), archive, header.state);
47
46
 
@@ -50,7 +49,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
50
49
 
51
50
  const txHashes = [TxHash.random(), TxHash.random()]; // Mock tx hashes
52
51
 
53
- return new BlockProposalClass(blockNumber, payload, signature, txHashes);
52
+ return new BlockProposalClass(payload, signature, txHashes);
54
53
  };
55
54
 
56
55
  // We compare buffers as the objects can have cached values attached to them which are not serialised
@@ -73,6 +72,11 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
73
72
  expect(retrievedAttestations.length).toBe(attestations.length);
74
73
  compareAttestations(retrievedAttestations, attestations);
75
74
 
75
+ // Check hasAttestation for added attestations
76
+ for (const attestation of attestations) {
77
+ expect(await ap.hasAttestation(attestation)).toBe(true);
78
+ }
79
+
76
80
  const retrievedAttestationsForSlot = await ap.getAttestationsForSlot(BigInt(slotNumber));
77
81
  expect(retrievedAttestationsForSlot.length).toBe(attestations.length);
78
82
  compareAttestations(retrievedAttestationsForSlot, attestations);
@@ -86,6 +90,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
86
90
  );
87
91
  expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1);
88
92
  compareAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]);
93
+ expect(await ap.hasAttestation(newAttestation)).toBe(true);
89
94
  const retrievedAttestationsForSlotAfterAdd = await ap.getAttestationsForSlot(BigInt(slotNumber));
90
95
  expect(retrievedAttestationsForSlotAfterAdd.length).toBe(attestations.length + 1);
91
96
  compareAttestations(retrievedAttestationsForSlotAfterAdd, [...attestations, newAttestation]);
@@ -98,6 +103,11 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
98
103
  archive.toString(),
99
104
  );
100
105
  expect(retreivedAttestationsAfterDelete.length).toBe(0);
106
+ // Check hasAttestation after deletion
107
+ for (const attestation of attestations) {
108
+ expect(await ap.hasAttestation(attestation)).toBe(false);
109
+ }
110
+ expect(await ap.hasAttestation(newAttestation)).toBe(false);
101
111
  });
102
112
 
103
113
  it('should handle duplicate proposals in a slot', async () => {
@@ -117,7 +127,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
117
127
  const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString());
118
128
  expect(retreivedAttestations.length).toBe(1);
119
129
  expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer());
120
- expect(retreivedAttestations[0].getSender().toString()).toEqual(signer.address.toString());
130
+ expect(retreivedAttestations[0].getSender()?.toString()).toEqual(signer.address.toString());
121
131
 
122
132
  // Try adding them on another operation and check they are still not duplicated
123
133
  await ap.addAttestations([attestations[0]]);
@@ -171,10 +181,20 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
171
181
  expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
172
182
  compareAttestations(retreivedAttestations, attestations);
173
183
 
184
+ // Check hasAttestation before deletion
185
+ for (const attestation of attestations) {
186
+ expect(await ap.hasAttestation(attestation)).toBe(true);
187
+ }
188
+
174
189
  await ap.deleteAttestations(attestations);
175
190
 
176
191
  const gottenAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
177
192
  expect(gottenAfterDelete.length).toBe(0);
193
+
194
+ // Check hasAttestation after deletion
195
+ for (const attestation of attestations) {
196
+ expect(await ap.hasAttestation(attestation)).toBe(false);
197
+ }
178
198
  });
179
199
 
180
200
  it('should blanket delete attestations per slot', async () => {
@@ -266,12 +286,19 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
266
286
 
267
287
  expect(retrievedProposal).toBeDefined();
268
288
  expect(retrievedProposal!).toEqual(proposal);
289
+
290
+ // Check hasBlockProposal with both id and object
291
+ expect(await ap.hasBlockProposal(proposalId)).toBe(true);
292
+ expect(await ap.hasBlockProposal(proposal)).toBe(true);
269
293
  });
270
294
 
271
295
  it('should return undefined for non-existent block proposal', async () => {
272
296
  const nonExistentId = Fr.random().toString();
273
297
  const retrievedProposal = await ap.getBlockProposal(nonExistentId);
274
298
  expect(retrievedProposal).toBeUndefined();
299
+
300
+ // Check hasBlockProposal returns false for non-existent proposal
301
+ expect(await ap.hasBlockProposal(nonExistentId)).toBe(false);
275
302
  });
276
303
 
277
304
  it('should update block proposal if added twice with same id', async () => {
@@ -291,7 +318,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
291
318
  expect(retrievedProposal).toBeDefined();
292
319
  // Should have the second proposal
293
320
  expect(retrievedProposal!.toBuffer()).toEqual(proposal2.toBuffer());
294
- expect(retrievedProposal!.getSender().toString()).toBe(signers[1].address.toString());
321
+ expect(retrievedProposal!.getSender()?.toString()).toBe(signers[1].address.toString());
295
322
  });
296
323
 
297
324
  it('should handle block proposals with different slots and same archive', async () => {
@@ -324,6 +351,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
324
351
  // Verify proposal exists
325
352
  let retrievedProposal = await ap.getBlockProposal(proposalId);
326
353
  expect(retrievedProposal).toBeDefined();
354
+ expect(await ap.hasBlockProposal(proposalId)).toBe(true);
327
355
 
328
356
  // Delete attestations for slot and proposal
329
357
  await ap.deleteAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
@@ -331,6 +359,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
331
359
  // Proposal should be deleted
332
360
  retrievedProposal = await ap.getBlockProposal(proposalId);
333
361
  expect(retrievedProposal).toBeUndefined();
362
+ expect(await ap.hasBlockProposal(proposalId)).toBe(false);
334
363
  });
335
364
 
336
365
  it('should delete block proposal when deleting attestations for slot', async () => {
@@ -345,6 +374,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
345
374
  // Verify proposal exists
346
375
  let retrievedProposal = await ap.getBlockProposal(proposalId);
347
376
  expect(retrievedProposal).toBeDefined();
377
+ expect(await ap.hasBlockProposal(proposal)).toBe(true);
348
378
 
349
379
  // Delete attestations for slot
350
380
  await ap.deleteAttestationsForSlot(BigInt(slotNumber));
@@ -352,6 +382,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
352
382
  // Proposal should be deleted
353
383
  retrievedProposal = await ap.getBlockProposal(proposalId);
354
384
  expect(retrievedProposal).toBeUndefined();
385
+ expect(await ap.hasBlockProposal(proposal)).toBe(false);
355
386
  });
356
387
 
357
388
  it('should be able to fetch both block proposal and attestations', async () => {
@@ -373,8 +404,13 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
373
404
 
374
405
  expect(retrievedProposal).toBeDefined();
375
406
  expect(retrievedProposal).toEqual(proposal);
407
+ expect(await ap.hasBlockProposal(proposalId)).toBe(true);
376
408
 
377
409
  compareAttestations(retrievedAttestations, attestations);
410
+ // Check hasAttestation for all attestations
411
+ for (const attestation of attestations) {
412
+ expect(await ap.hasAttestation(attestation)).toBe(true);
413
+ }
378
414
  });
379
415
  });
380
416
  }
@@ -66,7 +66,19 @@ export class KvAttestationPool implements AttestationPool {
66
66
  for (const attestation of attestations) {
67
67
  const slotNumber = attestation.payload.header.slotNumber;
68
68
  const proposalId = attestation.archive;
69
- const address = attestation.getSender().toString();
69
+ const sender = attestation.getSender();
70
+
71
+ // Skip attestations with invalid signatures
72
+ if (!sender) {
73
+ this.log.warn(`Skipping attestation with invalid signature for slot ${slotNumber.toBigInt()}`, {
74
+ signature: attestation.signature.toString(),
75
+ slotNumber,
76
+ proposalId,
77
+ });
78
+ continue;
79
+ }
80
+
81
+ const address = sender.toString();
70
82
 
71
83
  await this.attestations.set(this.getAttestationKey(slotNumber, proposalId, address), attestation.toBuffer());
72
84
 
@@ -176,7 +188,15 @@ export class KvAttestationPool implements AttestationPool {
176
188
  for (const attestation of attestations) {
177
189
  const slotNumber = attestation.payload.header.slotNumber;
178
190
  const proposalId = attestation.archive;
179
- const address = attestation.getSender().toString();
191
+ const sender = attestation.getSender();
192
+
193
+ // Skip attestations with invalid signatures
194
+ if (!sender) {
195
+ this.log.warn(`Skipping deletion of attestation with invalid signature for slot ${slotNumber.toBigInt()}`);
196
+ continue;
197
+ }
198
+
199
+ const address = sender.toString();
180
200
  const key = this.getAttestationKey(slotNumber, proposalId, address);
181
201
 
182
202
  if (await this.attestations.hasAsync(key)) {
@@ -193,6 +213,22 @@ export class KvAttestationPool implements AttestationPool {
193
213
  });
194
214
  }
195
215
 
216
+ public async hasAttestation(attestation: BlockAttestation): Promise<boolean> {
217
+ const slotNumber = attestation.payload.header.slotNumber;
218
+ const proposalId = attestation.archive;
219
+ const sender = attestation.getSender();
220
+
221
+ // Attestations with invalid signatures are never in the pool
222
+ if (!sender) {
223
+ return false;
224
+ }
225
+
226
+ const address = sender.toString();
227
+ const key = this.getAttestationKey(slotNumber, proposalId, address);
228
+
229
+ return await this.attestations.hasAsync(key);
230
+ }
231
+
196
232
  public async getBlockProposal(id: string): Promise<BlockProposal | undefined> {
197
233
  const buffer = await this.proposals.getAsync(id);
198
234
  try {
@@ -206,6 +242,11 @@ export class KvAttestationPool implements AttestationPool {
206
242
  return Promise.resolve(undefined);
207
243
  }
208
244
 
245
+ public async hasBlockProposal(idOrProposal: string | BlockProposal): Promise<boolean> {
246
+ const id = typeof idOrProposal === 'string' ? idOrProposal : idOrProposal.payload.archive.toString();
247
+ return await this.proposals.hasAsync(id);
248
+ }
249
+
209
250
  public async addBlockProposal(blockProposal: BlockProposal): Promise<void> {
210
251
  await this.store.transactionAsync(async () => {
211
252
  await this.proposalsForSlot.set(blockProposal.slotNumber.toString(), blockProposal.archive.toString());
@@ -55,16 +55,26 @@ export class InMemoryAttestationPool implements AttestationPool {
55
55
  const slotNumber = attestation.payload.header.slotNumber;
56
56
 
57
57
  const proposalId = attestation.archive.toString();
58
- const address = attestation.getSender();
58
+ const sender = attestation.getSender();
59
+
60
+ // Skip attestations with invalid signatures
61
+ if (!sender) {
62
+ this.log.warn(`Skipping attestation with invalid signature for slot ${slotNumber.toBigInt()}`, {
63
+ signature: attestation.signature.toString(),
64
+ slotNumber,
65
+ proposalId,
66
+ });
67
+ continue;
68
+ }
59
69
 
60
70
  const slotAttestationMap = getSlotOrDefault(this.attestations, slotNumber.toBigInt());
61
71
  const proposalAttestationMap = getProposalOrDefault(slotAttestationMap, proposalId);
62
- proposalAttestationMap.set(address.toString(), attestation);
72
+ proposalAttestationMap.set(sender.toString(), attestation);
63
73
 
64
- this.log.verbose(`Added attestation for slot ${slotNumber.toBigInt()} from ${address}`, {
74
+ this.log.verbose(`Added attestation for slot ${slotNumber.toBigInt()} from ${sender}`, {
65
75
  signature: attestation.signature.toString(),
66
76
  slotNumber,
67
- address,
77
+ address: sender,
68
78
  proposalId,
69
79
  });
70
80
  }
@@ -147,15 +157,45 @@ export class InMemoryAttestationPool implements AttestationPool {
147
157
  const proposalId = attestation.archive.toString();
148
158
  const proposalAttestationMap = getProposalOrDefault(slotAttestationMap, proposalId);
149
159
  if (proposalAttestationMap) {
150
- const address = attestation.getSender();
151
- proposalAttestationMap.delete(address.toString());
152
- this.log.debug(`Deleted attestation for slot ${slotNumber} from ${address}`);
160
+ const sender = attestation.getSender();
161
+
162
+ // Skip attestations with invalid signatures
163
+ if (!sender) {
164
+ this.log.warn(`Skipping deletion of attestation with invalid signature for slot ${slotNumber.toBigInt()}`);
165
+ continue;
166
+ }
167
+
168
+ proposalAttestationMap.delete(sender.toString());
169
+ this.log.debug(`Deleted attestation for slot ${slotNumber} from ${sender}`);
153
170
  }
154
171
  }
155
172
  }
156
173
  return Promise.resolve();
157
174
  }
158
175
 
176
+ public hasAttestation(attestation: BlockAttestation): Promise<boolean> {
177
+ const slotNumber = attestation.payload.header.slotNumber;
178
+ const proposalId = attestation.archive.toString();
179
+ const sender = attestation.getSender();
180
+
181
+ // Attestations with invalid signatures are never in the pool
182
+ if (!sender) {
183
+ return Promise.resolve(false);
184
+ }
185
+
186
+ const slotAttestationMap = this.attestations.get(slotNumber.toBigInt());
187
+ if (!slotAttestationMap) {
188
+ return Promise.resolve(false);
189
+ }
190
+
191
+ const proposalAttestationMap = slotAttestationMap.get(proposalId);
192
+ if (!proposalAttestationMap) {
193
+ return Promise.resolve(false);
194
+ }
195
+
196
+ return Promise.resolve(proposalAttestationMap.has(sender.toString()));
197
+ }
198
+
159
199
  public addBlockProposal(blockProposal: BlockProposal): Promise<void> {
160
200
  // We initialize slot-proposal mapping if it does not exist
161
201
  // This is important to ensure we can delete this proposal if there were not attestations for it
@@ -169,6 +209,11 @@ export class InMemoryAttestationPool implements AttestationPool {
169
209
  public getBlockProposal(id: string): Promise<BlockProposal | undefined> {
170
210
  return Promise.resolve(this.proposals.get(id));
171
211
  }
212
+
213
+ public hasBlockProposal(idOrProposal: string | BlockProposal): Promise<boolean> {
214
+ const id = typeof idOrProposal === 'string' ? idOrProposal : idOrProposal.payload.archive.toString();
215
+ return Promise.resolve(this.proposals.has(id));
216
+ }
172
217
  }
173
218
 
174
219
  /**
@@ -35,8 +35,11 @@ export const mockAttestation = (
35
35
  const header = makeHeader(1, 2, slot);
36
36
  const payload = new ConsensusPayload(header.toPropose(), archive, header.state);
37
37
 
38
- const hash = getHashedSignaturePayloadEthSignedMessage(payload, SignatureDomainSeparator.blockAttestation);
39
- const signature = signer.sign(hash);
38
+ const attestationHash = getHashedSignaturePayloadEthSignedMessage(payload, SignatureDomainSeparator.blockAttestation);
39
+ const attestationSignature = signer.sign(attestationHash);
40
40
 
41
- return new BlockAttestation(header.globalVariables.blockNumber, payload, signature);
41
+ const proposalHash = getHashedSignaturePayloadEthSignedMessage(payload, SignatureDomainSeparator.blockProposal);
42
+ const proposerSignature = signer.sign(proposalHash);
43
+
44
+ return new BlockAttestation(payload, attestationSignature, proposerSignature);
42
45
  };
@@ -273,6 +273,11 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
273
273
  return await Promise.all(txHashes.map(txHash => this.#txs.hasAsync(txHash.toString())));
274
274
  }
275
275
 
276
+ async hasTx(txHash: TxHash): Promise<boolean> {
277
+ const result = await this.hasTxs([txHash]);
278
+ return result[0];
279
+ }
280
+
276
281
  /**
277
282
  * Checks if an archived tx exists and returns it.
278
283
  * @param txHash - The tx hash.
@@ -151,6 +151,11 @@ export class InMemoryTxPool extends (EventEmitter as new () => TypedEventEmitter
151
151
  return Promise.resolve(txHashes.map(txHash => this.txs.has(txHash.toBigInt())));
152
152
  }
153
153
 
154
+ async hasTx(txHash: TxHash): Promise<boolean> {
155
+ const result = await this.hasTxs([txHash]);
156
+ return result[0];
157
+ }
158
+
154
159
  public getArchivedTxByHash(): Promise<Tx | undefined> {
155
160
  return Promise.resolve(undefined);
156
161
  }
@@ -43,6 +43,13 @@ export interface TxPool extends TypedEventEmitter<TxPoolEvents> {
43
43
  */
44
44
  hasTxs(txHashes: TxHash[]): Promise<boolean[]>;
45
45
 
46
+ /**
47
+ * Checks if a transaction exists in the pool
48
+ * @param txHash - The hash of the transaction to check for
49
+ * @returns True if the transaction exists, false otherwise
50
+ */
51
+ hasTx(txHash: TxHash): Promise<boolean>;
52
+
46
53
  /**
47
54
  * Checks if an archived transaction exists in the pool and returns it.
48
55
  * @param txHash - The hash of the transaction, used as an ID.