@aztec/p2p 0.71.0 → 0.72.1

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 (107) hide show
  1. package/dest/client/p2p_client.d.ts.map +1 -1
  2. package/dest/client/p2p_client.js +6 -6
  3. package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -1
  4. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  5. package/dest/mem_pools/attestation_pool/mocks.js +1 -1
  6. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  7. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +2 -2
  8. package/dest/mocks/index.d.ts +3 -3
  9. package/dest/mocks/index.d.ts.map +1 -1
  10. package/dest/mocks/index.js +19 -18
  11. package/dest/services/dummy_service.d.ts +7 -0
  12. package/dest/services/dummy_service.d.ts.map +1 -1
  13. package/dest/services/dummy_service.js +10 -1
  14. package/dest/services/libp2p/libp2p_service.d.ts +17 -13
  15. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  16. package/dest/services/libp2p/libp2p_service.js +109 -101
  17. package/dest/services/peer-manager/metrics.d.ts +12 -0
  18. package/dest/services/peer-manager/metrics.d.ts.map +1 -0
  19. package/dest/services/peer-manager/metrics.js +26 -0
  20. package/dest/services/{peer_manager.d.ts → peer-manager/peer_manager.d.ts} +23 -8
  21. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -0
  22. package/dest/services/peer-manager/peer_manager.js +392 -0
  23. package/dest/services/{peer-scoring → peer-manager}/peer_scoring.d.ts +3 -0
  24. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -0
  25. package/dest/services/peer-manager/peer_scoring.js +84 -0
  26. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +45 -0
  27. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +1 -0
  28. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +81 -0
  29. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +61 -0
  30. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -0
  31. package/dest/services/reqresp/connection-sampler/connection_sampler.js +175 -0
  32. package/dest/services/reqresp/interface.d.ts +17 -4
  33. package/dest/services/reqresp/interface.d.ts.map +1 -1
  34. package/dest/services/reqresp/interface.js +34 -11
  35. package/dest/services/reqresp/metrics.d.ts +15 -0
  36. package/dest/services/reqresp/metrics.d.ts.map +1 -0
  37. package/dest/services/reqresp/metrics.js +42 -0
  38. package/dest/services/reqresp/protocols/block.d.ts +4 -0
  39. package/dest/services/reqresp/protocols/block.d.ts.map +1 -0
  40. package/dest/services/reqresp/protocols/block.js +9 -0
  41. package/dest/services/reqresp/protocols/goodbye.d.ts +51 -0
  42. package/dest/services/reqresp/protocols/goodbye.d.ts.map +1 -0
  43. package/dest/services/reqresp/protocols/goodbye.js +92 -0
  44. package/dest/services/reqresp/protocols/index.d.ts +9 -0
  45. package/dest/services/reqresp/protocols/index.d.ts.map +1 -0
  46. package/dest/services/reqresp/protocols/index.js +9 -0
  47. package/dest/services/reqresp/protocols/ping.d.ts +9 -0
  48. package/dest/services/reqresp/protocols/ping.d.ts.map +1 -0
  49. package/dest/services/reqresp/protocols/ping.js +9 -0
  50. package/dest/services/reqresp/{handlers.d.ts → protocols/status.d.ts} +1 -7
  51. package/dest/services/reqresp/protocols/status.d.ts.map +1 -0
  52. package/dest/services/reqresp/protocols/status.js +9 -0
  53. package/dest/services/reqresp/protocols/tx.d.ts +13 -0
  54. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -0
  55. package/dest/services/reqresp/protocols/tx.js +23 -0
  56. package/dest/services/reqresp/rate-limiter/index.d.ts.map +1 -0
  57. package/dest/services/reqresp/{rate_limiter → rate-limiter}/index.js +1 -1
  58. package/dest/services/reqresp/{rate_limiter → rate-limiter}/rate_limiter.d.ts +3 -3
  59. package/dest/services/reqresp/{rate_limiter → rate-limiter}/rate_limiter.d.ts.map +1 -1
  60. package/dest/services/reqresp/{rate_limiter → rate-limiter}/rate_limiter.js +4 -4
  61. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -0
  62. package/dest/services/reqresp/rate-limiter/rate_limits.js +55 -0
  63. package/dest/services/reqresp/reqresp.d.ts +33 -6
  64. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  65. package/dest/services/reqresp/reqresp.js +414 -249
  66. package/dest/services/service.d.ts +8 -0
  67. package/dest/services/service.d.ts.map +1 -1
  68. package/dest/util.d.ts +4 -0
  69. package/dest/util.d.ts.map +1 -1
  70. package/dest/util.js +1 -1
  71. package/package.json +8 -8
  72. package/src/client/p2p_client.ts +5 -5
  73. package/src/mem_pools/attestation_pool/mocks.ts +2 -2
  74. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +0 -1
  75. package/src/mocks/index.ts +19 -20
  76. package/src/services/dummy_service.ts +13 -0
  77. package/src/services/libp2p/libp2p_service.ts +143 -128
  78. package/src/services/peer-manager/metrics.ts +41 -0
  79. package/src/services/{peer_manager.ts → peer-manager/peer_manager.ts} +67 -22
  80. package/src/services/{peer-scoring → peer-manager}/peer_scoring.ts +16 -3
  81. package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +94 -0
  82. package/src/services/reqresp/connection-sampler/connection_sampler.ts +211 -0
  83. package/src/services/reqresp/interface.ts +39 -16
  84. package/src/services/reqresp/metrics.ts +57 -0
  85. package/src/services/reqresp/protocols/block.ts +15 -0
  86. package/src/services/reqresp/protocols/goodbye.ts +101 -0
  87. package/src/services/reqresp/protocols/index.ts +8 -0
  88. package/src/services/reqresp/protocols/ping.ts +8 -0
  89. package/src/services/reqresp/{handlers.ts → protocols/status.ts} +0 -9
  90. package/src/services/reqresp/protocols/tx.ts +29 -0
  91. package/src/services/reqresp/{rate_limiter → rate-limiter}/rate_limiter.ts +3 -3
  92. package/src/services/reqresp/{rate_limiter → rate-limiter}/rate_limits.ts +24 -4
  93. package/src/services/reqresp/reqresp.ts +224 -25
  94. package/src/services/service.ts +12 -0
  95. package/src/util.ts +4 -0
  96. package/dest/services/peer-scoring/peer_scoring.d.ts.map +0 -1
  97. package/dest/services/peer-scoring/peer_scoring.js +0 -75
  98. package/dest/services/peer_manager.d.ts.map +0 -1
  99. package/dest/services/peer_manager.js +0 -358
  100. package/dest/services/reqresp/handlers.d.ts.map +0 -1
  101. package/dest/services/reqresp/handlers.js +0 -17
  102. package/dest/services/reqresp/rate_limiter/index.d.ts.map +0 -1
  103. package/dest/services/reqresp/rate_limiter/rate_limits.d.ts.map +0 -1
  104. package/dest/services/reqresp/rate_limiter/rate_limits.js +0 -35
  105. /package/dest/services/reqresp/{rate_limiter → rate-limiter}/index.d.ts +0 -0
  106. /package/dest/services/reqresp/{rate_limiter → rate-limiter}/rate_limits.d.ts +0 -0
  107. /package/src/services/reqresp/{rate_limiter → rate-limiter}/index.ts +0 -0
@@ -1,12 +1,17 @@
1
+ import { __esDecorate, __runInitializers } from "tslib";
1
2
  // @attribution: lodestar impl for inspiration
2
3
  import { PeerErrorSeverity } from '@aztec/circuit-types';
3
4
  import { createLogger } from '@aztec/foundation/log';
4
5
  import { executeTimeout } from '@aztec/foundation/timer';
6
+ import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
5
7
  import { pipe } from 'it-pipe';
6
8
  import { CollectiveReqRespTimeoutError, IndividualReqRespTimeoutError, InvalidResponseError, } from '../../errors/reqresp.error.js';
7
9
  import { SnappyTransform } from '../encoding.js';
10
+ import { BatchConnectionSampler } from './connection-sampler/batch_connection_sampler.js';
11
+ import { ConnectionSampler } from './connection-sampler/connection_sampler.js';
8
12
  import { DEFAULT_SUB_PROTOCOL_HANDLERS, DEFAULT_SUB_PROTOCOL_VALIDATORS, subProtocolMap, } from './interface.js';
9
- import { RequestResponseRateLimiter } from './rate_limiter/rate_limiter.js';
13
+ import { ReqRespMetrics } from './metrics.js';
14
+ import { RequestResponseRateLimiter } from './rate-limiter/rate_limiter.js';
10
15
  /**
11
16
  * The Request Response Service
12
17
  *
@@ -21,259 +26,419 @@ import { RequestResponseRateLimiter } from './rate_limiter/rate_limiter.js';
21
26
  *
22
27
  * see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md#the-reqresp-domain
23
28
  */
24
- export class ReqResp {
25
- constructor(config, libp2p, peerManager) {
26
- this.libp2p = libp2p;
27
- this.peerManager = peerManager;
28
- // Warning, if the `start` function is not called as the parent class constructor, then the default sub protocol handlers will be used ( not good )
29
- this.subProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS;
30
- this.subProtocolValidators = DEFAULT_SUB_PROTOCOL_VALIDATORS;
31
- this.logger = createLogger('p2p:reqresp');
32
- this.overallRequestTimeoutMs = config.overallRequestTimeoutMs;
33
- this.individualRequestTimeoutMs = config.individualRequestTimeoutMs;
34
- this.rateLimiter = new RequestResponseRateLimiter(peerManager);
35
- this.snappyTransform = new SnappyTransform();
36
- }
37
- /**
38
- * Start the reqresp service
39
- */
40
- async start(subProtocolHandlers, subProtocolValidators) {
41
- this.subProtocolHandlers = subProtocolHandlers;
42
- this.subProtocolValidators = subProtocolValidators;
43
- // Register all protocol handlers
44
- for (const subProtocol of Object.keys(this.subProtocolHandlers)) {
45
- await this.libp2p.handle(subProtocol, this.streamHandler.bind(this, subProtocol));
46
- }
47
- this.rateLimiter.start();
48
- }
49
- /**
50
- * Stop the reqresp service
51
- */
52
- async stop() {
53
- // Unregister all handlers
54
- for (const protocol of Object.keys(this.subProtocolHandlers)) {
55
- await this.libp2p.unhandle(protocol);
56
- }
57
- // Close all active connections
58
- const closeStreamPromises = this.libp2p.getConnections().map(connection => connection.close());
59
- await Promise.all(closeStreamPromises);
60
- this.logger.debug('ReqResp: All active streams closed');
61
- this.rateLimiter.stop();
62
- this.logger.debug('ReqResp: Rate limiter stopped');
63
- // NOTE: We assume libp2p instance is managed by the caller
64
- }
65
- /**
66
- * Send a request to peers, returns the first response
67
- *
68
- * @param subProtocol - The protocol being requested
69
- * @param request - The request to send
70
- * @returns - The response from the peer, otherwise undefined
71
- *
72
- * @description
73
- * This method attempts to send a request to all active peers using the specified sub-protocol.
74
- * It opens a stream with each peer, sends the request, and awaits a response.
75
- * If a valid response is received, it returns the response; otherwise, it continues to the next peer.
76
- * If no response is received from any peer, it returns undefined.
77
- *
78
- * The method performs the following steps:
79
- * - Iterates over all active peers.
80
- * - Opens a stream with each peer using the specified sub-protocol.
81
- *
82
- * When a response is received, it is validated using the given sub protocols response validator.
83
- * To see the interface for the response validator - see `interface.ts`
84
- *
85
- * Failing a response validation requests in a severe peer penalty, and will
86
- * prompt the node to continue to search to the next peer.
87
- * For example, a transaction request validator will check that the payload returned does in fact
88
- * match the txHash that was requested. A peer that fails this check an only be an extremely naughty peer.
89
- *
90
- * This entire operation is wrapped in an overall timeout, that is independent of the
91
- * peer it is requesting data from.
92
- *
93
- */
94
- async sendRequest(subProtocol, request) {
95
- const requestFunction = async () => {
96
- const responseValidator = this.subProtocolValidators[subProtocol];
97
- const requestBuffer = request.toBuffer();
98
- // Get active peers
99
- const peers = this.libp2p.getPeers();
100
- // Attempt to ask all of our peers
101
- for (const peer of peers) {
102
- const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffer);
103
- // If we get a response, return it, otherwise we iterate onto the next peer
104
- // We do not consider it a success if we have an empty buffer
105
- if (response && response.length > 0) {
106
- const object = subProtocolMap[subProtocol].response.fromBuffer(response);
107
- // The response validator handles peer punishment within
108
- const isValid = await responseValidator(request, object, peer);
109
- if (!isValid) {
110
- throw new InvalidResponseError();
29
+ let ReqResp = (() => {
30
+ var _a;
31
+ let _instanceExtraInitializers = [];
32
+ let _sendBatchRequest_decorators;
33
+ let _sendRequestToPeer_decorators;
34
+ let _streamHandler_decorators;
35
+ return _a = class ReqResp {
36
+ constructor(config, libp2p, peerScoring, telemetryClient = getTelemetryClient()) {
37
+ this.libp2p = (__runInitializers(this, _instanceExtraInitializers), libp2p);
38
+ this.peerScoring = peerScoring;
39
+ // Warning, if the `start` function is not called as the parent class constructor, then the default sub protocol handlers will be used ( not good )
40
+ this.subProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS;
41
+ this.subProtocolValidators = DEFAULT_SUB_PROTOCOL_VALIDATORS;
42
+ this.logger = createLogger('p2p:reqresp');
43
+ this.overallRequestTimeoutMs = config.overallRequestTimeoutMs;
44
+ this.individualRequestTimeoutMs = config.individualRequestTimeoutMs;
45
+ this.rateLimiter = new RequestResponseRateLimiter(peerScoring);
46
+ // Connection sampler is used to sample our connected peers
47
+ this.connectionSampler = new ConnectionSampler(libp2p);
48
+ this.snappyTransform = new SnappyTransform();
49
+ this.metrics = new ReqRespMetrics(telemetryClient);
50
+ }
51
+ get tracer() {
52
+ return this.metrics.tracer;
53
+ }
54
+ /**
55
+ * Start the reqresp service
56
+ */
57
+ async start(subProtocolHandlers, subProtocolValidators) {
58
+ this.subProtocolHandlers = subProtocolHandlers;
59
+ this.subProtocolValidators = subProtocolValidators;
60
+ // Register all protocol handlers
61
+ for (const subProtocol of Object.keys(this.subProtocolHandlers)) {
62
+ await this.libp2p.handle(subProtocol, this.streamHandler.bind(this, subProtocol));
63
+ }
64
+ this.rateLimiter.start();
65
+ }
66
+ /**
67
+ * Stop the reqresp service
68
+ */
69
+ async stop() {
70
+ // Unregister handlers in parallel
71
+ const unregisterPromises = Object.keys(this.subProtocolHandlers).map(protocol => this.libp2p.unhandle(protocol));
72
+ await Promise.all(unregisterPromises);
73
+ // Close connection sampler
74
+ await this.connectionSampler.stop();
75
+ this.logger.debug('ReqResp: Connection sampler stopped');
76
+ // Close streams in parallel
77
+ const closeStreamPromises = this.libp2p.getConnections().map(connection => connection.close());
78
+ await Promise.all(closeStreamPromises);
79
+ this.logger.debug('ReqResp: All active streams closed');
80
+ this.rateLimiter.stop();
81
+ this.logger.debug('ReqResp: Rate limiter stopped');
82
+ // NOTE: We assume libp2p instance is managed by the caller
83
+ }
84
+ /**
85
+ * Send a request to peers, returns the first response
86
+ *
87
+ * @param subProtocol - The protocol being requested
88
+ * @param request - The request to send
89
+ * @returns - The response from the peer, otherwise undefined
90
+ *
91
+ * @description
92
+ * This method attempts to send a request to all active peers using the specified sub-protocol.
93
+ * It opens a stream with each peer, sends the request, and awaits a response.
94
+ * If a valid response is received, it returns the response; otherwise, it continues to the next peer.
95
+ * If no response is received from any peer, it returns undefined.
96
+ *
97
+ * The method performs the following steps:
98
+ * - Sample a peer to send the request to.
99
+ * - Opens a stream with the peer using the specified sub-protocol.
100
+ *
101
+ * When a response is received, it is validated using the given sub protocols response validator.
102
+ * To see the interface for the response validator - see `interface.ts`
103
+ *
104
+ * Failing a response validation requests in a severe peer penalty, and will
105
+ * prompt the node to continue to search to the next peer.
106
+ * For example, a transaction request validator will check that the payload returned does in fact
107
+ * match the txHash that was requested. A peer that fails this check an only be an extremely naughty peer.
108
+ *
109
+ * This entire operation is wrapped in an overall timeout, that is independent of the
110
+ * peer it is requesting data from.
111
+ *
112
+ */
113
+ async sendRequest(subProtocol, request) {
114
+ const responseValidator = this.subProtocolValidators[subProtocol];
115
+ const requestBuffer = request.toBuffer();
116
+ const requestFunction = async () => {
117
+ // Attempt to ask all of our peers, but sampled in a random order
118
+ // This function is wrapped in a timeout, so we will exit the loop if we have not received a response
119
+ const numberOfPeers = this.libp2p.getPeers().length;
120
+ if (numberOfPeers === 0) {
121
+ this.logger.debug('No active peers to send requests to');
122
+ return undefined;
111
123
  }
112
- return object;
124
+ const attemptedPeers = new Map();
125
+ for (let i = 0; i < numberOfPeers; i++) {
126
+ // Sample a peer to make a request to
127
+ const peer = this.connectionSampler.getPeer(attemptedPeers);
128
+ this.logger.trace(`Attempting to send request to peer: ${peer?.toString()}`);
129
+ if (!peer) {
130
+ this.logger.debug('No peers available to send requests to');
131
+ return undefined;
132
+ }
133
+ attemptedPeers.set(peer.toString(), true);
134
+ this.logger.trace(`Sending request to peer: ${peer.toString()}`);
135
+ const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffer);
136
+ // If we get a response, return it, otherwise we iterate onto the next peer
137
+ // We do not consider it a success if we have an empty buffer
138
+ if (response && response.length > 0) {
139
+ const object = subProtocolMap[subProtocol].response.fromBuffer(response);
140
+ // The response validator handles peer punishment within
141
+ const isValid = await responseValidator(request, object, peer);
142
+ if (!isValid) {
143
+ throw new InvalidResponseError();
144
+ }
145
+ return object;
146
+ }
147
+ }
148
+ };
149
+ try {
150
+ return await executeTimeout(requestFunction, this.overallRequestTimeoutMs, () => new CollectiveReqRespTimeoutError());
151
+ }
152
+ catch (e) {
153
+ this.logger.debug(`${e.message} | subProtocol: ${subProtocol}`);
154
+ return undefined;
113
155
  }
114
156
  }
115
- return undefined;
116
- };
117
- try {
118
- return await executeTimeout(requestFunction, this.overallRequestTimeoutMs, () => new CollectiveReqRespTimeoutError());
119
- }
120
- catch (e) {
121
- this.logger.debug(`${e.message} | subProtocol: ${subProtocol}`);
122
- return undefined;
123
- }
124
- }
125
- /**
126
- * Sends a request to a specific peer
127
- *
128
- * We first dial a particular protocol for the peer, this ensures that the peer knows
129
- * what to respond with
130
- *
131
- *
132
- * @param peerId - The peer to send the request to
133
- * @param subProtocol - The protocol to use to request
134
- * @param payload - The payload to send
135
- * @returns If the request is successful, the response is returned, otherwise undefined
136
- *
137
- * @description
138
- * This method attempts to open a stream with the specified peer, send the payload,
139
- * and await a response.
140
- * If an error occurs, it penalizes the peer and returns undefined.
141
- *
142
- * The method performs the following steps:
143
- * - Opens a stream with the peer using the specified sub-protocol.
144
- * - Sends the payload and awaits a response with a timeout.
145
- *
146
- * If the stream is not closed by the dialled peer, and a timeout occurs, then
147
- * the stream is closed on the requester's end and sender (us) updates its peer score
148
- */
149
- async sendRequestToPeer(peerId, subProtocol, payload) {
150
- let stream;
151
- try {
152
- stream = await this.libp2p.dialProtocol(peerId, subProtocol);
153
- this.logger.trace(`Stream opened with ${peerId.toString()} for ${subProtocol}`);
154
- // Open the stream with a timeout
155
- const result = await executeTimeout(() => pipe([payload], stream, this.readMessage.bind(this)), this.individualRequestTimeoutMs, () => new IndividualReqRespTimeoutError());
156
- return result;
157
- }
158
- catch (e) {
159
- this.handleResponseError(e, peerId, subProtocol);
160
- }
161
- finally {
162
- if (stream) {
157
+ /**
158
+ * Request multiple messages over the same sub protocol, balancing the requests across peers.
159
+ *
160
+ * @devnote
161
+ * - The function prioritizes sending requests to free peers using a batch sampling strategy.
162
+ * - If a peer fails to respond or returns an invalid response, it is removed from the sampling pool and replaced.
163
+ * - The function stops retrying once all requests are processed, no active peers remain, or the maximum retry attempts are reached.
164
+ * - Responses are validated using a custom validator for the sub-protocol.*
165
+ *
166
+ * Requests are sent in parallel to each peer, but multiple requests are sent to the same peer in series
167
+ * - If a peer fails to respond or returns an invalid response, it is removed from the sampling pool and replaced.
168
+ * - The function stops retrying once all requests are processed, no active peers remain, or the maximum retry attempts are reached.
169
+ * - Responses are validated using a custom validator for the sub-protocol.*
170
+ *
171
+ * @param subProtocol
172
+ * @param requests
173
+ * @param timeoutMs
174
+ * @param maxPeers
175
+ * @returns
176
+ *
177
+ * @throws {CollectiveReqRespTimeoutError} - If the request batch exceeds the specified timeout (`timeoutMs`).
178
+ */
179
+ async sendBatchRequest(subProtocol, requests, timeoutMs = 10000, maxPeers = Math.min(10, requests.length), maxRetryAttempts = 3) {
180
+ const responseValidator = this.subProtocolValidators[subProtocol];
181
+ const responses = new Array(requests.length);
182
+ const requestBuffers = requests.map(req => req.toBuffer());
183
+ const requestFunction = async () => {
184
+ // Track which requests still need to be processed
185
+ const pendingRequestIndices = new Set(requestBuffers.map((_, i) => i));
186
+ // Create batch sampler with the total number of requests and max peers
187
+ const batchSampler = new BatchConnectionSampler(this.connectionSampler, requests.length, maxPeers);
188
+ if (batchSampler.activePeerCount === 0) {
189
+ this.logger.debug('No active peers to send requests to');
190
+ return [];
191
+ }
192
+ // This is where it gets fun
193
+ // The outer loop is the retry loop, we will continue to retry until we process all indices we have
194
+ // not received a response for, or we have reached the max retry attempts
195
+ // The inner loop is the batch loop, we will process all requests for each peer in parallel
196
+ // We will then process the results of the requests, and resample any peers that failed to respond
197
+ // We will continue to retry until we have processed all indices, or we have reached the max retry attempts
198
+ let retryAttempts = 0;
199
+ while (pendingRequestIndices.size > 0 && batchSampler.activePeerCount > 0 && retryAttempts < maxRetryAttempts) {
200
+ // Process requests in parallel for each available peer
201
+ const requestBatches = new Map();
202
+ // Group requests by peer
203
+ for (const requestIndex of pendingRequestIndices) {
204
+ const peer = batchSampler.getPeerForRequest(requestIndex);
205
+ if (!peer) {
206
+ break;
207
+ }
208
+ if (!requestBatches.has(peer)) {
209
+ requestBatches.set(peer, []);
210
+ }
211
+ requestBatches.get(peer).push(requestIndex);
212
+ }
213
+ // Make parallel requests for each peer's batch
214
+ // A batch entry will look something like this:
215
+ // PeerId0: [0, 1, 2, 3]
216
+ // PeerId1: [4, 5, 6, 7]
217
+ // Peer Id 0 will send requests 0, 1, 2, 3 in serial
218
+ // while simultaneously Peer Id 1 will send requests 4, 5, 6, 7 in serial
219
+ const batchResults = await Promise.all(Array.from(requestBatches.entries()).map(async ([peer, indices]) => {
220
+ try {
221
+ // Requests all going to the same peer are sent synchronously
222
+ const peerResults = [];
223
+ for (const index of indices) {
224
+ const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffers[index]);
225
+ if (response && response.length > 0) {
226
+ const object = subProtocolMap[subProtocol].response.fromBuffer(response);
227
+ const isValid = await responseValidator(requests[index], object, peer);
228
+ if (isValid) {
229
+ peerResults.push({ index, response: object });
230
+ }
231
+ }
232
+ }
233
+ return { peer, results: peerResults };
234
+ }
235
+ catch (error) {
236
+ this.logger.debug(`Failed batch request to peer ${peer.toString()}:`, error);
237
+ batchSampler.removePeerAndReplace(peer);
238
+ return { peer, results: [] };
239
+ }
240
+ }));
241
+ // Process results
242
+ for (const { results } of batchResults) {
243
+ for (const { index, response } of results) {
244
+ if (response) {
245
+ responses[index] = response;
246
+ pendingRequestIndices.delete(index);
247
+ }
248
+ }
249
+ }
250
+ retryAttempts++;
251
+ }
252
+ if (retryAttempts >= maxRetryAttempts) {
253
+ this.logger.debug(`Max retry attempts ${maxRetryAttempts} reached for batch request`);
254
+ }
255
+ return responses;
256
+ };
163
257
  try {
164
- await stream.close();
165
- this.logger.trace(`Stream closed with ${peerId.toString()} for ${subProtocol}`);
258
+ return await executeTimeout(requestFunction, timeoutMs, () => new CollectiveReqRespTimeoutError());
259
+ }
260
+ catch (e) {
261
+ this.logger.debug(`${e.message} | subProtocol: ${subProtocol}`);
262
+ return [];
263
+ }
264
+ }
265
+ /**
266
+ * Sends a request to a specific peer
267
+ *
268
+ * We first dial a particular protocol for the peer, this ensures that the peer knows
269
+ * what to respond with
270
+ *
271
+ *
272
+ * @param peerId - The peer to send the request to
273
+ * @param subProtocol - The protocol to use to request
274
+ * @param payload - The payload to send
275
+ * @returns If the request is successful, the response is returned, otherwise undefined
276
+ *
277
+ * @description
278
+ * This method attempts to open a stream with the specified peer, send the payload,
279
+ * and await a response.
280
+ * If an error occurs, it penalizes the peer and returns undefined.
281
+ *
282
+ * The method performs the following steps:
283
+ * - Opens a stream with the peer using the specified sub-protocol.
284
+ * - Sends the payload and awaits a response with a timeout.
285
+ *
286
+ * If the stream is not closed by the dialled peer, and a timeout occurs, then
287
+ * the stream is closed on the requester's end and sender (us) updates its peer score
288
+ */
289
+ async sendRequestToPeer(peerId, subProtocol, payload) {
290
+ let stream;
291
+ try {
292
+ this.metrics.recordRequestSent(subProtocol);
293
+ stream = await this.connectionSampler.dialProtocol(peerId, subProtocol);
294
+ // Open the stream with a timeout
295
+ const result = await executeTimeout(() => pipe([payload], stream, this.readMessage.bind(this)), this.individualRequestTimeoutMs, () => new IndividualReqRespTimeoutError());
296
+ return result;
297
+ }
298
+ catch (e) {
299
+ this.metrics.recordRequestError(subProtocol);
300
+ this.handleResponseError(e, peerId, subProtocol);
166
301
  }
167
- catch (closeError) {
168
- this.logger.error(`Error closing stream: ${closeError instanceof Error ? closeError.message : 'Unknown error'}`);
302
+ finally {
303
+ // Only close the stream if we created it
304
+ if (stream) {
305
+ try {
306
+ await this.connectionSampler.close(stream.id);
307
+ }
308
+ catch (closeError) {
309
+ this.logger.error(`Error closing stream: ${closeError instanceof Error ? closeError.message : 'Unknown error'}`);
310
+ }
311
+ }
312
+ }
313
+ }
314
+ /**
315
+ * Handle a response error
316
+ *
317
+ * ReqResp errors are punished differently depending on the severity of the offense
318
+ *
319
+ * @param e - The error
320
+ * @param peerId - The peer id
321
+ * @param subProtocol - The sub protocol
322
+ * @returns If the error is non pubishable, then undefined is returned, otherwise the peer is penalized
323
+ */
324
+ handleResponseError(e, peerId, subProtocol) {
325
+ const severity = this.categorizeError(e, peerId, subProtocol);
326
+ if (severity) {
327
+ this.peerScoring.penalizePeer(peerId, severity);
169
328
  }
170
329
  }
171
- }
172
- }
173
- /**
174
- * Handle a response error
175
- *
176
- * ReqResp errors are punished differently depending on the severity of the offense
177
- *
178
- * @param e - The error
179
- * @param peerId - The peer id
180
- * @param subProtocol - The sub protocol
181
- * @returns If the error is non pubishable, then undefined is returned, otherwise the peer is penalized
182
- */
183
- handleResponseError(e, peerId, subProtocol) {
184
- const severity = this.categorizeError(e, peerId, subProtocol);
185
- if (severity) {
186
- this.peerManager.penalizePeer(peerId, severity);
187
- }
188
- }
189
- /**
190
- * Categorize the error and log it.
191
- */
192
- categorizeError(e, peerId, subProtocol) {
193
- // Non pubishable errors
194
- // We do not punish a collective timeout, as the node triggers this interupt, independent of the peer's behaviour
195
- const logTags = {
196
- peerId: peerId.toString(),
197
- subProtocol,
198
- };
199
- if (e instanceof CollectiveReqRespTimeoutError || e instanceof InvalidResponseError) {
200
- this.logger.debug(`Non-punishable error: ${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`, logTags);
201
- return undefined;
202
- }
203
- // Pubishable errors
204
- // Connection reset errors in the networking stack are punished with high severity
205
- // it just signals an unreliable peer
206
- // We assume that the requesting node has a functioning networking stack.
207
- if (e?.code === 'ECONNRESET' || e?.code === 'EPIPE') {
208
- this.logger.debug(`Connection reset: ${peerId.toString()}`, logTags);
209
- return PeerErrorSeverity.HighToleranceError;
210
- }
211
- if (e?.code === 'ECONNREFUSED') {
212
- this.logger.debug(`Connection refused: ${peerId.toString()}`, logTags);
213
- return PeerErrorSeverity.HighToleranceError;
214
- }
215
- // Timeout errors are punished with high tolerance, they can be due to a geogrpahically far away peer or an
216
- // overloaded peer
217
- if (e instanceof IndividualReqRespTimeoutError) {
218
- this.logger.debug(`Timeout error: ${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`, logTags);
219
- return PeerErrorSeverity.HighToleranceError;
220
- }
221
- // Catch all error
222
- this.logger.error(`Unexpected error sending request to peer`, e, logTags);
223
- return PeerErrorSeverity.HighToleranceError;
224
- }
225
- /**
226
- * Read a message returned from a stream into a single buffer
227
- */
228
- async readMessage(source) {
229
- const chunks = [];
230
- for await (const chunk of source) {
231
- chunks.push(chunk.subarray());
232
- }
233
- const messageData = Buffer.concat(chunks);
234
- return this.snappyTransform.inboundTransformNoTopic(messageData);
235
- }
236
- /**
237
- * Stream Handler
238
- * Reads the incoming stream, determines the protocol, then triggers the appropriate handler
239
- *
240
- * @param param0 - The incoming stream data
241
- *
242
- * @description
243
- * An individual stream handler will be bound to each sub protocol, and handles returning data back
244
- * to the requesting peer.
245
- *
246
- * The sub protocol handler interface is defined within `interface.ts` and will be assigned to the
247
- * req resp service on start up.
248
- *
249
- * We check rate limits for each peer, note the peer will be penalised within the rate limiter implementation
250
- * if they exceed their peer specific limits.
251
- */
252
- async streamHandler(protocol, { stream, connection }) {
253
- // Store a reference to from this for the async generator
254
- if (!this.rateLimiter.allow(protocol, connection.remotePeer)) {
255
- this.logger.warn(`Rate limit exceeded for ${protocol} from ${connection.remotePeer}`);
256
- // TODO(#8483): handle changing peer scoring for failed rate limit, maybe differentiate between global and peer limits here when punishing
257
- await stream.close();
258
- return;
259
- }
260
- const handler = this.subProtocolHandlers[protocol];
261
- const transform = this.snappyTransform;
262
- try {
263
- await pipe(stream, async function* (source) {
264
- for await (const chunkList of source) {
265
- const msg = Buffer.from(chunkList.subarray());
266
- const response = await handler(msg);
267
- yield new Uint8Array(transform.outboundTransformNoTopic(response));
330
+ /**
331
+ * Categorize the error and log it.
332
+ */
333
+ categorizeError(e, peerId, subProtocol) {
334
+ // Non pubishable errors
335
+ // We do not punish a collective timeout, as the node triggers this interupt, independent of the peer's behaviour
336
+ const logTags = {
337
+ peerId: peerId.toString(),
338
+ subProtocol,
339
+ };
340
+ if (e instanceof CollectiveReqRespTimeoutError || e instanceof InvalidResponseError) {
341
+ this.logger.debug(`Non-punishable error: ${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`, logTags);
342
+ return undefined;
343
+ }
344
+ // Pubishable errors
345
+ // Connection reset errors in the networking stack are punished with high severity
346
+ // it just signals an unreliable peer
347
+ // We assume that the requesting node has a functioning networking stack.
348
+ if (e?.code === 'ECONNRESET' || e?.code === 'EPIPE') {
349
+ this.logger.debug(`Connection reset: ${peerId.toString()}`, logTags);
350
+ return PeerErrorSeverity.HighToleranceError;
351
+ }
352
+ if (e?.code === 'ECONNREFUSED') {
353
+ this.logger.debug(`Connection refused: ${peerId.toString()}`, logTags);
354
+ return PeerErrorSeverity.HighToleranceError;
268
355
  }
269
- }, stream);
270
- }
271
- catch (e) {
272
- this.logger.warn(e);
273
- }
274
- finally {
275
- await stream.close();
276
- }
277
- }
278
- }
279
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxcmVzcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9yZXFyZXNwL3JlcXJlc3AudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsOENBQThDO0FBQzlDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3pELE9BQU8sRUFBZSxZQUFZLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNsRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFHekQsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUkvQixPQUFPLEVBQ0wsNkJBQTZCLEVBQzdCLDZCQUE2QixFQUM3QixvQkFBb0IsR0FDckIsTUFBTSwrQkFBK0IsQ0FBQztBQUN2QyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFHakQsT0FBTyxFQUNMLDZCQUE2QixFQUM3QiwrQkFBK0IsRUFLL0IsY0FBYyxHQUNmLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFNUU7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sT0FBTyxPQUFPO0lBY2xCLFlBQVksTUFBd0IsRUFBcUIsTUFBYyxFQUFVLFdBQXdCO1FBQWhELFdBQU0sR0FBTixNQUFNLENBQVE7UUFBVSxnQkFBVyxHQUFYLFdBQVcsQ0FBYTtRQVJ6RyxtSkFBbUo7UUFDM0ksd0JBQW1CLEdBQStCLDZCQUE2QixDQUFDO1FBQ2hGLDBCQUFxQixHQUFpQywrQkFBK0IsQ0FBQztRQU81RixJQUFJLENBQUMsTUFBTSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUUxQyxJQUFJLENBQUMsdUJBQXVCLEdBQUcsTUFBTSxDQUFDLHVCQUF1QixDQUFDO1FBQzlELElBQUksQ0FBQywwQkFBMEIsR0FBRyxNQUFNLENBQUMsMEJBQTBCLENBQUM7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
356
+ // Timeout errors are punished with high tolerance, they can be due to a geogrpahically far away peer or an
357
+ // overloaded peer
358
+ if (e instanceof IndividualReqRespTimeoutError) {
359
+ this.logger.debug(`Timeout error: ${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`, logTags);
360
+ return PeerErrorSeverity.HighToleranceError;
361
+ }
362
+ // Catch all error
363
+ this.logger.error(`Unexpected error sending request to peer`, e, logTags);
364
+ return PeerErrorSeverity.HighToleranceError;
365
+ }
366
+ /**
367
+ * Read a message returned from a stream into a single buffer
368
+ */
369
+ async readMessage(source) {
370
+ const chunks = [];
371
+ for await (const chunk of source) {
372
+ chunks.push(chunk.subarray());
373
+ }
374
+ const messageData = Buffer.concat(chunks);
375
+ return this.snappyTransform.inboundTransformNoTopic(messageData);
376
+ }
377
+ /**
378
+ * Stream Handler
379
+ * Reads the incoming stream, determines the protocol, then triggers the appropriate handler
380
+ *
381
+ * @param param0 - The incoming stream data
382
+ *
383
+ * @description
384
+ * An individual stream handler will be bound to each sub protocol, and handles returning data back
385
+ * to the requesting peer.
386
+ *
387
+ * The sub protocol handler interface is defined within `interface.ts` and will be assigned to the
388
+ * req resp service on start up.
389
+ *
390
+ * We check rate limits for each peer, note the peer will be penalised within the rate limiter implementation
391
+ * if they exceed their peer specific limits.
392
+ */
393
+ async streamHandler(protocol, { stream, connection }) {
394
+ this.metrics.recordRequestReceived(protocol);
395
+ // Store a reference to from this for the async generator
396
+ if (!this.rateLimiter.allow(protocol, connection.remotePeer)) {
397
+ this.logger.warn(`Rate limit exceeded for ${protocol} from ${connection.remotePeer}`);
398
+ // TODO(#8483): handle changing peer scoring for failed rate limit, maybe differentiate between global and peer limits here when punishing
399
+ await stream.close();
400
+ return;
401
+ }
402
+ const handler = this.subProtocolHandlers[protocol];
403
+ const transform = this.snappyTransform;
404
+ try {
405
+ await pipe(stream, async function* (source) {
406
+ for await (const chunkList of source) {
407
+ const msg = Buffer.from(chunkList.subarray());
408
+ const response = await handler(connection.remotePeer, msg);
409
+ yield new Uint8Array(transform.outboundTransformNoTopic(response));
410
+ }
411
+ }, stream);
412
+ }
413
+ catch (e) {
414
+ this.logger.warn(e);
415
+ this.metrics.recordResponseError(protocol);
416
+ }
417
+ finally {
418
+ await stream.close();
419
+ }
420
+ }
421
+ },
422
+ (() => {
423
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
424
+ _sendBatchRequest_decorators = [trackSpan('ReqResp.sendBatchRequest', (subProtocol, requests) => ({
425
+ [Attributes.P2P_REQ_RESP_PROTOCOL]: subProtocol,
426
+ [Attributes.P2P_REQ_RESP_BATCH_REQUESTS_COUNT]: requests.length,
427
+ }))];
428
+ _sendRequestToPeer_decorators = [trackSpan('ReqResp.sendRequestToPeer', (peerId, subProtocol, _) => ({
429
+ [Attributes.P2P_ID]: peerId.toString(),
430
+ [Attributes.P2P_REQ_RESP_PROTOCOL]: subProtocol,
431
+ }))];
432
+ _streamHandler_decorators = [trackSpan('ReqResp.streamHandler', (protocol, { connection }) => ({
433
+ [Attributes.P2P_REQ_RESP_PROTOCOL]: protocol,
434
+ [Attributes.P2P_ID]: connection.remotePeer.toString(),
435
+ }))];
436
+ __esDecorate(_a, null, _sendBatchRequest_decorators, { kind: "method", name: "sendBatchRequest", static: false, private: false, access: { has: obj => "sendBatchRequest" in obj, get: obj => obj.sendBatchRequest }, metadata: _metadata }, null, _instanceExtraInitializers);
437
+ __esDecorate(_a, null, _sendRequestToPeer_decorators, { kind: "method", name: "sendRequestToPeer", static: false, private: false, access: { has: obj => "sendRequestToPeer" in obj, get: obj => obj.sendRequestToPeer }, metadata: _metadata }, null, _instanceExtraInitializers);
438
+ __esDecorate(_a, null, _streamHandler_decorators, { kind: "method", name: "streamHandler", static: false, private: false, access: { has: obj => "streamHandler" in obj, get: obj => obj.streamHandler }, metadata: _metadata }, null, _instanceExtraInitializers);
439
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
440
+ })(),
441
+ _a;
442
+ })();
443
+ export { ReqResp };
444
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxcmVzcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9yZXFyZXNwL3JlcXJlc3AudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLDhDQUE4QztBQUM5QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN6RCxPQUFPLEVBQWUsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDbEUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxVQUFVLEVBQXdCLGtCQUFrQixFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRzFHLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFJL0IsT0FBTyxFQUNMLDZCQUE2QixFQUM3Qiw2QkFBNkIsRUFDN0Isb0JBQW9CLEdBQ3JCLE1BQU0sK0JBQStCLENBQUM7QUFDdkMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBR2pELE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGtEQUFrRCxDQUFDO0FBQzFGLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDRDQUE0QyxDQUFDO0FBQy9FLE9BQU8sRUFDTCw2QkFBNkIsRUFDN0IsK0JBQStCLEVBSy9CLGNBQWMsR0FDZixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDOUMsT0FBTyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFNUU7Ozs7Ozs7Ozs7Ozs7R0FhRztJQUNVLE9BQU87Ozs7OztzQkFBUCxPQUFPO1lBaUJsQixZQUNFLE1BQXdCLEVBQ2hCLE1BQWMsRUFDZCxXQUF3QixFQUNoQyxrQkFBbUMsa0JBQWtCLEVBQUU7Z0JBRi9DLFdBQU0sSUFuQkwsbURBQU8sRUFtQlIsTUFBTSxFQUFRO2dCQUNkLGdC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