@aztec/p2p 0.0.1-commit.381b1a9 → 0.0.1-commit.3a4ae741b

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 (161) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +2 -2
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +20 -12
  5. package/dest/client/p2p_client.d.ts +1 -1
  6. package/dest/client/p2p_client.d.ts.map +1 -1
  7. package/dest/client/p2p_client.js +16 -10
  8. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +3 -3
  9. package/dest/config.d.ts +6 -6
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +6 -6
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -4
  13. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  14. package/dest/mem_pools/attestation_pool/attestation_pool.js +8 -4
  15. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
  16. package/dest/mem_pools/instrumentation.d.ts +4 -2
  17. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  18. package/dest/mem_pools/instrumentation.js +16 -14
  19. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  20. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  21. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  22. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
  25. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +9 -5
  26. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  27. package/dest/mem_pools/tx_pool_v2/interfaces.js +1 -0
  28. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +13 -7
  29. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +7 -1
  31. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +26 -43
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -0
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +29 -5
  40. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
  41. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  42. package/dest/msg_validators/attestation_validator/attestation_validator.js +5 -4
  43. package/dest/msg_validators/clock_tolerance.d.ts +1 -1
  44. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  45. package/dest/msg_validators/clock_tolerance.js +4 -3
  46. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +1 -1
  47. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  48. package/dest/msg_validators/proposal_validator/proposal_validator.js +5 -5
  49. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  50. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  51. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  52. package/dest/msg_validators/tx_validator/factory.d.ts +9 -1
  53. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  54. package/dest/msg_validators/tx_validator/factory.js +15 -2
  55. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  56. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  57. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  58. package/dest/msg_validators/tx_validator/phases_validator.d.ts +21 -1
  59. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  60. package/dest/msg_validators/tx_validator/phases_validator.js +28 -1
  61. package/dest/services/encoding.d.ts +5 -1
  62. package/dest/services/encoding.d.ts.map +1 -1
  63. package/dest/services/encoding.js +7 -1
  64. package/dest/services/libp2p/libp2p_service.d.ts +4 -9
  65. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  66. package/dest/services/libp2p/libp2p_service.js +105 -54
  67. package/dest/services/peer-manager/metrics.d.ts +3 -1
  68. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  69. package/dest/services/peer-manager/metrics.js +6 -0
  70. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  71. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  72. package/dest/services/peer-manager/peer_manager.js +6 -3
  73. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +11 -8
  74. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  75. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +82 -101
  76. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +3 -2
  77. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  78. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +5 -4
  79. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  80. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +13 -7
  81. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +19 -11
  82. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  83. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +52 -15
  84. package/dest/services/reqresp/reqresp.d.ts +1 -1
  85. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  86. package/dest/services/reqresp/reqresp.js +19 -10
  87. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -4
  88. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  89. package/dest/services/tx_collection/fast_tx_collection.js +57 -73
  90. package/dest/services/tx_collection/proposal_tx_collector.d.ts +6 -7
  91. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  92. package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
  93. package/dest/services/tx_collection/request_tracker.d.ts +53 -0
  94. package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
  95. package/dest/services/tx_collection/request_tracker.js +84 -0
  96. package/dest/services/tx_collection/slow_tx_collection.js +1 -1
  97. package/dest/services/tx_collection/tx_collection.d.ts +3 -6
  98. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  99. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  100. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  101. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  102. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  103. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  104. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  105. package/dest/test-helpers/testbench-utils.js +20 -2
  106. package/dest/testbench/p2p_client_testbench_worker.js +3 -3
  107. package/dest/testbench/worker_client_manager.d.ts +1 -1
  108. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  109. package/dest/testbench/worker_client_manager.js +2 -1
  110. package/dest/util.d.ts +1 -1
  111. package/package.json +14 -14
  112. package/src/client/factory.ts +36 -18
  113. package/src/client/p2p_client.ts +19 -10
  114. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +4 -6
  115. package/src/config.ts +10 -10
  116. package/src/mem_pools/attestation_pool/attestation_pool.ts +8 -7
  117. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
  118. package/src/mem_pools/instrumentation.ts +17 -13
  119. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  120. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  121. package/src/mem_pools/tx_pool_v2/interfaces.ts +9 -4
  122. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +15 -5
  123. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +29 -43
  124. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +16 -1
  125. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +31 -5
  126. package/src/msg_validators/attestation_validator/README.md +49 -0
  127. package/src/msg_validators/attestation_validator/attestation_validator.ts +5 -4
  128. package/src/msg_validators/clock_tolerance.ts +4 -3
  129. package/src/msg_validators/proposal_validator/README.md +123 -0
  130. package/src/msg_validators/proposal_validator/proposal_validator.ts +6 -5
  131. package/src/msg_validators/tx_validator/README.md +5 -1
  132. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  133. package/src/msg_validators/tx_validator/factory.ts +21 -1
  134. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  135. package/src/msg_validators/tx_validator/phases_validator.ts +31 -1
  136. package/src/services/encoding.ts +9 -1
  137. package/src/services/libp2p/libp2p_service.ts +97 -65
  138. package/src/services/peer-manager/metrics.ts +7 -0
  139. package/src/services/peer-manager/peer_manager.ts +7 -3
  140. package/src/services/reqresp/README.md +229 -0
  141. package/src/services/reqresp/batch-tx-requester/README.md +46 -7
  142. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +78 -111
  143. package/src/services/reqresp/batch-tx-requester/interface.ts +2 -1
  144. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +13 -6
  145. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +68 -24
  146. package/src/services/reqresp/reqresp.ts +22 -12
  147. package/src/services/tx_collection/fast_tx_collection.ts +57 -83
  148. package/src/services/tx_collection/proposal_tx_collector.ts +8 -13
  149. package/src/services/tx_collection/request_tracker.ts +127 -0
  150. package/src/services/tx_collection/slow_tx_collection.ts +1 -1
  151. package/src/services/tx_collection/tx_collection.ts +3 -5
  152. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  153. package/src/test-helpers/reqresp-nodes.ts +1 -1
  154. package/src/test-helpers/testbench-utils.ts +28 -3
  155. package/src/testbench/p2p_client_testbench_worker.ts +4 -6
  156. package/src/testbench/worker_client_manager.ts +2 -1
  157. package/src/util.ts +1 -1
  158. package/dest/services/tx_collection/missing_txs_tracker.d.ts +0 -32
  159. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +0 -1
  160. package/dest/services/tx_collection/missing_txs_tracker.js +0 -27
  161. package/src/services/tx_collection/missing_txs_tracker.ts +0 -52
@@ -40,7 +40,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
40
40
  // which are needed for public FPC flows, but fail if the account contract hasnt been deployed yet,
41
41
  // which is what we're trying to do as part of the current txs.
42
42
  // We only need to create/revert checkpoint here because of this addNewContracts call.
43
- await this.contractsDB.addNewContracts(tx);
43
+ this.contractsDB.addNewContracts(tx);
44
44
 
45
45
  if (!tx.data.forPublic) {
46
46
  this.#log.debug(
@@ -141,3 +141,33 @@ export class PhasesTxValidator implements TxValidator<Tx> {
141
141
  return TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED;
142
142
  }
143
143
  }
144
+
145
+ /** Structural interface for the allowed-setup-calls flag check. */
146
+ export interface HasAllowedSetupCallsData {
147
+ txHash: { toString(): string };
148
+ allowedSetupCalls: boolean;
149
+ }
150
+
151
+ /**
152
+ * Validates that a transaction's setup-phase calls were allowed at receipt time.
153
+ *
154
+ * Checks the precomputed `allowedSetupCalls` flag on TxMetaData. The flag is
155
+ * computed by running the PhasesTxValidator on the full Tx when it first enters
156
+ * the pool. This lightweight validator is used during pending pool migration to
157
+ * reject txs whose setup calls are not on the allow list.
158
+ */
159
+ export class AllowedSetupCallsMetaValidator<T extends HasAllowedSetupCallsData> implements TxValidator<T> {
160
+ #log: Logger;
161
+
162
+ constructor(bindings?: LoggerBindings) {
163
+ this.#log = createLogger('sequencer:tx_validator:tx_phases_meta', bindings);
164
+ }
165
+
166
+ validateTx(tx: T): Promise<TxValidationResult> {
167
+ if (!tx.allowedSetupCalls) {
168
+ this.#log.verbose(`Rejecting tx ${tx.txHash} because its setup calls are not on the allow list`);
169
+ return Promise.resolve({ result: 'invalid', reason: [TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED] });
170
+ }
171
+ return Promise.resolve({ result: 'valid' });
172
+ }
173
+ }
@@ -9,6 +9,14 @@ import { webcrypto } from 'node:crypto';
9
9
  import { compressSync, uncompressSync } from 'snappy';
10
10
  import xxhashFactory from 'xxhash-wasm';
11
11
 
12
+ /** Thrown when a Snappy-compressed response exceeds the allowed decompressed size. */
13
+ export class OversizedSnappyResponseError extends Error {
14
+ constructor(decompressedSize: number, maxSizeKb: number) {
15
+ super(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
16
+ this.name = 'OversizedSnappyResponseError';
17
+ }
18
+ }
19
+
12
20
  // Load WASM
13
21
  const xxhash = await xxhashFactory();
14
22
 
@@ -86,7 +94,7 @@ export class SnappyTransform implements DataTransform {
86
94
  const { decompressedSize } = readSnappyPreamble(data);
87
95
  if (decompressedSize > maxSizeKb * 1024) {
88
96
  this.logger.warn(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
89
- throw new Error(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
97
+ throw new OversizedSnappyResponseError(decompressedSize, maxSizeKb);
90
98
  }
91
99
 
92
100
  return Buffer.from(uncompressSync(data, { asBuffer: true }));
@@ -18,7 +18,6 @@ import {
18
18
  type CheckpointProposalCore,
19
19
  type Gossipable,
20
20
  P2PMessage,
21
- type ValidationResult as P2PValidationResult,
22
21
  PeerErrorSeverity,
23
22
  PeerErrorSeverityByHarshness,
24
23
  TopicType,
@@ -131,7 +130,7 @@ type ValidationOutcome = { allPassed: true } | { allPassed: false; failure: Vali
131
130
  // REFACTOR: Unify with the type above
132
131
  type ReceivedMessageValidationResult<T, M = undefined> =
133
132
  | { obj: T; result: Exclude<TopicValidatorResult, TopicValidatorResult.Reject>; metadata?: M }
134
- | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M };
133
+ | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M; severity: PeerErrorSeverity };
135
134
 
136
135
  /**
137
136
  * Lib P2P implementation of the P2PService interface.
@@ -754,6 +753,9 @@ export class LibP2PService extends WithTracer implements P2PService {
754
753
  if (!validator || !validator.addMessage(msgId)) {
755
754
  this.instrumentation.incMessagePrevalidationStatus(false, topicType);
756
755
  this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), TopicValidatorResult.Ignore);
756
+ if (topicType === TopicType.tx) {
757
+ this.logger.verbose(`Ignoring already-seen tx gossip message`, { msgId, source: source.toString() });
758
+ }
757
759
  return { result: false, topicType };
758
760
  }
759
761
 
@@ -880,30 +882,56 @@ export class LibP2PService extends WithTracer implements P2PService {
880
882
  source: PeerId,
881
883
  topicType: TopicType,
882
884
  ): Promise<ReceivedMessageValidationResult<T, M>> {
883
- let resultAndObj: ReceivedMessageValidationResult<T, M> = { result: TopicValidatorResult.Reject };
885
+ // Default to reject result with a penalty if validation function throws an error
886
+ let resultAndObj: ReceivedMessageValidationResult<T, M> = {
887
+ result: TopicValidatorResult.Reject,
888
+ severity: PeerErrorSeverity.MidToleranceError,
889
+ };
884
890
  const timer = new Timer();
885
891
  try {
886
892
  resultAndObj = await validationFunc();
887
893
  } catch (err) {
888
- this.peerManager.penalizePeer(source, PeerErrorSeverity.LowToleranceError);
889
- this.logger.error(`Error deserializing and validating gossipsub message`, err, {
890
- msgId,
891
- source: source.toString(),
892
- topicType,
893
- });
894
+ this.logger.error(`Error validating gossipsub message`, err, { msgId, source: source.toString(), topicType });
894
895
  }
895
896
 
896
897
  if (resultAndObj.result === TopicValidatorResult.Accept) {
898
+ this.logger.debug(`Message ${topicType} accepted by validator`, { msgId, source: source.toString(), topicType });
897
899
  this.instrumentation.recordMessageValidation(topicType, timer);
900
+ } else if (resultAndObj.result === TopicValidatorResult.Reject) {
901
+ this.logger.warn(`Message ${topicType} rejected by validator with severity ${resultAndObj.severity}`, {
902
+ msgId,
903
+ source: source.toString(),
904
+ topicType,
905
+ severity: resultAndObj.severity,
906
+ });
907
+ this.peerManager.penalizePeer(source, resultAndObj.severity);
908
+ } else {
909
+ this.logger.trace(`Message ${topicType} ignored by validator`, { msgId, source: source.toString(), topicType });
898
910
  }
899
911
 
900
912
  this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), resultAndObj.result);
901
913
  return resultAndObj;
902
914
  }
903
915
 
916
+ private tryDeserialize<T>(deserializeFunc: () => T, msgId: string, source: PeerId): T | undefined {
917
+ try {
918
+ return deserializeFunc();
919
+ } catch (err) {
920
+ this.logger.warn(`Failed to deserialize gossipsub message from buffer`, {
921
+ err,
922
+ msgId,
923
+ source: source.toString(),
924
+ });
925
+ return undefined;
926
+ }
927
+ }
928
+
904
929
  protected async handleGossipedTx(payloadData: Buffer, msgId: string, source: PeerId) {
905
930
  const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
906
- const tx = Tx.fromBuffer(payloadData);
931
+ const tx = this.tryDeserialize(() => Tx.fromBuffer(payloadData), msgId, source);
932
+ if (!tx) {
933
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.LowToleranceError };
934
+ }
907
935
 
908
936
  const currentBlockNumber = await this.archiver.getBlockNumber();
909
937
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
@@ -923,13 +951,20 @@ export class LibP2PService extends WithTracer implements P2PService {
923
951
  severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
924
952
  }
925
953
 
926
- this.peerManager.penalizePeer(source, severity);
927
- return { result: TopicValidatorResult.Reject };
954
+ this.logger.verbose(`Rejecting gossiped tx ${tx.getTxHash().toString()}: stage 1 validation failed`, {
955
+ validator: name,
956
+ severity,
957
+ source: source.toString(),
958
+ });
959
+ return { result: TopicValidatorResult.Reject, severity };
928
960
  }
929
961
 
930
962
  // Pool pre-check: see if the pool would accept this tx before doing expensive proof verification
931
963
  const canAdd = await this.mempools.txPool.canAddPendingTx(tx);
932
964
  if (canAdd === 'ignored') {
965
+ this.logger.verbose(`Ignoring gossiped tx ${tx.getTxHash().toString()}: pool pre-check returned ignored`, {
966
+ source: source.toString(),
967
+ });
933
968
  return { result: TopicValidatorResult.Ignore, obj: tx };
934
969
  }
935
970
 
@@ -937,9 +972,13 @@ export class LibP2PService extends WithTracer implements P2PService {
937
972
  const secondStageValidators = this.createSecondStageMessageValidators();
938
973
  const secondStageOutcome = await this.runValidations(tx, secondStageValidators);
939
974
  if (!secondStageOutcome.allPassed) {
940
- const { severity } = secondStageOutcome.failure;
941
- this.peerManager.penalizePeer(source, severity);
942
- return { result: TopicValidatorResult.Reject };
975
+ const { severity, name } = secondStageOutcome.failure;
976
+ this.logger.verbose(`Rejecting gossiped tx ${tx.getTxHash().toString()}: stage 2 validation failed`, {
977
+ validator: name,
978
+ severity,
979
+ source: source.toString(),
980
+ });
981
+ return { result: TopicValidatorResult.Reject, severity };
943
982
  }
944
983
 
945
984
  // Pool add: persist the tx
@@ -949,7 +988,7 @@ export class LibP2PService extends WithTracer implements P2PService {
949
988
  const wasAccepted = addResult.accepted.some(h => h.equals(txHash));
950
989
  const wasIgnored = addResult.ignored.some(h => h.equals(txHash));
951
990
 
952
- this.logger.trace(`Validate propagated tx`, {
991
+ this.logger.verbose(`Validate propagated tx ${txHash.toString()}`, {
953
992
  wasAccepted,
954
993
  wasIgnored,
955
994
  [Attributes.P2P_ID]: source.toString(),
@@ -960,7 +999,11 @@ export class LibP2PService extends WithTracer implements P2PService {
960
999
  } else if (wasIgnored) {
961
1000
  return { result: TopicValidatorResult.Ignore, obj: tx };
962
1001
  } else {
963
- return { result: TopicValidatorResult.Reject };
1002
+ this.logger.warn(`Gossiped tx ${txHash.toString()} unexpectedly rejected by pool`, {
1003
+ source: source.toString(),
1004
+ txHash: txHash.toString(),
1005
+ });
1006
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
964
1007
  }
965
1008
  };
966
1009
 
@@ -990,7 +1033,16 @@ export class LibP2PService extends WithTracer implements P2PService {
990
1033
  source: PeerId,
991
1034
  ): Promise<void> {
992
1035
  const { result, obj: attestation } = await this.validateReceivedMessage<CheckpointAttestation>(
993
- () => this.validateAndStoreCheckpointAttestation(source, CheckpointAttestation.fromBuffer(payloadData)),
1036
+ () => {
1037
+ const attestation = this.tryDeserialize(() => CheckpointAttestation.fromBuffer(payloadData), msgId, source);
1038
+ if (!attestation) {
1039
+ return Promise.resolve({
1040
+ result: TopicValidatorResult.Reject,
1041
+ severity: PeerErrorSeverity.LowToleranceError,
1042
+ });
1043
+ }
1044
+ return this.validateAndStoreCheckpointAttestation(source, attestation);
1045
+ },
994
1046
  msgId,
995
1047
  source,
996
1048
  TopicType.checkpoint_attestation,
@@ -1023,8 +1075,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1023
1075
 
1024
1076
  if (validationResult.result === 'reject') {
1025
1077
  this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1026
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1027
- return { result: TopicValidatorResult.Reject };
1078
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1028
1079
  }
1029
1080
 
1030
1081
  if (validationResult.result === 'ignore') {
@@ -1050,16 +1101,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1050
1101
  return { result: TopicValidatorResult.Ignore, obj: attestation };
1051
1102
  }
1052
1103
 
1053
- // Could not add (cap reached for signer), no need to re-broadcast
1104
+ // Could not add (cap reached for signer), penalize and do not re-broadcast
1054
1105
  if (!added) {
1055
- this.logger.warn(`Dropping checkpoint attestation due to cap`, {
1106
+ this.logger.warn(`Rejecting checkpoint attestation due to cap`, {
1056
1107
  slot: slot.toString(),
1057
1108
  archive: attestation.archive.toString(),
1058
1109
  source: peerId.toString(),
1059
1110
  attester: attestation.getSender()?.toString(),
1060
1111
  count,
1061
1112
  });
1062
- return { result: TopicValidatorResult.Ignore, obj: attestation };
1113
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
1063
1114
  }
1064
1115
 
1065
1116
  // Check if this is a duplicate attestation (signer attested to a different proposal at the same slot)
@@ -1114,8 +1165,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1114
1165
 
1115
1166
  if (validationResult.result === 'reject') {
1116
1167
  this.logger.warn(`Penalizing peer ${peerId} for block proposal validation failure`);
1117
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1118
- return { result: TopicValidatorResult.Reject };
1168
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1119
1169
  }
1120
1170
 
1121
1171
  if (validationResult.result === 'ignore') {
@@ -1139,7 +1189,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1139
1189
 
1140
1190
  // Too many blocks received for this slot and index, penalize peer and do not re-broadcast
1141
1191
  if (!added) {
1142
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1143
1192
  this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
1144
1193
  ...block.toBlockInfo(),
1145
1194
  indexWithinCheckpoint: block.indexWithinCheckpoint,
@@ -1147,7 +1196,11 @@ export class LibP2PService extends WithTracer implements P2PService {
1147
1196
  proposer: block.getSender()?.toString(),
1148
1197
  source: peerId.toString(),
1149
1198
  });
1150
- return { result: TopicValidatorResult.Reject, metadata: { isEquivocated } };
1199
+ return {
1200
+ result: TopicValidatorResult.Reject,
1201
+ metadata: { isEquivocated },
1202
+ severity: PeerErrorSeverity.HighToleranceError,
1203
+ };
1151
1204
  }
1152
1205
 
1153
1206
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
@@ -1240,8 +1293,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1240
1293
 
1241
1294
  if (validationResult.result === 'reject') {
1242
1295
  this.logger.warn(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
1243
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1244
- return { result: TopicValidatorResult.Reject };
1296
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1245
1297
  }
1246
1298
 
1247
1299
  if (validationResult.result === 'ignore') {
@@ -1256,20 +1308,21 @@ export class LibP2PService extends WithTracer implements P2PService {
1256
1308
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1257
1309
  [Attributes.P2P_ID]: peerId.toString(),
1258
1310
  });
1259
- const {
1260
- result,
1261
- obj,
1262
- metadata: { isEquivocated } = {},
1263
- } = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1264
- if (result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1311
+ const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1312
+ const { obj, metadata: { isEquivocated } = {} } = blockProposalResult;
1313
+ if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1265
1314
  this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
1266
1315
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1267
1316
  [Attributes.P2P_ID]: peerId.toString(),
1268
1317
  isEquivocated,
1269
- result,
1318
+ result: blockProposalResult.result,
1270
1319
  });
1271
- return { result: TopicValidatorResult.Reject };
1272
- } else if (result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1320
+ return {
1321
+ result: TopicValidatorResult.Reject,
1322
+ severity:
1323
+ 'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError,
1324
+ };
1325
+ } else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1273
1326
  processBlock = true;
1274
1327
  }
1275
1328
  }
@@ -1296,13 +1349,17 @@ export class LibP2PService extends WithTracer implements P2PService {
1296
1349
  // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
1297
1350
  // Note: We still return the checkpoint obj so the lastBlock can be processed if valid
1298
1351
  if (!added) {
1299
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1300
1352
  this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
1301
1353
  ...checkpoint.toCheckpointInfo(),
1302
1354
  count,
1303
1355
  source: peerId.toString(),
1304
1356
  });
1305
- return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } };
1357
+ return {
1358
+ result: TopicValidatorResult.Reject,
1359
+ obj: checkpoint,
1360
+ metadata: { isEquivocated, processBlock },
1361
+ severity: PeerErrorSeverity.HighToleranceError,
1362
+ };
1306
1363
  }
1307
1364
 
1308
1365
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
@@ -1726,31 +1783,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1726
1783
  return PeerErrorSeverity.HighToleranceError;
1727
1784
  }
1728
1785
 
1729
- /**
1730
- * Validate a checkpoint attestation.
1731
- *
1732
- * @param attestation - The checkpoint attestation to validate.
1733
- * @returns True if the checkpoint attestation is valid, false otherwise.
1734
- */
1735
- @trackSpan('Libp2pService.validateCheckpointAttestation', async (_, attestation) => ({
1736
- [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber,
1737
- [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
1738
- [Attributes.P2P_ID]: await attestation.p2pMessageLoggingIdentifier().then(i => i.toString()),
1739
- }))
1740
- public async validateCheckpointAttestation(
1741
- peerId: PeerId,
1742
- attestation: CheckpointAttestation,
1743
- ): Promise<P2PValidationResult> {
1744
- const result = await this.checkpointAttestationValidator.validate(attestation);
1745
-
1746
- if (result.result === 'reject') {
1747
- this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1748
- this.peerManager.penalizePeer(peerId, result.severity);
1749
- }
1750
-
1751
- return result;
1752
- }
1753
-
1754
1786
  public getPeerScore(peerId: PeerId): number {
1755
1787
  return this.node.services.pubsub.score.score(peerId.toString());
1756
1788
  }
@@ -18,6 +18,7 @@ export class PeerManagerMetrics {
18
18
  private sentGoodbyes: UpDownCounter;
19
19
  private receivedGoodbyes: UpDownCounter;
20
20
  private peerCount: Gauge;
21
+ private healthyPeerCount: Gauge;
21
22
  private lowScoreDisconnects: UpDownCounter;
22
23
  private peerConnectionDuration: Histogram;
23
24
 
@@ -49,6 +50,7 @@ export class PeerManagerMetrics {
49
50
  goodbyeReasonAttrs,
50
51
  );
51
52
  this.peerCount = meter.createGauge(Metrics.PEER_MANAGER_PEER_COUNT);
53
+ this.healthyPeerCount = meter.createGauge(Metrics.PEER_MANAGER_HEALTHY_PEER_COUNT);
52
54
  this.lowScoreDisconnects = createUpDownCounterWithDefault(meter, Metrics.PEER_MANAGER_LOW_SCORE_DISCONNECTS, {
53
55
  [Attributes.P2P_PEER_SCORE_STATE]: ['Banned', 'Disconnect'],
54
56
  });
@@ -67,6 +69,10 @@ export class PeerManagerMetrics {
67
69
  this.peerCount.record(count);
68
70
  }
69
71
 
72
+ public recordHealthyPeerCount(count: number) {
73
+ this.healthyPeerCount.record(count);
74
+ }
75
+
70
76
  public recordLowScoreDisconnect(scoreState: 'Banned' | 'Disconnect') {
71
77
  this.lowScoreDisconnects.add(1, { [Attributes.P2P_PEER_SCORE_STATE]: scoreState });
72
78
  }
@@ -79,6 +85,7 @@ export class PeerManagerMetrics {
79
85
  const connectedAt = this.peerConnectedAt.get(id.toString());
80
86
  if (connectedAt) {
81
87
  this.peerConnectionDuration.record(Date.now() - connectedAt);
88
+ this.peerConnectedAt.delete(id.toString());
82
89
  }
83
90
  }
84
91
  }
@@ -32,7 +32,7 @@ import { PeerScoreState, type PeerScoring } from './peer_scoring.js';
32
32
  const MAX_DIAL_ATTEMPTS = 3;
33
33
  const MAX_CACHED_PEERS = 100;
34
34
  const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
35
- const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
35
+ const DEFAULT_FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
36
36
  const GOODBYE_DIAL_TIMEOUT_MS = 1000;
37
37
  const FAILED_AUTH_HANDSHAKE_EXPIRY_MS = 60 * 60 * 1000; // 1 hour
38
38
 
@@ -515,7 +515,8 @@ export class PeerManager implements PeerManagerInterface {
515
515
  ...this.peerScoring.getStats(),
516
516
  });
517
517
 
518
- this.metrics.recordPeerCount(healthyConnections.length);
518
+ this.metrics.recordPeerCount(connections.length);
519
+ this.metrics.recordHealthyPeerCount(healthyConnections.length);
519
520
 
520
521
  // Exit if no peers to connect
521
522
  if (peersToConnect <= 0) {
@@ -775,7 +776,8 @@ export class PeerManager implements PeerManagerInterface {
775
776
  // Add to timed out peers
776
777
  this.timedOutPeers.set(id, {
777
778
  peerId: id,
778
- timeoutUntilMs: this.dateProvider.now() + FAILED_PEER_BAN_TIME_MS,
779
+ timeoutUntilMs:
780
+ this.dateProvider.now() + (this.config.peerFailedBanTimeMs ?? DEFAULT_FAILED_PEER_BAN_TIME_MS),
779
781
  });
780
782
  }
781
783
  }
@@ -937,6 +939,8 @@ export class PeerManager implements PeerManagerInterface {
937
939
  `Received auth for validator ${sender.toString()} from peer ${peerIdString}, but this validator is already authenticated to peer ${peerForAddress.toString()}`,
938
940
  { ...logData, address: sender.toString() },
939
941
  );
942
+ this.markAuthHandshakeFailed(peerId);
943
+ this.markPeerForDisconnect(peerId);
940
944
  return;
941
945
  }
942
946