@aztec/p2p 1.2.1 → 2.0.0-nightly.20250813

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 (221) hide show
  1. package/dest/client/factory.d.ts +5 -1
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +29 -12
  4. package/dest/client/interface.d.ts +8 -13
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +18 -22
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +86 -83
  9. package/dest/config.d.ts +30 -1
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +34 -1
  12. package/dest/index.d.ts +1 -0
  13. package/dest/index.d.ts.map +1 -1
  14. package/dest/index.js +1 -0
  15. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +13 -1
  16. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  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 +117 -10
  19. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +4 -1
  20. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +22 -1
  22. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +4 -1
  23. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +21 -1
  25. package/dest/mem_pools/attestation_pool/mocks.d.ts +1 -2
  26. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  27. package/dest/mem_pools/attestation_pool/mocks.js +2 -10
  28. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +8 -3
  29. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +64 -37
  31. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +8 -3
  32. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
  33. package/dest/mem_pools/tx_pool/memory_tx_pool.js +18 -10
  34. package/dest/mem_pools/tx_pool/tx_pool.d.ts +11 -2
  35. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
  36. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +73 -44
  38. package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
  39. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  40. package/dest/msg_validators/tx_validator/block_header_validator.js +2 -2
  41. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  42. package/dest/msg_validators/tx_validator/data_validator.js +35 -59
  43. package/dest/msg_validators/tx_validator/double_spend_validator.js +2 -2
  44. package/dest/msg_validators/tx_validator/gas_validator.js +4 -4
  45. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  46. package/dest/msg_validators/tx_validator/metadata_validator.js +21 -21
  47. package/dest/msg_validators/tx_validator/phases_validator.js +3 -3
  48. package/dest/msg_validators/tx_validator/tx_proof_validator.js +3 -3
  49. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  50. package/dest/services/discv5/discV5_service.js +4 -1
  51. package/dest/services/dummy_service.d.ts +13 -3
  52. package/dest/services/dummy_service.d.ts.map +1 -1
  53. package/dest/services/dummy_service.js +26 -3
  54. package/dest/services/index.d.ts +3 -1
  55. package/dest/services/index.d.ts.map +1 -1
  56. package/dest/services/index.js +3 -1
  57. package/dest/services/libp2p/libp2p_service.d.ts +13 -20
  58. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  59. package/dest/services/libp2p/libp2p_service.js +123 -46
  60. package/dest/services/peer-manager/interface.d.ts +8 -1
  61. package/dest/services/peer-manager/interface.d.ts.map +1 -1
  62. package/dest/services/peer-manager/peer_manager.d.ts +70 -3
  63. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  64. package/dest/services/peer-manager/peer_manager.js +369 -39
  65. package/dest/services/reqresp/config.d.ts +3 -3
  66. package/dest/services/reqresp/config.d.ts.map +1 -1
  67. package/dest/services/reqresp/config.js +3 -3
  68. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +3 -4
  69. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
  70. package/dest/services/reqresp/connection-sampler/connection_sampler.js +35 -52
  71. package/dest/services/reqresp/index.d.ts +2 -1
  72. package/dest/services/reqresp/index.d.ts.map +1 -1
  73. package/dest/services/reqresp/index.js +2 -1
  74. package/dest/services/reqresp/interface.d.ts +61 -10
  75. package/dest/services/reqresp/interface.d.ts.map +1 -1
  76. package/dest/services/reqresp/interface.js +41 -6
  77. package/dest/services/reqresp/protocols/auth.d.ts +43 -0
  78. package/dest/services/reqresp/protocols/auth.d.ts.map +1 -0
  79. package/dest/services/reqresp/protocols/auth.js +71 -0
  80. package/dest/services/reqresp/protocols/block.d.ts +5 -0
  81. package/dest/services/reqresp/protocols/block.d.ts.map +1 -1
  82. package/dest/services/reqresp/protocols/block.js +28 -5
  83. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +30 -0
  84. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -0
  85. package/dest/services/reqresp/protocols/block_txs/bitvector.js +75 -0
  86. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +11 -0
  87. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -0
  88. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +39 -0
  89. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +49 -0
  90. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -0
  91. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +75 -0
  92. package/dest/services/reqresp/protocols/block_txs/index.d.ts +4 -0
  93. package/dest/services/reqresp/protocols/block_txs/index.d.ts.map +1 -0
  94. package/dest/services/reqresp/protocols/block_txs/index.js +3 -0
  95. package/dest/services/reqresp/protocols/goodbye.js +3 -5
  96. package/dest/services/reqresp/protocols/index.d.ts +2 -0
  97. package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
  98. package/dest/services/reqresp/protocols/index.js +2 -0
  99. package/dest/services/reqresp/protocols/status.d.ts +2 -0
  100. package/dest/services/reqresp/protocols/status.d.ts.map +1 -1
  101. package/dest/services/reqresp/protocols/status.js +7 -0
  102. package/dest/services/reqresp/protocols/tx.d.ts +12 -1
  103. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  104. package/dest/services/reqresp/protocols/tx.js +34 -6
  105. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
  106. package/dest/services/reqresp/rate-limiter/rate_limits.js +20 -0
  107. package/dest/services/reqresp/reqresp.d.ts +37 -39
  108. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  109. package/dest/services/reqresp/reqresp.js +220 -220
  110. package/dest/services/reqresp/status.d.ts +8 -3
  111. package/dest/services/reqresp/status.d.ts.map +1 -1
  112. package/dest/services/reqresp/status.js +6 -2
  113. package/dest/services/service.d.ts +10 -10
  114. package/dest/services/service.d.ts.map +1 -1
  115. package/dest/services/tx_collection/config.d.ts +25 -0
  116. package/dest/services/tx_collection/config.d.ts.map +1 -0
  117. package/dest/services/tx_collection/config.js +58 -0
  118. package/dest/services/tx_collection/fast_tx_collection.d.ts +56 -0
  119. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -0
  120. package/dest/services/tx_collection/fast_tx_collection.js +295 -0
  121. package/dest/services/tx_collection/index.d.ts +3 -0
  122. package/dest/services/tx_collection/index.d.ts.map +1 -0
  123. package/dest/services/tx_collection/index.js +2 -0
  124. package/dest/services/tx_collection/instrumentation.d.ts +10 -0
  125. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -0
  126. package/dest/services/tx_collection/instrumentation.js +34 -0
  127. package/dest/services/tx_collection/slow_tx_collection.d.ts +54 -0
  128. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -0
  129. package/dest/services/tx_collection/slow_tx_collection.js +176 -0
  130. package/dest/services/tx_collection/tx_collection.d.ts +109 -0
  131. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -0
  132. package/dest/services/tx_collection/tx_collection.js +127 -0
  133. package/dest/services/tx_collection/tx_collection_sink.d.ts +30 -0
  134. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -0
  135. package/dest/services/tx_collection/tx_collection_sink.js +81 -0
  136. package/dest/services/tx_collection/tx_source.d.ts +18 -0
  137. package/dest/services/tx_collection/tx_source.d.ts.map +1 -0
  138. package/dest/services/tx_collection/tx_source.js +31 -0
  139. package/dest/services/tx_provider.d.ts +49 -0
  140. package/dest/services/tx_provider.d.ts.map +1 -0
  141. package/dest/services/tx_provider.js +206 -0
  142. package/dest/services/{tx_collect_instrumentation.d.ts → tx_provider_instrumentation.d.ts} +2 -2
  143. package/dest/services/tx_provider_instrumentation.d.ts.map +1 -0
  144. package/dest/services/{tx_collect_instrumentation.js → tx_provider_instrumentation.js} +5 -5
  145. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  146. package/dest/test-helpers/make-test-p2p-clients.js +4 -3
  147. package/dest/test-helpers/mock-pubsub.d.ts +2 -1
  148. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  149. package/dest/test-helpers/mock-pubsub.js +2 -1
  150. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  151. package/dest/test-helpers/reqresp-nodes.js +8 -4
  152. package/dest/testbench/p2p_client_testbench_worker.js +11 -5
  153. package/dest/util.d.ts +1 -1
  154. package/dest/util.d.ts.map +1 -1
  155. package/package.json +14 -15
  156. package/src/client/factory.ts +87 -12
  157. package/src/client/interface.ts +19 -15
  158. package/src/client/p2p_client.ts +108 -92
  159. package/src/config.ts +52 -1
  160. package/src/index.ts +1 -0
  161. package/src/mem_pools/attestation_pool/attestation_pool.ts +15 -1
  162. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +155 -4
  163. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +28 -1
  164. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +28 -2
  165. package/src/mem_pools/attestation_pool/mocks.ts +1 -3
  166. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +59 -41
  167. package/src/mem_pools/tx_pool/memory_tx_pool.ts +19 -9
  168. package/src/mem_pools/tx_pool/tx_pool.ts +7 -2
  169. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +63 -40
  170. package/src/msg_validators/attestation_validator/attestation_validator.ts +1 -1
  171. package/src/msg_validators/tx_validator/block_header_validator.ts +2 -2
  172. package/src/msg_validators/tx_validator/data_validator.ts +36 -27
  173. package/src/msg_validators/tx_validator/double_spend_validator.ts +2 -2
  174. package/src/msg_validators/tx_validator/gas_validator.ts +4 -4
  175. package/src/msg_validators/tx_validator/metadata_validator.ts +22 -28
  176. package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
  177. package/src/msg_validators/tx_validator/tx_proof_validator.ts +2 -2
  178. package/src/services/discv5/discV5_service.ts +4 -1
  179. package/src/services/dummy_service.ts +44 -4
  180. package/src/services/index.ts +3 -1
  181. package/src/services/libp2p/libp2p_service.ts +147 -55
  182. package/src/services/peer-manager/interface.ts +10 -1
  183. package/src/services/peer-manager/peer_manager.ts +441 -41
  184. package/src/services/reqresp/config.ts +3 -3
  185. package/src/services/reqresp/connection-sampler/connection_sampler.ts +38 -63
  186. package/src/services/reqresp/index.ts +2 -0
  187. package/src/services/reqresp/interface.ts +63 -17
  188. package/src/services/reqresp/protocols/auth.ts +83 -0
  189. package/src/services/reqresp/protocols/block.ts +24 -3
  190. package/src/services/reqresp/protocols/block_txs/bitvector.ts +90 -0
  191. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +53 -0
  192. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +79 -0
  193. package/src/services/reqresp/protocols/block_txs/index.ts +3 -0
  194. package/src/services/reqresp/protocols/goodbye.ts +3 -3
  195. package/src/services/reqresp/protocols/index.ts +2 -0
  196. package/src/services/reqresp/protocols/status.ts +20 -0
  197. package/src/services/reqresp/protocols/tx.ts +35 -6
  198. package/src/services/reqresp/rate-limiter/rate_limits.ts +20 -0
  199. package/src/services/reqresp/reqresp.ts +294 -264
  200. package/src/services/reqresp/status.ts +9 -3
  201. package/src/services/service.ts +23 -14
  202. package/src/services/tx_collection/config.ts +84 -0
  203. package/src/services/tx_collection/fast_tx_collection.ts +338 -0
  204. package/src/services/tx_collection/index.ts +2 -0
  205. package/src/services/tx_collection/instrumentation.ts +43 -0
  206. package/src/services/tx_collection/slow_tx_collection.ts +232 -0
  207. package/src/services/tx_collection/tx_collection.ts +214 -0
  208. package/src/services/tx_collection/tx_collection_sink.ts +98 -0
  209. package/src/services/tx_collection/tx_source.ts +37 -0
  210. package/src/services/tx_provider.ts +215 -0
  211. package/src/services/{tx_collect_instrumentation.ts → tx_provider_instrumentation.ts} +5 -5
  212. package/src/test-helpers/make-test-p2p-clients.ts +4 -2
  213. package/src/test-helpers/mock-pubsub.ts +1 -0
  214. package/src/test-helpers/reqresp-nodes.ts +7 -1
  215. package/src/testbench/p2p_client_testbench_worker.ts +9 -2
  216. package/src/util.ts +1 -1
  217. package/dest/services/tx_collect_instrumentation.d.ts.map +0 -1
  218. package/dest/services/tx_collector.d.ts +0 -23
  219. package/dest/services/tx_collector.d.ts.map +0 -1
  220. package/dest/services/tx_collector.js +0 -95
  221. package/src/services/tx_collector.ts +0 -134
@@ -0,0 +1,215 @@
1
+ import { compactArray } from '@aztec/foundation/collection';
2
+ import { type Logger, createLogger } from '@aztec/foundation/log';
3
+ import { elapsed } from '@aztec/foundation/timer';
4
+ import type { BlockInfo, L2Block } from '@aztec/stdlib/block';
5
+ import type { ITxProvider } from '@aztec/stdlib/interfaces/server';
6
+ import type { BlockProposal } from '@aztec/stdlib/p2p';
7
+ import { Tx, TxHash } from '@aztec/stdlib/tx';
8
+ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
9
+
10
+ import type { PeerId } from '@libp2p/interface';
11
+
12
+ import type { P2PClient } from '../client/p2p_client.js';
13
+ import type { TxPool } from '../mem_pools/index.js';
14
+ import type { FastCollectionRequestInput, TxCollection } from './tx_collection/tx_collection.js';
15
+ import { TxProviderInstrumentation } from './tx_provider_instrumentation.js';
16
+
17
+ /**
18
+ * Gathers and returns txs given a block proposal, block, or their hashes.
19
+ * Loads available txs from the tx pool, and relies on a TxCollection service to collect txs from the network and other nodes.
20
+ */
21
+ export class TxProvider implements ITxProvider {
22
+ protected instrumentation: TxProviderInstrumentation;
23
+
24
+ constructor(
25
+ private txCollection: TxCollection,
26
+ private txPool: TxPool,
27
+ private txValidator: Pick<P2PClient, 'validate'>,
28
+ private log: Logger = createLogger('p2p:tx-collector'),
29
+ client: TelemetryClient = getTelemetryClient(),
30
+ ) {
31
+ this.instrumentation = new TxProviderInstrumentation(client, 'TxProvider');
32
+ }
33
+
34
+ /** Returns txs from the tx pool given their hashes.*/
35
+ public async getAvailableTxs(txHashes: TxHash[]): Promise<{ txs: Tx[]; missingTxs: TxHash[] }> {
36
+ const response = await this.txPool.getTxsByHash(txHashes);
37
+ if (response.length !== txHashes.length) {
38
+ throw new Error(`Unexpected response size from tx pool: expected ${txHashes.length} but got ${response.length}`);
39
+ }
40
+ const txs: Tx[] = [];
41
+ const missingTxs: TxHash[] = [];
42
+
43
+ for (let i = 0; i < txHashes.length; i++) {
44
+ const tx = response[i];
45
+ if (tx === undefined) {
46
+ missingTxs.push(txHashes[i]);
47
+ } else {
48
+ txs.push(tx.setTxHash(txHashes[i]));
49
+ }
50
+ }
51
+
52
+ return { txs, missingTxs };
53
+ }
54
+
55
+ /** Gathers txs from the tx pool, proposal body, remote rpc nodes, and reqresp. */
56
+ public getTxsForBlockProposal(
57
+ blockProposal: BlockProposal,
58
+ opts: { pinnedPeer: PeerId | undefined; deadline: Date },
59
+ ): Promise<{ txs: Tx[]; missingTxs: TxHash[] }> {
60
+ return this.getOrderedTxsFromAllSources(
61
+ { type: 'proposal', blockProposal },
62
+ blockProposal.toBlockInfo(),
63
+ blockProposal.txHashes,
64
+ { ...opts, pinnedPeer: opts.pinnedPeer },
65
+ );
66
+ }
67
+
68
+ /** Gathers txs from the tx pool, remote rpc nodes, and reqresp. */
69
+ public getTxsForBlock(block: L2Block, opts: { deadline: Date }): Promise<{ txs: Tx[]; missingTxs: TxHash[] }> {
70
+ return this.getOrderedTxsFromAllSources(
71
+ { type: 'block', block },
72
+ block.toBlockInfo(),
73
+ block.body.txEffects.map(tx => tx.txHash),
74
+ { ...opts, pinnedPeer: undefined },
75
+ );
76
+ }
77
+
78
+ private async getOrderedTxsFromAllSources(
79
+ request: FastCollectionRequestInput,
80
+ blockInfo: BlockInfo,
81
+ txHashes: TxHash[],
82
+ opts: { pinnedPeer: PeerId | undefined; deadline: Date },
83
+ ) {
84
+ const [durationMs, result] = await elapsed(() => this.getTxsFromAllSources(request, blockInfo, txHashes, opts));
85
+ const { missingTxHashes, txsFromMempool, txsFromNetwork, txsFromProposal } = result;
86
+
87
+ const txs = [...(txsFromMempool ?? []), ...(txsFromProposal ?? []), ...(txsFromNetwork ?? [])];
88
+ const missingTxs = missingTxHashes?.length ?? 0;
89
+
90
+ const level = missingTxs === 0 ? 'verbose' : 'warn';
91
+ this.log[level](`Retrieved ${txs.length} out of ${blockInfo.txCount} txs for ${request.type}`, {
92
+ ...blockInfo,
93
+ txsFromProposal: txsFromProposal?.length,
94
+ txsFromMempool: txsFromMempool?.length,
95
+ txsFromNetwork: txsFromNetwork?.length,
96
+ missingTxs,
97
+ durationMs,
98
+ });
99
+
100
+ const orderedTxs = this.orderTxs(txs, txHashes);
101
+ if (orderedTxs.length + missingTxs !== txHashes.length) {
102
+ throw new Error(
103
+ `Error collecting txs for ${request.type} with ${txHashes.length} txs: found ${orderedTxs.length} and flagged ${missingTxs} as missing`,
104
+ );
105
+ }
106
+
107
+ return { txs: orderedTxs, missingTxs: (missingTxHashes ?? []).map(TxHash.fromString), durationMs };
108
+ }
109
+
110
+ private orderTxs(txs: Tx[], order: TxHash[]): Tx[] {
111
+ const txsMap = new Map(txs.map(tx => [tx.txHash.toString(), tx]));
112
+ return order.map(hash => txsMap.get(hash.toString())!).filter(tx => tx !== undefined);
113
+ }
114
+
115
+ private async getTxsFromAllSources(
116
+ request: FastCollectionRequestInput,
117
+ blockInfo: BlockInfo,
118
+ txHashes: TxHash[],
119
+ opts: { pinnedPeer: PeerId | undefined; deadline: Date },
120
+ ) {
121
+ const missingTxHashes = new Set(txHashes.map(txHash => txHash.toString()));
122
+ if (missingTxHashes.size === 0) {
123
+ this.log.debug(`Received request with no transactions`, blockInfo);
124
+ return {};
125
+ }
126
+
127
+ // First go to our tx pool and fetch whatever txs we have there
128
+ // We go to the mempool first since those txs are already validated
129
+ const txsFromMempool = compactArray(await this.txPool.getTxsByHash(txHashes));
130
+ txsFromMempool.forEach(tx => missingTxHashes.delete(tx.getTxHash().toString()));
131
+ this.instrumentation.incTxsFromMempool(txsFromMempool.length);
132
+ this.log.debug(
133
+ `Retrieved ${txsFromMempool.length} txs from mempool for block proposal (${missingTxHashes.size} pending)`,
134
+ { ...blockInfo, missingTxHashes: [...missingTxHashes] },
135
+ );
136
+
137
+ if (missingTxHashes.size === 0) {
138
+ return { txsFromMempool };
139
+ }
140
+
141
+ // Take txs from the proposal body if there are any
142
+ // Note that we still have to validate these txs, but we do it in parallel with tx collection
143
+ const proposal = request.type === 'proposal' ? request.blockProposal : undefined;
144
+ const txsFromProposal = this.extractFromProposal(proposal, [...missingTxHashes]);
145
+ if (txsFromProposal.length > 0) {
146
+ this.instrumentation.incTxsFromProposals(txsFromProposal.length);
147
+ txsFromProposal.forEach(tx => missingTxHashes.delete(tx.txHash.toString()));
148
+ this.log.debug(`Retrieved ${txsFromProposal.length} txs from proposal body (${missingTxHashes.size} pending)`, {
149
+ ...blockInfo,
150
+ missingTxHashes: [...missingTxHashes],
151
+ });
152
+ }
153
+
154
+ if (missingTxHashes.size === 0) {
155
+ await this.processProposalTxs(txsFromProposal);
156
+ return { txsFromMempool, txsFromProposal };
157
+ }
158
+
159
+ // Start tx collection from the network if needed, while we validate the txs taken from the proposal in parallel
160
+ const [txsFromNetwork] = await Promise.all([
161
+ this.txCollection.collectFastFor(request, [...missingTxHashes], opts),
162
+ this.processProposalTxs(txsFromProposal),
163
+ ] as const);
164
+
165
+ if (txsFromNetwork.length > 0) {
166
+ txsFromNetwork.forEach(tx => missingTxHashes.delete(tx.txHash.toString()));
167
+ this.instrumentation.incTxsFromP2P(txsFromNetwork.length);
168
+ this.log.debug(
169
+ `Retrieved ${txsFromNetwork.length} txs from network for block proposal (${missingTxHashes.size} pending)`,
170
+ { ...blockInfo, missingTxHashes: [...missingTxHashes] },
171
+ );
172
+ }
173
+
174
+ if (missingTxHashes.size === 0) {
175
+ return { txsFromNetwork, txsFromMempool, txsFromProposal };
176
+ }
177
+
178
+ // We are still missing txs, make one last attempt to collect them from our pool, in case they showed up somehow else
179
+ const moreTxsFromPool = compactArray(await this.txPool.getTxsByHash([...missingTxHashes].map(TxHash.fromString)));
180
+
181
+ if (moreTxsFromPool.length > 0) {
182
+ this.instrumentation.incTxsFromMempool(moreTxsFromPool.length);
183
+ this.log.debug(
184
+ `Retrieved ${moreTxsFromPool.length} txs from pool retry for block proposal (${missingTxHashes.size} pending)`,
185
+ { ...blockInfo, missingTxHashes: [...missingTxHashes] },
186
+ );
187
+ }
188
+
189
+ if (missingTxHashes.size > 0) {
190
+ this.instrumentation.incMissingTxs(missingTxHashes.size);
191
+ }
192
+
193
+ return {
194
+ txsFromNetwork,
195
+ txsFromMempool: [...txsFromMempool, ...moreTxsFromPool],
196
+ txsFromProposal,
197
+ missingTxHashes: [...missingTxHashes],
198
+ };
199
+ }
200
+
201
+ private extractFromProposal(proposal: BlockProposal | undefined, missingTxHashes: string[]): Tx[] {
202
+ if (!proposal) {
203
+ return [];
204
+ }
205
+ return compactArray(proposal.txs ?? []).filter(tx => missingTxHashes.includes(tx.getTxHash().toString()));
206
+ }
207
+
208
+ private async processProposalTxs(txs: Tx[]): Promise<void> {
209
+ if (txs.length === 0) {
210
+ return;
211
+ }
212
+ await this.txValidator.validate(txs);
213
+ await this.txPool.addTxs(txs);
214
+ }
215
+ }
@@ -1,6 +1,6 @@
1
1
  import { Metrics, type TelemetryClient, type UpDownCounter } from '@aztec/telemetry-client';
2
2
 
3
- export class TxCollectorInstrumentation {
3
+ export class TxProviderInstrumentation {
4
4
  private txFromProposalCount: UpDownCounter;
5
5
  private txFromMempoolCount: UpDownCounter;
6
6
  private txFromP2PCount: UpDownCounter;
@@ -9,19 +9,19 @@ export class TxCollectorInstrumentation {
9
9
  constructor(client: TelemetryClient, name: string) {
10
10
  const meter = client.getMeter(name);
11
11
 
12
- this.txFromProposalCount = meter.createUpDownCounter(Metrics.TX_COLLECTOR_TXS_FROM_PROPOSALS_COUNT, {
12
+ this.txFromProposalCount = meter.createUpDownCounter(Metrics.TX_PROVIDER_TXS_FROM_PROPOSALS_COUNT, {
13
13
  description: 'The number of txs taken from block proposals',
14
14
  });
15
15
 
16
- this.txFromMempoolCount = meter.createUpDownCounter(Metrics.TX_COLLECTOR_TXS_FROM_MEMPOOL_COUNT, {
16
+ this.txFromMempoolCount = meter.createUpDownCounter(Metrics.TX_PROVIDER_TXS_FROM_MEMPOOL_COUNT, {
17
17
  description: 'The number of txs taken from the local mempool',
18
18
  });
19
19
 
20
- this.txFromP2PCount = meter.createUpDownCounter(Metrics.TX_COLLECTOR_TXS_FROM_P2P_COUNT, {
20
+ this.txFromP2PCount = meter.createUpDownCounter(Metrics.TX_PROVIDER_TXS_FROM_P2P_COUNT, {
21
21
  description: 'The number of txs taken from the p2p network',
22
22
  });
23
23
 
24
- this.missingTxsCount = meter.createUpDownCounter(Metrics.TX_COLLECTOR_MISSING_TXS_COUNT, {
24
+ this.missingTxsCount = meter.createUpDownCounter(Metrics.TX_PROVIDER_MISSING_TXS_COUNT, {
25
25
  description: 'The number of txs not found anywhere',
26
26
  });
27
27
  }
@@ -2,6 +2,7 @@ import { MockL2BlockSource } from '@aztec/archiver/test';
2
2
  import type { EpochCache } from '@aztec/epoch-cache';
3
3
  import { SecretValue } from '@aztec/foundation/config';
4
4
  import { type Logger, createLogger } from '@aztec/foundation/log';
5
+ import { retryUntil } from '@aztec/foundation/retry';
5
6
  import { sleep } from '@aztec/foundation/sleep';
6
7
  import type { DataStoreConfig } from '@aztec/kv-store/config';
7
8
  import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
@@ -105,6 +106,7 @@ export async function makeTestP2PClient(
105
106
  mockEpochCache,
106
107
  'test-p2p-client',
107
108
  undefined,
109
+ undefined,
108
110
  {
109
111
  txPool: mockTxPool as unknown as TxPool,
110
112
  attestationPool: mockAttestationPool as unknown as AttestationPool,
@@ -147,7 +149,7 @@ export async function makeAndStartTestP2PClients(numberOfPeers: number, testConf
147
149
  clients.push(client);
148
150
  }
149
151
 
150
- await Promise.all(clients.map(client => client.isReady()));
152
+ await retryUntil(() => clients.every(c => c.isReady()), 'p2p clients started', 10, 0.5);
151
153
  testConfig.logger?.info(`Created and started ${clients.length} P2P clients at ports ${ports.join(',')}`, {
152
154
  ports,
153
155
  peerEnrs,
@@ -206,5 +208,5 @@ export async function makeTestP2PClients(numberOfPeers: number, testConfig: Make
206
208
 
207
209
  export async function startTestP2PClients(clients: P2PClient[]) {
208
210
  await Promise.all(clients.map(c => c.start()));
209
- await Promise.all(clients.map(c => c.isReady()));
211
+ await retryUntil(() => clients.every(c => c.isReady()), 'p2p clients started', 10, 0.5);
210
212
  }
@@ -110,6 +110,7 @@ export class MockPubSub implements PubSubLibp2p {
110
110
  class MockGossipSubService extends TypedEventEmitter<GossipsubEvents> implements GossipSubService {
111
111
  private logger = createLogger('p2p:test:mock-gossipsub');
112
112
  public subscribedTopics: Set<TopicStr> = new Set();
113
+ public readonly direct = new Set<string>();
113
114
 
114
115
  constructor(
115
116
  public peerId: PeerId,
@@ -151,10 +151,12 @@ export async function createTestLibP2PService<T extends P2PClientType>(
151
151
  reqresp,
152
152
  worldStateSynchronizer,
153
153
  protocolVersion,
154
+ epochCache,
154
155
  );
155
156
 
156
157
  p2pNode.services.pubsub.score.params.appSpecificWeight = 10;
157
- p2pNode.services.pubsub.score.params.appSpecificScore = (peerId: string) => peerManager.getPeerScore(peerId);
158
+ p2pNode.services.pubsub.score.params.appSpecificScore = (peerId: string) =>
159
+ peerManager.shouldDisableP2PGossip(peerId) ? -Infinity : peerManager.getPeerScore(peerId);
158
160
 
159
161
  return new LibP2PService<T>(
160
162
  clientType,
@@ -188,6 +190,8 @@ export const MOCK_SUB_PROTOCOL_HANDLERS: ReqRespSubProtocolHandlers = {
188
190
  [ReqRespSubProtocol.TX]: (_msg: any) => Promise.resolve(Buffer.from('tx')),
189
191
  [ReqRespSubProtocol.GOODBYE]: (_msg: any) => Promise.resolve(Buffer.from('goodbye')),
190
192
  [ReqRespSubProtocol.BLOCK]: (_msg: any) => Promise.resolve(Buffer.from('block')),
193
+ [ReqRespSubProtocol.AUTH]: (_msg: any) => Promise.resolve(Buffer.from('auth')),
194
+ [ReqRespSubProtocol.BLOCK_TXS]: (_msg: any) => Promise.resolve(Buffer.from('block_txs')),
191
195
  };
192
196
 
193
197
  // By default, all requests are valid
@@ -198,6 +202,8 @@ export const MOCK_SUB_PROTOCOL_VALIDATORS: ReqRespSubProtocolValidators = {
198
202
  [ReqRespSubProtocol.TX]: noopValidator,
199
203
  [ReqRespSubProtocol.GOODBYE]: noopValidator,
200
204
  [ReqRespSubProtocol.BLOCK]: noopValidator,
205
+ [ReqRespSubProtocol.AUTH]: noopValidator,
206
+ [ReqRespSubProtocol.BLOCK_TXS]: noopValidator,
201
207
  };
202
208
 
203
209
  /**
@@ -19,6 +19,7 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
19
19
 
20
20
  import type { Message, PeerId } from '@libp2p/interface';
21
21
  import { TopicValidatorResult } from '@libp2p/interface';
22
+ import EventEmitter from 'events';
22
23
 
23
24
  import type { P2PConfig } from '../config.js';
24
25
  import { createP2PClient } from '../index.js';
@@ -35,7 +36,7 @@ import type { PubSubLibp2p } from '../util.js';
35
36
  // Simple mock implementation
36
37
  function mockTxPool(): TxPool {
37
38
  // Mock all methods
38
- return {
39
+ const pool: Omit<TxPool, keyof EventEmitter> = {
39
40
  isEmpty: () => Promise.resolve(false),
40
41
  addTxs: () => Promise.resolve(1),
41
42
  getTxByHash: () => Promise.resolve(undefined),
@@ -54,6 +55,7 @@ function mockTxPool(): TxPool {
54
55
  updateConfig: () => {},
55
56
  markTxsAsNonEvictable: () => Promise.resolve(),
56
57
  };
58
+ return Object.assign(new EventEmitter(), pool);
57
59
  }
58
60
 
59
61
  function mockAttestationPool(): AttestationPool {
@@ -66,6 +68,8 @@ function mockAttestationPool(): AttestationPool {
66
68
  deleteAttestationsForSlotAndProposal: () => Promise.resolve(),
67
69
  getAttestationsForSlot: () => Promise.resolve([]),
68
70
  getAttestationsForSlotAndProposal: () => Promise.resolve([]),
71
+ addBlockProposal: () => Promise.resolve(),
72
+ getBlockProposal: () => Promise.resolve(undefined),
69
73
  };
70
74
  }
71
75
 
@@ -84,6 +88,8 @@ function mockEpochCache(): EpochCacheInterface {
84
88
  }),
85
89
  getEpochAndSlotInNextL1Slot: () => ({ epoch: 0n, slot: 0n, ts: 0n, now: 0n }),
86
90
  isInCommittee: () => Promise.resolve(false),
91
+ getRegisteredValidators: () => Promise.resolve([]),
92
+ filterInCommittee: () => Promise.resolve([]),
87
93
  };
88
94
  }
89
95
 
@@ -154,7 +160,7 @@ class TestLibP2PService<T extends P2PClientType = P2PClientType.Full> extends Li
154
160
  const tx = Tx.fromBuffer(p2pMessage.payload);
155
161
  this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), TopicValidatorResult.Accept);
156
162
 
157
- const txHash = await tx.getTxHash();
163
+ const txHash = tx.getTxHash();
158
164
  const txHashString = txHash.toString();
159
165
  this.logger.verbose(`Received tx ${txHashString} from external peer ${source.toString()}.`);
160
166
  await this.mempools.txPool.addTxs([tx]);
@@ -204,6 +210,7 @@ process.on('message', async msg => {
204
210
  worldState,
205
211
  epochCache,
206
212
  'test-p2p-bench-worker',
213
+ undefined,
207
214
  telemetry,
208
215
  deps,
209
216
  );
package/src/util.ts CHANGED
@@ -22,7 +22,7 @@ export interface PubSubLibp2p extends Pick<Libp2p, 'status' | 'start' | 'stop' |
22
22
  services: {
23
23
  pubsub: Pick<
24
24
  GossipSub,
25
- 'addEventListener' | 'removeEventListener' | 'publish' | 'subscribe' | 'reportMessageValidationResult'
25
+ 'addEventListener' | 'removeEventListener' | 'publish' | 'subscribe' | 'reportMessageValidationResult' | 'direct'
26
26
  > & { score: Pick<GossipSub['score'], 'score'> };
27
27
  };
28
28
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"tx_collect_instrumentation.d.ts","sourceRoot":"","sources":["../../src/services/tx_collect_instrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAE5F,qBAAa,0BAA0B;IACrC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,eAAe,CAAgB;gBAE3B,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM;IAoBjD,mBAAmB,CAAC,KAAK,EAAE,MAAM;IAIjC,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAI/B,aAAa,CAAC,KAAK,EAAE,MAAM;IAI3B,aAAa,CAAC,KAAK,EAAE,MAAM;CAG5B"}
@@ -1,23 +0,0 @@
1
- import { type Logger } from '@aztec/foundation/log';
2
- import type { ITxCollector } from '@aztec/stdlib/interfaces/server';
3
- import type { BlockProposal } from '@aztec/stdlib/p2p';
4
- import type { Tx, TxHash } from '@aztec/stdlib/tx';
5
- import { type TelemetryClient } from '@aztec/telemetry-client';
6
- import type { PeerId } from '@libp2p/interface';
7
- import type { P2PClient } from '../client/p2p_client.js';
8
- export declare class TxCollector implements ITxCollector {
9
- private p2pClient;
10
- private log;
11
- private instrumentation;
12
- constructor(p2pClient: Pick<P2PClient, 'getTxsByHashFromPool' | 'hasTxsInPool' | 'getTxsByHash' | 'validate' | 'requestTxsByHash' | 'addTxsToPool'>, log?: Logger, client?: TelemetryClient);
13
- private collectFromProposal;
14
- collectForBlockProposal(proposal: BlockProposal, peerWhoSentTheProposal: PeerId | undefined): Promise<{
15
- txs: Tx[];
16
- missing?: TxHash[];
17
- }>;
18
- collectTransactions(txHashes: TxHash[], peerWhoSentTheProposal: PeerId | undefined): Promise<{
19
- txs: Tx[];
20
- missing?: TxHash[];
21
- }>;
22
- }
23
- //# sourceMappingURL=tx_collector.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tx_collector.d.ts","sourceRoot":"","sources":["../../src/services/tx_collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAEnF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGzD,qBAAa,WAAY,YAAW,YAAY;IAG5C,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,GAAG;IANb,OAAO,CAAC,eAAe,CAA6B;gBAE1C,SAAS,EAAE,IAAI,CACrB,SAAS,EACT,sBAAsB,GAAG,cAAc,GAAG,cAAc,GAAG,UAAU,GAAG,kBAAkB,GAAG,cAAc,CAC5G,EACO,GAAG,GAAE,MAAyC,EACtD,MAAM,GAAE,eAAsC;YAMlC,mBAAmB;IAmD3B,uBAAuB,CAC3B,QAAQ,EAAE,aAAa,EACvB,sBAAsB,EAAE,MAAM,GAAG,SAAS,GACzC,OAAO,CAAC;QAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAmCvC,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,sBAAsB,EAAE,MAAM,GAAG,SAAS,GACzC,OAAO,CAAC;QAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAe9C"}
@@ -1,95 +0,0 @@
1
- import { compactArray } from '@aztec/foundation/collection';
2
- import { createLogger } from '@aztec/foundation/log';
3
- import { getTelemetryClient } from '@aztec/telemetry-client';
4
- import { TxCollectorInstrumentation } from './tx_collect_instrumentation.js';
5
- export class TxCollector {
6
- p2pClient;
7
- log;
8
- instrumentation;
9
- constructor(p2pClient, log = createLogger('p2p:tx-collector'), client = getTelemetryClient()){
10
- this.p2pClient = p2pClient;
11
- this.log = log;
12
- this.instrumentation = new TxCollectorInstrumentation(client, 'TxCollector');
13
- }
14
- // Checks the proposal for transactions we don't already have, validates them and adds them to our pool
15
- async collectFromProposal(proposal) {
16
- // Does this proposal have any transactions?
17
- if (!proposal.txs || proposal.txs.length === 0) {
18
- return 0;
19
- }
20
- const proposalHashes = new Set((proposal.payload.txHashes ?? []).map((txHash)=>txHash.toString()));
21
- // Get the transactions from the proposal and their hashes
22
- // also, we are only interested in txs that are part of the proposal
23
- const txsFromProposal = compactArray(await Promise.all(proposal.txs.map((tx)=>tx === undefined ? Promise.resolve(undefined) : tx.getTxHash().then((hash)=>({
24
- txHash: hash,
25
- tx
26
- }))))).filter((tx)=>proposalHashes.has(tx.txHash.toString()));
27
- // Of the transactions from the proposal, retrieve those that we have in the pool already
28
- const txsToValidate = [];
29
- const txsWeAlreadyHave = await this.p2pClient.getTxsByHashFromPool(txsFromProposal.map((tx)=>tx.txHash));
30
- // Txs we already have will have holes where we did not find them
31
- // Where that is the case we need to validate the tx in the proposal
32
- for(let i = 0; i < txsWeAlreadyHave.length; i++){
33
- if (txsWeAlreadyHave[i] === undefined) {
34
- txsToValidate.push(txsFromProposal[i].tx);
35
- }
36
- }
37
- // Now validate all the transactions from the proposal that we don't have
38
- // This will throw if any of the transactions are invalid, this is probably correct, if someone sends us a proposal with invalid
39
- // transactions we probably shouldn't spend any more effort on it
40
- try {
41
- await this.p2pClient.validate(txsToValidate);
42
- } catch (err) {
43
- this.log.error(`Received proposal with invalid transactions, skipping`);
44
- throw err;
45
- }
46
- // Now store these transactions in our pool, provided these are the txs in proposal.payload.txHashes they will be pinned already
47
- await this.p2pClient.addTxsToPool(txsToValidate);
48
- return txsToValidate.length;
49
- }
50
- async collectForBlockProposal(proposal, peerWhoSentTheProposal) {
51
- if (proposal.payload.txHashes.length === 0) {
52
- this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
53
- return {
54
- txs: []
55
- };
56
- }
57
- const txsInMempool = (await this.p2pClient.hasTxsInPool(proposal.payload.txHashes)).filter(Boolean).length;
58
- this.instrumentation.incTxsFromMempool(txsInMempool);
59
- // Take txs from the proposal if there are any
60
- const txTakenFromProposal = await this.collectFromProposal(proposal);
61
- this.instrumentation.incTxsFromProposals(txTakenFromProposal);
62
- // Now get the txs we need, either from the pool or the p2p network
63
- const txHashes = proposal.payload.txHashes;
64
- const { txs, missing } = await this.collectTransactions(txHashes, peerWhoSentTheProposal);
65
- this.instrumentation.incMissingTxs(missing?.length ?? 0);
66
- const txsFromP2P = txHashes.length - txTakenFromProposal - txsInMempool - (missing?.length ?? 0);
67
- this.instrumentation.incTxsFromP2P(txsFromP2P);
68
- this.log.info(`Retrieved ${txs.length}/${txHashes.length} txs for block proposal`, {
69
- blockNumber: proposal.blockNumber,
70
- slotNumber: proposal.slotNumber.toNumber(),
71
- totalTxsInProposal: txHashes.length,
72
- txsFromProposal: txTakenFromProposal,
73
- txsFromMempool: txsInMempool,
74
- txsFromP2P,
75
- missingTxs: missing?.length ?? 0
76
- });
77
- return {
78
- txs,
79
- missing
80
- };
81
- }
82
- async collectTransactions(txHashes, peerWhoSentTheProposal) {
83
- // This will request from the network any txs that are missing
84
- // NOTE: this could still return missing txs so we need to (1) be careful to handle undefined and (2) keep the txs in the correct order for re-execution
85
- const maybeRetrievedTxs = await this.p2pClient.getTxsByHash(txHashes, peerWhoSentTheProposal);
86
- // Get the txs that we didn't get from the network, if any. This will be empty if we got them al
87
- const missingTxs = compactArray(maybeRetrievedTxs.map((tx, index)=>tx === undefined ? txHashes[index] : undefined));
88
- // if we found all txs, this is a noop. If we didn't find all txs then tell the validator to skip attestations because missingTxs.length > 0
89
- const retrievedTxs = compactArray(maybeRetrievedTxs);
90
- return {
91
- txs: retrievedTxs,
92
- missing: missingTxs
93
- };
94
- }
95
- }
@@ -1,134 +0,0 @@
1
- import { compactArray } from '@aztec/foundation/collection';
2
- import { type Logger, createLogger } from '@aztec/foundation/log';
3
- import type { ITxCollector } from '@aztec/stdlib/interfaces/server';
4
- import type { BlockProposal } from '@aztec/stdlib/p2p';
5
- import type { Tx, TxHash } from '@aztec/stdlib/tx';
6
- import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
7
-
8
- import type { PeerId } from '@libp2p/interface';
9
-
10
- import type { P2PClient } from '../client/p2p_client.js';
11
- import { TxCollectorInstrumentation } from './tx_collect_instrumentation.js';
12
-
13
- export class TxCollector implements ITxCollector {
14
- private instrumentation: TxCollectorInstrumentation;
15
- constructor(
16
- private p2pClient: Pick<
17
- P2PClient,
18
- 'getTxsByHashFromPool' | 'hasTxsInPool' | 'getTxsByHash' | 'validate' | 'requestTxsByHash' | 'addTxsToPool'
19
- >,
20
- private log: Logger = createLogger('p2p:tx-collector'),
21
- client: TelemetryClient = getTelemetryClient(),
22
- ) {
23
- this.instrumentation = new TxCollectorInstrumentation(client, 'TxCollector');
24
- }
25
-
26
- // Checks the proposal for transactions we don't already have, validates them and adds them to our pool
27
- private async collectFromProposal(proposal: BlockProposal): Promise<number> {
28
- // Does this proposal have any transactions?
29
- if (!proposal.txs || proposal.txs.length === 0) {
30
- return 0;
31
- }
32
-
33
- const proposalHashes = new Set<string>((proposal.payload.txHashes ?? []).map(txHash => txHash.toString()));
34
-
35
- // Get the transactions from the proposal and their hashes
36
- // also, we are only interested in txs that are part of the proposal
37
- const txsFromProposal = compactArray(
38
- await Promise.all(
39
- proposal.txs.map(tx =>
40
- tx === undefined
41
- ? Promise.resolve(undefined)
42
- : tx.getTxHash().then(hash => ({
43
- txHash: hash,
44
- tx,
45
- })),
46
- ),
47
- ),
48
- ).filter(tx => proposalHashes.has(tx.txHash.toString()));
49
-
50
- // Of the transactions from the proposal, retrieve those that we have in the pool already
51
- const txsToValidate = [];
52
- const txsWeAlreadyHave = await this.p2pClient.getTxsByHashFromPool(txsFromProposal.map(tx => tx.txHash));
53
-
54
- // Txs we already have will have holes where we did not find them
55
- // Where that is the case we need to validate the tx in the proposal
56
- for (let i = 0; i < txsWeAlreadyHave.length; i++) {
57
- if (txsWeAlreadyHave[i] === undefined) {
58
- txsToValidate.push(txsFromProposal[i].tx);
59
- }
60
- }
61
-
62
- // Now validate all the transactions from the proposal that we don't have
63
- // This will throw if any of the transactions are invalid, this is probably correct, if someone sends us a proposal with invalid
64
- // transactions we probably shouldn't spend any more effort on it
65
- try {
66
- await this.p2pClient.validate(txsToValidate);
67
- } catch (err) {
68
- this.log.error(`Received proposal with invalid transactions, skipping`);
69
- throw err;
70
- }
71
-
72
- // Now store these transactions in our pool, provided these are the txs in proposal.payload.txHashes they will be pinned already
73
- await this.p2pClient.addTxsToPool(txsToValidate);
74
-
75
- return txsToValidate.length;
76
- }
77
-
78
- async collectForBlockProposal(
79
- proposal: BlockProposal,
80
- peerWhoSentTheProposal: PeerId | undefined,
81
- ): Promise<{ txs: Tx[]; missing?: TxHash[] }> {
82
- if (proposal.payload.txHashes.length === 0) {
83
- this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
84
- return { txs: [] };
85
- }
86
-
87
- const txsInMempool = (await this.p2pClient.hasTxsInPool(proposal.payload.txHashes)).filter(Boolean).length;
88
- this.instrumentation.incTxsFromMempool(txsInMempool);
89
-
90
- // Take txs from the proposal if there are any
91
- const txTakenFromProposal = await this.collectFromProposal(proposal);
92
- this.instrumentation.incTxsFromProposals(txTakenFromProposal);
93
-
94
- // Now get the txs we need, either from the pool or the p2p network
95
- const txHashes: TxHash[] = proposal.payload.txHashes;
96
- const { txs, missing } = await this.collectTransactions(txHashes, peerWhoSentTheProposal);
97
-
98
- this.instrumentation.incMissingTxs(missing?.length ?? 0);
99
-
100
- const txsFromP2P = txHashes.length - txTakenFromProposal - txsInMempool - (missing?.length ?? 0);
101
- this.instrumentation.incTxsFromP2P(txsFromP2P);
102
-
103
- this.log.info(`Retrieved ${txs.length}/${txHashes.length} txs for block proposal`, {
104
- blockNumber: proposal.blockNumber,
105
- slotNumber: proposal.slotNumber.toNumber(),
106
- totalTxsInProposal: txHashes.length,
107
- txsFromProposal: txTakenFromProposal,
108
- txsFromMempool: txsInMempool,
109
- txsFromP2P,
110
- missingTxs: missing?.length ?? 0,
111
- });
112
-
113
- return { txs, missing };
114
- }
115
-
116
- async collectTransactions(
117
- txHashes: TxHash[],
118
- peerWhoSentTheProposal: PeerId | undefined,
119
- ): Promise<{ txs: Tx[]; missing?: TxHash[] }> {
120
- // This will request from the network any txs that are missing
121
- // NOTE: this could still return missing txs so we need to (1) be careful to handle undefined and (2) keep the txs in the correct order for re-execution
122
- const maybeRetrievedTxs = await this.p2pClient.getTxsByHash(txHashes, peerWhoSentTheProposal);
123
-
124
- // Get the txs that we didn't get from the network, if any. This will be empty if we got them al
125
- const missingTxs = compactArray(
126
- maybeRetrievedTxs.map((tx, index) => (tx === undefined ? txHashes[index] : undefined)),
127
- );
128
-
129
- // if we found all txs, this is a noop. If we didn't find all txs then tell the validator to skip attestations because missingTxs.length > 0
130
- const retrievedTxs = compactArray(maybeRetrievedTxs);
131
-
132
- return { txs: retrievedTxs, missing: missingTxs };
133
- }
134
- }