@aztec/p2p 0.67.0 → 0.67.1-devnet

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 (144) hide show
  1. package/dest/bootstrap/bootstrap.js +2 -2
  2. package/dest/client/index.d.ts +5 -4
  3. package/dest/client/index.d.ts.map +1 -1
  4. package/dest/client/index.js +12 -9
  5. package/dest/client/p2p_client.d.ts +6 -6
  6. package/dest/client/p2p_client.d.ts.map +1 -1
  7. package/dest/client/p2p_client.js +12 -11
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +2 -2
  11. package/dest/errors/reqresp.error.d.ts +12 -1
  12. package/dest/errors/reqresp.error.d.ts.map +1 -1
  13. package/dest/errors/reqresp.error.js +15 -2
  14. package/dest/index.d.ts +1 -1
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +2 -2
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +9 -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 +3 -0
  20. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -0
  21. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +171 -0
  22. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +29 -0
  23. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -0
  24. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +114 -0
  25. package/dest/mem_pools/interface.d.ts +4 -3
  26. package/dest/mem_pools/interface.d.ts.map +1 -1
  27. package/dest/mocks/index.d.ts +6 -6
  28. package/dest/mocks/index.d.ts.map +1 -1
  29. package/dest/mocks/index.js +8 -8
  30. package/dest/services/data_store.d.ts.map +1 -0
  31. package/dest/services/data_store.js +188 -0
  32. package/dest/{service → services/discv5}/discV5_service.d.ts +2 -2
  33. package/dest/services/discv5/discV5_service.d.ts.map +1 -0
  34. package/dest/services/discv5/discV5_service.js +144 -0
  35. package/dest/services/dummy_service.d.ts.map +1 -0
  36. package/dest/{service → services}/dummy_service.js +1 -1
  37. package/dest/{service → services}/encoding.d.ts +5 -0
  38. package/dest/services/encoding.d.ts.map +1 -0
  39. package/dest/services/encoding.js +65 -0
  40. package/dest/services/index.d.ts +3 -0
  41. package/dest/services/index.d.ts.map +1 -0
  42. package/dest/services/index.js +3 -0
  43. package/dest/{service → services/libp2p}/libp2p_service.d.ts +48 -10
  44. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -0
  45. package/dest/services/libp2p/libp2p_service.js +573 -0
  46. package/dest/{service → services/peer-scoring}/peer_scoring.d.ts +1 -1
  47. package/dest/services/peer-scoring/peer_scoring.d.ts.map +1 -0
  48. package/dest/services/peer-scoring/peer_scoring.js +72 -0
  49. package/dest/{service → services}/peer_manager.d.ts +5 -3
  50. package/dest/services/peer_manager.d.ts.map +1 -0
  51. package/dest/services/peer_manager.js +230 -0
  52. package/dest/services/reqresp/config.d.ts.map +1 -0
  53. package/dest/{service → services}/reqresp/config.js +1 -1
  54. package/dest/services/reqresp/handlers.d.ts.map +1 -0
  55. package/dest/{service → services}/reqresp/handlers.js +1 -1
  56. package/dest/services/reqresp/index.d.ts.map +1 -0
  57. package/dest/{service → services}/reqresp/index.js +1 -1
  58. package/dest/services/reqresp/interface.d.ts.map +1 -0
  59. package/dest/{service → services}/reqresp/interface.js +1 -1
  60. package/dest/services/reqresp/rate_limiter/index.d.ts.map +1 -0
  61. package/dest/{service → services}/reqresp/rate_limiter/index.js +1 -1
  62. package/dest/services/reqresp/rate_limiter/rate_limiter.d.ts.map +1 -0
  63. package/dest/{service → services}/reqresp/rate_limiter/rate_limiter.js +2 -2
  64. package/dest/services/reqresp/rate_limiter/rate_limits.d.ts.map +1 -0
  65. package/dest/{service → services}/reqresp/rate_limiter/rate_limits.js +1 -1
  66. package/dest/{service → services}/reqresp/reqresp.d.ts +16 -0
  67. package/dest/services/reqresp/reqresp.d.ts.map +1 -0
  68. package/dest/services/reqresp/reqresp.js +279 -0
  69. package/dest/services/service.d.ts.map +1 -0
  70. package/dest/{service → services}/service.js +1 -1
  71. package/dest/tx_validator/aggregate_tx_validator.d.ts +1 -1
  72. package/dest/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  73. package/dest/tx_validator/aggregate_tx_validator.js +5 -3
  74. package/dest/tx_validator/double_spend_validator.d.ts +3 -2
  75. package/dest/tx_validator/double_spend_validator.d.ts.map +1 -1
  76. package/dest/tx_validator/double_spend_validator.js +6 -6
  77. package/package.json +8 -7
  78. package/src/bootstrap/bootstrap.ts +1 -1
  79. package/src/client/index.ts +38 -16
  80. package/src/client/p2p_client.ts +28 -15
  81. package/src/config.ts +1 -1
  82. package/src/errors/reqresp.error.ts +15 -1
  83. package/src/index.ts +1 -1
  84. package/src/mem_pools/attestation_pool/attestation_pool.ts +10 -0
  85. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +237 -0
  86. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +153 -0
  87. package/src/mem_pools/interface.ts +5 -3
  88. package/src/mocks/index.ts +13 -10
  89. package/src/{service → services/discv5}/discV5_service.ts +3 -3
  90. package/src/{service → services}/encoding.ts +21 -3
  91. package/src/services/index.ts +2 -0
  92. package/src/{service → services/libp2p}/libp2p_service.ts +192 -86
  93. package/src/{service → services/peer-scoring}/peer_scoring.ts +1 -1
  94. package/src/{service → services}/peer_manager.ts +5 -2
  95. package/src/{service → services}/reqresp/rate_limiter/rate_limiter.ts +1 -1
  96. package/src/{service → services}/reqresp/reqresp.ts +83 -17
  97. package/src/tx_validator/aggregate_tx_validator.ts +5 -3
  98. package/src/tx_validator/double_spend_validator.ts +6 -8
  99. package/dest/service/data_store.d.ts.map +0 -1
  100. package/dest/service/data_store.js +0 -188
  101. package/dest/service/discV5_service.d.ts.map +0 -1
  102. package/dest/service/discV5_service.js +0 -144
  103. package/dest/service/dummy_service.d.ts.map +0 -1
  104. package/dest/service/encoding.d.ts.map +0 -1
  105. package/dest/service/encoding.js +0 -49
  106. package/dest/service/index.d.ts +0 -3
  107. package/dest/service/index.d.ts.map +0 -1
  108. package/dest/service/index.js +0 -3
  109. package/dest/service/libp2p_service.d.ts.map +0 -1
  110. package/dest/service/libp2p_service.js +0 -500
  111. package/dest/service/peer_manager.d.ts.map +0 -1
  112. package/dest/service/peer_manager.js +0 -214
  113. package/dest/service/peer_scoring.d.ts.map +0 -1
  114. package/dest/service/peer_scoring.js +0 -72
  115. package/dest/service/reqresp/config.d.ts.map +0 -1
  116. package/dest/service/reqresp/handlers.d.ts.map +0 -1
  117. package/dest/service/reqresp/index.d.ts.map +0 -1
  118. package/dest/service/reqresp/interface.d.ts.map +0 -1
  119. package/dest/service/reqresp/rate_limiter/index.d.ts.map +0 -1
  120. package/dest/service/reqresp/rate_limiter/rate_limiter.d.ts.map +0 -1
  121. package/dest/service/reqresp/rate_limiter/rate_limits.d.ts.map +0 -1
  122. package/dest/service/reqresp/reqresp.d.ts.map +0 -1
  123. package/dest/service/reqresp/reqresp.js +0 -230
  124. package/dest/service/service.d.ts.map +0 -1
  125. package/src/service/index.ts +0 -2
  126. /package/dest/{service → services}/data_store.d.ts +0 -0
  127. /package/dest/{service → services}/dummy_service.d.ts +0 -0
  128. /package/dest/{service → services}/reqresp/config.d.ts +0 -0
  129. /package/dest/{service → services}/reqresp/handlers.d.ts +0 -0
  130. /package/dest/{service → services}/reqresp/index.d.ts +0 -0
  131. /package/dest/{service → services}/reqresp/interface.d.ts +0 -0
  132. /package/dest/{service → services}/reqresp/rate_limiter/index.d.ts +0 -0
  133. /package/dest/{service → services}/reqresp/rate_limiter/rate_limiter.d.ts +0 -0
  134. /package/dest/{service → services}/reqresp/rate_limiter/rate_limits.d.ts +0 -0
  135. /package/dest/{service → services}/service.d.ts +0 -0
  136. /package/src/{service → services}/data_store.ts +0 -0
  137. /package/src/{service → services}/dummy_service.ts +0 -0
  138. /package/src/{service → services}/reqresp/config.ts +0 -0
  139. /package/src/{service → services}/reqresp/handlers.ts +0 -0
  140. /package/src/{service → services}/reqresp/index.ts +0 -0
  141. /package/src/{service → services}/reqresp/interface.ts +0 -0
  142. /package/src/{service → services}/reqresp/rate_limiter/index.ts +0 -0
  143. /package/src/{service → services}/reqresp/rate_limiter/rate_limits.ts +0 -0
  144. /package/src/{service → services}/service.ts +0 -0
@@ -5,10 +5,10 @@ import {
5
5
  type L2Block,
6
6
  type L2BlockId,
7
7
  type L2BlockSource,
8
- L2BlockStream,
9
8
  type L2BlockStreamEvent,
10
9
  type L2Tips,
11
10
  type P2PApi,
11
+ type P2PClientType,
12
12
  type PeerInfo,
13
13
  type Tx,
14
14
  type TxHash,
@@ -16,7 +16,13 @@ import {
16
16
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js/constants';
17
17
  import { createLogger } from '@aztec/foundation/log';
18
18
  import { type AztecKVStore, type AztecMap, type AztecSingleton } from '@aztec/kv-store';
19
- import { Attributes, type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
19
+ import {
20
+ Attributes,
21
+ type TelemetryClient,
22
+ TraceableL2BlockStream,
23
+ WithTracer,
24
+ trackSpan,
25
+ } from '@aztec/telemetry-client';
20
26
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
21
27
 
22
28
  import { type ENR } from '@chainsafe/enr';
@@ -26,8 +32,8 @@ import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_
26
32
  import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js';
27
33
  import { type MemPools } from '../mem_pools/interface.js';
28
34
  import { type TxPool } from '../mem_pools/tx_pool/index.js';
29
- import { TX_REQ_PROTOCOL } from '../service/reqresp/interface.js';
30
- import type { P2PService } from '../service/service.js';
35
+ import { TX_REQ_PROTOCOL } from '../services/reqresp/interface.js';
36
+ import type { P2PService } from '../services/service.js';
31
37
 
32
38
  /**
33
39
  * Enum defining the possible states of the p2p client.
@@ -56,7 +62,7 @@ export interface P2PSyncState {
56
62
  /**
57
63
  * Interface of a P2P client.
58
64
  **/
59
- export interface P2P extends P2PApi {
65
+ export type P2P<T extends P2PClientType = P2PClientType.Full> = P2PApi<T> & {
60
66
  /**
61
67
  * Broadcasts a block proposal to other peers.
62
68
  *
@@ -166,12 +172,15 @@ export interface P2P extends P2PApi {
166
172
 
167
173
  /** Identifies a p2p client. */
168
174
  isP2PClient(): true;
169
- }
175
+ };
170
176
 
171
177
  /**
172
178
  * The P2P client implementation.
173
179
  */
174
- export class P2PClient extends WithTracer implements P2P {
180
+ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
181
+ extends WithTracer
182
+ implements P2P, P2P<P2PClientType.Prover>
183
+ {
175
184
  /** Property that indicates whether the client is running. */
176
185
  private stopping = false;
177
186
 
@@ -189,7 +198,7 @@ export class P2PClient extends WithTracer implements P2P {
189
198
  private synchedProvenBlockNumber: AztecSingleton<number>;
190
199
 
191
200
  private txPool: TxPool;
192
- private attestationPool: AttestationPool;
201
+ private attestationPool: T extends P2PClientType.Full ? AttestationPool : undefined;
193
202
  private epochProofQuotePool: EpochProofQuotePool;
194
203
 
195
204
  /** How many slots to keep attestations for. */
@@ -207,9 +216,10 @@ export class P2PClient extends WithTracer implements P2P {
207
216
  * @param log - A logger.
208
217
  */
209
218
  constructor(
219
+ clientType: T,
210
220
  store: AztecKVStore,
211
221
  private l2BlockSource: L2BlockSource,
212
- mempools: MemPools,
222
+ mempools: MemPools<T>,
213
223
  private p2pService: P2PService,
214
224
  private keepProvenTxsFor: number,
215
225
  telemetry: TelemetryClient = new NoopTelemetryClient(),
@@ -221,7 +231,9 @@ export class P2PClient extends WithTracer implements P2P {
221
231
 
222
232
  this.keepAttestationsInPoolFor = keepAttestationsInPoolFor;
223
233
 
224
- this.blockStream = new L2BlockStream(l2BlockSource, this, this, createLogger('p2p:block_stream'), {
234
+ const tracer = telemetry.getTracer('P2PL2BlockStream');
235
+ const logger = createLogger('p2p:l2-block-stream');
236
+ this.blockStream = new TraceableL2BlockStream(l2BlockSource, this, this, tracer, 'P2PL2BlockStream', logger, {
225
237
  batchSize: blockRequestBatchSize,
226
238
  pollIntervalMS: blockCheckIntervalMS,
227
239
  });
@@ -231,8 +243,8 @@ export class P2PClient extends WithTracer implements P2P {
231
243
  this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block');
232
244
 
233
245
  this.txPool = mempools.txPool;
234
- this.attestationPool = mempools.attestationPool;
235
246
  this.epochProofQuotePool = mempools.epochProofQuotePool;
247
+ this.attestationPool = mempools.attestationPool!;
236
248
  }
237
249
 
238
250
  public isP2PClient(): true {
@@ -399,7 +411,7 @@ export class P2PClient extends WithTracer implements P2P {
399
411
  }
400
412
 
401
413
  public getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
402
- return Promise.resolve(this.attestationPool.getAttestationsForSlot(slot, proposalId));
414
+ return Promise.resolve(this.attestationPool?.getAttestationsForSlot(slot, proposalId) ?? []);
403
415
  }
404
416
 
405
417
  // REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963
@@ -644,16 +656,17 @@ export class P2PClient extends WithTracer implements P2P {
644
656
  // We delete attestations older than the last block slot minus the number of slots we want to keep in the pool.
645
657
  const lastBlockSlotMinusKeepAttestationsInPoolFor = lastBlockSlot - BigInt(this.keepAttestationsInPoolFor);
646
658
  if (lastBlockSlotMinusKeepAttestationsInPoolFor >= BigInt(INITIAL_L2_BLOCK_NUM)) {
647
- await this.attestationPool.deleteAttestationsOlderThan(lastBlockSlotMinusKeepAttestationsInPoolFor);
659
+ await this.attestationPool?.deleteAttestationsOlderThan(lastBlockSlotMinusKeepAttestationsInPoolFor);
648
660
  }
649
661
 
650
- await this.synchedProvenBlockNumber.set(lastBlockNum);
651
- this.log.debug(`Synched to proven block ${lastBlockNum}`);
652
662
  const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber();
653
663
  if (provenEpochNumber !== undefined) {
654
664
  this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber));
655
665
  }
656
666
 
667
+ await this.synchedProvenBlockNumber.set(lastBlockNum);
668
+ this.log.debug(`Synched to proven block ${lastBlockNum}`);
669
+
657
670
  await this.startServiceIfSynched();
658
671
  }
659
672
 
package/src/config.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  } from '@aztec/foundation/config';
9
9
  import { type DataStoreConfig, dataConfigMappings } from '@aztec/kv-store/config';
10
10
 
11
- import { type P2PReqRespConfig, p2pReqRespConfigMappings } from './service/reqresp/config.js';
11
+ import { type P2PReqRespConfig, p2pReqRespConfigMappings } from './services/reqresp/config.js';
12
12
 
13
13
  /**
14
14
  * P2P client configuration values.
@@ -3,7 +3,7 @@
3
3
  * This error will be thrown when a request to a specific peer times out.
4
4
  * @category Errors
5
5
  */
6
- export class IndiviualReqRespTimeoutError extends Error {
6
+ export class IndividualReqRespTimeoutError extends Error {
7
7
  constructor() {
8
8
  super(`Request to peer timed out`);
9
9
  }
@@ -19,3 +19,17 @@ export class CollectiveReqRespTimeoutError extends Error {
19
19
  super(`Request to all peers timed out`);
20
20
  }
21
21
  }
22
+
23
+ /** Invalid response error
24
+ *
25
+ * This error will be thrown when a response is received that is not valid.
26
+ *
27
+ * This error does not need to be punished as message validators will handle punishing invalid
28
+ * requests
29
+ * @category Errors
30
+ */
31
+ export class InvalidResponseError extends Error {
32
+ constructor() {
33
+ super(`Invalid response received`);
34
+ }
35
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,6 @@ export * from './bootstrap/bootstrap.js';
3
3
  export * from './client/index.js';
4
4
  export * from './config.js';
5
5
  export * from './mem_pools/epoch_proof_quote_pool/index.js';
6
- export * from './service/index.js';
6
+ export * from './services/index.js';
7
7
  export * from './mem_pools/tx_pool/index.js';
8
8
  export * from './tx_validator/index.js';
@@ -39,6 +39,16 @@ export interface AttestationPool {
39
39
  */
40
40
  deleteAttestationsForSlot(slot: bigint): Promise<void>;
41
41
 
42
+ /**
43
+ * Delete Attestations for slot and proposal
44
+ *
45
+ * Removes all attestations associated with a slot and proposal
46
+ *
47
+ * @param slot - The slot to delete.
48
+ * @param proposalId - The proposal to delete.
49
+ */
50
+ deleteAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<void>;
51
+
42
52
  /**
43
53
  * Get Attestations for slot
44
54
  *
@@ -0,0 +1,237 @@
1
+ import { type BlockAttestation, TxHash } from '@aztec/circuit-types';
2
+ import { Secp256k1Signer } from '@aztec/foundation/crypto';
3
+ import { Fr } from '@aztec/foundation/fields';
4
+
5
+ import { jest } from '@jest/globals';
6
+ import { type MockProxy, mock } from 'jest-mock-extended';
7
+
8
+ import { type PoolInstrumentation } from '../instrumentation.js';
9
+ import { type AttestationPool } from './attestation_pool.js';
10
+ import { mockAttestation } from './mocks.js';
11
+
12
+ const NUMBER_OF_SIGNERS_PER_TEST = 4;
13
+
14
+ export function describeAttestationPool(getAttestationPool: () => AttestationPool) {
15
+ let ap: AttestationPool;
16
+ let signers: Secp256k1Signer[];
17
+
18
+ // Check that metrics are recorded correctly
19
+ let metricsMock: MockProxy<PoolInstrumentation<BlockAttestation>>;
20
+
21
+ beforeEach(() => {
22
+ ap = getAttestationPool();
23
+ signers = Array.from({ length: NUMBER_OF_SIGNERS_PER_TEST }, () => Secp256k1Signer.random());
24
+
25
+ metricsMock = mock<PoolInstrumentation<BlockAttestation>>();
26
+ // Can i overwrite this like this??
27
+ (ap as any).metrics = metricsMock;
28
+ });
29
+
30
+ const createAttestationsForSlot = (slotNumber: number) => {
31
+ const archive = Fr.random();
32
+ return signers.map(signer => mockAttestation(signer, slotNumber, archive));
33
+ };
34
+
35
+ // We compare buffers as the objects can have cached values attached to them which are not serialised
36
+ // using array containing as the kv store does not respect insertion order
37
+ const compareAttestations = (a1: BlockAttestation[], a2: BlockAttestation[]) => {
38
+ const a1Buffer = a1.map(attestation => attestation.toBuffer());
39
+ const a2Buffer = a2.map(attestation => attestation.toBuffer());
40
+ expect(a1Buffer.length).toBe(a2Buffer.length);
41
+ expect(a1Buffer).toEqual(expect.arrayContaining(a2Buffer));
42
+ };
43
+
44
+ it('should add attestations to pool', async () => {
45
+ const slotNumber = 420;
46
+ const archive = Fr.random();
47
+ const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
48
+
49
+ await ap.addAttestations(attestations);
50
+
51
+ // Check metrics have been updated.
52
+ expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
53
+
54
+ const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString());
55
+
56
+ expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
57
+
58
+ compareAttestations(retreivedAttestations, attestations);
59
+
60
+ // Delete by slot
61
+ await ap.deleteAttestationsForSlot(BigInt(slotNumber));
62
+
63
+ expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
64
+
65
+ const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString());
66
+ expect(retreivedAttestationsAfterDelete.length).toBe(0);
67
+ });
68
+
69
+ it('Should handle duplicate proposals in a slot', async () => {
70
+ const slotNumber = 420;
71
+ const archive = Fr.random();
72
+ const txs = [0, 1, 2, 3, 4, 5].map(() => TxHash.random());
73
+
74
+ // Use the same signer for all attestations
75
+ const attestations: BlockAttestation[] = [];
76
+ const signer = signers[0];
77
+ for (let i = 0; i < NUMBER_OF_SIGNERS_PER_TEST; i++) {
78
+ attestations.push(mockAttestation(signer, slotNumber, archive, txs));
79
+ }
80
+
81
+ await ap.addAttestations(attestations);
82
+
83
+ const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), archive.toString());
84
+ expect(retreivedAttestations.length).toBe(1);
85
+ expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer());
86
+ expect(retreivedAttestations[0].payload.txHashes).toEqual(txs);
87
+ expect(retreivedAttestations[0].getSender().toString()).toEqual(signer.address.toString());
88
+ });
89
+
90
+ it('Should store attestations by differing slot', async () => {
91
+ const slotNumbers = [1, 2, 3, 4];
92
+ const attestations = signers.map((signer, i) => mockAttestation(signer, slotNumbers[i]));
93
+
94
+ await ap.addAttestations(attestations);
95
+
96
+ for (const attestation of attestations) {
97
+ const slot = attestation.payload.header.globalVariables.slotNumber;
98
+ const archive = attestation.archive.toString();
99
+
100
+ const retreivedAttestations = await ap.getAttestationsForSlot(slot.toBigInt(), archive);
101
+ expect(retreivedAttestations.length).toBe(1);
102
+ expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
103
+ expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
104
+ }
105
+ });
106
+
107
+ it('Should store attestations by differing slot and archive', async () => {
108
+ const slotNumbers = [1, 1, 2, 3];
109
+ const archives = [Fr.random(), Fr.random(), Fr.random(), Fr.random()];
110
+ const attestations = signers.map((signer, i) => mockAttestation(signer, slotNumbers[i], archives[i]));
111
+
112
+ await ap.addAttestations(attestations);
113
+
114
+ for (const attestation of attestations) {
115
+ const slot = attestation.payload.header.globalVariables.slotNumber;
116
+ const proposalId = attestation.archive.toString();
117
+
118
+ const retreivedAttestations = await ap.getAttestationsForSlot(slot.toBigInt(), proposalId);
119
+ expect(retreivedAttestations.length).toBe(1);
120
+ expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
121
+ expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
122
+ }
123
+ });
124
+
125
+ it('Should delete attestations', async () => {
126
+ const slotNumber = 420;
127
+ const archive = Fr.random();
128
+ const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
129
+ const proposalId = attestations[0].archive.toString();
130
+
131
+ await ap.addAttestations(attestations);
132
+
133
+ expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
134
+
135
+ const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
136
+ expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
137
+ compareAttestations(retreivedAttestations, attestations);
138
+
139
+ await ap.deleteAttestations(attestations);
140
+
141
+ expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
142
+
143
+ const gottenAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
144
+ expect(gottenAfterDelete.length).toBe(0);
145
+ });
146
+
147
+ it('Should blanket delete attestations per slot', async () => {
148
+ const slotNumber = 420;
149
+ const archive = Fr.random();
150
+ const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber, archive)));
151
+ const proposalId = attestations[0].archive.toString();
152
+
153
+ await ap.addAttestations(attestations);
154
+
155
+ const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
156
+ expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
157
+ compareAttestations(retreivedAttestations, attestations);
158
+
159
+ await ap.deleteAttestationsForSlot(BigInt(slotNumber));
160
+
161
+ const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
162
+ expect(retreivedAttestationsAfterDelete.length).toBe(0);
163
+ });
164
+
165
+ it('Should blanket delete attestations per slot and proposal', async () => {
166
+ const slotNumber = 420;
167
+ const archive = Fr.random();
168
+ const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
169
+ const proposalId = attestations[0].archive.toString();
170
+
171
+ // Add another set of attestations with a different proposalId, yet the same slot
172
+ const archive2 = Fr.random();
173
+ const attestations2 = signers.map(signer => mockAttestation(signer, slotNumber, archive2));
174
+ const proposalId2 = attestations2[0].archive.toString();
175
+
176
+ await ap.addAttestations(attestations);
177
+ await ap.addAttestations(attestations2);
178
+
179
+ expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
180
+ expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations2.length);
181
+
182
+ const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
183
+ expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
184
+ compareAttestations(retreivedAttestations, attestations);
185
+
186
+ await ap.deleteAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
187
+
188
+ expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
189
+
190
+ const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
191
+ expect(retreivedAttestationsAfterDelete.length).toBe(0);
192
+
193
+ const retreivedAttestationsAfterDeleteForOtherProposal = await ap.getAttestationsForSlot(
194
+ BigInt(slotNumber),
195
+ proposalId2,
196
+ );
197
+ expect(retreivedAttestationsAfterDeleteForOtherProposal.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
198
+ compareAttestations(retreivedAttestationsAfterDeleteForOtherProposal, attestations2);
199
+ });
200
+
201
+ it('Should blanket delete attestations per slot and proposal (does not perform db ops if there are no attestations)', async () => {
202
+ const slotNumber = 420;
203
+ const proposalId = 'proposalId';
204
+
205
+ const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
206
+ expect(retreivedAttestations.length).toBe(0);
207
+
208
+ await ap.deleteAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
209
+
210
+ expect(metricsMock.recordRemovedObjects).toHaveBeenCalledTimes(0);
211
+ });
212
+
213
+ it('Should delete attestations older than a given slot', async () => {
214
+ const slotNumbers = [1, 2, 3, 69, 72, 74, 88, 420];
215
+ const attestations = slotNumbers.map(slotNumber => createAttestationsForSlot(slotNumber)).flat();
216
+ const proposalId = attestations[0].archive.toString();
217
+
218
+ await ap.addAttestations(attestations);
219
+
220
+ const attestationsForSlot1 = await ap.getAttestationsForSlot(BigInt(1), proposalId);
221
+ expect(attestationsForSlot1.length).toBe(signers.length);
222
+
223
+ const deleteAttestationsSpy = jest.spyOn(ap, 'deleteAttestationsForSlot');
224
+
225
+ await ap.deleteAttestationsOlderThan(BigInt(73));
226
+
227
+ const attestationsForSlot1AfterDelete = await ap.getAttestationsForSlot(BigInt(1), proposalId);
228
+ expect(attestationsForSlot1AfterDelete.length).toBe(0);
229
+
230
+ expect(deleteAttestationsSpy).toHaveBeenCalledTimes(5);
231
+ expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(1));
232
+ expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(2));
233
+ expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(3));
234
+ expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(69));
235
+ expect(deleteAttestationsSpy).toHaveBeenCalledWith(BigInt(72));
236
+ });
237
+ }
@@ -0,0 +1,153 @@
1
+ import { BlockAttestation } from '@aztec/circuit-types';
2
+ import { Fr } from '@aztec/foundation/fields';
3
+ import { createLogger } from '@aztec/foundation/log';
4
+ import { type AztecKVStore, type AztecMapWithSize, type AztecMultiMap } from '@aztec/kv-store';
5
+ import { type TelemetryClient } from '@aztec/telemetry-client';
6
+
7
+ import { PoolInstrumentation, PoolName } from '../instrumentation.js';
8
+ import { type AttestationPool } from './attestation_pool.js';
9
+
10
+ export class KvAttestationPool implements AttestationPool {
11
+ private metrics: PoolInstrumentation<BlockAttestation>;
12
+
13
+ // Index of all proposal ids in a slot
14
+ private attestations: AztecMultiMap<string, string>;
15
+
16
+ constructor(
17
+ private store: AztecKVStore,
18
+ telemetry: TelemetryClient,
19
+ private log = createLogger('aztec:attestation_pool'),
20
+ ) {
21
+ this.attestations = store.openMultiMap('attestations');
22
+ this.metrics = new PoolInstrumentation(telemetry, PoolName.ATTESTATION_POOL);
23
+ }
24
+
25
+ private getProposalMapKey(slot: string, proposalId: string): string {
26
+ return `proposal-${slot}-${proposalId}`;
27
+ }
28
+
29
+ /**
30
+ * Get the proposal map for a given slot and proposalId
31
+ *
32
+ * Essentially a nested mapping of address -> attestation
33
+ *
34
+ * @param slot - The slot to get the proposal map for
35
+ * @param proposalId - The proposalId to get the map for
36
+ * @returns The proposal map
37
+ */
38
+ private getProposalMap(slot: string, proposalId: string): AztecMapWithSize<string, Buffer> {
39
+ const mapKey = this.getProposalMapKey(slot, proposalId);
40
+ return this.store.openMapWithSize(mapKey);
41
+ }
42
+
43
+ public async addAttestations(attestations: BlockAttestation[]): Promise<void> {
44
+ for (const attestation of attestations) {
45
+ const slotNumber = attestation.payload.header.globalVariables.slotNumber.toString();
46
+ const proposalId = attestation.archive.toString();
47
+ const address = attestation.getSender().toString();
48
+
49
+ // Index the proposalId in the slot map
50
+ await this.attestations.set(slotNumber, proposalId);
51
+
52
+ // Store the actual attestation in the proposal map
53
+ const proposalMap = this.getProposalMap(slotNumber, proposalId);
54
+ await proposalMap.set(address, attestation.toBuffer());
55
+
56
+ this.log.verbose(`Added attestation for slot ${slotNumber} from ${address}`);
57
+ }
58
+
59
+ this.metrics.recordAddedObjects(attestations.length);
60
+ }
61
+
62
+ public getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
63
+ const slotNumber = new Fr(slot).toString();
64
+ const proposalMap = this.getProposalMap(slotNumber, proposalId);
65
+ const attestations = proposalMap.values();
66
+ const attestationsArray = Array.from(attestations).map(attestation => BlockAttestation.fromBuffer(attestation));
67
+ return Promise.resolve(attestationsArray);
68
+ }
69
+
70
+ public async deleteAttestationsOlderThan(oldestSlot: bigint): Promise<void> {
71
+ const olderThan = [];
72
+
73
+ const slots = this.attestations.keys();
74
+ for (const slot of slots) {
75
+ if (BigInt(slot) < oldestSlot) {
76
+ olderThan.push(slot);
77
+ }
78
+ }
79
+
80
+ await Promise.all(olderThan.map(oldSlot => this.deleteAttestationsForSlot(BigInt(oldSlot))));
81
+ return Promise.resolve();
82
+ }
83
+
84
+ public async deleteAttestationsForSlot(slot: bigint): Promise<void> {
85
+ const deletionPromises = [];
86
+
87
+ const slotString = new Fr(slot).toString();
88
+ let numberOfAttestations = 0;
89
+ const proposalIds = this.attestations.getValues(slotString);
90
+
91
+ if (proposalIds) {
92
+ for (const proposalId of proposalIds) {
93
+ const proposalMap = this.getProposalMap(slotString, proposalId);
94
+ numberOfAttestations += proposalMap.size();
95
+ deletionPromises.push(proposalMap.clear());
96
+ }
97
+ }
98
+
99
+ await Promise.all(deletionPromises);
100
+
101
+ this.log.verbose(`Removed ${numberOfAttestations} attestations for slot ${slot}`);
102
+ this.metrics.recordRemovedObjects(numberOfAttestations);
103
+ return Promise.resolve();
104
+ }
105
+
106
+ public async deleteAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<void> {
107
+ const deletionPromises = [];
108
+
109
+ const slotString = new Fr(slot).toString();
110
+ const exists = this.attestations.get(slotString);
111
+
112
+ if (exists) {
113
+ // Remove the proposalId from the slot index
114
+ deletionPromises.push(this.attestations.deleteValue(slotString, proposalId));
115
+
116
+ // Delete all attestations for the proposalId
117
+ const proposalMap = this.getProposalMap(slotString, proposalId);
118
+ const numberOfAttestations = proposalMap.size();
119
+ deletionPromises.push(proposalMap.clear());
120
+
121
+ this.log.verbose(`Removed ${numberOfAttestations} attestations for slot ${slot} and proposal ${proposalId}`);
122
+ this.metrics.recordRemovedObjects(numberOfAttestations);
123
+ }
124
+
125
+ await Promise.all(deletionPromises);
126
+ return Promise.resolve();
127
+ }
128
+
129
+ public async deleteAttestations(attestations: BlockAttestation[]): Promise<void> {
130
+ const deletionPromises = [];
131
+
132
+ for (const attestation of attestations) {
133
+ const slotNumber = attestation.payload.header.globalVariables.slotNumber.toString();
134
+ const proposalId = attestation.archive.toString();
135
+ const proposalMap = this.getProposalMap(slotNumber, proposalId);
136
+
137
+ if (proposalMap) {
138
+ const address = attestation.getSender().toString();
139
+ deletionPromises.push(proposalMap.delete(address));
140
+ this.log.debug(`Deleted attestation for slot ${slotNumber} from ${address}`);
141
+ }
142
+
143
+ if (proposalMap.size() === 0) {
144
+ deletionPromises.push(this.attestations.deleteValue(slotNumber, proposalId));
145
+ }
146
+ }
147
+
148
+ await Promise.all(deletionPromises);
149
+
150
+ this.metrics.recordRemovedObjects(attestations.length);
151
+ return Promise.resolve();
152
+ }
153
+ }
@@ -1,3 +1,5 @@
1
+ import { type P2PClientType } from '@aztec/circuit-types';
2
+
1
3
  import { type AttestationPool } from './attestation_pool/attestation_pool.js';
2
4
  import { type EpochProofQuotePool } from './epoch_proof_quote_pool/epoch_proof_quote_pool.js';
3
5
  import { type TxPool } from './tx_pool/tx_pool.js';
@@ -5,8 +7,8 @@ import { type TxPool } from './tx_pool/tx_pool.js';
5
7
  /**
6
8
  * A interface the combines all mempools
7
9
  */
8
- export interface MemPools {
10
+ export type MemPools<T extends P2PClientType = P2PClientType.Full> = {
9
11
  txPool: TxPool;
10
- attestationPool: AttestationPool;
12
+ attestationPool?: T extends P2PClientType.Full ? AttestationPool : undefined;
11
13
  epochProofQuotePool: EpochProofQuotePool;
12
- }
14
+ };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type ClientProtocolCircuitVerifier,
3
3
  type L2BlockSource,
4
+ type P2PClientType,
4
5
  type Tx,
5
6
  type WorldStateSynchronizer,
6
7
  } from '@aztec/circuit-types';
@@ -23,11 +24,11 @@ import { type Libp2p, type Libp2pOptions, createLibp2p } from 'libp2p';
23
24
  import { BootstrapNode } from '../bootstrap/bootstrap.js';
24
25
  import { type BootnodeConfig, type P2PConfig } from '../config.js';
25
26
  import { type MemPools } from '../mem_pools/interface.js';
26
- import { DiscV5Service } from '../service/discV5_service.js';
27
- import { LibP2PService } from '../service/libp2p_service.js';
28
- import { type PeerManager } from '../service/peer_manager.js';
29
- import { type P2PReqRespConfig } from '../service/reqresp/config.js';
30
- import { pingHandler, statusHandler } from '../service/reqresp/handlers.js';
27
+ import { DiscV5Service } from '../services/discv5/discV5_service.js';
28
+ import { LibP2PService } from '../services/libp2p/libp2p_service.js';
29
+ import { type PeerManager } from '../services/peer_manager.js';
30
+ import { type P2PReqRespConfig } from '../services/reqresp/config.js';
31
+ import { pingHandler, statusHandler } from '../services/reqresp/handlers.js';
31
32
  import {
32
33
  PING_PROTOCOL,
33
34
  type ReqRespSubProtocolHandlers,
@@ -35,8 +36,8 @@ import {
35
36
  STATUS_PROTOCOL,
36
37
  TX_REQ_PROTOCOL,
37
38
  noopValidator,
38
- } from '../service/reqresp/interface.js';
39
- import { ReqResp } from '../service/reqresp/reqresp.js';
39
+ } from '../services/reqresp/interface.js';
40
+ import { ReqResp } from '../services/reqresp/reqresp.js';
40
41
  import { type PubSubLibp2p } from '../util.js';
41
42
 
42
43
  /**
@@ -95,11 +96,12 @@ export async function createLibp2pNode(
95
96
  *
96
97
  *
97
98
  */
98
- export async function createTestLibP2PService(
99
+ export async function createTestLibP2PService<T extends P2PClientType>(
100
+ clientType: T,
99
101
  boostrapAddrs: string[] = [],
100
102
  l2BlockSource: L2BlockSource,
101
103
  worldStateSynchronizer: WorldStateSynchronizer,
102
- mempools: MemPools,
104
+ mempools: MemPools<T>,
103
105
  telemetry: TelemetryClient,
104
106
  port: number = 0,
105
107
  peerId?: PeerId,
@@ -123,7 +125,8 @@ export async function createTestLibP2PService(
123
125
  // No bootstrap nodes provided as the libp2p service will register them in the constructor
124
126
  const p2pNode = await createLibp2pNode([], peerId, port, /*enable gossip */ true, /**start */ false);
125
127
 
126
- return new LibP2PService(
128
+ return new LibP2PService<T>(
129
+ clientType,
127
130
  config,
128
131
  p2pNode as PubSubLibp2p,
129
132
  discoveryService,
@@ -8,9 +8,9 @@ import type { PeerId } from '@libp2p/interface';
8
8
  import { type Multiaddr, multiaddr } from '@multiformats/multiaddr';
9
9
  import EventEmitter from 'events';
10
10
 
11
- import type { P2PConfig } from '../config.js';
12
- import { convertToMultiaddr } from '../util.js';
13
- import { type PeerDiscoveryService, PeerDiscoveryState } from './service.js';
11
+ import type { P2PConfig } from '../../config.js';
12
+ import { convertToMultiaddr } from '../../util.js';
13
+ import { type PeerDiscoveryService, PeerDiscoveryState } from '../service.js';
14
14
 
15
15
  export const AZTEC_ENR_KEY = 'aztec_network';
16
16