@aztec/p2p 0.0.1-commit.e3c1de76 → 0.0.1-commit.e558bd1c

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 (188) hide show
  1. package/dest/client/factory.d.ts +3 -3
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +5 -3
  4. package/dest/client/interface.d.ts +9 -2
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +7 -4
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +22 -7
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
  10. package/dest/config.d.ts +9 -3
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +3 -1
  13. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +94 -87
  14. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  15. package/dest/mem_pools/attestation_pool/attestation_pool.js +411 -3
  16. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  17. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +351 -85
  19. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  20. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/index.js +1 -2
  22. package/dest/mem_pools/index.d.ts +2 -2
  23. package/dest/mem_pools/index.d.ts.map +1 -1
  24. package/dest/mem_pools/index.js +1 -1
  25. package/dest/mem_pools/interface.d.ts +3 -3
  26. package/dest/mem_pools/interface.d.ts.map +1 -1
  27. package/dest/mem_pools/tx_pool_v2/archive/index.d.ts +2 -0
  28. package/dest/mem_pools/tx_pool_v2/archive/index.d.ts.map +1 -0
  29. package/dest/mem_pools/tx_pool_v2/archive/index.js +1 -0
  30. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +43 -0
  31. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +103 -0
  33. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +47 -0
  34. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -0
  35. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +119 -0
  36. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +17 -0
  37. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -0
  38. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +90 -0
  39. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +19 -0
  40. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -0
  41. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +89 -0
  42. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +10 -0
  43. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -0
  44. package/dest/mem_pools/tx_pool_v2/eviction/index.js +11 -0
  45. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +131 -0
  46. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -0
  47. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +17 -0
  48. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts +15 -0
  49. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -0
  50. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +63 -0
  51. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts +17 -0
  52. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -0
  53. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +91 -0
  54. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +16 -0
  55. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -0
  56. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +70 -0
  57. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +20 -0
  58. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -0
  59. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +63 -0
  60. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +15 -0
  61. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -0
  62. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +19 -0
  63. package/dest/mem_pools/tx_pool_v2/index.d.ts +5 -0
  64. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -0
  65. package/dest/mem_pools/tx_pool_v2/index.js +4 -0
  66. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +197 -0
  67. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -0
  68. package/dest/mem_pools/tx_pool_v2/interfaces.js +6 -0
  69. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +71 -0
  70. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -0
  71. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +95 -0
  72. package/dest/mem_pools/tx_pool_v2/tx_pool_bench_metrics.d.ts +26 -0
  73. package/dest/mem_pools/tx_pool_v2/tx_pool_bench_metrics.d.ts.map +1 -0
  74. package/dest/mem_pools/tx_pool_v2/tx_pool_bench_metrics.js +70 -0
  75. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +99 -0
  76. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
  77. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +332 -0
  78. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +55 -0
  79. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -0
  80. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +156 -0
  81. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +69 -0
  82. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -0
  83. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +748 -0
  84. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
  85. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  86. package/dest/services/dummy_service.d.ts +6 -2
  87. package/dest/services/dummy_service.d.ts.map +1 -1
  88. package/dest/services/dummy_service.js +3 -0
  89. package/dest/services/index.d.ts +2 -1
  90. package/dest/services/index.d.ts.map +1 -1
  91. package/dest/services/index.js +1 -0
  92. package/dest/services/libp2p/libp2p_service.d.ts +74 -33
  93. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  94. package/dest/services/libp2p/libp2p_service.js +299 -228
  95. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -4
  96. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  97. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +8 -8
  98. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +6 -4
  99. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  100. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
  101. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +15 -10
  102. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  103. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +12 -11
  104. package/dest/services/service.d.ts +18 -1
  105. package/dest/services/service.d.ts.map +1 -1
  106. package/dest/services/tx_collection/config.d.ts +3 -3
  107. package/dest/services/tx_collection/config.js +3 -3
  108. package/dest/services/tx_collection/fast_tx_collection.d.ts +4 -5
  109. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  110. package/dest/services/tx_collection/fast_tx_collection.js +10 -14
  111. package/dest/services/tx_collection/index.d.ts +1 -1
  112. package/dest/services/tx_collection/proposal_tx_collector.d.ts +12 -12
  113. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  114. package/dest/services/tx_collection/proposal_tx_collector.js +4 -5
  115. package/dest/services/tx_file_store/config.d.ts +18 -0
  116. package/dest/services/tx_file_store/config.d.ts.map +1 -0
  117. package/dest/services/tx_file_store/config.js +26 -0
  118. package/dest/services/tx_file_store/index.d.ts +4 -0
  119. package/dest/services/tx_file_store/index.d.ts.map +1 -0
  120. package/dest/services/tx_file_store/index.js +3 -0
  121. package/dest/services/tx_file_store/instrumentation.d.ts +15 -0
  122. package/dest/services/tx_file_store/instrumentation.d.ts.map +1 -0
  123. package/dest/services/tx_file_store/instrumentation.js +29 -0
  124. package/dest/services/tx_file_store/tx_file_store.d.ts +47 -0
  125. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -0
  126. package/dest/services/tx_file_store/tx_file_store.js +149 -0
  127. package/dest/test-helpers/testbench-utils.d.ts +10 -16
  128. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  129. package/dest/test-helpers/testbench-utils.js +32 -30
  130. package/dest/testbench/p2p_client_testbench_worker.js +1 -1
  131. package/package.json +14 -14
  132. package/src/client/factory.ts +7 -4
  133. package/src/client/interface.ts +13 -1
  134. package/src/client/p2p_client.ts +30 -8
  135. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  136. package/src/config.ts +8 -1
  137. package/src/mem_pools/attestation_pool/attestation_pool.ts +444 -90
  138. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +436 -100
  139. package/src/mem_pools/attestation_pool/index.ts +9 -2
  140. package/src/mem_pools/index.ts +1 -1
  141. package/src/mem_pools/interface.ts +2 -2
  142. package/src/mem_pools/tx_pool_v2/README.md +209 -0
  143. package/src/mem_pools/tx_pool_v2/archive/index.ts +1 -0
  144. package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +120 -0
  145. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +147 -0
  146. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +118 -0
  147. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +111 -0
  148. package/src/mem_pools/tx_pool_v2/eviction/index.ts +23 -0
  149. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +164 -0
  150. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +74 -0
  151. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +101 -0
  152. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +86 -0
  153. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +72 -0
  154. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +31 -0
  155. package/src/mem_pools/tx_pool_v2/index.ts +11 -0
  156. package/src/mem_pools/tx_pool_v2/interfaces.ts +227 -0
  157. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +161 -0
  158. package/src/mem_pools/tx_pool_v2/tx_pool_bench_metrics.ts +77 -0
  159. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +417 -0
  160. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +212 -0
  161. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +882 -0
  162. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
  163. package/src/services/dummy_service.ts +6 -0
  164. package/src/services/index.ts +1 -0
  165. package/src/services/libp2p/libp2p_service.ts +304 -230
  166. package/src/services/reqresp/batch-tx-requester/README.md +7 -7
  167. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +11 -11
  168. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +22 -13
  169. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +21 -15
  170. package/src/services/service.ts +20 -0
  171. package/src/services/tx_collection/config.ts +6 -6
  172. package/src/services/tx_collection/fast_tx_collection.ts +14 -24
  173. package/src/services/tx_collection/index.ts +1 -1
  174. package/src/services/tx_collection/proposal_tx_collector.ts +12 -14
  175. package/src/services/tx_file_store/config.ts +43 -0
  176. package/src/services/tx_file_store/index.ts +3 -0
  177. package/src/services/tx_file_store/instrumentation.ts +36 -0
  178. package/src/services/tx_file_store/tx_file_store.ts +173 -0
  179. package/src/test-helpers/testbench-utils.ts +18 -39
  180. package/src/testbench/p2p_client_testbench_worker.ts +1 -1
  181. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  182. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  183. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  184. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  185. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  186. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  187. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  188. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
@@ -1,7 +1,7 @@
1
- import { SlotNumber } from '@aztec/foundation/branded-types';
1
+ import { IndexWithinCheckpoint, 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 { BlockProposal, CheckpointAttestation, CheckpointProposal } from '@aztec/stdlib/p2p';
4
+ import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore } from '@aztec/stdlib/p2p';
5
5
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
6
6
  import {
7
7
  makeBlockHeader,
@@ -10,8 +10,7 @@ import {
10
10
  makeCheckpointProposal,
11
11
  } from '@aztec/stdlib/testing';
12
12
 
13
- import type { AttestationPool } from './attestation_pool.js';
14
- import { MAX_PROPOSALS_PER_SLOT } from './kv_attestation_pool.js';
13
+ import { type AttestationPool, MAX_PROPOSALS_PER_POSITION, MAX_PROPOSALS_PER_SLOT } from './attestation_pool.js';
15
14
  import { mockCheckpointAttestation } from './mocks.js';
16
15
 
17
16
  const NUMBER_OF_SIGNERS_PER_TEST = 4;
@@ -58,7 +57,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
58
57
  const archive = Fr.random();
59
58
  const attestations = signers.slice(0, -1).map(signer => mockCheckpointAttestation(signer, slotNumber, archive));
60
59
 
61
- await ap.addCheckpointAttestations(attestations);
60
+ await ap.addOwnCheckpointAttestations(attestations);
62
61
 
63
62
  const retrievedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(
64
63
  SlotNumber(slotNumber),
@@ -67,42 +66,31 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
67
66
  expect(retrievedAttestations.length).toBe(attestations.length);
68
67
  compareCheckpointAttestations(retrievedAttestations, attestations);
69
68
 
70
- // Check hasCheckpointAttestation for added attestations
71
- for (const attestation of attestations) {
72
- expect(await ap.hasCheckpointAttestation(attestation)).toBe(true);
73
- }
74
-
75
69
  const retrievedAttestationsForSlot = await ap.getCheckpointAttestationsForSlot(SlotNumber(slotNumber));
76
70
  expect(retrievedAttestationsForSlot.length).toBe(attestations.length);
77
71
  compareCheckpointAttestations(retrievedAttestationsForSlot, attestations);
78
72
 
79
73
  // Add another one
80
74
  const newAttestation = mockCheckpointAttestation(signers[NUMBER_OF_SIGNERS_PER_TEST - 1], slotNumber, archive);
81
- await ap.addCheckpointAttestations([newAttestation]);
75
+ await ap.addOwnCheckpointAttestations([newAttestation]);
82
76
  const retrievedAttestationsAfterAdd = await ap.getCheckpointAttestationsForSlotAndProposal(
83
77
  SlotNumber(slotNumber),
84
78
  archive.toString(),
85
79
  );
86
80
  expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1);
87
81
  compareCheckpointAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]);
88
- expect(await ap.hasCheckpointAttestation(newAttestation)).toBe(true);
89
82
  const retrievedAttestationsForSlotAfterAdd = await ap.getCheckpointAttestationsForSlot(SlotNumber(slotNumber));
90
83
  expect(retrievedAttestationsForSlotAfterAdd.length).toBe(attestations.length + 1);
91
84
  compareCheckpointAttestations(retrievedAttestationsForSlotAfterAdd, [...attestations, newAttestation]);
92
85
 
93
86
  // Delete by slot
94
- await ap.deleteCheckpointAttestationsOlderThan(SlotNumber(slotNumber + 1));
87
+ await ap.deleteOlderThan(SlotNumber(slotNumber + 1));
95
88
 
96
89
  const retreivedAttestationsAfterDelete = await ap.getCheckpointAttestationsForSlotAndProposal(
97
90
  SlotNumber(slotNumber),
98
91
  archive.toString(),
99
92
  );
100
93
  expect(retreivedAttestationsAfterDelete.length).toBe(0);
101
- // Check hasCheckpointAttestation after deletion
102
- for (const attestation of attestations) {
103
- expect(await ap.hasCheckpointAttestation(attestation)).toBe(false);
104
- }
105
- expect(await ap.hasCheckpointAttestation(newAttestation)).toBe(false);
106
94
  });
107
95
 
108
96
  it('should handle duplicate proposals in a slot', async () => {
@@ -118,7 +106,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
118
106
  }
119
107
 
120
108
  // Add them to store and check we end up with only one
121
- await ap.addCheckpointAttestations(attestations);
109
+ await ap.addOwnCheckpointAttestations(attestations);
122
110
 
123
111
  const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(
124
112
  SlotNumber(slotNumber),
@@ -129,7 +117,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
129
117
  expect(retreivedAttestations[0].getSender()?.toString()).toEqual(signer.address.toString());
130
118
 
131
119
  // Try adding them on another operation and check they are still not duplicated
132
- await ap.addCheckpointAttestations([attestations[0]]);
120
+ await ap.addOwnCheckpointAttestations([attestations[0]]);
133
121
  expect(
134
122
  await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(slotNumber), archive.toString()),
135
123
  ).toHaveLength(1);
@@ -139,7 +127,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
139
127
  const slotNumbers = [1, 2, 3, 4];
140
128
  const attestations = signers.map((signer, i) => mockCheckpointAttestation(signer, slotNumbers[i]));
141
129
 
142
- await ap.addCheckpointAttestations(attestations);
130
+ await ap.addOwnCheckpointAttestations(attestations);
143
131
 
144
132
  for (const attestation of attestations) {
145
133
  const slot = attestation.payload.header.slotNumber;
@@ -157,7 +145,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
157
145
  const archives = [Fr.random(), Fr.random(), Fr.random(), Fr.random()];
158
146
  const attestations = signers.map((signer, i) => mockCheckpointAttestation(signer, slotNumbers[i], archives[i]));
159
147
 
160
- await ap.addCheckpointAttestations(attestations);
148
+ await ap.addOwnCheckpointAttestations(attestations);
161
149
 
162
150
  for (const attestation of attestations) {
163
151
  const slot = attestation.payload.header.slotNumber;
@@ -177,12 +165,12 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
177
165
  ).flat();
178
166
  const proposalId = attestations[0].archive.toString();
179
167
 
180
- await ap.addCheckpointAttestations(attestations);
168
+ await ap.addOwnCheckpointAttestations(attestations);
181
169
 
182
170
  const attestationsForSlot1 = await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(1), proposalId);
183
171
  expect(attestationsForSlot1.length).toBe(signers.length);
184
172
 
185
- await ap.deleteCheckpointAttestationsOlderThan(SlotNumber(73));
173
+ await ap.deleteOlderThan(SlotNumber(73));
186
174
 
187
175
  const attestationsForSlot1AfterDelete = await ap.getCheckpointAttestationsForSlotAndProposal(
188
176
  SlotNumber(1),
@@ -199,119 +187,86 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
199
187
  const proposal = await mockBlockProposalForPool(signers[0], slotNumber, archive);
200
188
  const proposalId = proposal.archive.toString();
201
189
 
202
- await ap.addBlockProposal(proposal);
190
+ const result = await ap.tryAddBlockProposal(proposal);
191
+
192
+ expect(result.added).toBe(true);
193
+ expect(result.alreadyExists).toBe(false);
194
+ expect(result.totalForPosition).toBe(1);
203
195
 
204
196
  const retrievedProposal = await ap.getBlockProposal(proposalId);
205
197
 
206
198
  expect(retrievedProposal).toBeDefined();
207
199
  expect(retrievedProposal!).toEqual(proposal);
208
-
209
- // Check hasBlockProposal with both id and object
210
- expect(await ap.hasBlockProposal(proposalId)).toBe(true);
211
- expect(await ap.hasBlockProposal(proposal)).toBe(true);
212
200
  });
213
201
 
214
202
  it('should return undefined for non-existent block proposal', async () => {
215
203
  const nonExistentId = Fr.random().toString();
216
204
  const retrievedProposal = await ap.getBlockProposal(nonExistentId);
217
205
  expect(retrievedProposal).toBeUndefined();
218
-
219
- // Check hasBlockProposal returns false for non-existent proposal
220
- expect(await ap.hasBlockProposal(nonExistentId)).toBe(false);
221
206
  });
222
207
 
223
- it('should update block proposal if added twice with same id', async () => {
208
+ it('should return alreadyExists when adding proposal with same id', async () => {
224
209
  const slotNumber = 420;
225
210
  const archive = Fr.random();
226
211
  const proposal1 = await mockBlockProposalForPool(signers[0], slotNumber, archive);
227
212
  const proposalId = proposal1.archive.toString();
228
213
 
229
- await ap.addBlockProposal(proposal1);
214
+ const result1 = await ap.tryAddBlockProposal(proposal1);
215
+ expect(result1.added).toBe(true);
216
+ expect(result1.alreadyExists).toBe(false);
230
217
 
231
218
  // Create a new proposal with same archive but different signer
232
219
  const proposal2 = await mockBlockProposalForPool(signers[1], slotNumber, archive);
233
220
 
234
- await ap.addBlockProposal(proposal2);
221
+ const result2 = await ap.tryAddBlockProposal(proposal2);
222
+ expect(result2.added).toBe(false);
223
+ expect(result2.alreadyExists).toBe(true);
235
224
 
225
+ // Should still have the first proposal
236
226
  const retrievedProposal = await ap.getBlockProposal(proposalId);
237
227
  expect(retrievedProposal).toBeDefined();
238
- // Should have the second proposal
239
- expect(retrievedProposal!.toBuffer()).toEqual(proposal2.toBuffer());
240
- expect(retrievedProposal!.getSender()?.toString()).toBe(signers[1].address.toString());
241
- });
242
-
243
- it('should handle block proposals with different slots and same archive', async () => {
244
- const archive = Fr.random();
245
- const proposal1 = await mockBlockProposalForPool(signers[0], 100, archive);
246
- const proposal2 = await mockBlockProposalForPool(signers[1], 200, archive);
247
- const proposalId = archive.toString();
248
-
249
- await ap.addBlockProposal(proposal1);
250
- await ap.addBlockProposal(proposal2);
251
-
252
- // Should get the latest one added
253
- const retrievedProposal = await ap.getBlockProposal(proposalId);
254
- expect(retrievedProposal).toBeDefined();
255
- expect(retrievedProposal!.toBuffer()).toEqual(proposal2.toBuffer());
256
- expect(retrievedProposal!.slotNumber).toBe(SlotNumber(200));
228
+ expect(retrievedProposal!.toBuffer()).toEqual(proposal1.toBuffer());
229
+ expect(retrievedProposal!.getSender()?.toString()).toBe(signers[0].address.toString());
257
230
  });
258
231
  });
259
232
 
260
233
  describe('CheckpointProposal in attestation pool', () => {
261
- const mockCheckpointProposalForPool = (
234
+ const mockCheckpointProposalForPool = async (
262
235
  signer: Secp256k1Signer,
263
236
  slotNumber: number,
264
237
  archive: Fr = Fr.random(),
265
- ): Promise<CheckpointProposal> => {
238
+ ): Promise<CheckpointProposalCore> => {
266
239
  const checkpointHeader = makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) });
267
240
  const blockHeader = makeBlockHeader(1);
268
- return makeCheckpointProposal({
241
+ const proposal = await makeCheckpointProposal({
269
242
  signer,
270
243
  checkpointHeader,
271
244
  archiveRoot: archive,
272
245
  lastBlock: { blockHeader },
273
246
  });
247
+ // Return the core version since tryAddCheckpointProposal now takes CheckpointProposalCore
248
+ return proposal.toCore();
274
249
  };
275
250
 
276
- it('should add and retrieve checkpoint proposal as core (without lastBlock)', async () => {
251
+ it('should add and retrieve checkpoint proposal', async () => {
277
252
  const slotNumber = 420;
278
253
  const archive = Fr.random();
279
254
  const proposal = await mockCheckpointProposalForPool(signers[0], slotNumber, archive);
280
255
  const proposalId = proposal.archive.toString();
281
256
 
282
- await ap.addCheckpointProposal(proposal);
257
+ const result = await ap.tryAddCheckpointProposal(proposal);
258
+
259
+ expect(result.added).toBe(true);
260
+ expect(result.alreadyExists).toBe(false);
261
+ expect(result.totalForPosition).toBe(1);
283
262
 
284
263
  const retrievedProposal = await ap.getCheckpointProposal(proposalId);
285
264
 
286
265
  expect(retrievedProposal).toBeDefined();
287
- // Should return core version (without lastBlock)
288
- expect(retrievedProposal!.toBuffer()).toEqual(proposal.toCore().toBuffer());
289
-
290
- // Check hasCheckpointProposal with both id and object
291
- expect(await ap.hasCheckpointProposal(proposalId)).toBe(true);
292
- expect(await ap.hasCheckpointProposal(proposal)).toBe(true);
293
- });
294
-
295
- it('should extract and store block proposal when adding checkpoint proposal with lastBlock', async () => {
296
- const slotNumber = 420;
297
- const archive = Fr.random();
298
- const proposal = await mockCheckpointProposalForPool(signers[0], slotNumber, archive);
299
- const proposalId = proposal.archive.toString();
300
-
301
- // Verify the proposal has a lastBlock
302
- const expectedBlockProposal = proposal.getBlockProposal();
303
- expect(expectedBlockProposal).toBeDefined();
304
-
305
- await ap.addCheckpointProposal(proposal);
306
-
307
- // The block proposal should be stored separately and retrievable
308
- const retrievedBlockProposal = await ap.getBlockProposal(proposalId);
309
- expect(retrievedBlockProposal).toBeDefined();
310
- expect(retrievedBlockProposal!.archive.toString()).toBe(archive.toString());
311
- expect(retrievedBlockProposal!.blockHeader.toBuffer()).toEqual(expectedBlockProposal!.blockHeader.toBuffer());
266
+ expect(retrievedProposal!.toBuffer()).toEqual(proposal.toBuffer());
312
267
  });
313
268
 
314
- it('should not store block proposal when checkpoint proposal has no lastBlock', async () => {
269
+ it('should handle checkpoint proposal without lastBlock (caller extracts and adds block separately)', async () => {
315
270
  const slotNumber = 420;
316
271
  const archive = Fr.random();
317
272
  const checkpointHeader = makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) });
@@ -324,13 +279,14 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
324
279
  });
325
280
  const proposalId = proposal.archive.toString();
326
281
 
327
- await ap.addCheckpointProposal(proposal);
282
+ // Add the checkpoint core - block extraction is now caller responsibility
283
+ await ap.tryAddCheckpointProposal(proposal.toCore());
328
284
 
329
285
  // The checkpoint proposal should be stored
330
286
  const retrievedCheckpointProposal = await ap.getCheckpointProposal(proposalId);
331
287
  expect(retrievedCheckpointProposal).toBeDefined();
332
288
 
333
- // But no block proposal should be stored (archive key won't have a block proposal)
289
+ // No block proposal was extracted (it had none anyway)
334
290
  const retrievedBlockProposal = await ap.getBlockProposal(proposalId);
335
291
  expect(retrievedBlockProposal).toBeUndefined();
336
292
  });
@@ -339,43 +295,423 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
339
295
  const nonExistentId = Fr.random().toString();
340
296
  const retrievedProposal = await ap.getCheckpointProposal(nonExistentId);
341
297
  expect(retrievedProposal).toBeUndefined();
342
-
343
- // Check hasCheckpointProposal returns false for non-existent proposal
344
- expect(await ap.hasCheckpointProposal(nonExistentId)).toBe(false);
345
298
  });
346
299
 
347
- it('should update checkpoint proposal if added twice with same id', async () => {
300
+ it('should return alreadyExists when adding proposal with same id', async () => {
348
301
  const slotNumber = 420;
349
302
  const archive = Fr.random();
350
303
  const proposal1 = await mockCheckpointProposalForPool(signers[0], slotNumber, archive);
351
304
  const proposalId = proposal1.archive.toString();
352
305
 
353
- await ap.addCheckpointProposal(proposal1);
306
+ const result1 = await ap.tryAddCheckpointProposal(proposal1);
307
+ expect(result1.added).toBe(true);
308
+ expect(result1.alreadyExists).toBe(false);
354
309
 
355
310
  // Create a new proposal with same archive but different signer
356
311
  const proposal2 = await mockCheckpointProposalForPool(signers[1], slotNumber, archive);
357
312
 
358
- await ap.addCheckpointProposal(proposal2);
313
+ const result2 = await ap.tryAddCheckpointProposal(proposal2);
314
+ expect(result2.added).toBe(false);
315
+ expect(result2.alreadyExists).toBe(true);
359
316
 
317
+ // Should still have the first proposal
360
318
  const retrievedProposal = await ap.getCheckpointProposal(proposalId);
361
319
  expect(retrievedProposal).toBeDefined();
362
- // Should have the second proposal (as core)
363
- expect(retrievedProposal!.toBuffer()).toEqual(proposal2.toCore().toBuffer());
364
- expect(retrievedProposal!.getSender()?.toString()).toBe(signers[1].address.toString());
320
+ expect(retrievedProposal!.toBuffer()).toEqual(proposal1.toBuffer());
321
+ expect(retrievedProposal!.getSender()?.toString()).toBe(signers[0].address.toString());
365
322
  });
366
323
 
367
- it('should throw ProposalSlotCapExceededError when exceeding capacity', async () => {
324
+ it('should return added=false when exceeding capacity', async () => {
368
325
  const slotNumber = 420;
369
326
 
370
327
  // Add MAX_PROPOSALS_PER_SLOT proposals
371
328
  for (let i = 0; i < MAX_PROPOSALS_PER_SLOT; i++) {
372
329
  const proposal = await mockCheckpointProposalForPool(signers[i % NUMBER_OF_SIGNERS_PER_TEST], slotNumber);
373
- await ap.addCheckpointProposal(proposal);
330
+ const result = await ap.tryAddCheckpointProposal(proposal);
331
+ expect(result.added).toBe(true);
332
+ expect(result.totalForPosition).toBe(i + 1);
374
333
  }
375
334
 
376
- // The next proposal should throw
335
+ // The next proposal should not be added
377
336
  const extraProposal = await mockCheckpointProposalForPool(signers[0], slotNumber);
378
- await expect(ap.addCheckpointProposal(extraProposal)).rejects.toThrow('Maximum checkpoint proposals per slot');
337
+ const result = await ap.tryAddCheckpointProposal(extraProposal);
338
+ expect(result.added).toBe(false);
339
+ expect(result.alreadyExists).toBe(false);
340
+ expect(result.totalForPosition).toBe(MAX_PROPOSALS_PER_SLOT);
341
+ });
342
+ });
343
+
344
+ describe('Duplicate proposal detection', () => {
345
+ const mockBlockProposalWithIndex = (
346
+ signer: Secp256k1Signer,
347
+ slotNumber: number,
348
+ indexWithinCheckpoint: number,
349
+ archive: Fr = Fr.random(),
350
+ ): Promise<BlockProposal> => {
351
+ const header = makeBlockHeader(1, { slotNumber: SlotNumber(slotNumber) });
352
+ return makeBlockProposal({
353
+ signer,
354
+ blockHeader: header,
355
+ archiveRoot: archive,
356
+ indexWithinCheckpoint: IndexWithinCheckpoint(indexWithinCheckpoint),
357
+ });
358
+ };
359
+
360
+ describe('tryAddBlockProposal duplicate detection', () => {
361
+ it('should return totalForPosition=1 when pool is empty', async () => {
362
+ const proposal = await mockBlockProposalWithIndex(signers[0], 100, 0);
363
+ const result = await ap.tryAddBlockProposal(proposal);
364
+
365
+ expect(result.added).toBe(true);
366
+ expect(result.alreadyExists).toBe(false);
367
+ expect(result.totalForPosition).toBe(1);
368
+ });
369
+
370
+ it('should return alreadyExists when same proposal exists', async () => {
371
+ const proposal = await mockBlockProposalWithIndex(signers[0], 100, 0);
372
+ await ap.tryAddBlockProposal(proposal);
373
+
374
+ const result = await ap.tryAddBlockProposal(proposal);
375
+
376
+ expect(result.added).toBe(false);
377
+ expect(result.alreadyExists).toBe(true);
378
+ expect(result.totalForPosition).toBe(1);
379
+ });
380
+
381
+ it('should detect duplicate via totalForPosition when different proposal exists at same position', async () => {
382
+ const slotNumber = 100;
383
+ const indexWithinCheckpoint = 2;
384
+
385
+ // Add first proposal
386
+ const proposal1 = await mockBlockProposalWithIndex(signers[0], slotNumber, indexWithinCheckpoint);
387
+ const result1 = await ap.tryAddBlockProposal(proposal1);
388
+ expect(result1.totalForPosition).toBe(1);
389
+
390
+ // Add a different proposal at same position - this is a duplicate (equivocation)
391
+ const proposal2 = await mockBlockProposalWithIndex(signers[1], slotNumber, indexWithinCheckpoint);
392
+ const result2 = await ap.tryAddBlockProposal(proposal2);
393
+
394
+ expect(result2.added).toBe(true);
395
+ expect(result2.alreadyExists).toBe(false);
396
+ // totalForPosition >= 2 indicates duplicate detection
397
+ expect(result2.totalForPosition).toBe(2);
398
+ });
399
+
400
+ it('should not detect duplicate for different positions in same slot', async () => {
401
+ const slotNumber = 100;
402
+
403
+ // Add proposal at index 0
404
+ const proposal1 = await mockBlockProposalWithIndex(signers[0], slotNumber, 0);
405
+ await ap.tryAddBlockProposal(proposal1);
406
+
407
+ // Add proposal at index 1 (different position)
408
+ const proposal2 = await mockBlockProposalWithIndex(signers[1], slotNumber, 1);
409
+ const result = await ap.tryAddBlockProposal(proposal2);
410
+
411
+ expect(result.added).toBe(true);
412
+ // totalForPosition = 1 means no duplicate for this position
413
+ expect(result.totalForPosition).toBe(1);
414
+ });
415
+
416
+ it('should not detect duplicate for same position in different slots', async () => {
417
+ const indexWithinCheckpoint = 0;
418
+
419
+ // Add proposal at slot 100
420
+ const proposal1 = await mockBlockProposalWithIndex(signers[0], 100, indexWithinCheckpoint);
421
+ await ap.tryAddBlockProposal(proposal1);
422
+
423
+ // Add proposal at slot 200 (different slot)
424
+ const proposal2 = await mockBlockProposalWithIndex(signers[1], 200, indexWithinCheckpoint);
425
+ const result = await ap.tryAddBlockProposal(proposal2);
426
+
427
+ expect(result.added).toBe(true);
428
+ // totalForPosition = 1 means no duplicate for this position
429
+ expect(result.totalForPosition).toBe(1);
430
+ });
431
+
432
+ it('should track multiple duplicates correctly via totalForPosition', async () => {
433
+ const slotNumber = 100;
434
+ const indexWithinCheckpoint = 0;
435
+
436
+ // Add multiple proposals for same position
437
+ const proposal1 = await mockBlockProposalWithIndex(signers[0], slotNumber, indexWithinCheckpoint);
438
+ const result1 = await ap.tryAddBlockProposal(proposal1);
439
+ expect(result1.totalForPosition).toBe(1);
440
+
441
+ const proposal2 = await mockBlockProposalWithIndex(signers[1], slotNumber, indexWithinCheckpoint);
442
+ const result2 = await ap.tryAddBlockProposal(proposal2);
443
+ expect(result2.totalForPosition).toBe(2);
444
+
445
+ // Add a third proposal for same position
446
+ const proposal3 = await mockBlockProposalWithIndex(signers[2], slotNumber, indexWithinCheckpoint);
447
+ const result3 = await ap.tryAddBlockProposal(proposal3);
448
+
449
+ expect(result3.added).toBe(true);
450
+ expect(result3.totalForPosition).toBe(3);
451
+ });
452
+
453
+ it('should return added=false when exceeding capacity', async () => {
454
+ const slotNumber = 100;
455
+ const indexWithinCheckpoint = 0;
456
+
457
+ // Add MAX_PROPOSALS_PER_POSITION proposals
458
+ for (let i = 0; i < MAX_PROPOSALS_PER_POSITION; i++) {
459
+ const proposal = await mockBlockProposalWithIndex(
460
+ signers[i % NUMBER_OF_SIGNERS_PER_TEST],
461
+ slotNumber,
462
+ indexWithinCheckpoint,
463
+ );
464
+ const result = await ap.tryAddBlockProposal(proposal);
465
+ expect(result.added).toBe(true);
466
+ expect(result.totalForPosition).toBe(i + 1);
467
+ }
468
+
469
+ // The next proposal should not be added
470
+ const extraProposal = await mockBlockProposalWithIndex(signers[0], slotNumber, indexWithinCheckpoint);
471
+ const result = await ap.tryAddBlockProposal(extraProposal);
472
+ expect(result.added).toBe(false);
473
+ expect(result.alreadyExists).toBe(false);
474
+ expect(result.totalForPosition).toBe(MAX_PROPOSALS_PER_POSITION);
475
+ });
476
+
477
+ it('should clean up block position index when deleting old data', async () => {
478
+ const slotNumber = 100;
479
+ const indexWithinCheckpoint = 0;
480
+
481
+ // Add proposal
482
+ const proposal1 = await mockBlockProposalWithIndex(signers[0], slotNumber, indexWithinCheckpoint);
483
+ await ap.tryAddBlockProposal(proposal1);
484
+
485
+ // Verify it's tracked (adding another should show totalForPosition = 2)
486
+ const proposal2 = await mockBlockProposalWithIndex(signers[1], slotNumber, indexWithinCheckpoint);
487
+ let result = await ap.tryAddBlockProposal(proposal2);
488
+ expect(result.totalForPosition).toBe(2);
489
+
490
+ // Delete old data
491
+ await ap.deleteOlderThan(SlotNumber(slotNumber + 1));
492
+
493
+ // Verify position index is cleaned up (totalForPosition should be 1 now)
494
+ const proposal3 = await mockBlockProposalWithIndex(signers[2], slotNumber, indexWithinCheckpoint);
495
+ result = await ap.tryAddBlockProposal(proposal3);
496
+ expect(result.totalForPosition).toBe(1);
497
+ });
498
+
499
+ it('should correctly delete block proposals at slot boundary', async () => {
500
+ // Add proposals at slots 99, 100, and 101 with various indices
501
+ const proposalSlot99Idx0 = await mockBlockProposalWithIndex(signers[0], 99, 0);
502
+ const proposalSlot99Idx1 = await mockBlockProposalWithIndex(signers[1], 99, 1);
503
+ const proposalSlot100Idx0 = await mockBlockProposalWithIndex(signers[2], 100, 0);
504
+ const proposalSlot101Idx0 = await mockBlockProposalWithIndex(signers[3], 101, 0);
505
+
506
+ await ap.tryAddBlockProposal(proposalSlot99Idx0);
507
+ await ap.tryAddBlockProposal(proposalSlot99Idx1);
508
+ await ap.tryAddBlockProposal(proposalSlot100Idx0);
509
+ await ap.tryAddBlockProposal(proposalSlot101Idx0);
510
+
511
+ // Delete slots older than 100 (should delete slot 99 only)
512
+ await ap.deleteOlderThan(SlotNumber(100));
513
+
514
+ // Slot 99 proposals should have their index cleaned up
515
+ const newProposal99 = await mockBlockProposalWithIndex(signers[0], 99, 0);
516
+ const result99 = await ap.tryAddBlockProposal(newProposal99);
517
+ expect(result99.totalForPosition).toBe(1); // Index was cleaned up
518
+
519
+ // Slot 100 and 101 should still be tracked
520
+ const newProposal100 = await mockBlockProposalWithIndex(signers[1], 100, 0);
521
+ const result100 = await ap.tryAddBlockProposal(newProposal100);
522
+ expect(result100.totalForPosition).toBe(2); // Still has the original
523
+
524
+ const newProposal101 = await mockBlockProposalWithIndex(signers[2], 101, 0);
525
+ const result101 = await ap.tryAddBlockProposal(newProposal101);
526
+ expect(result101.totalForPosition).toBe(2); // Still has the original
527
+ });
528
+
529
+ it('should delete all indices for a given slot', async () => {
530
+ const slotNumber = 50;
531
+
532
+ // Add proposals at multiple indices for the same slot
533
+ const proposal0 = await mockBlockProposalWithIndex(signers[0], slotNumber, 0);
534
+ const proposal1 = await mockBlockProposalWithIndex(signers[1], slotNumber, 1);
535
+ const proposal2 = await mockBlockProposalWithIndex(signers[2], slotNumber, 2);
536
+
537
+ await ap.tryAddBlockProposal(proposal0);
538
+ await ap.tryAddBlockProposal(proposal1);
539
+ await ap.tryAddBlockProposal(proposal2);
540
+
541
+ // Delete slots older than slotNumber + 1
542
+ await ap.deleteOlderThan(SlotNumber(slotNumber + 1));
543
+
544
+ // All indices should be cleaned up
545
+ const newProposal0 = await mockBlockProposalWithIndex(signers[0], slotNumber, 0);
546
+ const result0 = await ap.tryAddBlockProposal(newProposal0);
547
+ expect(result0.totalForPosition).toBe(1);
548
+
549
+ const newProposal1 = await mockBlockProposalWithIndex(signers[1], slotNumber, 1);
550
+ const result1 = await ap.tryAddBlockProposal(newProposal1);
551
+ expect(result1.totalForPosition).toBe(1);
552
+
553
+ const newProposal2 = await mockBlockProposalWithIndex(signers[2], slotNumber, 2);
554
+ const result2 = await ap.tryAddBlockProposal(newProposal2);
555
+ expect(result2.totalForPosition).toBe(1);
556
+ });
557
+
558
+ it('should delete block proposals from storage when deleting old data', async () => {
559
+ const oldSlot = 50;
560
+ const newSlot = 100;
561
+
562
+ // Add proposals at old and new slots
563
+ const oldProposal = await mockBlockProposalWithIndex(signers[0], oldSlot, 0);
564
+ const newProposal = await mockBlockProposalWithIndex(signers[1], newSlot, 0);
565
+
566
+ await ap.tryAddBlockProposal(oldProposal);
567
+ await ap.tryAddBlockProposal(newProposal);
568
+
569
+ // Verify both proposals exist
570
+ expect(await ap.getBlockProposal(oldProposal.archive.toString())).toBeDefined();
571
+ expect(await ap.getBlockProposal(newProposal.archive.toString())).toBeDefined();
572
+
573
+ // Delete slots older than newSlot (should delete oldSlot)
574
+ await ap.deleteOlderThan(SlotNumber(newSlot));
575
+
576
+ // Old proposal should be deleted from storage
577
+ expect(await ap.getBlockProposal(oldProposal.archive.toString())).toBeUndefined();
578
+
579
+ // New proposal should still exist
580
+ expect(await ap.getBlockProposal(newProposal.archive.toString())).toBeDefined();
581
+ });
582
+ });
583
+
584
+ describe('tryAddCheckpointProposal duplicate detection', () => {
585
+ const mockCheckpointProposalCoreForPool = async (
586
+ signer: Secp256k1Signer,
587
+ slotNumber: number,
588
+ archive: Fr = Fr.random(),
589
+ ): Promise<CheckpointProposalCore> => {
590
+ const checkpointHeader = makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) });
591
+ const blockHeader = makeBlockHeader(1);
592
+ const proposal = await makeCheckpointProposal({
593
+ signer,
594
+ checkpointHeader,
595
+ archiveRoot: archive,
596
+ lastBlock: { blockHeader },
597
+ });
598
+ return proposal.toCore();
599
+ };
600
+
601
+ it('should return totalForPosition=1 when pool is empty', async () => {
602
+ const proposal = await mockCheckpointProposalCoreForPool(signers[0], 100);
603
+ const result = await ap.tryAddCheckpointProposal(proposal);
604
+
605
+ expect(result.added).toBe(true);
606
+ expect(result.alreadyExists).toBe(false);
607
+ expect(result.totalForPosition).toBe(1);
608
+ });
609
+
610
+ it('should return alreadyExists when same proposal exists', async () => {
611
+ const proposal = await mockCheckpointProposalCoreForPool(signers[0], 100);
612
+ await ap.tryAddCheckpointProposal(proposal);
613
+
614
+ const result = await ap.tryAddCheckpointProposal(proposal);
615
+
616
+ expect(result.added).toBe(false);
617
+ expect(result.alreadyExists).toBe(true);
618
+ expect(result.totalForPosition).toBe(1);
619
+ });
620
+
621
+ it('should detect duplicate via totalForPosition when different proposal exists for same slot', async () => {
622
+ const slotNumber = 100;
623
+
624
+ // Add first proposal
625
+ const proposal1 = await mockCheckpointProposalCoreForPool(signers[0], slotNumber);
626
+ const result1 = await ap.tryAddCheckpointProposal(proposal1);
627
+ expect(result1.totalForPosition).toBe(1);
628
+
629
+ // Add a different proposal for same slot - this is a duplicate (equivocation)
630
+ const proposal2 = await mockCheckpointProposalCoreForPool(signers[1], slotNumber);
631
+ const result2 = await ap.tryAddCheckpointProposal(proposal2);
632
+
633
+ expect(result2.added).toBe(true);
634
+ expect(result2.alreadyExists).toBe(false);
635
+ // totalForPosition >= 2 indicates duplicate detection
636
+ expect(result2.totalForPosition).toBe(2);
637
+ });
638
+
639
+ it('should not detect duplicate for different slots', async () => {
640
+ // Add proposal at slot 100
641
+ const proposal1 = await mockCheckpointProposalCoreForPool(signers[0], 100);
642
+ await ap.tryAddCheckpointProposal(proposal1);
643
+
644
+ // Add proposal at slot 200 (different slot)
645
+ const proposal2 = await mockCheckpointProposalCoreForPool(signers[1], 200);
646
+ const result = await ap.tryAddCheckpointProposal(proposal2);
647
+
648
+ expect(result.added).toBe(true);
649
+ // totalForPosition = 1 means no duplicate for this slot
650
+ expect(result.totalForPosition).toBe(1);
651
+ });
652
+
653
+ it('should track multiple duplicates correctly via totalForPosition', async () => {
654
+ const slotNumber = 100;
655
+
656
+ // Add multiple proposals for same slot
657
+ const proposal1 = await mockCheckpointProposalCoreForPool(signers[0], slotNumber);
658
+ const result1 = await ap.tryAddCheckpointProposal(proposal1);
659
+ expect(result1.totalForPosition).toBe(1);
660
+
661
+ const proposal2 = await mockCheckpointProposalCoreForPool(signers[1], slotNumber);
662
+ const result2 = await ap.tryAddCheckpointProposal(proposal2);
663
+ expect(result2.totalForPosition).toBe(2);
664
+
665
+ // Add a third proposal for same slot
666
+ const proposal3 = await mockCheckpointProposalCoreForPool(signers[2], slotNumber);
667
+ const result3 = await ap.tryAddCheckpointProposal(proposal3);
668
+
669
+ expect(result3.added).toBe(true);
670
+ expect(result3.totalForPosition).toBe(3);
671
+ });
672
+
673
+ it('should not count attestations as proposals for duplicate detection', async () => {
674
+ const slotNumber = 100;
675
+ const archive = Fr.random();
676
+
677
+ // Attestation arrives BEFORE the checkpoint proposal (race condition in p2p)
678
+ const attestation = mockCheckpointAttestation(signers[0], slotNumber, archive);
679
+ await ap.addOwnCheckpointAttestations([attestation]);
680
+
681
+ // Now the checkpoint proposal arrives - this should NOT be detected as a duplicate
682
+ const proposal = await mockCheckpointProposalCoreForPool(signers[1], slotNumber, archive);
683
+ const result = await ap.tryAddCheckpointProposal(proposal);
684
+
685
+ expect(result.added).toBe(true);
686
+ expect(result.alreadyExists).toBe(false);
687
+ // totalForPosition should be 1, NOT 2 - attestations should not count as proposals
688
+ expect(result.totalForPosition).toBe(1);
689
+ });
690
+
691
+ it('should not count attestations for different proposals as duplicates', async () => {
692
+ const slotNumber = 100;
693
+ const archive1 = Fr.random();
694
+ const archive2 = Fr.random();
695
+
696
+ // Add attestations for two different proposals in the same slot
697
+ const attestation1 = mockCheckpointAttestation(signers[0], slotNumber, archive1);
698
+ const attestation2 = mockCheckpointAttestation(signers[1], slotNumber, archive2);
699
+ await ap.addOwnCheckpointAttestations([attestation1, attestation2]);
700
+
701
+ // Add the first checkpoint proposal - should not be affected by attestations
702
+ const proposal1 = await mockCheckpointProposalCoreForPool(signers[2], slotNumber, archive1);
703
+ const result1 = await ap.tryAddCheckpointProposal(proposal1);
704
+
705
+ expect(result1.added).toBe(true);
706
+ expect(result1.totalForPosition).toBe(1);
707
+
708
+ // Add the second checkpoint proposal - this IS a duplicate (different archive, same slot)
709
+ const proposal2 = await mockCheckpointProposalCoreForPool(signers[3], slotNumber, archive2);
710
+ const result2 = await ap.tryAddCheckpointProposal(proposal2);
711
+
712
+ expect(result2.added).toBe(true);
713
+ expect(result2.totalForPosition).toBe(2);
714
+ });
379
715
  });
380
716
  });
381
717
  }