@aztec/p2p 4.0.0-nightly.20260112 → 4.0.0-nightly.20260114

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 (98) hide show
  1. package/dest/client/interface.d.ts +18 -5
  2. package/dest/client/interface.d.ts.map +1 -1
  3. package/dest/client/p2p_client.d.ts +9 -12
  4. package/dest/client/p2p_client.d.ts.map +1 -1
  5. package/dest/client/p2p_client.js +59 -103
  6. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +61 -42
  7. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  8. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  9. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  10. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +225 -262
  11. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +21 -18
  12. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +113 -108
  14. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +17 -16
  15. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  16. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +89 -128
  17. package/dest/mem_pools/attestation_pool/mocks.d.ts +7 -6
  18. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  19. package/dest/mem_pools/attestation_pool/mocks.js +9 -8
  20. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +4 -4
  21. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  22. package/dest/msg_validators/attestation_validator/attestation_validator.js +12 -10
  23. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +5 -5
  24. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  25. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +5 -5
  26. package/dest/msg_validators/index.d.ts +2 -2
  27. package/dest/msg_validators/index.d.ts.map +1 -1
  28. package/dest/msg_validators/index.js +1 -1
  29. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +9 -0
  30. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -0
  31. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +6 -0
  32. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +9 -0
  33. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -0
  34. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +6 -0
  35. package/dest/msg_validators/proposal_validator/index.d.ts +4 -0
  36. package/dest/msg_validators/proposal_validator/index.d.ts.map +1 -0
  37. package/dest/msg_validators/proposal_validator/index.js +3 -0
  38. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -0
  39. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -0
  40. package/dest/msg_validators/{block_proposal_validator/block_proposal_validator.js → proposal_validator/proposal_validator.js} +19 -21
  41. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +23 -0
  42. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +1 -0
  43. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +183 -0
  44. package/dest/services/dummy_service.d.ts +6 -2
  45. package/dest/services/dummy_service.d.ts.map +1 -1
  46. package/dest/services/dummy_service.js +3 -0
  47. package/dest/services/encoding.d.ts +1 -1
  48. package/dest/services/encoding.d.ts.map +1 -1
  49. package/dest/services/encoding.js +4 -2
  50. package/dest/services/libp2p/libp2p_service.d.ts +26 -10
  51. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  52. package/dest/services/libp2p/libp2p_service.js +218 -88
  53. package/dest/services/reqresp/constants.d.ts +12 -0
  54. package/dest/services/reqresp/constants.d.ts.map +1 -0
  55. package/dest/services/reqresp/constants.js +7 -0
  56. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +1 -1
  57. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
  58. package/dest/services/reqresp/protocols/block_txs/bitvector.js +7 -0
  59. package/dest/services/reqresp/protocols/status.d.ts +1 -1
  60. package/dest/services/reqresp/protocols/status.d.ts.map +1 -1
  61. package/dest/services/reqresp/protocols/status.js +2 -1
  62. package/dest/services/service.d.ts +16 -3
  63. package/dest/services/service.d.ts.map +1 -1
  64. package/dest/testbench/p2p_client_testbench_worker.js +25 -11
  65. package/dest/testbench/worker_client_manager.d.ts +1 -1
  66. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  67. package/dest/testbench/worker_client_manager.js +6 -1
  68. package/package.json +14 -14
  69. package/src/client/interface.ts +19 -4
  70. package/src/client/p2p_client.ts +69 -110
  71. package/src/mem_pools/attestation_pool/attestation_pool.ts +68 -41
  72. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +231 -287
  73. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +162 -140
  74. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +141 -164
  75. package/src/mem_pools/attestation_pool/mocks.ts +13 -9
  76. package/src/msg_validators/attestation_validator/attestation_validator.ts +16 -13
  77. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +7 -7
  78. package/src/msg_validators/index.ts +1 -1
  79. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +10 -0
  80. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +13 -0
  81. package/src/msg_validators/proposal_validator/index.ts +3 -0
  82. package/src/msg_validators/{block_proposal_validator/block_proposal_validator.ts → proposal_validator/proposal_validator.ts} +23 -28
  83. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +206 -0
  84. package/src/services/dummy_service.ts +6 -0
  85. package/src/services/encoding.ts +3 -1
  86. package/src/services/libp2p/libp2p_service.ts +258 -94
  87. package/src/services/reqresp/constants.ts +14 -0
  88. package/src/services/reqresp/protocols/block_txs/bitvector.ts +9 -0
  89. package/src/services/reqresp/protocols/status.ts +5 -3
  90. package/src/services/service.ts +19 -4
  91. package/src/testbench/p2p_client_testbench_worker.ts +34 -11
  92. package/src/testbench/worker_client_manager.ts +6 -1
  93. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +0 -12
  94. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +0 -1
  95. package/dest/msg_validators/block_proposal_validator/index.d.ts +0 -2
  96. package/dest/msg_validators/block_proposal_validator/index.d.ts.map +0 -1
  97. package/dest/msg_validators/block_proposal_validator/index.js +0 -1
  98. package/src/msg_validators/block_proposal_validator/index.ts +0 -1
@@ -1,22 +1,12 @@
1
1
  import { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
- import type { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
5
- import {
6
- BlockProposal as BlockProposalClass,
7
- ConsensusPayload,
8
- SignatureDomainSeparator,
9
- getHashedSignaturePayloadEthSignedMessage,
10
- } from '@aztec/stdlib/p2p';
11
- import { makeL2BlockHeader } from '@aztec/stdlib/testing';
12
- import { TxHash } from '@aztec/stdlib/tx';
13
-
14
- import { jest } from '@jest/globals';
15
- import { type MockProxy, mock } from 'jest-mock-extended';
16
-
17
- import type { PoolInstrumentation } from '../instrumentation.js';
4
+ import type { BlockProposal, CheckpointAttestation, CheckpointProposal } from '@aztec/stdlib/p2p';
5
+ import { makeBlockProposal, makeCheckpointProposal, makeL2BlockHeader } from '@aztec/stdlib/testing';
6
+
18
7
  import type { AttestationPool } from './attestation_pool.js';
19
- import { mockAttestation } from './mocks.js';
8
+ import { MAX_PROPOSALS_PER_SLOT } from './kv_attestation_pool.js';
9
+ import { mockCheckpointAttestation } from './mocks.js';
20
10
 
21
11
  const NUMBER_OF_SIGNERS_PER_TEST = 4;
22
12
 
@@ -24,273 +14,182 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
24
14
  let ap: AttestationPool;
25
15
  let signers: Secp256k1Signer[];
26
16
 
27
- // Check that metrics are recorded correctly
28
- let metricsMock: MockProxy<PoolInstrumentation<BlockAttestation>>;
29
-
30
17
  beforeEach(() => {
31
18
  ap = getAttestationPool();
32
19
  signers = Array.from({ length: NUMBER_OF_SIGNERS_PER_TEST }, () => Secp256k1Signer.random());
33
-
34
- metricsMock = mock<PoolInstrumentation<BlockAttestation>>();
35
- // Can i overwrite this like this??
36
- (ap as any).metrics = metricsMock;
37
20
  });
38
21
 
39
- const createAttestationsForSlot = (slotNumber: number) => {
40
- const archive = Fr.random();
41
- return signers.map(signer => mockAttestation(signer, slotNumber, archive));
22
+ const createCheckpointAttestationsForSlot = (slotNumber: number, archive?: Fr) => {
23
+ const archiveToUse = archive ?? Fr.random();
24
+ return signers.map(signer => mockCheckpointAttestation(signer, slotNumber, archiveToUse));
42
25
  };
43
26
 
44
- const mockBlockProposal = (signer: Secp256k1Signer, slotNumber: number, archive: Fr = Fr.random()): BlockProposal => {
27
+ const mockBlockProposalForPool = (
28
+ signer: Secp256k1Signer,
29
+ slotNumber: number,
30
+ archive: Fr = Fr.random(),
31
+ ): Promise<BlockProposal> => {
45
32
  const header = makeL2BlockHeader(1, 2, slotNumber);
46
- const payload = new ConsensusPayload(header.toCheckpointHeader(), archive);
47
-
48
- const hash = getHashedSignaturePayloadEthSignedMessage(payload, SignatureDomainSeparator.blockProposal);
49
- const signature = signer.sign(hash);
50
-
51
- const txHashes = [TxHash.random(), TxHash.random()]; // Mock tx hashes
52
-
53
- return new BlockProposalClass(payload, signature, txHashes);
33
+ return makeBlockProposal({
34
+ signer,
35
+ blockHeader: header,
36
+ archiveRoot: archive,
37
+ });
54
38
  };
55
39
 
56
- // We compare buffers as the objects can have cached values attached to them which are not serialised
57
- // using array containing as the kv store does not respect insertion order
58
- const compareAttestations = (a1: BlockAttestation[], a2: BlockAttestation[]) => {
40
+ // Compare checkpoint attestations buffers
41
+ // Using array containing as the kv store does not respect insertion order
42
+ const compareCheckpointAttestations = (a1: CheckpointAttestation[], a2: CheckpointAttestation[]) => {
59
43
  const a1Buffer = a1.map(attestation => attestation.toBuffer());
60
44
  const a2Buffer = a2.map(attestation => attestation.toBuffer());
61
45
  expect(a1Buffer.length).toBe(a2Buffer.length);
62
46
  expect(a1Buffer).toEqual(expect.arrayContaining(a2Buffer));
63
47
  };
64
48
 
65
- it('should add attestations to pool', async () => {
66
- const slotNumber = 420;
67
- const archive = Fr.random();
68
- const attestations = signers.slice(0, -1).map(signer => mockAttestation(signer, slotNumber, archive));
69
-
70
- await ap.addAttestations(attestations);
71
-
72
- const retrievedAttestations = await ap.getAttestationsForSlotAndProposal(
73
- SlotNumber(slotNumber),
74
- archive.toString(),
75
- );
76
- expect(retrievedAttestations.length).toBe(attestations.length);
77
- compareAttestations(retrievedAttestations, attestations);
78
-
79
- // Check hasAttestation for added attestations
80
- for (const attestation of attestations) {
81
- expect(await ap.hasAttestation(attestation)).toBe(true);
82
- }
83
-
84
- const retrievedAttestationsForSlot = await ap.getAttestationsForSlot(SlotNumber(slotNumber));
85
- expect(retrievedAttestationsForSlot.length).toBe(attestations.length);
86
- compareAttestations(retrievedAttestationsForSlot, attestations);
87
-
88
- // Add another one
89
- const newAttestation = mockAttestation(signers[NUMBER_OF_SIGNERS_PER_TEST - 1], slotNumber, archive);
90
- await ap.addAttestations([newAttestation]);
91
- const retrievedAttestationsAfterAdd = await ap.getAttestationsForSlotAndProposal(
92
- SlotNumber(slotNumber),
93
- archive.toString(),
94
- );
95
- expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1);
96
- compareAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]);
97
- expect(await ap.hasAttestation(newAttestation)).toBe(true);
98
- const retrievedAttestationsForSlotAfterAdd = await ap.getAttestationsForSlot(SlotNumber(slotNumber));
99
- expect(retrievedAttestationsForSlotAfterAdd.length).toBe(attestations.length + 1);
100
- compareAttestations(retrievedAttestationsForSlotAfterAdd, [...attestations, newAttestation]);
101
-
102
- // Delete by slot
103
- await ap.deleteAttestationsForSlot(SlotNumber(slotNumber));
104
-
105
- const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(
106
- SlotNumber(slotNumber),
107
- archive.toString(),
108
- );
109
- expect(retreivedAttestationsAfterDelete.length).toBe(0);
110
- // Check hasAttestation after deletion
111
- for (const attestation of attestations) {
112
- expect(await ap.hasAttestation(attestation)).toBe(false);
113
- }
114
- expect(await ap.hasAttestation(newAttestation)).toBe(false);
115
- });
116
-
117
- it('should handle duplicate proposals in a slot', async () => {
118
- const slotNumber = 420;
119
- const archive = Fr.random();
120
-
121
- // Use the same signer for all attestations
122
- const attestations: BlockAttestation[] = [];
123
- const signer = signers[0];
124
- for (let i = 0; i < NUMBER_OF_SIGNERS_PER_TEST; i++) {
125
- attestations.push(mockAttestation(signer, slotNumber, archive));
126
- }
127
-
128
- // Add them to store and check we end up with only one
129
- await ap.addAttestations(attestations);
130
-
131
- const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(
132
- SlotNumber(slotNumber),
133
- archive.toString(),
134
- );
135
- expect(retreivedAttestations.length).toBe(1);
136
- expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer());
137
- expect(retreivedAttestations[0].getSender()?.toString()).toEqual(signer.address.toString());
138
-
139
- // Try adding them on another operation and check they are still not duplicated
140
- await ap.addAttestations([attestations[0]]);
141
- expect(await ap.getAttestationsForSlotAndProposal(SlotNumber(slotNumber), archive.toString())).toHaveLength(1);
142
- });
49
+ describe('CheckpointAttestation', () => {
50
+ it('should add attestations to pool', async () => {
51
+ const slotNumber = 420;
52
+ const archive = Fr.random();
53
+ const attestations = signers.slice(0, -1).map(signer => mockCheckpointAttestation(signer, slotNumber, archive));
143
54
 
144
- it('should store attestations by differing slot', async () => {
145
- const slotNumbers = [1, 2, 3, 4];
146
- const attestations = signers.map((signer, i) => mockAttestation(signer, slotNumbers[i]));
55
+ await ap.addCheckpointAttestations(attestations);
147
56
 
148
- await ap.addAttestations(attestations);
57
+ const retrievedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(
58
+ SlotNumber(slotNumber),
59
+ archive.toString(),
60
+ );
61
+ expect(retrievedAttestations.length).toBe(attestations.length);
62
+ compareCheckpointAttestations(retrievedAttestations, attestations);
149
63
 
150
- for (const attestation of attestations) {
151
- const slot = attestation.payload.header.slotNumber;
152
- const archive = attestation.archive.toString();
64
+ // Check hasCheckpointAttestation for added attestations
65
+ for (const attestation of attestations) {
66
+ expect(await ap.hasCheckpointAttestation(attestation)).toBe(true);
67
+ }
153
68
 
154
- const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(slot, archive);
155
- expect(retreivedAttestations.length).toBe(1);
156
- expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
157
- expect(retreivedAttestations[0].payload.header.slotNumber).toEqual(slot);
158
- }
159
- });
69
+ const retrievedAttestationsForSlot = await ap.getCheckpointAttestationsForSlot(SlotNumber(slotNumber));
70
+ expect(retrievedAttestationsForSlot.length).toBe(attestations.length);
71
+ compareCheckpointAttestations(retrievedAttestationsForSlot, attestations);
72
+
73
+ // Add another one
74
+ const newAttestation = mockCheckpointAttestation(signers[NUMBER_OF_SIGNERS_PER_TEST - 1], slotNumber, archive);
75
+ await ap.addCheckpointAttestations([newAttestation]);
76
+ const retrievedAttestationsAfterAdd = await ap.getCheckpointAttestationsForSlotAndProposal(
77
+ SlotNumber(slotNumber),
78
+ archive.toString(),
79
+ );
80
+ expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1);
81
+ compareCheckpointAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]);
82
+ expect(await ap.hasCheckpointAttestation(newAttestation)).toBe(true);
83
+ const retrievedAttestationsForSlotAfterAdd = await ap.getCheckpointAttestationsForSlot(SlotNumber(slotNumber));
84
+ expect(retrievedAttestationsForSlotAfterAdd.length).toBe(attestations.length + 1);
85
+ compareCheckpointAttestations(retrievedAttestationsForSlotAfterAdd, [...attestations, newAttestation]);
86
+
87
+ // Delete by slot
88
+ await ap.deleteCheckpointAttestationsOlderThan(SlotNumber(slotNumber + 1));
89
+
90
+ const retreivedAttestationsAfterDelete = await ap.getCheckpointAttestationsForSlotAndProposal(
91
+ SlotNumber(slotNumber),
92
+ archive.toString(),
93
+ );
94
+ expect(retreivedAttestationsAfterDelete.length).toBe(0);
95
+ // Check hasCheckpointAttestation after deletion
96
+ for (const attestation of attestations) {
97
+ expect(await ap.hasCheckpointAttestation(attestation)).toBe(false);
98
+ }
99
+ expect(await ap.hasCheckpointAttestation(newAttestation)).toBe(false);
100
+ });
160
101
 
161
- it('should store attestations by differing slot and archive', async () => {
162
- const slotNumbers = [1, 1, 2, 3];
163
- const archives = [Fr.random(), Fr.random(), Fr.random(), Fr.random()];
164
- const attestations = signers.map((signer, i) => mockAttestation(signer, slotNumbers[i], archives[i]));
102
+ it('should handle duplicate proposals in a slot', async () => {
103
+ const slotNumber = 420;
104
+ const archive = Fr.random();
165
105
 
166
- await ap.addAttestations(attestations);
106
+ // Use the same signer for all attestations
107
+ const attestations: CheckpointAttestation[] = [];
108
+ const signer = signers[0];
109
+ for (let i = 0; i < NUMBER_OF_SIGNERS_PER_TEST; i++) {
110
+ attestations.push(mockCheckpointAttestation(signer, slotNumber, archive));
111
+ }
167
112
 
168
- for (const attestation of attestations) {
169
- const slot = attestation.payload.header.slotNumber;
170
- const proposalId = attestation.archive.toString();
113
+ // Add them to store and check we end up with only one
114
+ await ap.addCheckpointAttestations(attestations);
171
115
 
172
- const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(slot, proposalId);
116
+ const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(
117
+ SlotNumber(slotNumber),
118
+ archive.toString(),
119
+ );
173
120
  expect(retreivedAttestations.length).toBe(1);
174
- expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
175
- expect(retreivedAttestations[0].payload.header.slotNumber).toEqual(slot);
176
- }
177
- });
178
-
179
- it('should delete attestations', async () => {
180
- const slotNumber = 420;
181
- const archive = Fr.random();
182
- const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
183
- const proposalId = attestations[0].archive.toString();
184
-
185
- await ap.addAttestations(attestations);
186
-
187
- const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
188
- expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
189
- compareAttestations(retreivedAttestations, attestations);
190
-
191
- // Check hasAttestation before deletion
192
- for (const attestation of attestations) {
193
- expect(await ap.hasAttestation(attestation)).toBe(true);
194
- }
195
-
196
- await ap.deleteAttestations(attestations);
121
+ expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer());
122
+ expect(retreivedAttestations[0].getSender()?.toString()).toEqual(signer.address.toString());
123
+
124
+ // Try adding them on another operation and check they are still not duplicated
125
+ await ap.addCheckpointAttestations([attestations[0]]);
126
+ expect(
127
+ await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(slotNumber), archive.toString()),
128
+ ).toHaveLength(1);
129
+ });
197
130
 
198
- const gottenAfterDelete = await ap.getAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
199
- expect(gottenAfterDelete.length).toBe(0);
131
+ it('should store attestations by differing slot', async () => {
132
+ const slotNumbers = [1, 2, 3, 4];
133
+ const attestations = signers.map((signer, i) => mockCheckpointAttestation(signer, slotNumbers[i]));
200
134
 
201
- // Check hasAttestation after deletion
202
- for (const attestation of attestations) {
203
- expect(await ap.hasAttestation(attestation)).toBe(false);
204
- }
205
- });
135
+ await ap.addCheckpointAttestations(attestations);
206
136
 
207
- it('should blanket delete attestations per slot', async () => {
208
- const slotNumber = 420;
209
- const archive = Fr.random();
210
- const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
211
- const proposalId = attestations[0].archive.toString();
212
-
213
- await ap.addAttestations(attestations);
137
+ for (const attestation of attestations) {
138
+ const slot = attestation.payload.header.slotNumber;
139
+ const archive = attestation.archive.toString();
214
140
 
215
- const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
216
- expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
217
- compareAttestations(retreivedAttestations, attestations);
141
+ const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(slot, archive);
142
+ expect(retreivedAttestations.length).toBe(1);
143
+ expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
144
+ expect(retreivedAttestations[0].payload.header.slotNumber).toEqual(slot);
145
+ }
146
+ });
218
147
 
219
- await ap.deleteAttestationsForSlot(SlotNumber(slotNumber));
148
+ it('should store attestations by differing slot and archive', async () => {
149
+ const slotNumbers = [1, 1, 2, 3];
150
+ const archives = [Fr.random(), Fr.random(), Fr.random(), Fr.random()];
151
+ const attestations = signers.map((signer, i) => mockCheckpointAttestation(signer, slotNumbers[i], archives[i]));
220
152
 
221
- const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(
222
- SlotNumber(slotNumber),
223
- proposalId,
224
- );
225
- expect(retreivedAttestationsAfterDelete.length).toBe(0);
226
- });
153
+ await ap.addCheckpointAttestations(attestations);
227
154
 
228
- it('should blanket delete attestations per slot and proposal', async () => {
229
- const slotNumber = 420;
230
- const archive = Fr.random();
231
- const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
232
- const proposalId = attestations[0].archive.toString();
233
-
234
- // Add another set of attestations with a different proposalId, yet the same slot
235
- const archive2 = Fr.random();
236
- const attestations2 = signers.map(signer => mockAttestation(signer, slotNumber, archive2));
237
- const proposalId2 = attestations2[0].archive.toString();
238
-
239
- await ap.addAttestations(attestations);
240
- await ap.addAttestations(attestations2);
241
-
242
- const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
243
- expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
244
- compareAttestations(retreivedAttestations, attestations);
245
-
246
- await ap.deleteAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
247
-
248
- const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(
249
- SlotNumber(slotNumber),
250
- proposalId,
251
- );
252
- expect(retreivedAttestationsAfterDelete.length).toBe(0);
253
-
254
- const retreivedAttestationsAfterDeleteForOtherProposal = await ap.getAttestationsForSlotAndProposal(
255
- SlotNumber(slotNumber),
256
- proposalId2,
257
- );
258
- expect(retreivedAttestationsAfterDeleteForOtherProposal.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
259
- compareAttestations(retreivedAttestationsAfterDeleteForOtherProposal, attestations2);
260
- });
261
-
262
- it('should delete attestations older than a given slot', async () => {
263
- const slotNumbers = [1, 2, 3, 69, 72, 74, 88, 420];
264
- const attestations = (
265
- await Promise.all(slotNumbers.map(slotNumber => createAttestationsForSlot(slotNumber)))
266
- ).flat();
267
- const proposalId = attestations[0].archive.toString();
155
+ for (const attestation of attestations) {
156
+ const slot = attestation.payload.header.slotNumber;
157
+ const proposalId = attestation.archive.toString();
268
158
 
269
- await ap.addAttestations(attestations);
159
+ const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(slot, proposalId);
160
+ expect(retreivedAttestations.length).toBe(1);
161
+ expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
162
+ expect(retreivedAttestations[0].payload.header.slotNumber).toEqual(slot);
163
+ }
164
+ });
270
165
 
271
- const attestationsForSlot1 = await ap.getAttestationsForSlotAndProposal(SlotNumber(1), proposalId);
272
- expect(attestationsForSlot1.length).toBe(signers.length);
166
+ it('should delete attestations older than a given slot', async () => {
167
+ const slotNumbers = [1, 2, 3, 69, 72, 74, 88, 420];
168
+ const attestations = (
169
+ await Promise.all(slotNumbers.map(slotNumber => createCheckpointAttestationsForSlot(slotNumber)))
170
+ ).flat();
171
+ const proposalId = attestations[0].archive.toString();
273
172
 
274
- const deleteAttestationsSpy = jest.spyOn(ap, 'deleteAttestationsForSlot');
173
+ await ap.addCheckpointAttestations(attestations);
275
174
 
276
- await ap.deleteAttestationsOlderThan(SlotNumber(73));
175
+ const attestationsForSlot1 = await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(1), proposalId);
176
+ expect(attestationsForSlot1.length).toBe(signers.length);
277
177
 
278
- const attestationsForSlot1AfterDelete = await ap.getAttestationsForSlotAndProposal(SlotNumber(1), proposalId);
279
- expect(attestationsForSlot1AfterDelete.length).toBe(0);
178
+ await ap.deleteCheckpointAttestationsOlderThan(SlotNumber(73));
280
179
 
281
- expect(deleteAttestationsSpy).toHaveBeenCalledTimes(5);
282
- expect(deleteAttestationsSpy).toHaveBeenCalledWith(SlotNumber(1));
283
- expect(deleteAttestationsSpy).toHaveBeenCalledWith(SlotNumber(2));
284
- expect(deleteAttestationsSpy).toHaveBeenCalledWith(SlotNumber(3));
285
- expect(deleteAttestationsSpy).toHaveBeenCalledWith(SlotNumber(69));
286
- expect(deleteAttestationsSpy).toHaveBeenCalledWith(SlotNumber(72));
180
+ const attestationsForSlot1AfterDelete = await ap.getCheckpointAttestationsForSlotAndProposal(
181
+ SlotNumber(1),
182
+ proposalId,
183
+ );
184
+ expect(attestationsForSlot1AfterDelete.length).toBe(0);
185
+ });
287
186
  });
288
187
 
289
188
  describe('BlockProposal in attestation pool', () => {
290
189
  it('should add and retrieve block proposal', async () => {
291
190
  const slotNumber = 420;
292
191
  const archive = Fr.random();
293
- const proposal = mockBlockProposal(signers[0], slotNumber, archive);
192
+ const proposal = await mockBlockProposalForPool(signers[0], slotNumber, archive);
294
193
  const proposalId = proposal.archive.toString();
295
194
 
296
195
  await ap.addBlockProposal(proposal);
@@ -317,13 +216,13 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
317
216
  it('should update block proposal if added twice with same id', async () => {
318
217
  const slotNumber = 420;
319
218
  const archive = Fr.random();
320
- const proposal1 = mockBlockProposal(signers[0], slotNumber, archive);
219
+ const proposal1 = await mockBlockProposalForPool(signers[0], slotNumber, archive);
321
220
  const proposalId = proposal1.archive.toString();
322
221
 
323
222
  await ap.addBlockProposal(proposal1);
324
223
 
325
224
  // Create a new proposal with same archive but different signer
326
- const proposal2 = mockBlockProposal(signers[1], slotNumber, archive);
225
+ const proposal2 = await mockBlockProposalForPool(signers[1], slotNumber, archive);
327
226
 
328
227
  await ap.addBlockProposal(proposal2);
329
228
 
@@ -336,8 +235,8 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
336
235
 
337
236
  it('should handle block proposals with different slots and same archive', async () => {
338
237
  const archive = Fr.random();
339
- const proposal1 = mockBlockProposal(signers[0], 100, archive);
340
- const proposal2 = mockBlockProposal(signers[1], 200, archive);
238
+ const proposal1 = await mockBlockProposalForPool(signers[0], 100, archive);
239
+ const proposal2 = await mockBlockProposalForPool(signers[1], 200, archive);
341
240
  const proposalId = archive.toString();
342
241
 
343
242
  await ap.addBlockProposal(proposal1);
@@ -349,81 +248,126 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
349
248
  expect(retrievedProposal!.toBuffer()).toEqual(proposal2.toBuffer());
350
249
  expect(retrievedProposal!.slotNumber).toBe(SlotNumber(200));
351
250
  });
251
+ });
352
252
 
353
- it('should delete block proposal when deleting attestations for slot and proposal', async () => {
253
+ describe('CheckpointProposal in attestation pool', () => {
254
+ const mockCheckpointProposalForPool = (
255
+ signer: Secp256k1Signer,
256
+ slotNumber: number,
257
+ archive: Fr = Fr.random(),
258
+ ): Promise<CheckpointProposal> => {
259
+ const header = makeL2BlockHeader(1, 2, slotNumber);
260
+ return makeCheckpointProposal({
261
+ signer,
262
+ checkpointHeader: header.toCheckpointHeader(),
263
+ archiveRoot: archive,
264
+ lastBlock: { blockHeader: header },
265
+ });
266
+ };
267
+
268
+ it('should add and retrieve checkpoint proposal as core (without lastBlock)', async () => {
354
269
  const slotNumber = 420;
355
270
  const archive = Fr.random();
356
- const proposal = mockBlockProposal(signers[0], slotNumber, archive);
271
+ const proposal = await mockCheckpointProposalForPool(signers[0], slotNumber, archive);
357
272
  const proposalId = proposal.archive.toString();
358
273
 
359
- // Add proposal and some attestations
360
- await ap.addBlockProposal(proposal);
361
- const attestations = signers.map(signer => mockAttestation(signer, slotNumber, archive));
362
- await ap.addAttestations(attestations);
274
+ await ap.addCheckpointProposal(proposal);
275
+
276
+ const retrievedProposal = await ap.getCheckpointProposal(proposalId);
363
277
 
364
- // Verify proposal exists
365
- let retrievedProposal = await ap.getBlockProposal(proposalId);
366
278
  expect(retrievedProposal).toBeDefined();
367
- expect(await ap.hasBlockProposal(proposalId)).toBe(true);
279
+ // Should return core version (without lastBlock)
280
+ expect(retrievedProposal!.toBuffer()).toEqual(proposal.toCore().toBuffer());
368
281
 
369
- // Delete attestations for slot and proposal
370
- await ap.deleteAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
282
+ // Check hasCheckpointProposal with both id and object
283
+ expect(await ap.hasCheckpointProposal(proposalId)).toBe(true);
284
+ expect(await ap.hasCheckpointProposal(proposal)).toBe(true);
285
+ });
371
286
 
372
- // Proposal should be deleted
373
- retrievedProposal = await ap.getBlockProposal(proposalId);
374
- expect(retrievedProposal).toBeUndefined();
375
- expect(await ap.hasBlockProposal(proposalId)).toBe(false);
287
+ it('should extract and store block proposal when adding checkpoint proposal with lastBlock', async () => {
288
+ const slotNumber = 420;
289
+ const archive = Fr.random();
290
+ const proposal = await mockCheckpointProposalForPool(signers[0], slotNumber, archive);
291
+ const proposalId = proposal.archive.toString();
292
+
293
+ // Verify the proposal has a lastBlock
294
+ const expectedBlockProposal = proposal.getBlockProposal();
295
+ expect(expectedBlockProposal).toBeDefined();
296
+
297
+ await ap.addCheckpointProposal(proposal);
298
+
299
+ // The block proposal should be stored separately and retrievable
300
+ const retrievedBlockProposal = await ap.getBlockProposal(proposalId);
301
+ expect(retrievedBlockProposal).toBeDefined();
302
+ expect(retrievedBlockProposal!.archive.toString()).toBe(archive.toString());
303
+ expect(retrievedBlockProposal!.blockHeader.toBuffer()).toEqual(expectedBlockProposal!.blockHeader.toBuffer());
376
304
  });
377
305
 
378
- it('should delete block proposal when deleting attestations for slot', async () => {
306
+ it('should not store block proposal when checkpoint proposal has no lastBlock', async () => {
379
307
  const slotNumber = 420;
380
308
  const archive = Fr.random();
381
- const proposal = mockBlockProposal(signers[0], slotNumber, archive);
309
+ const header = makeL2BlockHeader(1, 2, slotNumber);
310
+ // Create a checkpoint proposal WITHOUT lastBlock
311
+ const proposal = await makeCheckpointProposal({
312
+ signer: signers[0],
313
+ checkpointHeader: header.toCheckpointHeader(),
314
+ archiveRoot: archive,
315
+ // No lastBlock
316
+ });
382
317
  const proposalId = proposal.archive.toString();
383
318
 
384
- // Add proposal
385
- await ap.addBlockProposal(proposal);
319
+ await ap.addCheckpointProposal(proposal);
386
320
 
387
- // Verify proposal exists
388
- let retrievedProposal = await ap.getBlockProposal(proposalId);
389
- expect(retrievedProposal).toBeDefined();
390
- expect(await ap.hasBlockProposal(proposal)).toBe(true);
321
+ // The checkpoint proposal should be stored
322
+ const retrievedCheckpointProposal = await ap.getCheckpointProposal(proposalId);
323
+ expect(retrievedCheckpointProposal).toBeDefined();
391
324
 
392
- // Delete attestations for slot
393
- await ap.deleteAttestationsForSlot(SlotNumber(slotNumber));
325
+ // But no block proposal should be stored (archive key won't have a block proposal)
326
+ const retrievedBlockProposal = await ap.getBlockProposal(proposalId);
327
+ expect(retrievedBlockProposal).toBeUndefined();
328
+ });
394
329
 
395
- // Proposal should be deleted
396
- retrievedProposal = await ap.getBlockProposal(proposalId);
330
+ it('should return undefined for non-existent checkpoint proposal', async () => {
331
+ const nonExistentId = Fr.random().toString();
332
+ const retrievedProposal = await ap.getCheckpointProposal(nonExistentId);
397
333
  expect(retrievedProposal).toBeUndefined();
398
- expect(await ap.hasBlockProposal(proposal)).toBe(false);
334
+
335
+ // Check hasCheckpointProposal returns false for non-existent proposal
336
+ expect(await ap.hasCheckpointProposal(nonExistentId)).toBe(false);
399
337
  });
400
338
 
401
- it('should be able to fetch both block proposal and attestations', async () => {
339
+ it('should update checkpoint proposal if added twice with same id', async () => {
402
340
  const slotNumber = 420;
403
341
  const archive = Fr.random();
404
- const proposal = mockBlockProposal(signers[0], slotNumber, archive);
405
- const proposalId = proposal.archive.toString();
342
+ const proposal1 = await mockCheckpointProposalForPool(signers[0], slotNumber, archive);
343
+ const proposalId = proposal1.archive.toString();
406
344
 
407
- // Add proposal first
408
- await ap.addBlockProposal(proposal);
345
+ await ap.addCheckpointProposal(proposal1);
409
346
 
410
- // Add attestations for the same proposal
411
- const attestations = signers.slice(1).map(signer => mockAttestation(signer, slotNumber, archive));
412
- await ap.addAttestations(attestations);
347
+ // Create a new proposal with same archive but different signer
348
+ const proposal2 = await mockCheckpointProposalForPool(signers[1], slotNumber, archive);
413
349
 
414
- // Retrieve both proposal and attestations
415
- const retrievedProposal = await ap.getBlockProposal(proposalId);
416
- const retrievedAttestations = await ap.getAttestationsForSlotAndProposal(SlotNumber(slotNumber), proposalId);
350
+ await ap.addCheckpointProposal(proposal2);
417
351
 
352
+ const retrievedProposal = await ap.getCheckpointProposal(proposalId);
418
353
  expect(retrievedProposal).toBeDefined();
419
- expect(retrievedProposal).toEqual(proposal);
420
- expect(await ap.hasBlockProposal(proposalId)).toBe(true);
354
+ // Should have the second proposal (as core)
355
+ expect(retrievedProposal!.toBuffer()).toEqual(proposal2.toCore().toBuffer());
356
+ expect(retrievedProposal!.getSender()?.toString()).toBe(signers[1].address.toString());
357
+ });
421
358
 
422
- compareAttestations(retrievedAttestations, attestations);
423
- // Check hasAttestation for all attestations
424
- for (const attestation of attestations) {
425
- expect(await ap.hasAttestation(attestation)).toBe(true);
359
+ it('should throw ProposalSlotCapExceededError when exceeding capacity', async () => {
360
+ const slotNumber = 420;
361
+
362
+ // Add MAX_PROPOSALS_PER_SLOT proposals
363
+ for (let i = 0; i < MAX_PROPOSALS_PER_SLOT; i++) {
364
+ const proposal = await mockCheckpointProposalForPool(signers[i % NUMBER_OF_SIGNERS_PER_TEST], slotNumber);
365
+ await ap.addCheckpointProposal(proposal);
426
366
  }
367
+
368
+ // The next proposal should throw
369
+ const extraProposal = await mockCheckpointProposalForPool(signers[0], slotNumber);
370
+ await expect(ap.addCheckpointProposal(extraProposal)).rejects.toThrow('Maximum checkpoint proposals per slot');
427
371
  });
428
372
  });
429
373
  }