@lodestar/reqresp 1.41.0-dev.a35cbde8b3 → 1.41.0-dev.aeab9f930d

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 (151) hide show
  1. package/README.md +1 -1
  2. package/lib/ReqResp.d.ts +3 -3
  3. package/lib/ReqResp.d.ts.map +1 -1
  4. package/lib/ReqResp.js +4 -4
  5. package/lib/ReqResp.js.map +1 -1
  6. package/lib/encoders/requestDecode.d.ts +2 -3
  7. package/lib/encoders/requestDecode.d.ts.map +1 -1
  8. package/lib/encoders/requestDecode.js +28 -11
  9. package/lib/encoders/requestDecode.js.map +1 -1
  10. package/lib/encoders/requestEncode.d.ts +1 -1
  11. package/lib/encoders/requestEncode.d.ts.map +1 -1
  12. package/lib/encoders/requestEncode.js +1 -1
  13. package/lib/encoders/requestEncode.js.map +1 -1
  14. package/lib/encoders/responseDecode.d.ts +10 -10
  15. package/lib/encoders/responseDecode.d.ts.map +1 -1
  16. package/lib/encoders/responseDecode.js +63 -60
  17. package/lib/encoders/responseDecode.js.map +1 -1
  18. package/lib/encoders/responseEncode.d.ts +2 -4
  19. package/lib/encoders/responseEncode.d.ts.map +1 -1
  20. package/lib/encoders/responseEncode.js +13 -22
  21. package/lib/encoders/responseEncode.js.map +1 -1
  22. package/lib/encodingStrategies/index.d.ts +4 -3
  23. package/lib/encodingStrategies/index.d.ts.map +1 -1
  24. package/lib/encodingStrategies/index.js +4 -4
  25. package/lib/encodingStrategies/index.js.map +1 -1
  26. package/lib/encodingStrategies/sszSnappy/decode.d.ts +5 -4
  27. package/lib/encodingStrategies/sszSnappy/decode.d.ts.map +1 -1
  28. package/lib/encodingStrategies/sszSnappy/decode.js +83 -52
  29. package/lib/encodingStrategies/sszSnappy/decode.js.map +1 -1
  30. package/lib/encodingStrategies/sszSnappy/encode.d.ts +2 -2
  31. package/lib/encodingStrategies/sszSnappy/encode.d.ts.map +1 -1
  32. package/lib/encodingStrategies/sszSnappy/encode.js +1 -1
  33. package/lib/encodingStrategies/sszSnappy/encode.js.map +1 -1
  34. package/lib/encodingStrategies/sszSnappy/errors.d.ts +0 -8
  35. package/lib/encodingStrategies/sszSnappy/errors.d.ts.map +1 -1
  36. package/lib/encodingStrategies/sszSnappy/errors.js +2 -3
  37. package/lib/encodingStrategies/sszSnappy/errors.js.map +1 -1
  38. package/lib/encodingStrategies/sszSnappy/index.d.ts +0 -1
  39. package/lib/encodingStrategies/sszSnappy/index.d.ts.map +1 -1
  40. package/lib/encodingStrategies/sszSnappy/index.js +0 -1
  41. package/lib/encodingStrategies/sszSnappy/index.js.map +1 -1
  42. package/lib/encodingStrategies/sszSnappy/utils.js.map +1 -1
  43. package/lib/interface.js +2 -1
  44. package/lib/interface.js.map +1 -1
  45. package/lib/metrics.d.ts +1 -7
  46. package/lib/metrics.d.ts.map +1 -1
  47. package/lib/metrics.js +1 -17
  48. package/lib/metrics.js.map +1 -1
  49. package/lib/rate_limiter/ReqRespRateLimiter.d.ts.map +1 -1
  50. package/lib/rate_limiter/ReqRespRateLimiter.js.map +1 -1
  51. package/lib/rate_limiter/rateLimiterGRCA.d.ts.map +1 -1
  52. package/lib/rate_limiter/rateLimiterGRCA.js.map +1 -1
  53. package/lib/rate_limiter/selfRateLimiter.d.ts.map +1 -1
  54. package/lib/rate_limiter/selfRateLimiter.js.map +1 -1
  55. package/lib/request/errors.d.ts +1 -7
  56. package/lib/request/errors.d.ts.map +1 -1
  57. package/lib/request/errors.js +3 -6
  58. package/lib/request/errors.js.map +1 -1
  59. package/lib/request/index.d.ts +0 -3
  60. package/lib/request/index.d.ts.map +1 -1
  61. package/lib/request/index.js +85 -70
  62. package/lib/request/index.js.map +1 -1
  63. package/lib/response/errors.d.ts.map +1 -1
  64. package/lib/response/errors.js +2 -1
  65. package/lib/response/errors.js.map +1 -1
  66. package/lib/response/index.d.ts +2 -2
  67. package/lib/response/index.d.ts.map +1 -1
  68. package/lib/response/index.js +46 -50
  69. package/lib/response/index.js.map +1 -1
  70. package/lib/types.d.ts +1 -2
  71. package/lib/types.d.ts.map +1 -1
  72. package/lib/types.js +7 -5
  73. package/lib/types.js.map +1 -1
  74. package/lib/utils/collectExactOne.js.map +1 -1
  75. package/lib/utils/collectMaxResponse.d.ts.map +1 -1
  76. package/lib/utils/collectMaxResponse.js +1 -2
  77. package/lib/utils/collectMaxResponse.js.map +1 -1
  78. package/lib/utils/errorMessage.d.ts +3 -3
  79. package/lib/utils/errorMessage.d.ts.map +1 -1
  80. package/lib/utils/errorMessage.js +14 -13
  81. package/lib/utils/errorMessage.js.map +1 -1
  82. package/lib/utils/index.d.ts +1 -3
  83. package/lib/utils/index.d.ts.map +1 -1
  84. package/lib/utils/index.js +1 -3
  85. package/lib/utils/index.js.map +1 -1
  86. package/lib/utils/peerId.js.map +1 -1
  87. package/lib/utils/protocolId.d.ts +2 -2
  88. package/lib/utils/protocolId.js +2 -2
  89. package/lib/utils/protocolId.js.map +1 -1
  90. package/lib/utils/snappyCommon.js +2 -1
  91. package/lib/utils/snappyCommon.js.map +1 -1
  92. package/lib/utils/snappyCompress.d.ts +1 -1
  93. package/lib/utils/snappyCompress.d.ts.map +1 -1
  94. package/lib/utils/snappyCompress.js +1 -1
  95. package/lib/utils/snappyCompress.js.map +1 -1
  96. package/lib/utils/snappyIndex.d.ts +1 -1
  97. package/lib/utils/snappyIndex.d.ts.map +1 -1
  98. package/lib/utils/snappyIndex.js +1 -1
  99. package/lib/utils/snappyIndex.js.map +1 -1
  100. package/lib/utils/snappyUncompress.d.ts +7 -11
  101. package/lib/utils/snappyUncompress.d.ts.map +1 -1
  102. package/lib/utils/snappyUncompress.js +68 -68
  103. package/lib/utils/snappyUncompress.js.map +1 -1
  104. package/lib/utils/stream.d.ts +6 -0
  105. package/lib/utils/stream.d.ts.map +1 -0
  106. package/lib/utils/stream.js +21 -0
  107. package/lib/utils/stream.js.map +1 -0
  108. package/package.json +16 -18
  109. package/src/ReqResp.ts +4 -4
  110. package/src/encoders/requestDecode.ts +32 -16
  111. package/src/encoders/requestEncode.ts +1 -1
  112. package/src/encoders/responseDecode.ts +68 -72
  113. package/src/encoders/responseEncode.ts +17 -29
  114. package/src/encodingStrategies/index.ts +8 -6
  115. package/src/encodingStrategies/sszSnappy/decode.ts +111 -53
  116. package/src/encodingStrategies/sszSnappy/encode.ts +2 -2
  117. package/src/encodingStrategies/sszSnappy/errors.ts +0 -4
  118. package/src/encodingStrategies/sszSnappy/index.ts +0 -1
  119. package/src/metrics.ts +1 -17
  120. package/src/request/errors.ts +1 -6
  121. package/src/request/index.ts +109 -86
  122. package/src/response/index.ts +55 -61
  123. package/src/types.ts +1 -3
  124. package/src/utils/collectMaxResponse.ts +1 -2
  125. package/src/utils/errorMessage.ts +14 -13
  126. package/src/utils/index.ts +1 -3
  127. package/src/utils/protocolId.ts +2 -2
  128. package/src/utils/snappyCompress.ts +1 -1
  129. package/src/utils/snappyIndex.ts +1 -1
  130. package/src/utils/snappyUncompress.ts +73 -75
  131. package/src/utils/stream.ts +34 -0
  132. package/lib/utils/abortableSource.d.ts +0 -12
  133. package/lib/utils/abortableSource.d.ts.map +0 -1
  134. package/lib/utils/abortableSource.js +0 -69
  135. package/lib/utils/abortableSource.js.map +0 -1
  136. package/lib/utils/bufferedSource.d.ts +0 -16
  137. package/lib/utils/bufferedSource.d.ts.map +0 -1
  138. package/lib/utils/bufferedSource.js +0 -40
  139. package/lib/utils/bufferedSource.js.map +0 -1
  140. package/lib/utils/onChunk.d.ts +0 -6
  141. package/lib/utils/onChunk.d.ts.map +0 -1
  142. package/lib/utils/onChunk.js +0 -13
  143. package/lib/utils/onChunk.js.map +0 -1
  144. package/lib/utils/snappy.d.ts +0 -3
  145. package/lib/utils/snappy.d.ts.map +0 -1
  146. package/lib/utils/snappy.js +0 -3
  147. package/lib/utils/snappy.js.map +0 -1
  148. package/src/utils/abortableSource.ts +0 -80
  149. package/src/utils/bufferedSource.ts +0 -46
  150. package/src/utils/onChunk.ts +0 -12
  151. package/src/utils/snappy.ts +0 -2
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.41.0-dev.a35cbde8b3",
14
+ "version": "1.41.0-dev.aeab9f930d",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -32,11 +32,11 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "clean": "rm -rf lib && rm -f *.tsbuildinfo",
35
- "build": "tsc -p tsconfig.build.json",
35
+ "build": "tsgo -p tsconfig.build.json",
36
36
  "build:watch": "pnpm run build --watch",
37
37
  "build:release": "pnpm clean && pnpm run build",
38
38
  "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"",
39
- "check-types": "tsc",
39
+ "check-types": "tsgo",
40
40
  "lint": "biome check src/ test/",
41
41
  "lint:fix": "pnpm run lint --write",
42
42
  "test": "pnpm test:unit",
@@ -45,12 +45,11 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@chainsafe/fast-crc32c": "^4.2.0",
48
- "@libp2p/interface": "^2.7.0",
49
- "@lodestar/config": "^1.41.0-dev.a35cbde8b3",
50
- "@lodestar/params": "^1.41.0-dev.a35cbde8b3",
51
- "@lodestar/utils": "^1.41.0-dev.a35cbde8b3",
52
- "it-all": "^3.0.4",
53
- "it-pipe": "^3.0.1",
48
+ "@libp2p/interface": "^3.1.0",
49
+ "@libp2p/utils": "^7.0.12",
50
+ "@lodestar/config": "^1.41.0-dev.aeab9f930d",
51
+ "@lodestar/params": "^1.41.0-dev.aeab9f930d",
52
+ "@lodestar/utils": "^1.41.0-dev.aeab9f930d",
54
53
  "snappy": "^7.2.2",
55
54
  "snappyjs": "^0.7.0",
56
55
  "uint8-varint": "^2.0.2",
@@ -58,16 +57,15 @@
58
57
  },
59
58
  "devDependencies": {
60
59
  "@chainsafe/ssz": "^1.2.2",
61
- "@libp2p/crypto": "^5.1.7",
62
- "@libp2p/logger": "^5.1.21",
63
- "@libp2p/peer-id": "^5.1.8",
64
- "@lodestar/logger": "^1.41.0-dev.a35cbde8b3",
65
- "@lodestar/types": "^1.41.0-dev.a35cbde8b3",
66
- "it-stream-types": "^2.0.2",
67
- "libp2p": "2.9.0"
60
+ "@libp2p/crypto": "^5.1.13",
61
+ "@libp2p/logger": "^6.2.2",
62
+ "@libp2p/peer-id": "^6.0.4",
63
+ "@lodestar/logger": "^1.41.0-dev.aeab9f930d",
64
+ "@lodestar/types": "^1.41.0-dev.aeab9f930d",
65
+ "libp2p": "3.1.5"
68
66
  },
69
67
  "peerDependencies": {
70
- "libp2p": "~2.9.0"
68
+ "libp2p": "^3.1.4"
71
69
  },
72
70
  "keywords": [
73
71
  "ethereum",
@@ -77,5 +75,5 @@
77
75
  "reqresp",
78
76
  "blockchain"
79
77
  ],
80
- "gitHead": "090cdf258f4c067ebe6b5f0bdba221c2c23b9962"
78
+ "gitHead": "89f360d7bf466b84570b2b831517069776a24643"
81
79
  }
package/src/ReqResp.ts CHANGED
@@ -37,8 +37,8 @@ export interface ReqRespOpts extends SendRequestOpts, ReqRespRateLimiterOpts {
37
37
  /**
38
38
  * Implementation of Ethereum Consensus p2p Req/Resp domain.
39
39
  * For the spec that this code is based on, see:
40
- * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-reqresp-domain
41
- * https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#the-reqresp-domain
40
+ * https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#the-reqresp-domain
41
+ * https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/altair/light-client/p2p-interface.md#the-reqresp-domain
42
42
  */
43
43
  export class ReqResp {
44
44
  // protected to be usable by extending class
@@ -221,7 +221,7 @@ export class ReqResp {
221
221
  }
222
222
 
223
223
  private getRequestHandler(protocol: MixedProtocol, protocolID: string) {
224
- return async ({connection, stream}: {connection: Connection; stream: Stream}) => {
224
+ return async (stream: Stream, connection: Connection) => {
225
225
  if (this.dialOnlyProtocols.get(protocolID)) {
226
226
  throw new Error(`Received request on dial only protocol '${protocolID}'`);
227
227
  }
@@ -281,7 +281,7 @@ export class ReqResp {
281
281
  * ```
282
282
  * /ProtocolPrefix/MessageName/SchemaVersion/Encoding
283
283
  * ```
284
- * https://github.com/ethereum/consensus-specs/blob/v1.2.0/specs/phase0/p2p-interface.md#protocol-identification
284
+ * https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#protocol-identification
285
285
  */
286
286
  protected formatProtocolID(protocol: Pick<MixedProtocol, "method" | "version" | "encoding">): string {
287
287
  return formatProtocolID(this.protocolPrefix, protocol.method, protocol.version, protocol.encoding);
@@ -1,8 +1,8 @@
1
- import type {Sink} from "it-stream-types";
2
- import {Uint8ArrayList} from "uint8arraylist";
1
+ import type {Stream} from "@libp2p/interface";
2
+ import {byteStream} from "@libp2p/utils";
3
3
  import {readEncodedPayload} from "../encodingStrategies/index.js";
4
4
  import {MixedProtocol} from "../types.js";
5
- import {BufferedSource} from "../utils/index.js";
5
+ import {drainByteStream} from "../utils/stream.js";
6
6
 
7
7
  const EMPTY_DATA = new Uint8Array();
8
8
 
@@ -12,18 +12,34 @@ const EMPTY_DATA = new Uint8Array();
12
12
  * request ::= <encoding-dependent-header> | <encoded-payload>
13
13
  * ```
14
14
  */
15
- export function requestDecode(
16
- protocol: MixedProtocol
17
- ): Sink<AsyncIterable<Uint8Array | Uint8ArrayList>, Promise<Uint8Array>> {
18
- return async function requestDecodeSink(source) {
19
- const type = protocol.requestSizes;
20
- if (type === null) {
21
- // method has no body
22
- return EMPTY_DATA;
23
- }
15
+ export async function requestDecode(
16
+ protocol: MixedProtocol,
17
+ stream: Stream,
18
+ signal?: AbortSignal
19
+ ): Promise<Uint8Array> {
20
+ const type = protocol.requestSizes;
21
+ if (type === null) {
22
+ // method has no body
23
+ return EMPTY_DATA;
24
+ }
24
25
 
25
- // Request has a single payload, so return immediately
26
- const bufferedSource = new BufferedSource(source as AsyncGenerator<Uint8ArrayList>);
27
- return readEncodedPayload(bufferedSource, protocol.encoding, type);
28
- };
26
+ // Request has a single payload, so return immediately
27
+ const bytes = byteStream(stream);
28
+ let requestReadDone = false;
29
+ try {
30
+ const requestBody = await readEncodedPayload(bytes, protocol.encoding, type, signal);
31
+ requestReadDone = true;
32
+ return requestBody;
33
+ } finally {
34
+ try {
35
+ if (!requestReadDone) {
36
+ // Do not push partial bytes back into the stream on decode failure/abort.
37
+ // This stream is consumed by req/resp only once.
38
+ drainByteStream(bytes);
39
+ }
40
+ bytes.unwrap();
41
+ } catch {
42
+ // Ignore unwrap errors - stream may already be closed
43
+ }
44
+ }
29
45
  }
@@ -9,7 +9,7 @@ import {MixedProtocol} from "../types.js";
9
9
  * Requests may contain no payload (e.g. /eth2/beacon_chain/req/metadata/1/)
10
10
  * if so, it would yield no byte chunks
11
11
  */
12
- export async function* requestEncode(protocol: MixedProtocol, requestBody: Uint8Array): AsyncGenerator<Buffer> {
12
+ export function* requestEncode(protocol: MixedProtocol, requestBody: Uint8Array): Generator<Buffer> {
13
13
  const type = protocol.requestSizes;
14
14
 
15
15
  if (type && requestBody !== null) {
@@ -1,4 +1,6 @@
1
- import {Uint8ArrayList} from "uint8arraylist";
1
+ import type {Stream} from "@libp2p/interface";
2
+ import type {ByteStream} from "@libp2p/utils";
3
+ import {byteStream} from "@libp2p/utils";
2
4
  import {ForkName} from "@lodestar/params";
3
5
  import {readEncodedPayload} from "../encodingStrategies/index.js";
4
6
  import {RespStatus} from "../interface.js";
@@ -10,7 +12,7 @@ import {
10
12
  MixedProtocol,
11
13
  ResponseIncoming,
12
14
  } from "../types.js";
13
- import {BufferedSource, decodeErrorMessage} from "../utils/index.js";
15
+ import {decodeErrorMessage, drainByteStream} from "../utils/index.js";
14
16
 
15
17
  /**
16
18
  * Internal helper type to signal stream ended early
@@ -27,22 +29,17 @@ enum StreamStatus {
27
29
  * result ::= "0" | "1" | "2" | ["128" ... "255"]
28
30
  * ```
29
31
  */
30
- export function responseDecode(
32
+ export async function* responseDecode(
31
33
  protocol: MixedProtocol,
32
- cbs: {
33
- onFirstHeader: () => void;
34
- onFirstResponseChunk: () => void;
35
- }
36
- ): (source: AsyncIterable<Uint8Array | Uint8ArrayList>) => AsyncIterable<ResponseIncoming> {
37
- return async function* responseDecodeSink(source) {
38
- const bufferedSource = new BufferedSource(source as AsyncGenerator<Uint8ArrayList>);
39
-
40
- let readFirstHeader = false;
41
- let readFirstResponseChunk = false;
34
+ stream: Stream,
35
+ opts: {signal?: AbortSignal; getError?: () => Error} = {}
36
+ ): AsyncIterable<ResponseIncoming> {
37
+ const bytes = byteStream(stream);
38
+ let responseReadDone = false;
42
39
 
43
- // Consumers of `responseDecode()` may limit the number of <response_chunk> and break out of the while loop
44
- while (!bufferedSource.isDone) {
45
- const status = await readResultHeader(bufferedSource);
40
+ try {
41
+ while (true) {
42
+ const status = await readResultHeader(bytes, opts.signal);
46
43
 
47
44
  // Stream is only allowed to end at the start of a <response_chunk> block
48
45
  // The happens when source ends before readResultHeader() can fetch 1 byte
@@ -50,34 +47,41 @@ export function responseDecode(
50
47
  break;
51
48
  }
52
49
 
53
- if (!readFirstHeader) {
54
- cbs.onFirstHeader();
55
- readFirstHeader = true;
56
- }
57
-
58
50
  // For multiple chunks, only the last chunk is allowed to have a non-zero error
59
51
  // code (i.e. The chunk stream is terminated once an error occurs
60
52
  if (status !== RespStatus.SUCCESS) {
61
- const errorMessage = await readErrorMessage(bufferedSource);
53
+ const errorMessage = await readErrorMessage(bytes, opts.signal);
62
54
  throw new ResponseError(status, errorMessage);
63
55
  }
64
56
 
65
- const forkName = await readContextBytes(protocol.contextBytes, bufferedSource);
57
+ const forkName = await readContextBytes(protocol.contextBytes, bytes, opts.signal);
66
58
  const typeSizes = protocol.responseSizes(forkName);
67
- const chunkData = await readEncodedPayload(bufferedSource, protocol.encoding, typeSizes);
59
+ const chunkData = await readEncodedPayload(bytes, protocol.encoding, typeSizes, opts.signal);
68
60
 
69
61
  yield {
70
62
  data: chunkData,
71
63
  fork: forkName,
72
64
  protocolVersion: protocol.version,
73
65
  };
74
-
75
- if (!readFirstResponseChunk) {
76
- cbs.onFirstResponseChunk();
77
- readFirstResponseChunk = true;
66
+ }
67
+ responseReadDone = true;
68
+ } catch (e) {
69
+ if (opts.signal?.aborted && opts.getError) {
70
+ throw opts.getError();
71
+ }
72
+ throw e;
73
+ } finally {
74
+ try {
75
+ if (!responseReadDone) {
76
+ // Do not push partial bytes back into the stream on decode failure/abort.
77
+ // This stream is consumed by req/resp only once.
78
+ drainByteStream(bytes);
78
79
  }
80
+ bytes.unwrap();
81
+ } catch {
82
+ // Ignore unwrap errors - stream may already be closed
79
83
  }
80
- };
84
+ }
81
85
  }
82
86
 
83
87
  /**
@@ -87,18 +91,17 @@ export function responseDecode(
87
91
  * ```
88
92
  * `<response_chunk>` starts with a single-byte response code which determines the contents of the response_chunk
89
93
  */
90
- export async function readResultHeader(bufferedSource: BufferedSource): Promise<RespStatus | StreamStatus> {
91
- for await (const buffer of bufferedSource) {
92
- const status = buffer.get(0);
93
- buffer.consume(1);
94
-
95
- // If first chunk had zero bytes status === null, get next
96
- if (status !== null) {
97
- return status;
98
- }
99
- }
100
-
101
- return StreamStatus.Ended;
94
+ export async function readResultHeader(
95
+ bytes: ByteStream<Stream>,
96
+ signal?: AbortSignal
97
+ ): Promise<RespStatus | StreamStatus> {
98
+ const chunk = await bytes.read({bytes: 1, signal}).catch((e) => {
99
+ if ((e as Error).name === "UnexpectedEOFError") return null;
100
+ throw e;
101
+ });
102
+ if (chunk === null) return StreamStatus.Ended;
103
+
104
+ return chunk.get(0);
102
105
  }
103
106
 
104
107
  /**
@@ -108,28 +111,29 @@ export async function readResultHeader(bufferedSource: BufferedSource): Promise<
108
111
  * result ::= "1" | "2" | ["128" ... "255"]
109
112
  * ```
110
113
  */
111
- export async function readErrorMessage(bufferedSource: BufferedSource): Promise<string> {
112
- // Read at least 256 or wait for the stream to end
113
- let length: number | undefined;
114
- for await (const buffer of bufferedSource) {
115
- // Wait for next chunk with bytes or for the stream to end
116
- // Note: The entire <error_message> is expected to be in the same chunk
117
- if (buffer.length >= 256) {
118
- length = 256;
114
+ export async function readErrorMessage(bytes: ByteStream<Stream>, signal?: AbortSignal): Promise<string> {
115
+ const chunks: Uint8Array[] = [];
116
+ let total = 0;
117
+
118
+ while (total < 256) {
119
+ const chunk = await bytes.read({signal}).catch((e) => {
120
+ if ((e as Error).name === "UnexpectedEOFError") return null;
121
+ throw e;
122
+ });
123
+ if (chunk === null) {
124
+ // If EOF is reached while satisfying a larger read, libp2p v3 may still have
125
+ // buffered bytes available. Drain them so error_message matches pre-v3 behavior.
126
+ const remaining = drainByteStream(bytes);
127
+ if (remaining) {
128
+ chunks.push(remaining);
129
+ }
119
130
  break;
120
131
  }
121
- length = buffer.length;
132
+ chunks.push(chunk.subarray());
133
+ total += chunk.byteLength;
122
134
  }
123
135
 
124
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
125
- const bytes = bufferedSource["buffer"].slice(0, length);
126
-
127
- try {
128
- return decodeErrorMessage(bytes);
129
- } catch (_e) {
130
- // Error message is optional and may not be included in the response stream
131
- return Buffer.prototype.toString.call(bytes, "hex");
132
- }
136
+ return decodeErrorMessage(Buffer.concat(chunks).subarray(0, 256));
133
137
  }
134
138
 
135
139
  /**
@@ -139,14 +143,15 @@ export async function readErrorMessage(bufferedSource: BufferedSource): Promise<
139
143
  */
140
144
  export async function readContextBytes(
141
145
  contextBytes: ContextBytesFactory,
142
- bufferedSource: BufferedSource
146
+ bytes: ByteStream<Stream>,
147
+ signal?: AbortSignal
143
148
  ): Promise<ForkName> {
144
149
  switch (contextBytes.type) {
145
150
  case ContextBytesType.Empty:
146
151
  return ForkName.phase0;
147
152
 
148
153
  case ContextBytesType.ForkDigest: {
149
- const forkDigest = await readContextBytesForkDigest(bufferedSource);
154
+ const forkDigest = await readContextBytesForkDigest(bytes, signal);
150
155
  return contextBytes.config.forkDigest2ForkBoundary(forkDigest).fork;
151
156
  }
152
157
  }
@@ -155,15 +160,6 @@ export async function readContextBytes(
155
160
  /**
156
161
  * Consumes a stream source to read `<context-bytes>`, where it's a fixed-width 4 byte
157
162
  */
158
- export async function readContextBytesForkDigest(bufferedSource: BufferedSource): Promise<Uint8Array> {
159
- for await (const buffer of bufferedSource) {
160
- if (buffer.length >= CONTEXT_BYTES_FORK_DIGEST_LENGTH) {
161
- const bytes = buffer.slice(0, CONTEXT_BYTES_FORK_DIGEST_LENGTH);
162
- buffer.consume(CONTEXT_BYTES_FORK_DIGEST_LENGTH);
163
- return bytes;
164
- }
165
- }
166
-
167
- // TODO: Use typed error
168
- throw Error("Source ended while reading context bytes");
163
+ export async function readContextBytesForkDigest(bytes: ByteStream<Stream>, signal?: AbortSignal): Promise<Uint8Array> {
164
+ return (await bytes.read({bytes: CONTEXT_BYTES_FORK_DIGEST_LENGTH, signal})).subarray();
169
165
  }
@@ -14,30 +14,23 @@ const SUCCESS_BUFFER = Buffer.from([RespStatus.SUCCESS]);
14
14
  * ```
15
15
  * Note: `response` has zero or more chunks (denoted by `<>*`)
16
16
  */
17
- export function responseEncodeSuccess(
17
+ export async function* responseEncodeSuccess(
18
18
  protocol: Protocol,
19
- cbs: {onChunk: (chunkIndex: number) => void}
20
- ): (source: AsyncIterable<ResponseOutgoing>) => AsyncIterable<Buffer> {
21
- return async function* responseEncodeSuccessTransform(source) {
22
- let chunkIndex = 0;
19
+ source: AsyncIterable<ResponseOutgoing>
20
+ ): AsyncIterable<Uint8Array> {
21
+ for await (const chunk of source) {
22
+ // <result>
23
+ yield SUCCESS_BUFFER;
23
24
 
24
- for await (const chunk of source) {
25
- // Postfix increment, return 0 as first chunk
26
- cbs.onChunk(chunkIndex++);
27
-
28
- // <result>
29
- yield SUCCESS_BUFFER;
30
-
31
- // <context-bytes> - from altair
32
- const contextBytes = getContextBytes(protocol.contextBytes, chunk);
33
- if (contextBytes) {
34
- yield contextBytes as Buffer;
35
- }
36
-
37
- // <encoding-dependent-header> | <encoded-payload>
38
- yield* writeEncodedPayload(chunk.data, protocol.encoding);
25
+ // <context-bytes> - from altair
26
+ const contextBytes = getContextBytes(protocol.contextBytes, chunk);
27
+ if (contextBytes) {
28
+ yield contextBytes;
39
29
  }
40
- };
30
+
31
+ // <encoding-dependent-header> | <encoded-payload>
32
+ yield* writeEncodedPayload(chunk.data, protocol.encoding);
33
+ }
41
34
  }
42
35
 
43
36
  /**
@@ -50,20 +43,15 @@ export function responseEncodeSuccess(
50
43
  * Only the last `<response_chunk>` is allowed to have a non-zero error code, so this
51
44
  * fn yields exactly one `<error_response>` and afterwards the stream must be terminated
52
45
  */
53
- export async function* responseEncodeError(
46
+ export function* responseEncodeError(
54
47
  protocol: Pick<MixedProtocol, "encoding">,
55
48
  status: RpcResponseStatusError,
56
49
  errorMessage: string
57
- ): AsyncGenerator<Buffer> {
58
- if (!errorMessage) {
59
- yield Buffer.from([status]);
60
- return;
61
- }
62
-
50
+ ): Generator<Buffer> {
63
51
  // Combine <result> and <error_message> into a single chunk for atomic delivery.
64
52
  // Yielding them separately causes a race condition where the stream closes after the
65
53
  // status byte but before the error message arrives on the reader side.
66
- const errorMessageBuffer = await encodeErrorMessageToBuffer(errorMessage, protocol.encoding);
54
+ const errorMessageBuffer = encodeErrorMessageToBuffer(errorMessage, protocol.encoding);
67
55
  yield Buffer.concat([Buffer.from([status]), errorMessageBuffer]);
68
56
  }
69
57
 
@@ -1,10 +1,11 @@
1
+ import type {Stream} from "@libp2p/interface";
2
+ import type {ByteStream} from "@libp2p/utils";
1
3
  import {Encoding, TypeSizes} from "../types.js";
2
- import {BufferedSource} from "../utils/index.js";
3
4
  import {readSszSnappyPayload} from "./sszSnappy/decode.js";
4
5
  import {writeSszSnappyPayload} from "./sszSnappy/encode.js";
5
6
 
6
7
  // For more info about Ethereum Consensus request/response encoding strategies, see:
7
- // https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#encoding-strategies
8
+ // https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#encoding-strategies
8
9
  // Supported encoding strategies:
9
10
  // - ssz_snappy
10
11
 
@@ -15,13 +16,14 @@ import {writeSszSnappyPayload} from "./sszSnappy/encode.js";
15
16
  * ```
16
17
  */
17
18
  export async function readEncodedPayload(
18
- bufferedSource: BufferedSource,
19
+ stream: ByteStream<Stream>,
19
20
  encoding: Encoding,
20
- type: TypeSizes
21
+ type: TypeSizes,
22
+ signal?: AbortSignal
21
23
  ): Promise<Uint8Array> {
22
24
  switch (encoding) {
23
25
  case Encoding.SSZ_SNAPPY:
24
- return readSszSnappyPayload(bufferedSource, type);
26
+ return readSszSnappyPayload(stream, type, signal);
25
27
 
26
28
  default:
27
29
  throw Error("Unsupported encoding");
@@ -34,7 +36,7 @@ export async function readEncodedPayload(
34
36
  * <encoding-dependent-header> | <encoded-payload>
35
37
  * ```
36
38
  */
37
- export async function* writeEncodedPayload(chunkData: Uint8Array, encoding: Encoding): AsyncGenerator<Buffer> {
39
+ export function* writeEncodedPayload(chunkData: Uint8Array, encoding: Encoding): Generator<Buffer> {
38
40
  switch (encoding) {
39
41
  case Encoding.SSZ_SNAPPY:
40
42
  yield* writeSszSnappyPayload(chunkData);