@aztec/p2p 3.0.0-nightly.20251113 → 3.0.0-nightly.20251115

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 (24) hide show
  1. package/dest/errors/attestation-pool.error.d.ts +7 -0
  2. package/dest/errors/attestation-pool.error.d.ts.map +1 -0
  3. package/dest/errors/attestation-pool.error.js +12 -0
  4. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +21 -0
  5. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  6. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +6 -0
  7. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  8. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +26 -2
  9. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +4 -0
  10. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  11. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +20 -0
  12. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -1
  13. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +13 -1
  14. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  15. package/dest/services/libp2p/libp2p_service.js +50 -4
  16. package/dest/testbench/p2p_client_testbench_worker.js +3 -1
  17. package/package.json +14 -14
  18. package/src/errors/attestation-pool.error.ts +13 -0
  19. package/src/mem_pools/attestation_pool/attestation_pool.ts +23 -0
  20. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +45 -2
  21. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +30 -0
  22. package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +17 -1
  23. package/src/services/libp2p/libp2p_service.ts +46 -4
  24. package/src/testbench/p2p_client_testbench_worker.ts +2 -0
@@ -0,0 +1,7 @@
1
+ export declare class AttestationPoolError extends Error {
2
+ constructor(message?: string);
3
+ }
4
+ export declare class ProposalSlotCapExceededError extends AttestationPoolError {
5
+ constructor(message?: string);
6
+ }
7
+ //# sourceMappingURL=attestation-pool.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestation-pool.error.d.ts","sourceRoot":"","sources":["../../src/errors/attestation-pool.error.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,CAAC,EAAE,MAAM;CAI7B;AAED,qBAAa,4BAA6B,SAAQ,oBAAoB;gBACxD,OAAO,CAAC,EAAE,MAAM;CAI7B"}
@@ -0,0 +1,12 @@
1
+ export class AttestationPoolError extends Error {
2
+ constructor(message){
3
+ super(message);
4
+ this.name = 'AttestationPoolError';
5
+ }
6
+ }
7
+ export class ProposalSlotCapExceededError extends AttestationPoolError {
8
+ constructor(message){
9
+ super(message);
10
+ this.name = 'ProposalSlotCapExceededError';
11
+ }
12
+ }
@@ -89,6 +89,27 @@ export interface AttestationPool {
89
89
  * @return True if the attestation exists, false otherwise
90
90
  */
91
91
  hasAttestation(attestation: BlockAttestation): Promise<boolean>;
92
+ /**
93
+ * Returns whether adding this proposal is permitted at current capacity:
94
+ * - True if the proposal already exists, allow overwrite to keep parity with tests.
95
+ * - True if the slot is below the proposal cap.
96
+ * - False if the slot is at/above cap and this would be a new unique proposal.
97
+ *
98
+ * @param block - The block proposal to check
99
+ * @returns True if the proposal can be added (or already exists), false otherwise.
100
+ */
101
+ canAddProposal(block: BlockProposal): Promise<boolean>;
102
+ /**
103
+ * Returns whether an attestation would be accepted for (slot, proposalId):
104
+ * - True if the attestation already exists for this sender.
105
+ * - True if the attestation cap for (slot, proposalId) has not been reached.
106
+ * - False if the cap is reached and this attestation would be a new unique entry.
107
+ *
108
+ * @param attestation - The attestation to check
109
+ * @param committeeSize - Committee size for the attestation's slot, implementation may add a small buffer
110
+ * @returns True if the attestation can be added, false otherwise.
111
+ */
112
+ canAddAttestation(attestation: BlockAttestation, committeeSize: number): Promise<boolean>;
92
113
  /** Returns whether the pool is empty. */
93
114
  isEmpty(): Promise<boolean>;
94
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"attestation_pool.d.ts","sourceRoot":"","sources":["../../../src/mem_pools/attestation_pool/attestation_pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IAEjE;;;;;;OAMG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzE;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE;;;;OAIG;IACH,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpE;;;;;;OAMG;IACH,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD;;;;;;OAMG;IACH,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvD;;;;;;;OAOG;IACH,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtF;;;;;;;OAOG;IACH,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAElE;;;;;;;;OAQG;IACH,iCAAiC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAEjG;;;;;OAKG;IACH,cAAc,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhE,yCAAyC;IACzC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7B"}
1
+ {"version":3,"file":"attestation_pool.d.ts","sourceRoot":"","sources":["../../../src/mem_pools/attestation_pool/attestation_pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IAEjE;;;;;;OAMG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzE;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE;;;;OAIG;IACH,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpE;;;;;;OAMG;IACH,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD;;;;;;OAMG;IACH,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvD;;;;;;;OAOG;IACH,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtF;;;;;;;OAOG;IACH,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAElE;;;;;;;;OAQG;IACH,iCAAiC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAEjG;;;;;OAKG;IACH,cAAc,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhE;;;;;;;;OAQG;IACH,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1F,yCAAyC;IACzC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7B"}
@@ -2,6 +2,8 @@ import type { AztecAsyncKVStore } from '@aztec/kv-store';
2
2
  import { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
3
3
  import { type TelemetryClient } from '@aztec/telemetry-client';
4
4
  import type { AttestationPool } from './attestation_pool.js';
5
+ export declare const MAX_PROPOSALS_PER_SLOT = 5;
6
+ export declare const ATTESTATION_CAP_BUFFER = 10;
5
7
  export declare class KvAttestationPool implements AttestationPool {
6
8
  private store;
7
9
  private log;
@@ -26,5 +28,9 @@ export declare class KvAttestationPool implements AttestationPool {
26
28
  getBlockProposal(id: string): Promise<BlockProposal | undefined>;
27
29
  hasBlockProposal(idOrProposal: string | BlockProposal): Promise<boolean>;
28
30
  addBlockProposal(blockProposal: BlockProposal): Promise<void>;
31
+ hasReachedProposalCap(slot: bigint): Promise<boolean>;
32
+ hasReachedAttestationCap(slot: bigint, proposalId: string, committeeSize: number): Promise<boolean>;
33
+ canAddProposal(block: BlockProposal): Promise<boolean>;
34
+ canAddAttestation(attestation: BlockAttestation, committeeSize: number): Promise<boolean>;
29
35
  }
30
36
  //# sourceMappingURL=kv_attestation_pool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"kv_attestation_pool.d.ts","sourceRoot":"","sources":["../../../src/mem_pools/attestation_pool/kv_attestation_pool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAqC,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAGnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,qBAAa,iBAAkB,YAAW,eAAe;IAYrD,OAAO,CAAC,KAAK;IAEb,OAAO,CAAC,GAAG;IAbb,OAAO,CAAC,OAAO,CAAwC;IAEvD,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,SAAS,CAGf;IACF,OAAO,CAAC,gBAAgB,CAAqC;IAC7D,OAAO,CAAC,uBAAuB,CAAqC;gBAG1D,KAAK,EAAE,iBAAiB,EAChC,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAAyC;IAUtD,OAAO,CAAC,SAAS,CAIf;IAEW,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAOxC,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,iBAAiB;IAIZ,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqChE,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAYjE,iCAAiC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAsBhG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBtD,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBrF,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BnE,cAAc,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB/D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAahE,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxE,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAM3E"}
1
+ {"version":3,"file":"kv_attestation_pool.d.ts","sourceRoot":"","sources":["../../../src/mem_pools/attestation_pool/kv_attestation_pool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAqC,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAInF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,qBAAa,iBAAkB,YAAW,eAAe;IAYrD,OAAO,CAAC,KAAK;IAEb,OAAO,CAAC,GAAG;IAbb,OAAO,CAAC,OAAO,CAAwC;IAEvD,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,SAAS,CAGf;IACF,OAAO,CAAC,gBAAgB,CAAqC;IAC7D,OAAO,CAAC,uBAAuB,CAAqC;gBAG1D,KAAK,EAAE,iBAAiB,EAChC,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAAyC;IAUtD,OAAO,CAAC,SAAS,CAIf;IAEW,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAOxC,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,iBAAiB;IAIZ,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqChE,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAYjE,iCAAiC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAsBhG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBtD,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBrF,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BnE,cAAc,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB/D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAahE,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxE,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB7D,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMrD,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnG,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAOtD,iBAAiB,CAAC,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUvG"}
@@ -3,7 +3,10 @@ import { toArray } from '@aztec/foundation/iterable';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
4
  import { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
5
5
  import { getTelemetryClient } from '@aztec/telemetry-client';
6
+ import { ProposalSlotCapExceededError } from '../../errors/attestation-pool.error.js';
6
7
  import { PoolInstrumentation, PoolName } from '../instrumentation.js';
8
+ export const MAX_PROPOSALS_PER_SLOT = 5;
9
+ export const ATTESTATION_CAP_BUFFER = 10;
7
10
  export class KvAttestationPool {
8
11
  store;
9
12
  log;
@@ -183,8 +186,29 @@ export class KvAttestationPool {
183
186
  }
184
187
  async addBlockProposal(blockProposal) {
185
188
  await this.store.transactionAsync(async ()=>{
186
- await this.proposalsForSlot.set(blockProposal.slotNumber.toString(), blockProposal.archive.toString());
187
- await this.proposals.set(blockProposal.payload.archive.toString(), blockProposal.toBuffer());
189
+ const slotKey = blockProposal.slotNumber.toString();
190
+ const proposalId = blockProposal.archive.toString();
191
+ if (!await this.canAddProposal(blockProposal)) {
192
+ throw new ProposalSlotCapExceededError(`Maximum proposals per slot reached: slot=${slotKey} cap=${MAX_PROPOSALS_PER_SLOT} proposal=${proposalId}`);
193
+ }
194
+ await this.proposalsForSlot.set(slotKey, proposalId);
195
+ // Always update the stored proposal buffer so re-adds overwrite with latest data
196
+ await this.proposals.set(proposalId, blockProposal.toBuffer());
188
197
  });
189
198
  }
199
+ async hasReachedProposalCap(slot) {
200
+ const slotKey = new Fr(slot).toString();
201
+ const uniqueProposalCount = await this.proposalsForSlot.getValueCountAsync(slotKey);
202
+ return uniqueProposalCount >= MAX_PROPOSALS_PER_SLOT;
203
+ }
204
+ async hasReachedAttestationCap(slot, proposalId, committeeSize) {
205
+ const limit = committeeSize + ATTESTATION_CAP_BUFFER;
206
+ return await this.attestationsForProposal.getValueCountAsync(this.getProposalKey(slot, proposalId)) >= limit;
207
+ }
208
+ async canAddProposal(block) {
209
+ return await this.proposals.hasAsync(block.archive.toString()) || !await this.hasReachedProposalCap(block.slotNumber.toBigInt());
210
+ }
211
+ async canAddAttestation(attestation, committeeSize) {
212
+ return await this.hasAttestation(attestation) || !await this.hasReachedAttestationCap(attestation.payload.header.slotNumber.toBigInt(), attestation.archive.toString(), committeeSize);
213
+ }
190
214
  }
@@ -21,5 +21,9 @@ export declare class InMemoryAttestationPool implements AttestationPool {
21
21
  addBlockProposal(blockProposal: BlockProposal): Promise<void>;
22
22
  getBlockProposal(id: string): Promise<BlockProposal | undefined>;
23
23
  hasBlockProposal(idOrProposal: string | BlockProposal): Promise<boolean>;
24
+ hasReachedProposalCap(slot: bigint): Promise<boolean>;
25
+ hasReachedAttestationCap(slot: bigint, proposalId: string, committeeSize: number): Promise<boolean>;
26
+ canAddProposal(block: BlockProposal): Promise<boolean>;
27
+ canAddAttestation(attestation: BlockAttestation, committeeSize: number): Promise<boolean>;
24
28
  }
25
29
  //# sourceMappingURL=memory_attestation_pool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory_attestation_pool.d.ts","sourceRoot":"","sources":["../../../src/mem_pools/attestation_pool/memory_attestation_pool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAGnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,qBAAa,uBAAwB,YAAW,eAAe;;IAQ3D,OAAO,CAAC,GAAG;IAPb,OAAO,CAAC,OAAO,CAAwC;IAEvD,OAAO,CAAC,YAAY,CAAgG;IACpH,OAAO,CAAC,SAAS,CAA6B;gBAG5C,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAAuC;IAOpD,OAAO,CAAC,SAAS,CAIf;IAEK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAQjE,iCAAiC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAWhG,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA6C1D,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBpE,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBtD,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrF,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBnE,cAAc,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAuB/D,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAU7D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAIhE,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;CAIhF"}
1
+ {"version":3,"file":"memory_attestation_pool.d.ts","sourceRoot":"","sources":["../../../src/mem_pools/attestation_pool/memory_attestation_pool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAGnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,qBAAa,uBAAwB,YAAW,eAAe;;IAQ3D,OAAO,CAAC,GAAG;IAPb,OAAO,CAAC,OAAO,CAAwC;IAEvD,OAAO,CAAC,YAAY,CAAgG;IACpH,OAAO,CAAC,SAAS,CAA6B;gBAG5C,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAAuC;IAOpD,OAAO,CAAC,SAAS,CAIf;IAEK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAQjE,iCAAiC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAWhG,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA6C1D,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBpE,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBtD,oCAAoC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrF,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBnE,cAAc,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAuB/D,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAU7D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAIhE,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxE,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMrD,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM7F,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAMtD,iBAAiB,CAAC,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUvG"}
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from '@aztec/foundation/log';
2
2
  import { getTelemetryClient } from '@aztec/telemetry-client';
3
3
  import { PoolInstrumentation, PoolName } from '../instrumentation.js';
4
+ import { ATTESTATION_CAP_BUFFER, MAX_PROPOSALS_PER_SLOT } from './kv_attestation_pool.js';
4
5
  export class InMemoryAttestationPool {
5
6
  log;
6
7
  metrics;
@@ -168,6 +169,25 @@ export class InMemoryAttestationPool {
168
169
  const id = typeof idOrProposal === 'string' ? idOrProposal : idOrProposal.payload.archive.toString();
169
170
  return Promise.resolve(this.proposals.has(id));
170
171
  }
172
+ hasReachedProposalCap(slot) {
173
+ const slotAttestationMap = this.attestations.get(slot);
174
+ const proposalCount = slotAttestationMap?.size ?? 0;
175
+ return Promise.resolve(proposalCount >= MAX_PROPOSALS_PER_SLOT);
176
+ }
177
+ hasReachedAttestationCap(slot, proposalId, committeeSize) {
178
+ const limit = committeeSize + ATTESTATION_CAP_BUFFER;
179
+ const count = this.attestations.get(slot)?.get(proposalId)?.size ?? 0;
180
+ return Promise.resolve(limit <= 0 || count >= limit);
181
+ }
182
+ async canAddProposal(block) {
183
+ return this.proposals.has(block.archive.toString()) || !await this.hasReachedProposalCap(block.slotNumber.toBigInt());
184
+ }
185
+ async canAddAttestation(attestation, committeeSize) {
186
+ const sender = attestation.getSender();
187
+ const slot = attestation.payload.header.slotNumber.toBigInt();
188
+ const pid = attestation.archive.toString();
189
+ return !!sender && ((this.attestations.get(slot)?.get(pid)?.has(sender.toString()) ?? false) || !await this.hasReachedAttestationCap(slot, pid, committeeSize));
190
+ }
171
191
  }
172
192
  /**
173
193
  * Get Slot or Default
@@ -1 +1 @@
1
- {"version":3,"file":"block_proposal_validator.d.ts","sourceRoot":"","sources":["../../../src/msg_validators/block_proposal_validator/block_proposal_validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7F,qBAAa,sBAAuB,YAAW,YAAY,CAAC,aAAa,CAAC;IACxE,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAU;gBAElB,UAAU,EAAE,mBAAmB,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE;IAMtE,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;CAgE7E"}
1
+ {"version":3,"file":"block_proposal_validator.d.ts","sourceRoot":"","sources":["../../../src/msg_validators/block_proposal_validator/block_proposal_validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7F,qBAAa,sBAAuB,YAAW,YAAY,CAAC,aAAa,CAAC;IACxE,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAU;gBAElB,UAAU,EAAE,mBAAmB,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE;IAMtE,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;CAgF7E"}
@@ -19,10 +19,22 @@ export class BlockProposalValidator {
19
19
  return PeerErrorSeverity.MidToleranceError;
20
20
  }
21
21
  // Check if transactions are permitted when the proposal contains transaction hashes
22
- if (!this.txsPermitted && block.txHashes.length > 0) {
22
+ const embeddedTxCount = block.txs?.length ?? 0;
23
+ if (!this.txsPermitted && (block.txHashes.length > 0 || embeddedTxCount > 0)) {
23
24
  this.logger.debug(`Penalizing peer for block proposal with ${block.txHashes.length} transaction(s) when transactions are not permitted`);
24
25
  return PeerErrorSeverity.MidToleranceError;
25
26
  }
27
+ // If there are embedded txs, they must be listed in txHashes; if there are no txHashes, there must be no txs
28
+ const hashSet = new Set(block.txHashes.map((h)=>h.toString()));
29
+ const missingTxHashes = embeddedTxCount > 0 ? block.txs.filter((tx)=>!hashSet.has(tx.getTxHash().toString())).map((tx)=>tx.getTxHash().toString()) : [];
30
+ if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
31
+ this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
32
+ embeddedTxCount,
33
+ txHashesLength: block.txHashes.length,
34
+ missingTxHashes
35
+ });
36
+ return PeerErrorSeverity.MidToleranceError;
37
+ }
26
38
  const { currentProposer, nextProposer, currentSlot, nextSlot } = await this.epochCache.getProposerAttesterAddressInCurrentOrNextSlot();
27
39
  // Check that the attestation is for the current or next slot
28
40
  const slotNumberBigInt = block.payload.header.slotNumber.toBigInt();
@@ -1 +1 @@
1
- {"version":3,"file":"libp2p_service.d.ts","sourceRoot":"","sources":["../../../src/services/libp2p/libp2p_service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"libp2p_service.d.ts","sourceRoot":"","sources":["../../../src/services/libp2p/libp2p_service.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,7 @@ import { mplex } from '@libp2p/mplex';
30
30
  import { tcp } from '@libp2p/tcp';
31
31
  import { ENR } from '@nethermindeth/enr';
32
32
  import { createLibp2p } from 'libp2p';
33
+ import { ProposalSlotCapExceededError } from '../../errors/attestation-pool.error.js';
33
34
  import { AttestationValidator, BlockProposalValidator, FishermanAttestationValidator } from '../../msg_validators/index.js';
34
35
  import { MessageSeenValidator } from '../../msg_validators/msg_seen_validator/msg_seen_validator.js';
35
36
  import { getDefaultAllowedSetupFunctions } from '../../msg_validators/tx_validator/allowed_public_setup.js';
@@ -584,11 +585,20 @@ import { P2PInstrumentation } from './instrumentation.js';
584
585
  */ async processAttestationFromPeer(payloadData, msgId, source) {
585
586
  const validationFunc = async ()=>{
586
587
  const attestation = BlockAttestation.fromBuffer(payloadData);
588
+ const pool = this.mempools.attestationPool;
587
589
  const isValid = await this.validateAttestation(source, attestation);
588
- const exists = isValid && await this.mempools.attestationPool.hasAttestation(attestation);
590
+ const exists = isValid && await pool.hasAttestation(attestation);
591
+ let canAdd = true;
592
+ if (isValid && !exists) {
593
+ const slot = attestation.payload.header.slotNumber.toBigInt();
594
+ const { committee } = await this.epochCache.getCommittee(slot);
595
+ const committeeSize = committee?.length ?? 0;
596
+ canAdd = await pool.canAddAttestation(attestation, committeeSize);
597
+ }
589
598
  this.logger.trace(`Validate propagated block attestation`, {
590
599
  isValid,
591
600
  exists,
601
+ canAdd,
592
602
  [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber.toString(),
593
603
  [Attributes.P2P_ID]: source.toString()
594
604
  });
@@ -601,6 +611,16 @@ import { P2PInstrumentation } from './instrumentation.js';
601
611
  result: TopicValidatorResult.Ignore,
602
612
  obj: attestation
603
613
  };
614
+ } else if (!canAdd) {
615
+ this.logger.warn(`Dropping block attestation due to per-(slot, proposalId) attestation cap`, {
616
+ slot: attestation.payload.header.slotNumber.toString(),
617
+ archive: attestation.archive.toString(),
618
+ source: source.toString()
619
+ });
620
+ return {
621
+ result: TopicValidatorResult.Ignore,
622
+ obj: attestation
623
+ };
604
624
  } else {
605
625
  return {
606
626
  result: TopicValidatorResult.Accept,
@@ -626,12 +646,15 @@ import { P2PInstrumentation } from './instrumentation.js';
626
646
  const validationFunc = async ()=>{
627
647
  const block = BlockProposal.fromBuffer(payloadData);
628
648
  const isValid = await this.validateBlockProposal(source, block);
649
+ const pool = this.mempools.attestationPool;
629
650
  // Note that we dont have an attestation pool if we're a prover node, but we still
630
651
  // subscribe to block proposal topics in order to prevent their txs from being cleared.
631
- const exists = isValid && await this.mempools.attestationPool?.hasBlockProposal(block);
652
+ const exists = isValid && await pool?.hasBlockProposal(block);
653
+ const canAdd = isValid && await pool?.canAddProposal(block);
632
654
  this.logger.trace(`Validate propagated block proposal`, {
633
655
  isValid,
634
656
  exists,
657
+ canAdd,
635
658
  [Attributes.SLOT_NUMBER]: block.payload.header.slotNumber.toString(),
636
659
  [Attributes.P2P_ID]: source.toString()
637
660
  });
@@ -644,6 +667,16 @@ import { P2PInstrumentation } from './instrumentation.js';
644
667
  result: TopicValidatorResult.Ignore,
645
668
  obj: block
646
669
  };
670
+ } else if (!canAdd) {
671
+ this.peerManager.penalizePeer(source, PeerErrorSeverity.MidToleranceError);
672
+ this.logger.warn(`Penalizing peer for block proposal exceeding per-slot cap`, {
673
+ slot: block.slotNumber.toString(),
674
+ archive: block.archive.toString(),
675
+ source: source.toString()
676
+ });
677
+ return {
678
+ result: TopicValidatorResult.Reject
679
+ };
647
680
  } else {
648
681
  return {
649
682
  result: TopicValidatorResult.Accept,
@@ -671,9 +704,22 @@ import { P2PInstrumentation } from './instrumentation.js';
671
704
  if (attestationsForPreviousSlot !== undefined) {
672
705
  this.logger.verbose(`Received ${attestationsForPreviousSlot.length} attestations for slot ${previousSlot}`);
673
706
  }
674
- // Mark the txs in this proposal as non-evictable
707
+ // Attempt to add proposal, then mark the txs in this proposal as non-evictable
708
+ try {
709
+ await this.mempools.attestationPool?.addBlockProposal(block);
710
+ } catch (err) {
711
+ // Drop proposals if we hit per-slot cap in the attestation pool; rethrow unknown errors
712
+ if (err instanceof ProposalSlotCapExceededError) {
713
+ this.logger.warn(`Dropping block proposal due to per-slot proposal cap`, {
714
+ slot: slot.toString(),
715
+ archive: block.archive.toString(),
716
+ error: err.message
717
+ });
718
+ return;
719
+ }
720
+ throw err;
721
+ }
675
722
  await this.mempools.txPool.markTxsAsNonEvictable(block.txHashes);
676
- await this.mempools.attestationPool?.addBlockProposal(block);
677
723
  const attestations = await this.blockReceivedCallback(block, sender);
678
724
  // TODO: fix up this pattern - the abstraction is not nice
679
725
  // The attestation can be undefined if no handler is registered / the validator deems the block invalid / in fisherman mode
@@ -54,7 +54,9 @@ function mockAttestationPool() {
54
54
  addBlockProposal: ()=>Promise.resolve(),
55
55
  getBlockProposal: ()=>Promise.resolve(undefined),
56
56
  hasBlockProposal: ()=>Promise.resolve(false),
57
- hasAttestation: ()=>Promise.resolve(false)
57
+ hasAttestation: ()=>Promise.resolve(false),
58
+ canAddProposal: ()=>Promise.resolve(true),
59
+ canAddAttestation: ()=>Promise.resolve(true)
58
60
  };
59
61
  }
60
62
  function mockEpochCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/p2p",
3
- "version": "3.0.0-nightly.20251113",
3
+ "version": "3.0.0-nightly.20251115",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -67,17 +67,17 @@
67
67
  ]
68
68
  },
69
69
  "dependencies": {
70
- "@aztec/constants": "3.0.0-nightly.20251113",
71
- "@aztec/epoch-cache": "3.0.0-nightly.20251113",
72
- "@aztec/ethereum": "3.0.0-nightly.20251113",
73
- "@aztec/foundation": "3.0.0-nightly.20251113",
74
- "@aztec/kv-store": "3.0.0-nightly.20251113",
75
- "@aztec/noir-contracts.js": "3.0.0-nightly.20251113",
76
- "@aztec/noir-protocol-circuits-types": "3.0.0-nightly.20251113",
77
- "@aztec/protocol-contracts": "3.0.0-nightly.20251113",
78
- "@aztec/simulator": "3.0.0-nightly.20251113",
79
- "@aztec/stdlib": "3.0.0-nightly.20251113",
80
- "@aztec/telemetry-client": "3.0.0-nightly.20251113",
70
+ "@aztec/constants": "3.0.0-nightly.20251115",
71
+ "@aztec/epoch-cache": "3.0.0-nightly.20251115",
72
+ "@aztec/ethereum": "3.0.0-nightly.20251115",
73
+ "@aztec/foundation": "3.0.0-nightly.20251115",
74
+ "@aztec/kv-store": "3.0.0-nightly.20251115",
75
+ "@aztec/noir-contracts.js": "3.0.0-nightly.20251115",
76
+ "@aztec/noir-protocol-circuits-types": "3.0.0-nightly.20251115",
77
+ "@aztec/protocol-contracts": "3.0.0-nightly.20251115",
78
+ "@aztec/simulator": "3.0.0-nightly.20251115",
79
+ "@aztec/stdlib": "3.0.0-nightly.20251115",
80
+ "@aztec/telemetry-client": "3.0.0-nightly.20251115",
81
81
  "@chainsafe/libp2p-gossipsub": "13.0.0",
82
82
  "@chainsafe/libp2p-noise": "^15.0.0",
83
83
  "@chainsafe/libp2p-yamux": "^6.0.2",
@@ -104,8 +104,8 @@
104
104
  "xxhash-wasm": "^1.1.0"
105
105
  },
106
106
  "devDependencies": {
107
- "@aztec/archiver": "3.0.0-nightly.20251113",
108
- "@aztec/world-state": "3.0.0-nightly.20251113",
107
+ "@aztec/archiver": "3.0.0-nightly.20251115",
108
+ "@aztec/world-state": "3.0.0-nightly.20251115",
109
109
  "@jest/globals": "^30.0.0",
110
110
  "@types/jest": "^30.0.0",
111
111
  "@types/node": "^22.15.17",
@@ -0,0 +1,13 @@
1
+ export class AttestationPoolError extends Error {
2
+ constructor(message?: string) {
3
+ super(message);
4
+ this.name = 'AttestationPoolError';
5
+ }
6
+ }
7
+
8
+ export class ProposalSlotCapExceededError extends AttestationPoolError {
9
+ constructor(message?: string) {
10
+ super(message);
11
+ this.name = 'ProposalSlotCapExceededError';
12
+ }
13
+ }
@@ -101,6 +101,29 @@ export interface AttestationPool {
101
101
  */
102
102
  hasAttestation(attestation: BlockAttestation): Promise<boolean>;
103
103
 
104
+ /**
105
+ * Returns whether adding this proposal is permitted at current capacity:
106
+ * - True if the proposal already exists, allow overwrite to keep parity with tests.
107
+ * - True if the slot is below the proposal cap.
108
+ * - False if the slot is at/above cap and this would be a new unique proposal.
109
+ *
110
+ * @param block - The block proposal to check
111
+ * @returns True if the proposal can be added (or already exists), false otherwise.
112
+ */
113
+ canAddProposal(block: BlockProposal): Promise<boolean>;
114
+
115
+ /**
116
+ * Returns whether an attestation would be accepted for (slot, proposalId):
117
+ * - True if the attestation already exists for this sender.
118
+ * - True if the attestation cap for (slot, proposalId) has not been reached.
119
+ * - False if the cap is reached and this attestation would be a new unique entry.
120
+ *
121
+ * @param attestation - The attestation to check
122
+ * @param committeeSize - Committee size for the attestation's slot, implementation may add a small buffer
123
+ * @returns True if the attestation can be added, false otherwise.
124
+ */
125
+ canAddAttestation(attestation: BlockAttestation, committeeSize: number): Promise<boolean>;
126
+
104
127
  /** Returns whether the pool is empty. */
105
128
  isEmpty(): Promise<boolean>;
106
129
  }
@@ -5,9 +5,13 @@ import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@azte
5
5
  import { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
6
6
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
7
7
 
8
+ import { ProposalSlotCapExceededError } from '../../errors/attestation-pool.error.js';
8
9
  import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instrumentation.js';
9
10
  import type { AttestationPool } from './attestation_pool.js';
10
11
 
12
+ export const MAX_PROPOSALS_PER_SLOT = 5;
13
+ export const ATTESTATION_CAP_BUFFER = 10;
14
+
11
15
  export class KvAttestationPool implements AttestationPool {
12
16
  private metrics: PoolInstrumentation<BlockAttestation>;
13
17
 
@@ -249,8 +253,47 @@ export class KvAttestationPool implements AttestationPool {
249
253
 
250
254
  public async addBlockProposal(blockProposal: BlockProposal): Promise<void> {
251
255
  await this.store.transactionAsync(async () => {
252
- await this.proposalsForSlot.set(blockProposal.slotNumber.toString(), blockProposal.archive.toString());
253
- await this.proposals.set(blockProposal.payload.archive.toString(), blockProposal.toBuffer());
256
+ const slotKey = blockProposal.slotNumber.toString();
257
+ const proposalId = blockProposal.archive.toString();
258
+
259
+ if (!(await this.canAddProposal(blockProposal))) {
260
+ throw new ProposalSlotCapExceededError(
261
+ `Maximum proposals per slot reached: slot=${slotKey} cap=${MAX_PROPOSALS_PER_SLOT} proposal=${proposalId}`,
262
+ );
263
+ }
264
+
265
+ await this.proposalsForSlot.set(slotKey, proposalId);
266
+ // Always update the stored proposal buffer so re-adds overwrite with latest data
267
+ await this.proposals.set(proposalId, blockProposal.toBuffer());
254
268
  });
255
269
  }
270
+
271
+ public async hasReachedProposalCap(slot: bigint): Promise<boolean> {
272
+ const slotKey = new Fr(slot).toString();
273
+ const uniqueProposalCount = await this.proposalsForSlot.getValueCountAsync(slotKey);
274
+ return uniqueProposalCount >= MAX_PROPOSALS_PER_SLOT;
275
+ }
276
+
277
+ public async hasReachedAttestationCap(slot: bigint, proposalId: string, committeeSize: number): Promise<boolean> {
278
+ const limit = committeeSize + ATTESTATION_CAP_BUFFER;
279
+ return (await this.attestationsForProposal.getValueCountAsync(this.getProposalKey(slot, proposalId))) >= limit;
280
+ }
281
+
282
+ public async canAddProposal(block: BlockProposal): Promise<boolean> {
283
+ return (
284
+ (await this.proposals.hasAsync(block.archive.toString())) ||
285
+ !(await this.hasReachedProposalCap(block.slotNumber.toBigInt()))
286
+ );
287
+ }
288
+
289
+ public async canAddAttestation(attestation: BlockAttestation, committeeSize: number): Promise<boolean> {
290
+ return (
291
+ (await this.hasAttestation(attestation)) ||
292
+ !(await this.hasReachedAttestationCap(
293
+ attestation.payload.header.slotNumber.toBigInt(),
294
+ attestation.archive.toString(),
295
+ committeeSize,
296
+ ))
297
+ );
298
+ }
256
299
  }
@@ -4,6 +4,7 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
4
4
 
5
5
  import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instrumentation.js';
6
6
  import type { AttestationPool } from './attestation_pool.js';
7
+ import { ATTESTATION_CAP_BUFFER, MAX_PROPOSALS_PER_SLOT } from './kv_attestation_pool.js';
7
8
 
8
9
  export class InMemoryAttestationPool implements AttestationPool {
9
10
  private metrics: PoolInstrumentation<BlockAttestation>;
@@ -214,6 +215,35 @@ export class InMemoryAttestationPool implements AttestationPool {
214
215
  const id = typeof idOrProposal === 'string' ? idOrProposal : idOrProposal.payload.archive.toString();
215
216
  return Promise.resolve(this.proposals.has(id));
216
217
  }
218
+
219
+ public hasReachedProposalCap(slot: bigint): Promise<boolean> {
220
+ const slotAttestationMap = this.attestations.get(slot);
221
+ const proposalCount = slotAttestationMap?.size ?? 0;
222
+ return Promise.resolve(proposalCount >= MAX_PROPOSALS_PER_SLOT);
223
+ }
224
+
225
+ public hasReachedAttestationCap(slot: bigint, proposalId: string, committeeSize: number): Promise<boolean> {
226
+ const limit = committeeSize + ATTESTATION_CAP_BUFFER;
227
+ const count = this.attestations.get(slot)?.get(proposalId)?.size ?? 0;
228
+ return Promise.resolve(limit <= 0 || count >= limit);
229
+ }
230
+
231
+ public async canAddProposal(block: BlockProposal): Promise<boolean> {
232
+ return (
233
+ this.proposals.has(block.archive.toString()) || !(await this.hasReachedProposalCap(block.slotNumber.toBigInt()))
234
+ );
235
+ }
236
+
237
+ public async canAddAttestation(attestation: BlockAttestation, committeeSize: number): Promise<boolean> {
238
+ const sender = attestation.getSender();
239
+ const slot = attestation.payload.header.slotNumber.toBigInt();
240
+ const pid = attestation.archive.toString();
241
+ return (
242
+ !!sender &&
243
+ ((this.attestations.get(slot)?.get(pid)?.has(sender.toString()) ?? false) ||
244
+ !(await this.hasReachedAttestationCap(slot, pid, committeeSize)))
245
+ );
246
+ }
217
247
  }
218
248
 
219
249
  /**
@@ -24,13 +24,29 @@ export class BlockProposalValidator implements P2PValidator<BlockProposal> {
24
24
  }
25
25
 
26
26
  // Check if transactions are permitted when the proposal contains transaction hashes
27
- if (!this.txsPermitted && block.txHashes.length > 0) {
27
+ const embeddedTxCount = block.txs?.length ?? 0;
28
+ if (!this.txsPermitted && (block.txHashes.length > 0 || embeddedTxCount > 0)) {
28
29
  this.logger.debug(
29
30
  `Penalizing peer for block proposal with ${block.txHashes.length} transaction(s) when transactions are not permitted`,
30
31
  );
31
32
  return PeerErrorSeverity.MidToleranceError;
32
33
  }
33
34
 
35
+ // If there are embedded txs, they must be listed in txHashes; if there are no txHashes, there must be no txs
36
+ const hashSet = new Set(block.txHashes.map(h => h.toString()));
37
+ const missingTxHashes =
38
+ embeddedTxCount > 0
39
+ ? block.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
40
+ : [];
41
+ if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
42
+ this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
43
+ embeddedTxCount,
44
+ txHashesLength: block.txHashes.length,
45
+ missingTxHashes,
46
+ });
47
+ return PeerErrorSeverity.MidToleranceError;
48
+ }
49
+
34
50
  const { currentProposer, nextProposer, currentSlot, nextSlot } =
35
51
  await this.epochCache.getProposerAttesterAddressInCurrentOrNextSlot();
36
52
 
@@ -50,6 +50,7 @@ import { ENR } from '@nethermindeth/enr';
50
50
  import { createLibp2p } from 'libp2p';
51
51
 
52
52
  import type { P2PConfig } from '../../config.js';
53
+ import { ProposalSlotCapExceededError } from '../../errors/attestation-pool.error.js';
53
54
  import type { MemPools } from '../../mem_pools/interface.js';
54
55
  import {
55
56
  AttestationValidator,
@@ -800,12 +801,22 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
800
801
  private async processAttestationFromPeer(payloadData: Buffer, msgId: string, source: PeerId): Promise<void> {
801
802
  const validationFunc: () => Promise<ReceivedMessageValidationResult<BlockAttestation>> = async () => {
802
803
  const attestation = BlockAttestation.fromBuffer(payloadData);
804
+ const pool = this.mempools.attestationPool!;
803
805
  const isValid = await this.validateAttestation(source, attestation);
804
- const exists = isValid && (await this.mempools.attestationPool!.hasAttestation(attestation));
806
+ const exists = isValid && (await pool.hasAttestation(attestation));
807
+
808
+ let canAdd = true;
809
+ if (isValid && !exists) {
810
+ const slot = attestation.payload.header.slotNumber.toBigInt();
811
+ const { committee } = await this.epochCache.getCommittee(slot);
812
+ const committeeSize = committee?.length ?? 0;
813
+ canAdd = await pool.canAddAttestation(attestation, committeeSize);
814
+ }
805
815
 
806
816
  this.logger.trace(`Validate propagated block attestation`, {
807
817
  isValid,
808
818
  exists,
819
+ canAdd,
809
820
  [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber.toString(),
810
821
  [Attributes.P2P_ID]: source.toString(),
811
822
  });
@@ -814,6 +825,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
814
825
  return { result: TopicValidatorResult.Reject };
815
826
  } else if (exists) {
816
827
  return { result: TopicValidatorResult.Ignore, obj: attestation };
828
+ } else if (!canAdd) {
829
+ this.logger.warn(`Dropping block attestation due to per-(slot, proposalId) attestation cap`, {
830
+ slot: attestation.payload.header.slotNumber.toString(),
831
+ archive: attestation.archive.toString(),
832
+ source: source.toString(),
833
+ });
834
+ return { result: TopicValidatorResult.Ignore, obj: attestation };
817
835
  } else {
818
836
  return { result: TopicValidatorResult.Accept, obj: attestation };
819
837
  }
@@ -847,14 +865,17 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
847
865
  const validationFunc: () => Promise<ReceivedMessageValidationResult<BlockProposal>> = async () => {
848
866
  const block = BlockProposal.fromBuffer(payloadData);
849
867
  const isValid = await this.validateBlockProposal(source, block);
868
+ const pool = this.mempools.attestationPool;
850
869
 
851
870
  // Note that we dont have an attestation pool if we're a prover node, but we still
852
871
  // subscribe to block proposal topics in order to prevent their txs from being cleared.
853
- const exists = isValid && (await this.mempools.attestationPool?.hasBlockProposal(block));
872
+ const exists = isValid && (await pool?.hasBlockProposal(block));
873
+ const canAdd = isValid && (await pool?.canAddProposal(block));
854
874
 
855
875
  this.logger.trace(`Validate propagated block proposal`, {
856
876
  isValid,
857
877
  exists,
878
+ canAdd,
858
879
  [Attributes.SLOT_NUMBER]: block.payload.header.slotNumber.toString(),
859
880
  [Attributes.P2P_ID]: source.toString(),
860
881
  });
@@ -863,6 +884,14 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
863
884
  return { result: TopicValidatorResult.Reject };
864
885
  } else if (exists) {
865
886
  return { result: TopicValidatorResult.Ignore, obj: block };
887
+ } else if (!canAdd) {
888
+ this.peerManager.penalizePeer(source, PeerErrorSeverity.MidToleranceError);
889
+ this.logger.warn(`Penalizing peer for block proposal exceeding per-slot cap`, {
890
+ slot: block.slotNumber.toString(),
891
+ archive: block.archive.toString(),
892
+ source: source.toString(),
893
+ });
894
+ return { result: TopicValidatorResult.Reject };
866
895
  } else {
867
896
  return { result: TopicValidatorResult.Accept, obj: block };
868
897
  }
@@ -902,9 +931,22 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
902
931
  this.logger.verbose(`Received ${attestationsForPreviousSlot.length} attestations for slot ${previousSlot}`);
903
932
  }
904
933
 
905
- // Mark the txs in this proposal as non-evictable
934
+ // Attempt to add proposal, then mark the txs in this proposal as non-evictable
935
+ try {
936
+ await this.mempools.attestationPool?.addBlockProposal(block);
937
+ } catch (err: unknown) {
938
+ // Drop proposals if we hit per-slot cap in the attestation pool; rethrow unknown errors
939
+ if (err instanceof ProposalSlotCapExceededError) {
940
+ this.logger.warn(`Dropping block proposal due to per-slot proposal cap`, {
941
+ slot: slot.toString(),
942
+ archive: block.archive.toString(),
943
+ error: (err as Error).message,
944
+ });
945
+ return;
946
+ }
947
+ throw err;
948
+ }
906
949
  await this.mempools.txPool.markTxsAsNonEvictable(block.txHashes);
907
- await this.mempools.attestationPool?.addBlockProposal(block);
908
950
  const attestations = await this.blockReceivedCallback(block, sender);
909
951
 
910
952
  // TODO: fix up this pattern - the abstraction is not nice
@@ -74,6 +74,8 @@ function mockAttestationPool(): AttestationPool {
74
74
  getBlockProposal: () => Promise.resolve(undefined),
75
75
  hasBlockProposal: () => Promise.resolve(false),
76
76
  hasAttestation: () => Promise.resolve(false),
77
+ canAddProposal: () => Promise.resolve(true),
78
+ canAddAttestation: () => Promise.resolve(true),
77
79
  };
78
80
  }
79
81