@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.
- package/README.md +1 -1
- package/lib/ReqResp.d.ts +3 -3
- package/lib/ReqResp.d.ts.map +1 -1
- package/lib/ReqResp.js +4 -4
- package/lib/ReqResp.js.map +1 -1
- package/lib/encoders/requestDecode.d.ts +2 -3
- package/lib/encoders/requestDecode.d.ts.map +1 -1
- package/lib/encoders/requestDecode.js +28 -11
- package/lib/encoders/requestDecode.js.map +1 -1
- package/lib/encoders/requestEncode.d.ts +1 -1
- package/lib/encoders/requestEncode.d.ts.map +1 -1
- package/lib/encoders/requestEncode.js +1 -1
- package/lib/encoders/requestEncode.js.map +1 -1
- package/lib/encoders/responseDecode.d.ts +10 -10
- package/lib/encoders/responseDecode.d.ts.map +1 -1
- package/lib/encoders/responseDecode.js +63 -60
- package/lib/encoders/responseDecode.js.map +1 -1
- package/lib/encoders/responseEncode.d.ts +2 -4
- package/lib/encoders/responseEncode.d.ts.map +1 -1
- package/lib/encoders/responseEncode.js +13 -22
- package/lib/encoders/responseEncode.js.map +1 -1
- package/lib/encodingStrategies/index.d.ts +4 -3
- package/lib/encodingStrategies/index.d.ts.map +1 -1
- package/lib/encodingStrategies/index.js +4 -4
- package/lib/encodingStrategies/index.js.map +1 -1
- package/lib/encodingStrategies/sszSnappy/decode.d.ts +5 -4
- package/lib/encodingStrategies/sszSnappy/decode.d.ts.map +1 -1
- package/lib/encodingStrategies/sszSnappy/decode.js +83 -52
- package/lib/encodingStrategies/sszSnappy/decode.js.map +1 -1
- package/lib/encodingStrategies/sszSnappy/encode.d.ts +2 -2
- package/lib/encodingStrategies/sszSnappy/encode.d.ts.map +1 -1
- package/lib/encodingStrategies/sszSnappy/encode.js +1 -1
- package/lib/encodingStrategies/sszSnappy/encode.js.map +1 -1
- package/lib/encodingStrategies/sszSnappy/errors.d.ts +0 -8
- package/lib/encodingStrategies/sszSnappy/errors.d.ts.map +1 -1
- package/lib/encodingStrategies/sszSnappy/errors.js +2 -3
- package/lib/encodingStrategies/sszSnappy/errors.js.map +1 -1
- package/lib/encodingStrategies/sszSnappy/index.d.ts +0 -1
- package/lib/encodingStrategies/sszSnappy/index.d.ts.map +1 -1
- package/lib/encodingStrategies/sszSnappy/index.js +0 -1
- package/lib/encodingStrategies/sszSnappy/index.js.map +1 -1
- package/lib/encodingStrategies/sszSnappy/utils.js.map +1 -1
- package/lib/interface.js +2 -1
- package/lib/interface.js.map +1 -1
- package/lib/metrics.d.ts +1 -7
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js +1 -17
- package/lib/metrics.js.map +1 -1
- package/lib/rate_limiter/ReqRespRateLimiter.d.ts.map +1 -1
- package/lib/rate_limiter/ReqRespRateLimiter.js.map +1 -1
- package/lib/rate_limiter/rateLimiterGRCA.d.ts.map +1 -1
- package/lib/rate_limiter/rateLimiterGRCA.js.map +1 -1
- package/lib/rate_limiter/selfRateLimiter.d.ts.map +1 -1
- package/lib/rate_limiter/selfRateLimiter.js.map +1 -1
- package/lib/request/errors.d.ts +1 -7
- package/lib/request/errors.d.ts.map +1 -1
- package/lib/request/errors.js +3 -6
- package/lib/request/errors.js.map +1 -1
- package/lib/request/index.d.ts +0 -3
- package/lib/request/index.d.ts.map +1 -1
- package/lib/request/index.js +85 -70
- package/lib/request/index.js.map +1 -1
- package/lib/response/errors.d.ts.map +1 -1
- package/lib/response/errors.js +2 -1
- package/lib/response/errors.js.map +1 -1
- package/lib/response/index.d.ts +2 -2
- package/lib/response/index.d.ts.map +1 -1
- package/lib/response/index.js +46 -50
- package/lib/response/index.js.map +1 -1
- package/lib/types.d.ts +1 -2
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +7 -5
- package/lib/types.js.map +1 -1
- package/lib/utils/collectExactOne.js.map +1 -1
- package/lib/utils/collectMaxResponse.d.ts.map +1 -1
- package/lib/utils/collectMaxResponse.js +1 -2
- package/lib/utils/collectMaxResponse.js.map +1 -1
- package/lib/utils/errorMessage.d.ts +3 -3
- package/lib/utils/errorMessage.d.ts.map +1 -1
- package/lib/utils/errorMessage.js +14 -13
- package/lib/utils/errorMessage.js.map +1 -1
- package/lib/utils/index.d.ts +1 -3
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +1 -3
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/peerId.js.map +1 -1
- package/lib/utils/protocolId.d.ts +2 -2
- package/lib/utils/protocolId.js +2 -2
- package/lib/utils/protocolId.js.map +1 -1
- package/lib/utils/snappyCommon.js +2 -1
- package/lib/utils/snappyCommon.js.map +1 -1
- package/lib/utils/snappyCompress.d.ts +1 -1
- package/lib/utils/snappyCompress.d.ts.map +1 -1
- package/lib/utils/snappyCompress.js +1 -1
- package/lib/utils/snappyCompress.js.map +1 -1
- package/lib/utils/snappyIndex.d.ts +1 -1
- package/lib/utils/snappyIndex.d.ts.map +1 -1
- package/lib/utils/snappyIndex.js +1 -1
- package/lib/utils/snappyIndex.js.map +1 -1
- package/lib/utils/snappyUncompress.d.ts +7 -11
- package/lib/utils/snappyUncompress.d.ts.map +1 -1
- package/lib/utils/snappyUncompress.js +68 -68
- package/lib/utils/snappyUncompress.js.map +1 -1
- package/lib/utils/stream.d.ts +6 -0
- package/lib/utils/stream.d.ts.map +1 -0
- package/lib/utils/stream.js +21 -0
- package/lib/utils/stream.js.map +1 -0
- package/package.json +16 -18
- package/src/ReqResp.ts +4 -4
- package/src/encoders/requestDecode.ts +32 -16
- package/src/encoders/requestEncode.ts +1 -1
- package/src/encoders/responseDecode.ts +68 -72
- package/src/encoders/responseEncode.ts +17 -29
- package/src/encodingStrategies/index.ts +8 -6
- package/src/encodingStrategies/sszSnappy/decode.ts +111 -53
- package/src/encodingStrategies/sszSnappy/encode.ts +2 -2
- package/src/encodingStrategies/sszSnappy/errors.ts +0 -4
- package/src/encodingStrategies/sszSnappy/index.ts +0 -1
- package/src/metrics.ts +1 -17
- package/src/request/errors.ts +1 -6
- package/src/request/index.ts +109 -86
- package/src/response/index.ts +55 -61
- package/src/types.ts +1 -3
- package/src/utils/collectMaxResponse.ts +1 -2
- package/src/utils/errorMessage.ts +14 -13
- package/src/utils/index.ts +1 -3
- package/src/utils/protocolId.ts +2 -2
- package/src/utils/snappyCompress.ts +1 -1
- package/src/utils/snappyIndex.ts +1 -1
- package/src/utils/snappyUncompress.ts +73 -75
- package/src/utils/stream.ts +34 -0
- package/lib/utils/abortableSource.d.ts +0 -12
- package/lib/utils/abortableSource.d.ts.map +0 -1
- package/lib/utils/abortableSource.js +0 -69
- package/lib/utils/abortableSource.js.map +0 -1
- package/lib/utils/bufferedSource.d.ts +0 -16
- package/lib/utils/bufferedSource.d.ts.map +0 -1
- package/lib/utils/bufferedSource.js +0 -40
- package/lib/utils/bufferedSource.js.map +0 -1
- package/lib/utils/onChunk.d.ts +0 -6
- package/lib/utils/onChunk.d.ts.map +0 -1
- package/lib/utils/onChunk.js +0 -13
- package/lib/utils/onChunk.js.map +0 -1
- package/lib/utils/snappy.d.ts +0 -3
- package/lib/utils/snappy.d.ts.map +0 -1
- package/lib/utils/snappy.js +0 -3
- package/lib/utils/snappy.js.map +0 -1
- package/src/utils/abortableSource.ts +0 -80
- package/src/utils/bufferedSource.ts +0 -46
- package/src/utils/onChunk.ts +0 -12
- package/src/utils/snappy.ts +0 -2
package/src/response/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {PeerId, Stream} from "@libp2p/interface";
|
|
2
|
-
import {pipe} from "it-pipe";
|
|
3
2
|
import {Logger, TimeoutError, withTimeout} from "@lodestar/utils";
|
|
4
3
|
import {requestDecode} from "../encoders/requestDecode.js";
|
|
5
4
|
import {responseEncodeError, responseEncodeSuccess} from "../encoders/responseEncode.js";
|
|
@@ -8,12 +7,12 @@ import {Metrics} from "../metrics.js";
|
|
|
8
7
|
import {ReqRespRateLimiter} from "../rate_limiter/ReqRespRateLimiter.js";
|
|
9
8
|
import {RequestError, RequestErrorCode} from "../request/errors.js";
|
|
10
9
|
import {Protocol, ReqRespRequest} from "../types.js";
|
|
11
|
-
import {prettyPrintPeerId} from "../utils/index.js";
|
|
10
|
+
import {prettyPrintPeerId, sendChunks} from "../utils/index.js";
|
|
12
11
|
import {ResponseError} from "./errors.js";
|
|
13
12
|
|
|
14
13
|
export {ResponseError};
|
|
15
14
|
|
|
16
|
-
//
|
|
15
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#the-reqresp-domain
|
|
17
16
|
export const DEFAULT_REQUEST_TIMEOUT = 5 * 1000; // 5 sec
|
|
18
17
|
|
|
19
18
|
export interface HandleRequestOpts {
|
|
@@ -28,7 +27,7 @@ export interface HandleRequestOpts {
|
|
|
28
27
|
requestId?: number;
|
|
29
28
|
/** Peer client type for logging and metrics: 'prysm' | 'lighthouse' */
|
|
30
29
|
peerClient?: string;
|
|
31
|
-
/**
|
|
30
|
+
/** Timeout for reading the incoming request payload */
|
|
32
31
|
requestTimeoutMs?: number;
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -67,71 +66,66 @@ export async function handleRequest({
|
|
|
67
66
|
metrics?.incomingOpenedStreams.inc({method: protocol.method});
|
|
68
67
|
|
|
69
68
|
let responseError: Error | null = null;
|
|
70
|
-
|
|
69
|
+
let streamSendError: Error | null = null;
|
|
70
|
+
|
|
71
|
+
try {
|
|
71
72
|
// Yields success chunks and error chunks in the same generator
|
|
72
|
-
// This syntax allows to recycle stream
|
|
73
|
+
// This syntax allows to recycle stream to send success and error chunks without returning
|
|
73
74
|
// in case request whose body is a List fails at chunk_i > 0, without breaking out of the for..await..of
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
await sendChunks(
|
|
76
|
+
stream,
|
|
77
|
+
(async function* requestHandlerSource() {
|
|
78
|
+
try {
|
|
79
|
+
const requestBody = await withTimeout(
|
|
80
|
+
(timeoutAndParentSignal) => requestDecode(protocol, stream, timeoutAndParentSignal),
|
|
81
|
+
REQUEST_TIMEOUT,
|
|
82
|
+
signal
|
|
83
|
+
).catch((e: unknown) => {
|
|
84
|
+
if (e instanceof TimeoutError) {
|
|
85
|
+
throw e; // Let outer catch re-type the error as SERVER_ERROR
|
|
86
|
+
}
|
|
87
|
+
throw new ResponseError(RespStatus.INVALID_REQUEST, (e as Error).message);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
logger.debug("Req received", logCtx);
|
|
91
|
+
|
|
92
|
+
// Max count by request for byRange and byRoot
|
|
93
|
+
const requestCount = protocol?.inboundRateLimits?.getRequestCount?.(requestBody) ?? 1;
|
|
94
|
+
|
|
95
|
+
if (!rateLimiter.allows(peerId, protocolID, requestCount)) {
|
|
96
|
+
throw new RequestError({code: RequestErrorCode.REQUEST_RATE_LIMITED});
|
|
86
97
|
}
|
|
87
|
-
throw new ResponseError(RespStatus.INVALID_REQUEST, (e as Error).message);
|
|
88
|
-
});
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
const requestChunk: ReqRespRequest = {
|
|
100
|
+
data: requestBody,
|
|
101
|
+
version: protocol.version,
|
|
102
|
+
};
|
|
91
103
|
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
yield* responseEncodeSuccess(protocol, protocol.handler(requestChunk, peerId, peerClient));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
const status = e instanceof ResponseError ? e.status : RespStatus.SERVER_ERROR;
|
|
107
|
+
yield* responseEncodeError(protocol, status, (e as Error).message);
|
|
94
108
|
|
|
95
|
-
|
|
96
|
-
throw new RequestError({code: RequestErrorCode.REQUEST_RATE_LIMITED});
|
|
109
|
+
responseError = e as Error;
|
|
97
110
|
}
|
|
111
|
+
})(),
|
|
112
|
+
signal
|
|
113
|
+
);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
streamSendError = e as Error;
|
|
116
|
+
throw e;
|
|
117
|
+
} finally {
|
|
118
|
+
if (streamSendError) {
|
|
119
|
+
stream.abort(streamSendError);
|
|
120
|
+
} else {
|
|
121
|
+
await stream.close().catch((e) => {
|
|
122
|
+
stream.abort(e as Error);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
metrics?.incomingClosedStreams.inc({method: protocol.method});
|
|
126
|
+
}
|
|
98
127
|
|
|
99
|
-
|
|
100
|
-
data: requestBody,
|
|
101
|
-
version: protocol.version,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
yield* pipe(
|
|
105
|
-
// TODO: Debug the reason for type conversion here
|
|
106
|
-
protocol.handler(requestChunk, peerId, peerClient),
|
|
107
|
-
// NOTE: Do not log the resp chunk contents, logs get extremely cluttered
|
|
108
|
-
// Note: Not logging on each chunk since after 1 year it hasn't add any value when debugging
|
|
109
|
-
// onChunk(() => logger.debug("Resp sending chunk", logCtx)),
|
|
110
|
-
responseEncodeSuccess(protocol, {
|
|
111
|
-
onChunk(chunkIndex) {
|
|
112
|
-
if (chunkIndex === 0) timerTTFB?.();
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
);
|
|
116
|
-
} catch (e) {
|
|
117
|
-
const status = e instanceof ResponseError ? e.status : RespStatus.SERVER_ERROR;
|
|
118
|
-
yield* responseEncodeError(protocol, status, (e as Error).message);
|
|
119
|
-
|
|
120
|
-
// Should not throw an error here or libp2p-mplex throws with 'AbortError: stream reset'
|
|
121
|
-
// throw e;
|
|
122
|
-
responseError = e as Error;
|
|
123
|
-
}
|
|
124
|
-
})(),
|
|
125
|
-
stream.sink
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// If streak.sink throws, libp2p-mplex will close stream.source
|
|
129
|
-
// If `requestDecode()` throws the stream.source must be closed manually
|
|
130
|
-
// To ensure the stream.source it-pushable instance is always closed, stream.close() is called always
|
|
131
|
-
await stream.close();
|
|
132
|
-
metrics?.incomingClosedStreams.inc({method: protocol.method});
|
|
133
|
-
|
|
134
|
-
// TODO: It may happen that stream.sink returns before returning stream.source first,
|
|
128
|
+
// TODO: It may happen that the response write completes before the request is fully read,
|
|
135
129
|
// so you never see "Resp received request" in the logs and the response ends without
|
|
136
130
|
// sending any chunk, triggering EMPTY_RESPONSE error on the requesting side
|
|
137
131
|
// It has only happened when doing a request too fast upon immediate connection on inbound peer
|
package/src/types.ts
CHANGED
|
@@ -4,11 +4,9 @@ import {ForkName} from "@lodestar/params";
|
|
|
4
4
|
import {LodestarError} from "@lodestar/utils";
|
|
5
5
|
import {RateLimiterQuota} from "./rate_limiter/rateLimiterGRCA.js";
|
|
6
6
|
|
|
7
|
-
export const protocolPrefix = "/eth2/beacon_chain/req";
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* Available request/response encoding strategies:
|
|
11
|
-
* https://github.com/ethereum/consensus-specs/blob/v1.1
|
|
9
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#encoding-strategies
|
|
12
10
|
*/
|
|
13
11
|
export enum Encoding {
|
|
14
12
|
SSZ_SNAPPY = "ssz_snappy",
|
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
* Collects a bounded list of responses up to `maxResponses`
|
|
7
7
|
*/
|
|
8
8
|
export async function collectMaxResponse<T>(source: AsyncIterable<T>, maxResponses: number): Promise<T[]> {
|
|
9
|
-
// else: zero or more responses
|
|
10
9
|
const responses: T[] = [];
|
|
11
10
|
for await (const response of source) {
|
|
12
11
|
responses.push(response);
|
|
13
12
|
|
|
14
|
-
if (
|
|
13
|
+
if (responses.length >= maxResponses) {
|
|
15
14
|
break;
|
|
16
15
|
}
|
|
17
16
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {decode as varintDecode, encodingLength as varintEncodingLength} from "uint8-varint";
|
|
2
|
-
import {Uint8ArrayList} from "uint8arraylist";
|
|
3
2
|
import {writeSszSnappyPayload} from "../encodingStrategies/sszSnappy/encode.js";
|
|
4
3
|
import {Encoding} from "../types.js";
|
|
5
|
-
import {
|
|
4
|
+
import {decodeSnappyFrames} from "./snappyIndex.js";
|
|
6
5
|
|
|
7
6
|
// ErrorMessage schema:
|
|
8
7
|
//
|
|
@@ -13,18 +12,21 @@ import {SnappyFramesUncompress} from "./snappyIndex.js";
|
|
|
13
12
|
// By convention, the error_message is a sequence of bytes that MAY be interpreted as a
|
|
14
13
|
// UTF-8 string (for debugging purposes). Clients MUST treat as valid any byte sequences
|
|
15
14
|
//
|
|
16
|
-
//
|
|
15
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#responding-side
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Encodes a UTF-8 string to 256 bytes max
|
|
20
19
|
*/
|
|
21
|
-
export
|
|
20
|
+
export function* encodeErrorMessage(errorMessage: string, encoding: Encoding): Generator<Buffer> {
|
|
22
21
|
const encoder = new TextEncoder();
|
|
23
22
|
const bytes = encoder.encode(errorMessage).slice(0, 256);
|
|
24
23
|
|
|
25
24
|
switch (encoding) {
|
|
26
25
|
case Encoding.SSZ_SNAPPY:
|
|
27
26
|
yield* writeSszSnappyPayload(bytes);
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
throw Error("Unsupported encoding");
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -32,9 +34,9 @@ export async function* encodeErrorMessage(errorMessage: string, encoding: Encodi
|
|
|
32
34
|
* Encodes a UTF-8 error message string into a single buffer (max 256 bytes before encoding).
|
|
33
35
|
* Unlike `encodeErrorMessage`, this collects all encoded chunks into one buffer.
|
|
34
36
|
*/
|
|
35
|
-
export
|
|
37
|
+
export function encodeErrorMessageToBuffer(errorMessage: string, encoding: Encoding): Buffer {
|
|
36
38
|
const chunks: Buffer[] = [];
|
|
37
|
-
for
|
|
39
|
+
for (const chunk of encodeErrorMessage(errorMessage, encoding)) {
|
|
38
40
|
chunks.push(chunk);
|
|
39
41
|
}
|
|
40
42
|
return Buffer.concat(chunks);
|
|
@@ -43,21 +45,20 @@ export async function encodeErrorMessageToBuffer(errorMessage: string, encoding:
|
|
|
43
45
|
/**
|
|
44
46
|
* Decodes error message from network bytes and removes non printable, non ascii characters.
|
|
45
47
|
*/
|
|
46
|
-
export
|
|
47
|
-
const
|
|
48
|
+
export function decodeErrorMessage(encodedErrorMessage: Uint8Array): string {
|
|
49
|
+
const decoder = new TextDecoder();
|
|
48
50
|
let sszDataLength: number;
|
|
49
51
|
try {
|
|
50
52
|
sszDataLength = varintDecode(encodedErrorMessage);
|
|
51
|
-
const decompressor = new SnappyFramesUncompress();
|
|
52
53
|
const varintBytes = varintEncodingLength(sszDataLength);
|
|
53
|
-
const errorMessage =
|
|
54
|
-
if (errorMessage
|
|
54
|
+
const errorMessage = decodeSnappyFrames(encodedErrorMessage.subarray(varintBytes));
|
|
55
|
+
if (errorMessage.length !== sszDataLength) {
|
|
55
56
|
throw new Error("Malformed input: data length mismatch");
|
|
56
57
|
}
|
|
57
58
|
// remove non ascii characters from string
|
|
58
|
-
return
|
|
59
|
+
return decoder.decode(errorMessage.subarray(0)).replace(/[^\x20-\x7F]/g, "");
|
|
59
60
|
} catch (_e) {
|
|
60
61
|
// remove non ascii characters from string
|
|
61
|
-
return
|
|
62
|
+
return decoder.decode(encodedErrorMessage.slice(0, 256)).replace(/[^\x20-\x7F]/g, "");
|
|
62
63
|
}
|
|
63
64
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
export * from "./abortableSource.js";
|
|
2
|
-
export * from "./bufferedSource.js";
|
|
3
1
|
export * from "./collectExactOne.js";
|
|
4
2
|
export * from "./collectMaxResponse.js";
|
|
5
3
|
export * from "./errorMessage.js";
|
|
6
|
-
export * from "./onChunk.js";
|
|
7
4
|
export * from "./peerId.js";
|
|
8
5
|
export * from "./protocolId.js";
|
|
9
6
|
export * from "./snappyIndex.js";
|
|
7
|
+
export * from "./stream.js";
|
package/src/utils/protocolId.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {Encoding, ProtocolAttributes} from "../types.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* https://github.com/ethereum/consensus-specs/blob/v1.
|
|
4
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#protocol-identification
|
|
5
5
|
*/
|
|
6
6
|
export function formatProtocolID(protocolPrefix: string, method: string, version: number, encoding: Encoding): string {
|
|
7
7
|
return `${protocolPrefix}/${method}/${version}/${encoding}`;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* https://github.com/ethereum/consensus-specs/blob/v1.
|
|
11
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#protocol-identification
|
|
12
12
|
*/
|
|
13
13
|
export function parseProtocolID(protocolId: string): ProtocolAttributes {
|
|
14
14
|
const result = protocolId.split("/");
|
|
@@ -3,7 +3,7 @@ import {compressSync} from "snappy";
|
|
|
3
3
|
import {ChunkType, IDENTIFIER_FRAME, UNCOMPRESSED_CHUNK_SIZE, crc} from "./snappyCommon.js";
|
|
4
4
|
|
|
5
5
|
// The logic in this file is largely copied (in simplified form) from https://github.com/ChainSafe/node-snappy-stream/
|
|
6
|
-
export
|
|
6
|
+
export function* encodeSnappy(bytes: Buffer): Generator<Buffer> {
|
|
7
7
|
yield IDENTIFIER_FRAME;
|
|
8
8
|
|
|
9
9
|
for (let i = 0; i < bytes.length; i += UNCOMPRESSED_CHUNK_SIZE) {
|
package/src/utils/snappyIndex.ts
CHANGED
|
@@ -2,96 +2,94 @@ import {uncompress} from "snappyjs";
|
|
|
2
2
|
import {Uint8ArrayList} from "uint8arraylist";
|
|
3
3
|
import {ChunkType, IDENTIFIER, UNCOMPRESSED_CHUNK_SIZE, crc} from "./snappyCommon.js";
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
foundIdentifier: false,
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Accepts chunk of data containing some part of snappy frames stream
|
|
14
|
-
* @param chunk
|
|
15
|
-
* @return Buffer if there is one or more whole frames, null if it's partial
|
|
16
|
-
*/
|
|
17
|
-
uncompress(chunk: Uint8ArrayList): Uint8ArrayList | null {
|
|
18
|
-
this.buffer.append(chunk);
|
|
19
|
-
const result = new Uint8ArrayList();
|
|
20
|
-
while (this.buffer.length > 0) {
|
|
21
|
-
if (this.buffer.length < 4) break;
|
|
5
|
+
export function parseSnappyFrameHeader(header: Uint8Array): {type: ChunkType; frameSize: number} {
|
|
6
|
+
if (header.length !== 4) {
|
|
7
|
+
throw new Error("malformed input: incomplete frame header");
|
|
8
|
+
}
|
|
22
9
|
|
|
23
|
-
|
|
10
|
+
const type = getChunkType(header[0]);
|
|
11
|
+
const frameSize = header[1] + (header[2] << 8) + (header[3] << 16);
|
|
12
|
+
return {type, frameSize};
|
|
13
|
+
}
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
export function decodeSnappyFrameData(type: ChunkType, frame: Uint8Array): Uint8Array | null {
|
|
16
|
+
switch (type) {
|
|
17
|
+
case ChunkType.IDENTIFIER: {
|
|
18
|
+
if (!Buffer.prototype.equals.call(frame, IDENTIFIER)) {
|
|
19
|
+
throw new Error("malformed input: bad identifier");
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
case ChunkType.PADDING:
|
|
24
|
+
case ChunkType.SKIPPABLE:
|
|
25
|
+
return null;
|
|
26
|
+
case ChunkType.COMPRESSED: {
|
|
27
|
+
if (frame.length < 4) {
|
|
28
|
+
throw new Error("malformed input: too short");
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
const
|
|
31
|
+
const checksum = frame.subarray(0, 4);
|
|
32
|
+
const data = frame.subarray(4);
|
|
33
|
+
const uncompressed = uncompress(data, UNCOMPRESSED_CHUNK_SIZE);
|
|
34
|
+
if (crc(uncompressed).compare(checksum) !== 0) {
|
|
35
|
+
throw new Error("malformed input: bad checksum");
|
|
36
|
+
}
|
|
37
|
+
return uncompressed;
|
|
38
|
+
}
|
|
39
|
+
case ChunkType.UNCOMPRESSED: {
|
|
40
|
+
if (frame.length < 4) {
|
|
41
|
+
throw new Error("malformed input: too short");
|
|
42
|
+
}
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
const checksum = frame.subarray(0, 4);
|
|
45
|
+
const uncompressed = frame.subarray(4);
|
|
46
|
+
if (uncompressed.length > UNCOMPRESSED_CHUNK_SIZE) {
|
|
47
|
+
throw new Error("malformed input: too large");
|
|
48
|
+
}
|
|
49
|
+
if (crc(uncompressed).compare(checksum) !== 0) {
|
|
50
|
+
throw new Error("malformed input: bad checksum");
|
|
33
51
|
}
|
|
52
|
+
return uncompressed;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
34
56
|
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
export function decodeSnappyFrames(data: Uint8Array): Uint8ArrayList {
|
|
58
|
+
const out = new Uint8ArrayList();
|
|
59
|
+
let foundIdentifier = false;
|
|
60
|
+
let offset = 0;
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.state.foundIdentifier = true;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
case ChunkType.PADDING:
|
|
47
|
-
case ChunkType.SKIPPABLE:
|
|
48
|
-
continue;
|
|
49
|
-
case ChunkType.COMPRESSED: {
|
|
50
|
-
const checksum = frame.subarray(0, 4);
|
|
51
|
-
const data = frame.subarray(4);
|
|
62
|
+
while (offset < data.length) {
|
|
63
|
+
const remaining = data.length - offset;
|
|
64
|
+
if (remaining < 4) {
|
|
65
|
+
throw new Error("malformed input: incomplete frame header");
|
|
66
|
+
}
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
result.append(uncompressed);
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
case ChunkType.UNCOMPRESSED: {
|
|
61
|
-
const checksum = frame.subarray(0, 4);
|
|
62
|
-
const uncompressed = frame.subarray(4);
|
|
68
|
+
const {type, frameSize} = parseSnappyFrameHeader(data.subarray(offset, offset + 4));
|
|
69
|
+
if (!foundIdentifier && type !== ChunkType.IDENTIFIER) {
|
|
70
|
+
throw new Error("malformed input: must begin with an identifier");
|
|
71
|
+
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw "malformed input: bad checksum";
|
|
69
|
-
}
|
|
70
|
-
result.append(uncompressed);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
73
|
+
offset += 4;
|
|
74
|
+
|
|
75
|
+
if (data.length - offset < frameSize) {
|
|
76
|
+
throw new Error("malformed input: incomplete frame");
|
|
74
77
|
}
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
|
|
79
|
+
const frame = data.subarray(offset, offset + frameSize);
|
|
80
|
+
offset += frameSize;
|
|
81
|
+
|
|
82
|
+
if (type === ChunkType.IDENTIFIER) {
|
|
83
|
+
foundIdentifier = true;
|
|
77
84
|
}
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
};
|
|
86
|
+
const uncompressed = decodeSnappyFrameData(type, frame);
|
|
87
|
+
if (uncompressed !== null) {
|
|
88
|
+
out.append(uncompressed);
|
|
89
|
+
}
|
|
86
90
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
type UncompressState = {
|
|
90
|
-
foundIdentifier: boolean;
|
|
91
|
-
};
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
return buffer.get(offset) + (buffer.get(offset + 1) << 8) + (buffer.get(offset + 2) << 16);
|
|
92
|
+
return out;
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
function getChunkType(value: number): ChunkType {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type {Stream} from "@libp2p/interface";
|
|
2
|
+
import {ByteStream} from "@libp2p/utils";
|
|
3
|
+
import {Uint8ArrayList} from "uint8arraylist";
|
|
4
|
+
import {ErrorAborted} from "@lodestar/utils";
|
|
5
|
+
|
|
6
|
+
export async function sendChunks(
|
|
7
|
+
stream: Stream,
|
|
8
|
+
source: Iterable<Uint8Array | Uint8ArrayList> | AsyncIterable<Uint8Array | Uint8ArrayList>,
|
|
9
|
+
signal?: AbortSignal
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
for await (const chunk of source) {
|
|
12
|
+
if (signal?.aborted) {
|
|
13
|
+
throw new ErrorAborted("sendChunks");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!stream.send(chunk)) {
|
|
17
|
+
await stream.onDrain({signal});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function drainByteStream(bytes: ByteStream<Stream>): Uint8Array | undefined {
|
|
23
|
+
const readBuffer = (
|
|
24
|
+
bytes as unknown as {
|
|
25
|
+
readBuffer?: {byteLength: number; subarray: () => Uint8Array; consume: (bytes: number) => void};
|
|
26
|
+
}
|
|
27
|
+
).readBuffer;
|
|
28
|
+
if (readBuffer && readBuffer.byteLength > 0) {
|
|
29
|
+
const drained = readBuffer.subarray();
|
|
30
|
+
readBuffer.consume(readBuffer.byteLength);
|
|
31
|
+
return drained;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wraps an AsyncIterable and rejects early if any signal aborts.
|
|
3
|
-
* Throws the error returned by `getError()` of each signal options.
|
|
4
|
-
*
|
|
5
|
-
* Simplified fork of `"abortable-iterator"`.
|
|
6
|
-
* Read function's source for reasoning of the fork.
|
|
7
|
-
*/
|
|
8
|
-
export declare function abortableSource<T>(sourceArg: AsyncIterable<T>, signals: {
|
|
9
|
-
signal: AbortSignal;
|
|
10
|
-
getError: () => Error;
|
|
11
|
-
}[]): AsyncIterable<T>;
|
|
12
|
-
//# sourceMappingURL=abortableSource.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"abortableSource.d.ts","sourceRoot":"","sources":["../../src/utils/abortableSource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE;IACP,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,KAAK,CAAC;CACvB,EAAE,GACF,aAAa,CAAC,CAAC,CAAC,CAkElB"}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wraps an AsyncIterable and rejects early if any signal aborts.
|
|
3
|
-
* Throws the error returned by `getError()` of each signal options.
|
|
4
|
-
*
|
|
5
|
-
* Simplified fork of `"abortable-iterator"`.
|
|
6
|
-
* Read function's source for reasoning of the fork.
|
|
7
|
-
*/
|
|
8
|
-
export function abortableSource(sourceArg, signals) {
|
|
9
|
-
const source = sourceArg;
|
|
10
|
-
async function* abortable() {
|
|
11
|
-
// Handler that will hold a reference to the `abort()` promise,
|
|
12
|
-
// necessary for the signal abort listeners to reject the iterable promise
|
|
13
|
-
let nextAbortHandler = null;
|
|
14
|
-
// For each signal register an abortHandler(), and prepare clean-up with `onDoneCbs`
|
|
15
|
-
const onDoneCbs = [];
|
|
16
|
-
for (const { signal, getError } of signals) {
|
|
17
|
-
const abortHandler = () => {
|
|
18
|
-
if (nextAbortHandler)
|
|
19
|
-
nextAbortHandler(getError());
|
|
20
|
-
};
|
|
21
|
-
signal.addEventListener("abort", abortHandler);
|
|
22
|
-
onDoneCbs.push(() => {
|
|
23
|
-
signal.removeEventListener("abort", abortHandler);
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
while (true) {
|
|
28
|
-
// Abort early if any signal is aborted
|
|
29
|
-
for (const { signal, getError } of signals) {
|
|
30
|
-
if (signal.aborted) {
|
|
31
|
-
throw getError();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Race the iterator and the abort signals
|
|
35
|
-
const result = await Promise.race([
|
|
36
|
-
new Promise((_, reject) => {
|
|
37
|
-
nextAbortHandler = (error) => reject(error);
|
|
38
|
-
}),
|
|
39
|
-
source.next(),
|
|
40
|
-
]);
|
|
41
|
-
// source.next() resolved first
|
|
42
|
-
nextAbortHandler = null;
|
|
43
|
-
if (result.done) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
yield result.value;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
// End the iterator if it is a generator
|
|
51
|
-
if (typeof source.return === "function") {
|
|
52
|
-
// This source.return() function may never resolve depending on the source AsyncGenerator implementation.
|
|
53
|
-
// This is the main reason to fork "abortable-iterator", which caused our node to get stuck during Sync.
|
|
54
|
-
// We choose to call .return() but not await it. In general, source.return should never throw. If it does,
|
|
55
|
-
// it a problem of the source implementor, and thus logged as an unhandled rejection. If that happens,
|
|
56
|
-
// the source implementor should fix the upstream code.
|
|
57
|
-
void source.return(null);
|
|
58
|
-
}
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
for (const cb of onDoneCbs) {
|
|
63
|
-
cb();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return abortable();
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=abortableSource.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"abortableSource.js","sourceRoot":"","sources":["../../src/utils/abortableSource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,SAA2B,EAC3B,OAGG;IAEH,MAAM,MAAM,GAAG,SAA8B,CAAC;IAE9C,KAAK,SAAS,CAAC,CAAC,SAAS;QACvB,+DAA+D;QAC/D,0EAA0E;QAC1E,IAAI,gBAAgB,GAAoC,IAAI,CAAC;QAE7D,oFAAoF;QACpF,MAAM,SAAS,GAAmB,EAAE,CAAC;QACrC,KAAK,MAAM,EAAC,MAAM,EAAE,QAAQ,EAAC,IAAI,OAAO,EAAE,CAAC;YACzC,MAAM,YAAY,GAAG,GAAS,EAAE;gBAC9B,IAAI,gBAAgB;oBAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;YACrD,CAAC,CAAC;YACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAC/C,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;gBAClB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC;YACH,OAAO,IAAI,EAAE,CAAC;gBACZ,uCAAuC;gBACvC,KAAK,MAAM,EAAC,MAAM,EAAE,QAAQ,EAAC,IAAI,OAAO,EAAE,CAAC;oBACzC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;wBACnB,MAAM,QAAQ,EAAE,CAAC;oBACnB,CAAC;gBACH,CAAC;gBAED,0CAA0C;gBAC1C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;oBAChC,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;wBAC/B,gBAAgB,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBAC9C,CAAC,CAAC;oBACF,MAAM,CAAC,IAAI,EAAE;iBACd,CAAC,CAAC;gBAEH,+BAA+B;gBAC/B,gBAAgB,GAAG,IAAI,CAAC;gBAExB,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;oBAChB,OAAO;gBACT,CAAC;gBAED,MAAM,MAAM,CAAC,KAAK,CAAC;YACrB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wCAAwC;YACxC,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACxC,yGAAyG;gBACzG,wGAAwG;gBACxG,0GAA0G;gBAC1G,sGAAsG;gBACtG,uDAAuD;gBACvD,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;YAED,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;gBAC3B,EAAE,EAAE,CAAC;YACP,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,EAAE,CAAC;AACrB,CAAC"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Uint8ArrayList } from "uint8arraylist";
|
|
2
|
-
/**
|
|
3
|
-
* Wraps a buffer chunk stream source with another async iterable
|
|
4
|
-
* so it can be reused in multiple for..of statements.
|
|
5
|
-
*
|
|
6
|
-
* Uses a BufferList internally to make sure all chunks are consumed
|
|
7
|
-
* when switching consumers
|
|
8
|
-
*/
|
|
9
|
-
export declare class BufferedSource {
|
|
10
|
-
isDone: boolean;
|
|
11
|
-
private buffer;
|
|
12
|
-
private source;
|
|
13
|
-
constructor(source: AsyncGenerator<Uint8ArrayList | Uint8Array>);
|
|
14
|
-
[Symbol.asyncIterator](): AsyncIterator<Uint8ArrayList>;
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=bufferedSource.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bufferedSource.d.ts","sourceRoot":"","sources":["../../src/utils/bufferedSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAE9C;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,MAAM,UAAS;IACf,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAA8C;gBAEhD,MAAM,EAAE,cAAc,CAAC,cAAc,GAAG,UAAU,CAAC;IAK/D,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,cAAc,CAAC;CA0BxD"}
|