@aztec/p2p 0.0.1-commit.343b43af6 → 0.0.1-commit.35158ae7e

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 (154) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +1 -1
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +20 -13
  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 +7 -1
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +10 -0
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +3 -3
  13. package/dest/mem_pools/attestation_pool/attestation_pool.js +3 -3
  14. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
  15. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  16. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  17. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  18. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  19. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  20. package/dest/mem_pools/tx_pool/priority.js +2 -3
  21. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  22. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
  24. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -5
  25. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +9 -2
  27. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +7 -1
  29. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  30. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +11 -4
  35. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
  36. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  37. package/dest/msg_validators/attestation_validator/attestation_validator.js +5 -4
  38. package/dest/msg_validators/clock_tolerance.d.ts +1 -1
  39. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  40. package/dest/msg_validators/clock_tolerance.js +4 -3
  41. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +1 -1
  42. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  43. package/dest/msg_validators/proposal_validator/proposal_validator.js +5 -5
  44. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  45. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  46. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  47. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  48. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  49. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  50. package/dest/msg_validators/tx_validator/factory.d.ts +9 -1
  51. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  52. package/dest/msg_validators/tx_validator/factory.js +23 -4
  53. package/dest/msg_validators/tx_validator/phases_validator.d.ts +21 -1
  54. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  55. package/dest/msg_validators/tx_validator/phases_validator.js +28 -1
  56. package/dest/services/discv5/discV5_service.d.ts +1 -1
  57. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  58. package/dest/services/discv5/discV5_service.js +4 -2
  59. package/dest/services/encoding.d.ts +5 -1
  60. package/dest/services/encoding.d.ts.map +1 -1
  61. package/dest/services/encoding.js +7 -1
  62. package/dest/services/libp2p/libp2p_service.d.ts +7 -9
  63. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  64. package/dest/services/libp2p/libp2p_service.js +121 -57
  65. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  66. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  67. package/dest/services/peer-manager/peer_manager.js +4 -2
  68. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +11 -8
  69. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  70. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +69 -65
  71. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +3 -2
  72. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  73. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +5 -4
  74. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  75. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +13 -7
  76. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +3 -1
  77. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  78. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +3 -0
  79. package/dest/services/reqresp/reqresp.d.ts +1 -1
  80. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  81. package/dest/services/reqresp/reqresp.js +17 -9
  82. package/dest/services/service.d.ts +7 -1
  83. package/dest/services/service.d.ts.map +1 -1
  84. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -4
  85. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  86. package/dest/services/tx_collection/fast_tx_collection.js +57 -73
  87. package/dest/services/tx_collection/proposal_tx_collector.d.ts +6 -7
  88. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  89. package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
  90. package/dest/services/tx_collection/request_tracker.d.ts +53 -0
  91. package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
  92. package/dest/services/tx_collection/request_tracker.js +84 -0
  93. package/dest/services/tx_collection/slow_tx_collection.js +1 -1
  94. package/dest/services/tx_collection/tx_collection.d.ts +3 -6
  95. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  96. package/dest/test-helpers/mock-pubsub.d.ts +6 -1
  97. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  98. package/dest/test-helpers/mock-pubsub.js +9 -1
  99. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  100. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  101. package/dest/test-helpers/testbench-utils.js +20 -2
  102. package/dest/testbench/p2p_client_testbench_worker.js +3 -3
  103. package/dest/util.d.ts +8 -3
  104. package/dest/util.d.ts.map +1 -1
  105. package/dest/util.js +2 -9
  106. package/package.json +14 -14
  107. package/src/client/factory.ts +35 -18
  108. package/src/client/p2p_client.ts +19 -10
  109. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +3 -5
  110. package/src/config.ts +17 -0
  111. package/src/mem_pools/attestation_pool/attestation_pool.ts +3 -3
  112. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
  113. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  114. package/src/mem_pools/tx_pool/priority.ts +2 -5
  115. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  116. package/src/mem_pools/tx_pool_v2/interfaces.ts +6 -4
  117. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +11 -1
  118. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +13 -1
  119. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +12 -4
  120. package/src/msg_validators/attestation_validator/README.md +49 -0
  121. package/src/msg_validators/attestation_validator/attestation_validator.ts +5 -4
  122. package/src/msg_validators/clock_tolerance.ts +4 -3
  123. package/src/msg_validators/proposal_validator/README.md +123 -0
  124. package/src/msg_validators/proposal_validator/proposal_validator.ts +6 -5
  125. package/src/msg_validators/tx_validator/README.md +5 -1
  126. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  127. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  128. package/src/msg_validators/tx_validator/factory.ts +28 -1
  129. package/src/msg_validators/tx_validator/phases_validator.ts +31 -1
  130. package/src/services/discv5/discV5_service.ts +4 -2
  131. package/src/services/encoding.ts +9 -1
  132. package/src/services/libp2p/libp2p_service.ts +122 -69
  133. package/src/services/peer-manager/peer_manager.ts +5 -2
  134. package/src/services/reqresp/README.md +229 -0
  135. package/src/services/reqresp/batch-tx-requester/README.md +46 -7
  136. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +64 -69
  137. package/src/services/reqresp/batch-tx-requester/interface.ts +2 -1
  138. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +13 -6
  139. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +5 -0
  140. package/src/services/reqresp/reqresp.ts +19 -11
  141. package/src/services/service.ts +7 -0
  142. package/src/services/tx_collection/fast_tx_collection.ts +57 -83
  143. package/src/services/tx_collection/proposal_tx_collector.ts +8 -13
  144. package/src/services/tx_collection/request_tracker.ts +127 -0
  145. package/src/services/tx_collection/slow_tx_collection.ts +1 -1
  146. package/src/services/tx_collection/tx_collection.ts +3 -5
  147. package/src/test-helpers/mock-pubsub.ts +9 -0
  148. package/src/test-helpers/testbench-utils.ts +28 -3
  149. package/src/testbench/p2p_client_testbench_worker.ts +3 -5
  150. package/src/util.ts +8 -12
  151. package/dest/services/tx_collection/missing_txs_tracker.d.ts +0 -32
  152. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +0 -1
  153. package/dest/services/tx_collection/missing_txs_tracker.js +0 -27
  154. package/src/services/tx_collection/missing_txs_tracker.ts +0 -52
@@ -1,5 +1,7 @@
1
1
  import { MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS } from '@aztec/constants';
2
2
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
+ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
4
+ import { computeContractClassId } from '@aztec/stdlib/contract';
3
5
  import { computeCalldataHash } from '@aztec/stdlib/hash';
4
6
  import {
5
7
  TX_ERROR_CALLDATA_COUNT_MISMATCH,
@@ -9,7 +11,9 @@ import {
9
11
  TX_ERROR_CONTRACT_CLASS_LOG_LENGTH,
10
12
  TX_ERROR_CONTRACT_CLASS_LOG_SORTING,
11
13
  TX_ERROR_INCORRECT_CALLDATA,
14
+ TX_ERROR_INCORRECT_CONTRACT_CLASS_ID,
12
15
  TX_ERROR_INCORRECT_HASH,
16
+ TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG,
13
17
  Tx,
14
18
  type TxValidationResult,
15
19
  type TxValidator,
@@ -26,7 +30,8 @@ export class DataTxValidator implements TxValidator<Tx> {
26
30
  const reason =
27
31
  (await this.#hasCorrectHash(tx)) ??
28
32
  (await this.#hasCorrectCalldata(tx)) ??
29
- (await this.#hasCorrectContractClassLogs(tx));
33
+ (await this.#hasCorrectContractClassLogs(tx)) ??
34
+ (await this.#hasCorrectContractClassIds(tx));
30
35
  return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' };
31
36
  }
32
37
 
@@ -127,4 +132,40 @@ export class DataTxValidator implements TxValidator<Tx> {
127
132
 
128
133
  return undefined;
129
134
  }
135
+
136
+ async #hasCorrectContractClassIds(tx: Tx): Promise<string | undefined> {
137
+ const contractClassLogs = tx.getContractClassLogs();
138
+ for (const log of contractClassLogs) {
139
+ if (!ContractClassPublishedEvent.isContractClassPublishedEvent(log)) {
140
+ continue;
141
+ }
142
+
143
+ let event;
144
+ try {
145
+ event = ContractClassPublishedEvent.fromLog(log);
146
+ } catch (e) {
147
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract class event: ${e}`);
148
+ return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG;
149
+ }
150
+
151
+ try {
152
+ const { publicBytecodeCommitment } = await event.toContractClassPublicWithBytecodeCommitment();
153
+ const computedClassId = await computeContractClassId({
154
+ artifactHash: event.artifactHash,
155
+ privateFunctionsRoot: event.privateFunctionsRoot,
156
+ publicBytecodeCommitment,
157
+ });
158
+ if (!computedClassId.equals(event.contractClassId)) {
159
+ this.#log.warn(
160
+ `Rejecting tx ${tx.getTxHash()}: contract class id mismatch. Claimed ${event.contractClassId}, computed ${computedClassId}`,
161
+ );
162
+ return TX_ERROR_INCORRECT_CONTRACT_CLASS_ID;
163
+ }
164
+ } catch (e) {
165
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract class id: ${e}`);
166
+ return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG;
167
+ }
168
+ }
169
+ return undefined;
170
+ }
130
171
  }
@@ -53,12 +53,13 @@ import type { TxMetaData } from '../../mem_pools/tx_pool_v2/tx_metadata.js';
53
53
  import { AggregateTxValidator } from './aggregate_tx_validator.js';
54
54
  import { ArchiveCache } from './archive_cache.js';
55
55
  import { type ArchiveSource, BlockHeaderTxValidator } from './block_header_validator.js';
56
+ import { ContractInstanceTxValidator } from './contract_instance_validator.js';
56
57
  import { DataTxValidator } from './data_validator.js';
57
58
  import { DoubleSpendTxValidator, type NullifierSource } from './double_spend_validator.js';
58
59
  import { GasLimitsValidator, GasTxValidator } from './gas_validator.js';
59
60
  import { MetadataTxValidator } from './metadata_validator.js';
60
61
  import { NullifierCache } from './nullifier_cache.js';
61
- import { PhasesTxValidator } from './phases_validator.js';
62
+ import { AllowedSetupCallsMetaValidator, PhasesTxValidator } from './phases_validator.js';
62
63
  import { SizeTxValidator } from './size_validator.js';
63
64
  import { TimestampTxValidator } from './timestamp_validator.js';
64
65
  import { TxPermittedValidator } from './tx_permitted_validator.js';
@@ -167,6 +168,10 @@ export function createFirstStageTxValidationsForGossipedTransactions(
167
168
  validator: new DataTxValidator(bindings),
168
169
  severity: PeerErrorSeverity.MidToleranceError,
169
170
  },
171
+ contractInstanceValidator: {
172
+ validator: new ContractInstanceTxValidator(bindings),
173
+ severity: PeerErrorSeverity.MidToleranceError,
174
+ },
170
175
  };
171
176
  }
172
177
 
@@ -218,6 +223,7 @@ function createTxValidatorForMinimumTxIntegrityChecks(
218
223
  ),
219
224
  new SizeTxValidator(bindings),
220
225
  new DataTxValidator(bindings),
226
+ new ContractInstanceTxValidator(bindings),
221
227
  new TxProofValidator(verifier, bindings),
222
228
  );
223
229
  }
@@ -321,6 +327,7 @@ export function createTxValidatorForAcceptingTxsOverRPC(
321
327
  new BlockHeaderTxValidator(new ArchiveCache(db), bindings),
322
328
  new DoubleSpendTxValidator(new NullifierCache(db), bindings),
323
329
  new DataTxValidator(bindings),
330
+ new ContractInstanceTxValidator(bindings),
324
331
  ];
325
332
 
326
333
  if (!skipFeeEnforcement) {
@@ -436,5 +443,25 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
436
443
  new TimestampTxValidator<TxMetaData>({ timestamp, blockNumber }, bindings),
437
444
  new DoubleSpendTxValidator<TxMetaData>(nullifierSource, bindings),
438
445
  new BlockHeaderTxValidator<TxMetaData>(archiveSource, bindings),
446
+ new AllowedSetupCallsMetaValidator<TxMetaData>(bindings),
439
447
  );
440
448
  }
449
+
450
+ /**
451
+ * Creates a function that checks whether a tx's setup-phase calls are on the allow list.
452
+ *
453
+ * Uses the `PhasesTxValidator` on the full Tx. The result is stored as a boolean
454
+ * flag in `TxMetaData.allowedSetupCalls` at receipt time, so the pending pool
455
+ * migration validator can check it without needing the full Tx or its dependencies.
456
+ */
457
+ export function createCheckAllowedSetupCalls(
458
+ contractDataSource: ContractDataSource,
459
+ setupAllowList: AllowedElement[],
460
+ getTimestamp: () => UInt64,
461
+ ): (tx: Tx) => Promise<boolean> {
462
+ return async (tx: Tx) => {
463
+ const validator = new PhasesTxValidator(contractDataSource, setupAllowList, getTimestamp());
464
+ const result = await validator.validateTx(tx);
465
+ return result.result === 'valid';
466
+ };
467
+ }
@@ -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
+ }
@@ -96,7 +96,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
96
96
  lookupTimeout: 2000,
97
97
  requestTimeout: 2000,
98
98
  allowUnverifiedSessions: true,
99
- enrUpdate: !p2pIp ? true : false, // If no p2p IP is set, enrUpdate can automatically resolve it
99
+ enrUpdate: config.queryForIp && !p2pIp, // Enable native ENR IP discovery when no static IP is configured
100
100
  ...configOverrides.config,
101
101
  },
102
102
  metricsRegistry,
@@ -129,9 +129,11 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
129
129
  private onMultiaddrUpdated(m: Multiaddr) {
130
130
  // We want to update our tcp port to match the udp port
131
131
  // p2pBroadcastPort is optional on config, however it is set to default within the p2p client factory
132
- const multiAddrTcp = multiaddr(convertToMultiaddr(m.nodeAddress().address, this.config.p2pBroadcastPort!, 'tcp'));
132
+ const address = m.nodeAddress().address;
133
+ const multiAddrTcp = multiaddr(convertToMultiaddr(address, this.config.p2pBroadcastPort!, 'tcp'));
133
134
  this.enr.setLocationMultiaddr(multiAddrTcp);
134
135
  this.logger.info('Multiaddr updated', { multiaddr: multiAddrTcp.toString() });
136
+ this.emit('ip:changed', address);
135
137
  }
136
138
 
137
139
  public async start(): Promise<void> {
@@ -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,
@@ -52,9 +51,10 @@ import { yamux } from '@chainsafe/libp2p-yamux';
52
51
  import { bootstrap } from '@libp2p/bootstrap';
53
52
  import { identify } from '@libp2p/identify';
54
53
  import { type Message, type MultiaddrConnection, type PeerId, TopicValidatorResult } from '@libp2p/interface';
55
- import type { ConnectionManager } from '@libp2p/interface-internal';
54
+ import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal';
56
55
  import { mplex } from '@libp2p/mplex';
57
56
  import { tcp } from '@libp2p/tcp';
57
+ import { multiaddr } from '@multiformats/multiaddr';
58
58
  import { ENR } from '@nethermindeth/enr';
59
59
  import { createLibp2p } from 'libp2p';
60
60
 
@@ -131,7 +131,7 @@ type ValidationOutcome = { allPassed: true } | { allPassed: false; failure: Vali
131
131
  // REFACTOR: Unify with the type above
132
132
  type ReceivedMessageValidationResult<T, M = undefined> =
133
133
  | { obj: T; result: Exclude<TopicValidatorResult, TopicValidatorResult.Reject>; metadata?: M }
134
- | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M };
134
+ | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M; severity: PeerErrorSeverity };
135
135
 
136
136
  /**
137
137
  * Lib P2P implementation of the P2PService interface.
@@ -175,6 +175,10 @@ export class LibP2PService extends WithTracer implements P2PService {
175
175
  private checkpointReceivedCallback: P2PCheckpointReceivedCallback;
176
176
 
177
177
  private gossipSubEventHandler: (e: CustomEvent<GossipsubMessage>) => void;
178
+ private ipChangedHandler?: (ip: string) => void;
179
+
180
+ /** Discovered public IP address (set when queryForIp is enabled and no static IP was configured). */
181
+ private discoveredP2pIp?: string;
178
182
 
179
183
  private instrumentation: P2PInstrumentation;
180
184
 
@@ -226,7 +230,7 @@ export class LibP2PService extends WithTracer implements P2PService {
226
230
 
227
231
  const proposalValidatorOpts = {
228
232
  txsPermitted: !config.disableTransactions,
229
- maxTxsPerBlock: config.validateMaxTxsPerBlock,
233
+ maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
230
234
  };
231
235
  this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
232
236
  this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
@@ -443,8 +447,9 @@ export class LibP2PService extends WithTracer implements P2PService {
443
447
  topics: topicScoreParams,
444
448
  }),
445
449
  }) as (components: GossipSubComponents) => GossipSub,
446
- components: (components: { connectionManager: ConnectionManager }) => ({
450
+ components: (components: { connectionManager: ConnectionManager; addressManager: AddressManager }) => ({
447
451
  connectionManager: components.connectionManager,
452
+ addressManager: components.addressManager,
448
453
  }),
449
454
  },
450
455
  logger: createLibp2pComponentLogger(logger.module, logger.getBindings()),
@@ -503,10 +508,10 @@ export class LibP2PService extends WithTracer implements P2PService {
503
508
 
504
509
  // Get listen & announce addresses for logging
505
510
  const { p2pIp, p2pPort } = this.config;
506
- if (!p2pIp) {
511
+ if (!p2pIp && !this.config.queryForIp) {
507
512
  throw new Error('Announce address not provided.');
508
513
  }
509
- const announceTcpMultiaddr = convertToMultiaddr(p2pIp, p2pPort, 'tcp');
514
+ const announceTcpMultiaddr = p2pIp ? convertToMultiaddr(p2pIp, p2pPort, 'tcp') : undefined;
510
515
 
511
516
  // Create request response protocol handlers
512
517
  const txHandler = reqRespTxHandler(this.mempools);
@@ -560,6 +565,31 @@ export class LibP2PService extends WithTracer implements P2PService {
560
565
  if (!this.config.p2pDiscoveryDisabled) {
561
566
  await this.peerDiscoveryService.start();
562
567
  }
568
+
569
+ // When queryForIp is enabled and no static IP was configured, bridge discv5 IP discovery to libp2p.
570
+ // Discv5 discovers our public IP via peer WHOAREYOU exchanges (enrUpdate=true) and emits 'ip:changed'.
571
+ // We confirm the discovered address in the libp2p AddressManager so it appears in getMultiaddrs()
572
+ // and is pushed to all connected peers via the identify protocol.
573
+ if (this.config.queryForIp && !p2pIp) {
574
+ this.ipChangedHandler = (ip: string) => {
575
+ const addressManager = this.node.services.components.addressManager;
576
+ const newAddr = multiaddr(convertToMultiaddr(ip, this.config.p2pPort, 'tcp'));
577
+
578
+ // Remove old discovered IP if one exists
579
+ if (this.discoveredP2pIp) {
580
+ const oldAddr = multiaddr(convertToMultiaddr(this.discoveredP2pIp, this.config.p2pPort, 'tcp'));
581
+ addressManager.removeObservedAddr(oldAddr);
582
+ }
583
+
584
+ addressManager.addObservedAddr(newAddr);
585
+ addressManager.confirmObservedAddr(newAddr);
586
+ // Store discovered IP
587
+ this.discoveredP2pIp = ip;
588
+ this.logger.info('Public IP discovered via discv5', { ip });
589
+ };
590
+ this.peerDiscoveryService.on('ip:changed', this.ipChangedHandler);
591
+ }
592
+
563
593
  this.discoveryRunningPromise = new RunningPromise(
564
594
  async () => {
565
595
  await this.peerManager.heartbeat();
@@ -572,7 +602,7 @@ export class LibP2PService extends WithTracer implements P2PService {
572
602
  this.logger.info(`Started P2P service`, {
573
603
  listen: this.config.listenAddress,
574
604
  port: this.config.p2pPort,
575
- announce: announceTcpMultiaddr,
605
+ announce: announceTcpMultiaddr ?? 'pending (queryForIp=true)',
576
606
  peerId: this.node.peerId.toString(),
577
607
  });
578
608
  }
@@ -585,6 +615,12 @@ export class LibP2PService extends WithTracer implements P2PService {
585
615
  // Remove gossip sub listener
586
616
  this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
587
617
 
618
+ // Remove ip:changed listener if registered
619
+ if (this.ipChangedHandler) {
620
+ this.peerDiscoveryService.off('ip:changed', this.ipChangedHandler);
621
+ this.ipChangedHandler = undefined;
622
+ }
623
+
588
624
  // Stop peer manager
589
625
  this.logger.debug('Stopping peer manager...');
590
626
  await this.peerManager.stop();
@@ -883,30 +919,56 @@ export class LibP2PService extends WithTracer implements P2PService {
883
919
  source: PeerId,
884
920
  topicType: TopicType,
885
921
  ): Promise<ReceivedMessageValidationResult<T, M>> {
886
- let resultAndObj: ReceivedMessageValidationResult<T, M> = { result: TopicValidatorResult.Reject };
922
+ // Default to reject result with a penalty if validation function throws an error
923
+ let resultAndObj: ReceivedMessageValidationResult<T, M> = {
924
+ result: TopicValidatorResult.Reject,
925
+ severity: PeerErrorSeverity.MidToleranceError,
926
+ };
887
927
  const timer = new Timer();
888
928
  try {
889
929
  resultAndObj = await validationFunc();
890
930
  } catch (err) {
891
- this.peerManager.penalizePeer(source, PeerErrorSeverity.LowToleranceError);
892
- this.logger.error(`Error deserializing and validating gossipsub message`, err, {
893
- msgId,
894
- source: source.toString(),
895
- topicType,
896
- });
931
+ this.logger.error(`Error validating gossipsub message`, err, { msgId, source: source.toString(), topicType });
897
932
  }
898
933
 
899
934
  if (resultAndObj.result === TopicValidatorResult.Accept) {
935
+ this.logger.debug(`Message ${topicType} accepted by validator`, { msgId, source: source.toString(), topicType });
900
936
  this.instrumentation.recordMessageValidation(topicType, timer);
937
+ } else if (resultAndObj.result === TopicValidatorResult.Reject) {
938
+ this.logger.warn(`Message ${topicType} rejected by validator with severity ${resultAndObj.severity}`, {
939
+ msgId,
940
+ source: source.toString(),
941
+ topicType,
942
+ severity: resultAndObj.severity,
943
+ });
944
+ this.peerManager.penalizePeer(source, resultAndObj.severity);
945
+ } else {
946
+ this.logger.trace(`Message ${topicType} ignored by validator`, { msgId, source: source.toString(), topicType });
901
947
  }
902
948
 
903
949
  this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), resultAndObj.result);
904
950
  return resultAndObj;
905
951
  }
906
952
 
953
+ private tryDeserialize<T>(deserializeFunc: () => T, msgId: string, source: PeerId): T | undefined {
954
+ try {
955
+ return deserializeFunc();
956
+ } catch (err) {
957
+ this.logger.warn(`Failed to deserialize gossipsub message from buffer`, {
958
+ err,
959
+ msgId,
960
+ source: source.toString(),
961
+ });
962
+ return undefined;
963
+ }
964
+ }
965
+
907
966
  protected async handleGossipedTx(payloadData: Buffer, msgId: string, source: PeerId) {
908
967
  const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
909
- const tx = Tx.fromBuffer(payloadData);
968
+ const tx = this.tryDeserialize(() => Tx.fromBuffer(payloadData), msgId, source);
969
+ if (!tx) {
970
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.LowToleranceError };
971
+ }
910
972
 
911
973
  const currentBlockNumber = await this.archiver.getBlockNumber();
912
974
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
@@ -931,8 +993,7 @@ export class LibP2PService extends WithTracer implements P2PService {
931
993
  severity,
932
994
  source: source.toString(),
933
995
  });
934
- this.peerManager.penalizePeer(source, severity);
935
- return { result: TopicValidatorResult.Reject };
996
+ return { result: TopicValidatorResult.Reject, severity };
936
997
  }
937
998
 
938
999
  // Pool pre-check: see if the pool would accept this tx before doing expensive proof verification
@@ -954,8 +1015,7 @@ export class LibP2PService extends WithTracer implements P2PService {
954
1015
  severity,
955
1016
  source: source.toString(),
956
1017
  });
957
- this.peerManager.penalizePeer(source, severity);
958
- return { result: TopicValidatorResult.Reject };
1018
+ return { result: TopicValidatorResult.Reject, severity };
959
1019
  }
960
1020
 
961
1021
  // Pool add: persist the tx
@@ -976,7 +1036,11 @@ export class LibP2PService extends WithTracer implements P2PService {
976
1036
  } else if (wasIgnored) {
977
1037
  return { result: TopicValidatorResult.Ignore, obj: tx };
978
1038
  } else {
979
- return { result: TopicValidatorResult.Reject };
1039
+ this.logger.warn(`Gossiped tx ${txHash.toString()} unexpectedly rejected by pool`, {
1040
+ source: source.toString(),
1041
+ txHash: txHash.toString(),
1042
+ });
1043
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
980
1044
  }
981
1045
  };
982
1046
 
@@ -1006,7 +1070,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1006
1070
  source: PeerId,
1007
1071
  ): Promise<void> {
1008
1072
  const { result, obj: attestation } = await this.validateReceivedMessage<CheckpointAttestation>(
1009
- () => this.validateAndStoreCheckpointAttestation(source, CheckpointAttestation.fromBuffer(payloadData)),
1073
+ () => {
1074
+ const attestation = this.tryDeserialize(() => CheckpointAttestation.fromBuffer(payloadData), msgId, source);
1075
+ if (!attestation) {
1076
+ return Promise.resolve({
1077
+ result: TopicValidatorResult.Reject,
1078
+ severity: PeerErrorSeverity.LowToleranceError,
1079
+ });
1080
+ }
1081
+ return this.validateAndStoreCheckpointAttestation(source, attestation);
1082
+ },
1010
1083
  msgId,
1011
1084
  source,
1012
1085
  TopicType.checkpoint_attestation,
@@ -1039,8 +1112,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1039
1112
 
1040
1113
  if (validationResult.result === 'reject') {
1041
1114
  this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1042
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1043
- return { result: TopicValidatorResult.Reject };
1115
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1044
1116
  }
1045
1117
 
1046
1118
  if (validationResult.result === 'ignore') {
@@ -1066,16 +1138,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1066
1138
  return { result: TopicValidatorResult.Ignore, obj: attestation };
1067
1139
  }
1068
1140
 
1069
- // Could not add (cap reached for signer), no need to re-broadcast
1141
+ // Could not add (cap reached for signer), penalize and do not re-broadcast
1070
1142
  if (!added) {
1071
- this.logger.warn(`Dropping checkpoint attestation due to cap`, {
1143
+ this.logger.warn(`Rejecting checkpoint attestation due to cap`, {
1072
1144
  slot: slot.toString(),
1073
1145
  archive: attestation.archive.toString(),
1074
1146
  source: peerId.toString(),
1075
1147
  attester: attestation.getSender()?.toString(),
1076
1148
  count,
1077
1149
  });
1078
- return { result: TopicValidatorResult.Ignore, obj: attestation };
1150
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
1079
1151
  }
1080
1152
 
1081
1153
  // Check if this is a duplicate attestation (signer attested to a different proposal at the same slot)
@@ -1130,8 +1202,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1130
1202
 
1131
1203
  if (validationResult.result === 'reject') {
1132
1204
  this.logger.warn(`Penalizing peer ${peerId} for block proposal validation failure`);
1133
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1134
- return { result: TopicValidatorResult.Reject };
1205
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1135
1206
  }
1136
1207
 
1137
1208
  if (validationResult.result === 'ignore') {
@@ -1155,7 +1226,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1155
1226
 
1156
1227
  // Too many blocks received for this slot and index, penalize peer and do not re-broadcast
1157
1228
  if (!added) {
1158
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1159
1229
  this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
1160
1230
  ...block.toBlockInfo(),
1161
1231
  indexWithinCheckpoint: block.indexWithinCheckpoint,
@@ -1163,7 +1233,11 @@ export class LibP2PService extends WithTracer implements P2PService {
1163
1233
  proposer: block.getSender()?.toString(),
1164
1234
  source: peerId.toString(),
1165
1235
  });
1166
- return { result: TopicValidatorResult.Reject, metadata: { isEquivocated } };
1236
+ return {
1237
+ result: TopicValidatorResult.Reject,
1238
+ metadata: { isEquivocated },
1239
+ severity: PeerErrorSeverity.HighToleranceError,
1240
+ };
1167
1241
  }
1168
1242
 
1169
1243
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
@@ -1256,8 +1330,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1256
1330
 
1257
1331
  if (validationResult.result === 'reject') {
1258
1332
  this.logger.warn(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
1259
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1260
- return { result: TopicValidatorResult.Reject };
1333
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1261
1334
  }
1262
1335
 
1263
1336
  if (validationResult.result === 'ignore') {
@@ -1272,20 +1345,21 @@ export class LibP2PService extends WithTracer implements P2PService {
1272
1345
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1273
1346
  [Attributes.P2P_ID]: peerId.toString(),
1274
1347
  });
1275
- const {
1276
- result,
1277
- obj,
1278
- metadata: { isEquivocated } = {},
1279
- } = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1280
- if (result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1348
+ const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1349
+ const { obj, metadata: { isEquivocated } = {} } = blockProposalResult;
1350
+ if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1281
1351
  this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
1282
1352
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1283
1353
  [Attributes.P2P_ID]: peerId.toString(),
1284
1354
  isEquivocated,
1285
- result,
1355
+ result: blockProposalResult.result,
1286
1356
  });
1287
- return { result: TopicValidatorResult.Reject };
1288
- } else if (result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1357
+ return {
1358
+ result: TopicValidatorResult.Reject,
1359
+ severity:
1360
+ 'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError,
1361
+ };
1362
+ } else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1289
1363
  processBlock = true;
1290
1364
  }
1291
1365
  }
@@ -1312,13 +1386,17 @@ export class LibP2PService extends WithTracer implements P2PService {
1312
1386
  // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
1313
1387
  // Note: We still return the checkpoint obj so the lastBlock can be processed if valid
1314
1388
  if (!added) {
1315
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1316
1389
  this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
1317
1390
  ...checkpoint.toCheckpointInfo(),
1318
1391
  count,
1319
1392
  source: peerId.toString(),
1320
1393
  });
1321
- return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } };
1394
+ return {
1395
+ result: TopicValidatorResult.Reject,
1396
+ obj: checkpoint,
1397
+ metadata: { isEquivocated, processBlock },
1398
+ severity: PeerErrorSeverity.HighToleranceError,
1399
+ };
1322
1400
  }
1323
1401
 
1324
1402
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
@@ -1742,31 +1820,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1742
1820
  return PeerErrorSeverity.HighToleranceError;
1743
1821
  }
1744
1822
 
1745
- /**
1746
- * Validate a checkpoint attestation.
1747
- *
1748
- * @param attestation - The checkpoint attestation to validate.
1749
- * @returns True if the checkpoint attestation is valid, false otherwise.
1750
- */
1751
- @trackSpan('Libp2pService.validateCheckpointAttestation', async (_, attestation) => ({
1752
- [Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber,
1753
- [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
1754
- [Attributes.P2P_ID]: await attestation.p2pMessageLoggingIdentifier().then(i => i.toString()),
1755
- }))
1756
- public async validateCheckpointAttestation(
1757
- peerId: PeerId,
1758
- attestation: CheckpointAttestation,
1759
- ): Promise<P2PValidationResult> {
1760
- const result = await this.checkpointAttestationValidator.validate(attestation);
1761
-
1762
- if (result.result === 'reject') {
1763
- this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1764
- this.peerManager.penalizePeer(peerId, result.severity);
1765
- }
1766
-
1767
- return result;
1768
- }
1769
-
1770
1823
  public getPeerScore(peerId: PeerId): number {
1771
1824
  return this.node.services.pubsub.score.score(peerId.toString());
1772
1825
  }
@@ -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
 
@@ -776,7 +776,8 @@ export class PeerManager implements PeerManagerInterface {
776
776
  // Add to timed out peers
777
777
  this.timedOutPeers.set(id, {
778
778
  peerId: id,
779
- timeoutUntilMs: this.dateProvider.now() + FAILED_PEER_BAN_TIME_MS,
779
+ timeoutUntilMs:
780
+ this.dateProvider.now() + (this.config.peerFailedBanTimeMs ?? DEFAULT_FAILED_PEER_BAN_TIME_MS),
780
781
  });
781
782
  }
782
783
  }
@@ -938,6 +939,8 @@ export class PeerManager implements PeerManagerInterface {
938
939
  `Received auth for validator ${sender.toString()} from peer ${peerIdString}, but this validator is already authenticated to peer ${peerForAddress.toString()}`,
939
940
  { ...logData, address: sender.toString() },
940
941
  );
942
+ this.markAuthHandshakeFailed(peerId);
943
+ this.markPeerForDisconnect(peerId);
941
944
  return;
942
945
  }
943
946