@lidofinance/next-pages 0.45.1 → 0.46.0
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/dist/api/index.js +94 -15
- package/dist/api/index.js.map +1 -1
- package/dist/api/rpcFactory.d.ts +16 -9
- package/dist/api/rpcFactory.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/api/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Readable as $FgOCV$Readable} from "node:stream";
|
|
1
|
+
import {Transform as $FgOCV$Transform, Readable as $FgOCV$Readable} from "node:stream";
|
|
2
2
|
import {Counter as $FgOCV$Counter} from "prom-client";
|
|
3
3
|
import {iterateUrls as $FgOCV$iterateUrls} from "@lidofinance/rpc";
|
|
4
4
|
|
|
@@ -20,17 +20,45 @@ const $29c7c52f2735c037$export$fc20b1372f40c2c5 = ({ registry: registry })=>asyn
|
|
|
20
20
|
|
|
21
21
|
const $7d34ac0315ec4c05$export$c982278a0ddc4dbe = "Something went wrong. Sorry, try again later :(";
|
|
22
22
|
const $7d34ac0315ec4c05$export$56a189db5508cdcb = "Healthy RPC services are over!";
|
|
23
|
-
class $7d34ac0315ec4c05$export$
|
|
23
|
+
class $7d34ac0315ec4c05$export$bb8c263ad32caa8 extends Error {
|
|
24
|
+
}
|
|
25
|
+
class $7d34ac0315ec4c05$export$fc97cac2ba066ec7 extends $7d34ac0315ec4c05$export$bb8c263ad32caa8 {
|
|
24
26
|
constructor(message){
|
|
25
27
|
super(message || "Unsupported chainId");
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
class $7d34ac0315ec4c05$export$2dfa2d8949b43fdc extends
|
|
30
|
+
class $7d34ac0315ec4c05$export$2dfa2d8949b43fdc extends $7d34ac0315ec4c05$export$bb8c263ad32caa8 {
|
|
29
31
|
constructor(message){
|
|
30
32
|
super(message || "Unsupported HTTP method");
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
|
-
|
|
35
|
+
class $7d34ac0315ec4c05$export$2adbfa76b71ba748 extends $7d34ac0315ec4c05$export$bb8c263ad32caa8 {
|
|
36
|
+
constructor(message){
|
|
37
|
+
super(message || "Invalid Request");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class $7d34ac0315ec4c05$export$e224c1080d950012 extends $7d34ac0315ec4c05$export$bb8c263ad32caa8 {
|
|
41
|
+
constructor(message){
|
|
42
|
+
super(message || "Invalid Request");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const $7d34ac0315ec4c05$var$createSizeLogger = (MAX_SIZE)=>{
|
|
46
|
+
let bytesWritten = 0;
|
|
47
|
+
const logSizeStream = new (0, $FgOCV$Transform)({
|
|
48
|
+
transform (chunk, _encoding, callback) {
|
|
49
|
+
bytesWritten += chunk.length;
|
|
50
|
+
if (bytesWritten > MAX_SIZE) // Emit an error if size exceeds MAX_SIZE
|
|
51
|
+
return callback(new $7d34ac0315ec4c05$export$e224c1080d950012(`Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`));
|
|
52
|
+
return callback(null, chunk) // Pass the chunk through
|
|
53
|
+
;
|
|
54
|
+
},
|
|
55
|
+
flush (callback) {
|
|
56
|
+
callback();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return logSizeStream;
|
|
60
|
+
};
|
|
61
|
+
const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix, registry: registry }, providers: providers, fetchRPC: fetchRPC, defaultChain: defaultChain, allowedRPCMethods: allowedRPCMethods, allowedCallAddresses: allowedCallAddresses = {}, allowedLogsAddresses: allowedLogsAddresses = {}, maxBatchCount: maxBatchCount, maxResponseSize: maxResponseSize = 1000000, disallowEmptyAddressGetLogs: disallowEmptyAddressGetLogs = false })=>{
|
|
34
62
|
const rpcRequestBlocked = new (0, $FgOCV$Counter)({
|
|
35
63
|
name: prefix + "rpc_service_request_blocked",
|
|
36
64
|
help: "RPC service request blocked",
|
|
@@ -38,6 +66,14 @@ const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix,
|
|
|
38
66
|
registers: []
|
|
39
67
|
});
|
|
40
68
|
registry.registerMetric(rpcRequestBlocked);
|
|
69
|
+
const allowedCallAddressMap = Object.entries(allowedCallAddresses).reduce((acc, [chainId, addresses])=>{
|
|
70
|
+
acc[chainId] = new Set(addresses.map((a)=>a.toLowerCase()));
|
|
71
|
+
return acc;
|
|
72
|
+
}, {});
|
|
73
|
+
const allowedLogsAddressMap = Object.entries(allowedLogsAddresses).reduce((acc, [chainId, addresses])=>{
|
|
74
|
+
acc[chainId] = new Set(addresses.map((a)=>a.toLowerCase()));
|
|
75
|
+
return acc;
|
|
76
|
+
}, {});
|
|
41
77
|
return async (req, res)=>{
|
|
42
78
|
try {
|
|
43
79
|
// Accept only POST requests
|
|
@@ -47,15 +83,43 @@ const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix,
|
|
|
47
83
|
// Allow only chainId of specified chains
|
|
48
84
|
if (providers[chainId] == null) // We don't care about tracking blocked requests here
|
|
49
85
|
throw new $7d34ac0315ec4c05$export$fc97cac2ba066ec7();
|
|
50
|
-
|
|
51
|
-
// Check if provided methods are allowed
|
|
52
|
-
for (const { method: method } of Array.isArray(req.body) ? req.body : [
|
|
86
|
+
const requests = Array.isArray(req.body) ? req.body : [
|
|
53
87
|
req.body
|
|
54
|
-
]
|
|
55
|
-
|
|
88
|
+
];
|
|
89
|
+
if (typeof maxBatchCount === "number" && requests.length > maxBatchCount) throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Too many batched requests`);
|
|
90
|
+
// Check if provided methods are allowed
|
|
91
|
+
// We throw HTTP error for ANY invalid RPC request out of batch
|
|
92
|
+
// because we assume that frontend must not send invalid requests
|
|
93
|
+
for (const { method: method, params: params } of requests){
|
|
94
|
+
if (typeof method !== "string") throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`RPC method isn't string`);
|
|
56
95
|
if (!allowedRPCMethods.includes(method)) {
|
|
57
96
|
rpcRequestBlocked.inc();
|
|
58
|
-
throw new
|
|
97
|
+
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`RPC method ${method} isn't allowed`);
|
|
98
|
+
}
|
|
99
|
+
if (method === "eth_call" && allowedCallAddressMap[chainId]) {
|
|
100
|
+
if (Array.isArray(params) && typeof params[0] === "object" && typeof params[0].to === "string") {
|
|
101
|
+
if (!allowedCallAddressMap[chainId].has(params[0].to.toLowerCase())) {
|
|
102
|
+
rpcRequestBlocked.inc();
|
|
103
|
+
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Address not allowed for eth_call`);
|
|
104
|
+
}
|
|
105
|
+
} else throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`RPC method eth_call is invalid`);
|
|
106
|
+
}
|
|
107
|
+
if (method === "eth_getLogs" && (disallowEmptyAddressGetLogs || allowedLogsAddressMap[chainId])) {
|
|
108
|
+
if (Array.isArray(params) && typeof params[0] === "object") {
|
|
109
|
+
const address = params[0].address;
|
|
110
|
+
if (disallowEmptyAddressGetLogs && (!address || Array.isArray(address) && address.length === 0)) {
|
|
111
|
+
rpcRequestBlocked.inc();
|
|
112
|
+
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`No empty address on eth_getLogs`);
|
|
113
|
+
}
|
|
114
|
+
const addresses = Array.isArray(address) ? address : [
|
|
115
|
+
address
|
|
116
|
+
];
|
|
117
|
+
if (addresses.some((eventAddress)=>// needs this check before toLowerCase
|
|
118
|
+
typeof eventAddress !== "string" || !allowedLogsAddressMap[chainId].has(eventAddress.toLowerCase()))) {
|
|
119
|
+
rpcRequestBlocked.inc();
|
|
120
|
+
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Address not allowed for eth_getLogs`);
|
|
121
|
+
}
|
|
122
|
+
} else throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Invalid eth_getLogs`);
|
|
59
123
|
}
|
|
60
124
|
}
|
|
61
125
|
const requested = await (0, $FgOCV$iterateUrls)(providers[chainId], // TODO: consider adding verification that body is actually matches FetchRpcInitBody
|
|
@@ -63,14 +127,29 @@ const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix,
|
|
|
63
127
|
body: req.body
|
|
64
128
|
}, {
|
|
65
129
|
chainId: chainId
|
|
66
|
-
}),
|
|
130
|
+
}), // eslint-disable-next-line @typescript-eslint/unbound-method
|
|
131
|
+
console.error);
|
|
67
132
|
res.setHeader("Content-Type", requested.headers.get("Content-Type") ?? "application/json");
|
|
68
|
-
if (requested.body)
|
|
69
|
-
|
|
133
|
+
if (requested.body) {
|
|
134
|
+
const sizeLimit = $7d34ac0315ec4c05$var$createSizeLogger(maxResponseSize);
|
|
135
|
+
const readableStream = (0, $FgOCV$Readable).fromWeb(requested.body);
|
|
136
|
+
readableStream.pipe(sizeLimit).on("error", (error)=>{
|
|
137
|
+
if (error instanceof $7d34ac0315ec4c05$export$e224c1080d950012) {
|
|
138
|
+
console.warn(`[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`);
|
|
139
|
+
res.status(413).end() // Payload Too Large
|
|
140
|
+
;
|
|
141
|
+
} else {
|
|
142
|
+
res.statusCode = 500;
|
|
143
|
+
res.end($7d34ac0315ec4c05$export$c982278a0ddc4dbe);
|
|
144
|
+
}
|
|
145
|
+
readableStream.destroy();
|
|
146
|
+
}).pipe(res);
|
|
147
|
+
} else res.status(requested.status).json("There are a problems with RPC provider");
|
|
70
148
|
} catch (error) {
|
|
71
|
-
if (error instanceof
|
|
149
|
+
if (error instanceof $7d34ac0315ec4c05$export$bb8c263ad32caa8) res.status(400).json(error.message ?? $7d34ac0315ec4c05$export$c982278a0ddc4dbe);
|
|
150
|
+
else if (error instanceof Error) {
|
|
72
151
|
// TODO: check if there are errors duplication with iterateUrls
|
|
73
|
-
|
|
152
|
+
console.error(error.message ?? $7d34ac0315ec4c05$export$c982278a0ddc4dbe);
|
|
74
153
|
res.status(500).json(error.message ?? $7d34ac0315ec4c05$export$c982278a0ddc4dbe);
|
|
75
154
|
} else res.status(500).json($7d34ac0315ec4c05$export$56a189db5508cdcb);
|
|
76
155
|
}
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;;;ACEO,MAAM,4CAAS,CAAC,MAAsB;IAC3C,IAAI,OAAO,KAAK,KAAK;QAAE,QAAQ;IAAK;AACtC;;;ACGO,MAAM,4CACX,CAAC,YAAE,QAAQ,EAA4B,GACvC,OAAO,MAAsB;QAC3B,MAAM,mBAAmB,MAAM,SAAS;QACxC,IAAI,KAAK;IACX;;;;;;ACDK,MAAM,4CAA4B;AAElC,MAAM,4CAAgC;AAEtC,MAAM,kDAAgC;IAC3C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAEO,MAAM,kDAAmC;IAC9C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAqBO,MAAM,4CAAa,CAAC,EACzB,SAAS,UAAE,MAAM,YAAE,QAAQ,EAAE,aAC7B,SAAS,YACT,QAAQ,gBACR,eAAe,uBACf,YAAY,qBACZ,iBAAiB,EACA;IACjB,MAAM,oBAAoB,IAAI,CAAA,GAAA,cAAM,EAAE;QACpC,MAAM,SAAS;QACf,MAAM;QACN,YAAY,EAAE;QACd,WAAW,EAAE;IACf;IACA,SAAS,eAAe;IAExB,OAAO,OAAO,KAAqB;QACjC,IAAI;YACF,4BAA4B;YAC5B,IAAI,IAAI,WAAW,QACjB,qDAAqD;YACrD,MAAM,IAAI;YAGZ,MAAM,UAAU,OAAO,IAAI,MAAM,WAAW;YAE5C,yCAAyC;YACzC,IAAI,SAAS,CAAC,QAAQ,IAAI,MACxB,qDAAqD;YACrD,MAAM,IAAI;YAGZ,oFAAoF;YACpF,wCAAwC;YACxC,KAAK,MAAM,UAAE,MAAM,EAAE,IAAI,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO;gBAAC,IAAI;aAAK,CAAE;gBACxE,IAAI,OAAO,WAAW,UACpB,MAAM,IAAI,MAAM,CAAC,uBAAuB,CAAC;gBAE3C,IAAI,CAAC,kBAAkB,SAAS,SAAS;oBACvC,kBAAkB;oBAClB,MAAM,IAAI,MAAM,CAAC,WAAW,EAAE,OAAO,cAAc,CAAC;gBACtD;YACF;YAEA,MAAM,YAAY,MAAM,CAAA,GAAA,kBAAU,EAChC,SAAS,CAAC,QAAQ,EAClB,oFAAoF;YACpF,CAAC,MAAQ,SAAS,KAAK;oBAAE,MAAM,IAAI;gBAAyB,GAAG;6BAAE;gBAAQ,IACzE,aAAa;YAGf,IAAI,UAAU,gBAAgB,UAAU,QAAQ,IAAI,mBAAmB;YACvE,IAAI,UAAU,MACZ,CAAA,GAAA,eAAO,EAAE,QAAQ,UAAU,MAAwB,KAAK;iBAExD,IAAI,OAAO,UAAU,QAAQ,KAAK;QAEtC,EAAE,OAAO,OAAO;YACd,IAAI,iBAAiB,OAAO;gBAC1B,+DAA+D;gBAC/D,aAAa,MAAM,MAAM,WAAW;gBACpC,IAAI,OAAO,KAAK,KAAK,MAAM,WAAW;YACxC,OACE,IAAI,OAAO,KAAK,KAAK;QAEzB;IACF;AACF;","sources":["packages/next/pages/src/api/index.ts","packages/next/pages/src/api/health.ts","packages/next/pages/src/api/metricsFactory.ts","packages/next/pages/src/api/rpcFactory.ts"],"sourcesContent":["export { health } from './health'\nexport { type MetricsFactoryParameters, metricsFactory } from './metricsFactory'\nexport {\n type RpcProviders,\n DEFAULT_API_ERROR_MESSAGE,\n HEALTHY_RPC_SERVICES_ARE_OVER,\n UnsupportedChainIdError,\n UnsupportedHTTPMethodError,\n type RPCFactoryParams,\n rpcFactory,\n} from './rpcFactory'\n","import { NextApiRequest, NextApiResponse } from 'next'\n\nexport const health = (_req: NextApiRequest, res: NextApiResponse): void => {\n res.status(200).send({ status: 'ok' })\n}\n","import { NextApiRequest, NextApiResponse } from 'next'\nimport { Registry } from 'prom-client'\n\nexport type MetricsFactoryParameters = {\n registry: Registry\n}\n\nexport const metricsFactory =\n ({ registry }: MetricsFactoryParameters) =>\n async (_req: NextApiRequest, res: NextApiResponse) => {\n const collectedMetrics = await registry.metrics()\n res.send(collectedMetrics)\n }\n","import { Readable } from 'node:stream'\nimport { ReadableStream } from 'node:stream/web'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { Counter, Registry } from 'prom-client'\nimport type { TrackedFetchRPC } from '@lidofinance/api-rpc'\nimport type { ServerLogger } from '@lidofinance/api-logger'\nimport type { FetchRpcInitBody } from '@lidofinance/rpc'\nimport { iterateUrls } from '@lidofinance/rpc'\n\nexport type RpcProviders = Record<string | number, [string, ...string[]]>\n\nexport const DEFAULT_API_ERROR_MESSAGE = 'Something went wrong. Sorry, try again later :('\n\nexport const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'\n\nexport class UnsupportedChainIdError extends Error {\n constructor(message?: string) {\n super(message || 'Unsupported chainId')\n }\n}\n\nexport class UnsupportedHTTPMethodError extends Error {\n constructor(message?: string) {\n super(message || 'Unsupported HTTP method')\n }\n}\n\nexport type RPCFactoryParams = {\n metrics: {\n prefix: string\n registry: Registry\n }\n providers: RpcProviders\n fetchRPC: TrackedFetchRPC\n /**\n * @deprecated you should mask logs via pino & satanizer, see policies\n * for additional details or reach out ui/secops\n */\n serverLogger?: ServerLogger\n defaultChain: string | number\n // If we don't specify allowed RPC methods, then we can't use\n // fetchRPC with prometheus, otherwise it will blow up, if someone will send arbitrary\n // methods\n allowedRPCMethods: string[]\n}\n\nexport const rpcFactory = ({\n metrics: { prefix, registry },\n providers,\n fetchRPC,\n serverLogger = console,\n defaultChain,\n allowedRPCMethods,\n}: RPCFactoryParams) => {\n const rpcRequestBlocked = new Counter({\n name: prefix + 'rpc_service_request_blocked',\n help: 'RPC service request blocked',\n labelNames: [],\n registers: [],\n })\n registry.registerMetric(rpcRequestBlocked)\n\n return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {\n try {\n // Accept only POST requests\n if (req.method !== 'POST') {\n // We don't care about tracking blocked requests here\n throw new UnsupportedHTTPMethodError()\n }\n\n const chainId = Number(req.query.chainId || defaultChain)\n\n // Allow only chainId of specified chains\n if (providers[chainId] == null) {\n // We don't care about tracking blocked requests here\n throw new UnsupportedChainIdError()\n }\n\n // TODO: consider returning array of validators instead of throwing error right away\n // Check if provided methods are allowed\n for (const { method } of Array.isArray(req.body) ? req.body : [req.body]) {\n if (typeof method !== 'string') {\n throw new Error(`RPC method isn't string`)\n }\n if (!allowedRPCMethods.includes(method)) {\n rpcRequestBlocked.inc()\n throw new Error(`RPC method ${method} isn't allowed`)\n }\n }\n\n const requested = await iterateUrls(\n providers[chainId],\n // TODO: consider adding verification that body is actually matches FetchRpcInitBody\n (url) => fetchRPC(url, { body: req.body as FetchRpcInitBody }, { chainId }),\n serverLogger.error,\n )\n\n res.setHeader('Content-Type', requested.headers.get('Content-Type') ?? 'application/json')\n if (requested.body) {\n Readable.fromWeb(requested.body as ReadableStream).pipe(res)\n } else {\n res.status(requested.status).json('There are a problems with RPC provider')\n }\n } catch (error) {\n if (error instanceof Error) {\n // TODO: check if there are errors duplication with iterateUrls\n serverLogger.error(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n res.status(500).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n } else {\n res.status(500).json(HEALTHY_RPC_SERVICES_ARE_OVER)\n }\n }\n }\n}\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../../../"}
|
|
1
|
+
{"mappings":"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","sources":["packages/next/pages/src/api/index.ts","packages/next/pages/src/api/health.ts","packages/next/pages/src/api/metricsFactory.ts","packages/next/pages/src/api/rpcFactory.ts"],"sourcesContent":["export { health } from './health'\nexport { type MetricsFactoryParameters, metricsFactory } from './metricsFactory'\nexport {\n type RpcProviders,\n DEFAULT_API_ERROR_MESSAGE,\n HEALTHY_RPC_SERVICES_ARE_OVER,\n UnsupportedChainIdError,\n UnsupportedHTTPMethodError,\n type RPCFactoryParams,\n rpcFactory,\n} from './rpcFactory'\n","import { NextApiRequest, NextApiResponse } from 'next'\n\nexport const health = (_req: NextApiRequest, res: NextApiResponse): void => {\n res.status(200).send({ status: 'ok' })\n}\n","import { NextApiRequest, NextApiResponse } from 'next'\nimport { Registry } from 'prom-client'\n\nexport type MetricsFactoryParameters = {\n registry: Registry\n}\n\nexport const metricsFactory =\n ({ registry }: MetricsFactoryParameters) =>\n async (_req: NextApiRequest, res: NextApiResponse) => {\n const collectedMetrics = await registry.metrics()\n res.send(collectedMetrics)\n }\n","import { Readable, Transform } from 'node:stream'\nimport { ReadableStream } from 'node:stream/web'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { Counter, Registry } from 'prom-client'\nimport type { TrackedFetchRPC } from '@lidofinance/api-rpc'\nimport type { FetchRpcInitBody } from '@lidofinance/rpc'\nimport { iterateUrls } from '@lidofinance/rpc'\n\nexport type RpcProviders = Record<string | number, [string, ...string[]]>\n\nexport const DEFAULT_API_ERROR_MESSAGE = 'Something went wrong. Sorry, try again later :('\n\nexport const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'\n\nexport class ClientError extends Error {}\nexport class UnsupportedChainIdError extends ClientError {\n constructor(message?: string) {\n super(message || 'Unsupported chainId')\n }\n}\n\nexport class UnsupportedHTTPMethodError extends ClientError {\n constructor(message?: string) {\n super(message || 'Unsupported HTTP method')\n }\n}\n\nexport class InvalidRequestError extends ClientError {\n constructor(message?: string) {\n super(message || 'Invalid Request')\n }\n}\n\nexport class SizeTooLargeError extends ClientError {\n constructor(message?: string) {\n super(message || 'Invalid Request')\n }\n}\n\nconst createSizeLogger = (MAX_SIZE: number) => {\n let bytesWritten = 0\n const logSizeStream = new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length\n if (bytesWritten > MAX_SIZE) {\n // Emit an error if size exceeds MAX_SIZE\n return callback(new SizeTooLargeError(`Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`))\n }\n return callback(null, chunk) // Pass the chunk through\n },\n flush(callback) {\n callback()\n },\n })\n return logSizeStream\n}\n\nexport type RPCFactoryParams = {\n metrics: {\n prefix: string\n registry: Registry\n }\n providers: RpcProviders\n fetchRPC: TrackedFetchRPC\n defaultChain: string | number\n // If we don't specify allowed RPC methods, then we can't use\n // fetchRPC with prometheus, otherwise it will blow up, if someone will send arbitrary\n // methods\n allowedRPCMethods: string[]\n // filtration by eth_call to addresses\n allowedCallAddresses?: Record<number, string[]>\n allowedLogsAddresses?: Record<number, string[]>\n disallowEmptyAddressGetLogs?: boolean\n maxBatchCount?: number\n maxResponseSize?: number\n}\n\nexport const rpcFactory = ({\n metrics: { prefix, registry },\n providers,\n fetchRPC,\n defaultChain,\n allowedRPCMethods,\n allowedCallAddresses = {},\n allowedLogsAddresses = {},\n maxBatchCount,\n maxResponseSize = 1_000_000, // ~1MB,\n disallowEmptyAddressGetLogs = false,\n}: RPCFactoryParams) => {\n const rpcRequestBlocked = new Counter({\n name: prefix + 'rpc_service_request_blocked',\n help: 'RPC service request blocked',\n labelNames: [],\n registers: [],\n })\n registry.registerMetric(rpcRequestBlocked)\n\n const allowedCallAddressMap = Object.entries(allowedCallAddresses).reduce((acc, [chainId, addresses]) => {\n acc[chainId] = new Set(addresses.map((a) => a.toLowerCase()))\n return acc\n }, {} as Record<string, Set<string>>)\n\n const allowedLogsAddressMap = Object.entries(allowedLogsAddresses).reduce((acc, [chainId, addresses]) => {\n acc[chainId] = new Set(addresses.map((a) => a.toLowerCase()))\n return acc\n }, {} as Record<string, Set<string>>)\n\n return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {\n try {\n // Accept only POST requests\n if (req.method !== 'POST') {\n // We don't care about tracking blocked requests here\n throw new UnsupportedHTTPMethodError()\n }\n\n const chainId = Number(req.query.chainId || defaultChain)\n\n // Allow only chainId of specified chains\n if (providers[chainId] == null) {\n // We don't care about tracking blocked requests here\n throw new UnsupportedChainIdError()\n }\n\n const requests = Array.isArray(req.body) ? req.body : [req.body]\n\n if (typeof maxBatchCount === 'number' && requests.length > maxBatchCount) {\n throw new InvalidRequestError(`Too many batched requests`)\n }\n\n // Check if provided methods are allowed\n // We throw HTTP error for ANY invalid RPC request out of batch\n // because we assume that frontend must not send invalid requests\n for (const { method, params } of requests) {\n if (typeof method !== 'string') {\n throw new InvalidRequestError(`RPC method isn't string`)\n }\n if (!allowedRPCMethods.includes(method)) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`RPC method ${method} isn't allowed`)\n }\n if (method === 'eth_call' && allowedCallAddressMap[chainId]) {\n if (Array.isArray(params) && typeof params[0] === 'object' && typeof params[0].to === 'string') {\n if (!allowedCallAddressMap[chainId].has(params[0].to.toLowerCase())) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`Address not allowed for eth_call`)\n }\n } else throw new InvalidRequestError(`RPC method eth_call is invalid`)\n }\n if (method === 'eth_getLogs' && (disallowEmptyAddressGetLogs || allowedLogsAddressMap[chainId])) {\n if (Array.isArray(params) && typeof params[0] === 'object') {\n const address = params[0].address\n if (disallowEmptyAddressGetLogs && (!address || (Array.isArray(address) && address.length === 0))) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`No empty address on eth_getLogs`)\n }\n const addresses = Array.isArray(address) ? address : [address]\n if (\n addresses.some(\n (eventAddress) =>\n // needs this check before toLowerCase\n typeof eventAddress !== 'string' || !allowedLogsAddressMap[chainId].has(eventAddress.toLowerCase()),\n )\n ) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`Address not allowed for eth_getLogs`)\n }\n } else throw new InvalidRequestError(`Invalid eth_getLogs`)\n }\n }\n\n const requested = await iterateUrls(\n providers[chainId],\n // TODO: consider adding verification that body is actually matches FetchRpcInitBody\n (url) => fetchRPC(url, { body: req.body as FetchRpcInitBody }, { chainId }),\n // eslint-disable-next-line @typescript-eslint/unbound-method\n console.error,\n )\n\n res.setHeader('Content-Type', requested.headers.get('Content-Type') ?? 'application/json')\n if (requested.body) {\n const sizeLimit = createSizeLogger(maxResponseSize)\n const readableStream = Readable.fromWeb(requested.body as ReadableStream)\n readableStream\n .pipe(sizeLimit)\n .on('error', (error) => {\n if (error instanceof SizeTooLargeError) {\n console.warn(`[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`)\n res.status(413).end() // Payload Too Large\n } else {\n res.statusCode = 500\n res.end(DEFAULT_API_ERROR_MESSAGE)\n }\n readableStream.destroy()\n })\n .pipe(res)\n } else {\n res.status(requested.status).json('There are a problems with RPC provider')\n }\n } catch (error) {\n if (error instanceof ClientError) {\n res.status(400).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n } else if (error instanceof Error) {\n // TODO: check if there are errors duplication with iterateUrls\n console.error(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n res.status(500).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n } else {\n res.status(500).json(HEALTHY_RPC_SERVICES_ARE_OVER)\n }\n }\n }\n}\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../../../"}
|
package/dist/api/rpcFactory.d.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
2
|
import { Registry } from 'prom-client';
|
|
3
3
|
import type { TrackedFetchRPC } from '@lidofinance/api-rpc';
|
|
4
|
-
import type { ServerLogger } from '@lidofinance/api-logger';
|
|
5
4
|
export type RpcProviders = Record<string | number, [string, ...string[]]>;
|
|
6
5
|
export declare const DEFAULT_API_ERROR_MESSAGE = "Something went wrong. Sorry, try again later :(";
|
|
7
6
|
export declare const HEALTHY_RPC_SERVICES_ARE_OVER = "Healthy RPC services are over!";
|
|
8
|
-
export declare class
|
|
7
|
+
export declare class ClientError extends Error {
|
|
8
|
+
}
|
|
9
|
+
export declare class UnsupportedChainIdError extends ClientError {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class UnsupportedHTTPMethodError extends ClientError {
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class InvalidRequestError extends ClientError {
|
|
9
16
|
constructor(message?: string);
|
|
10
17
|
}
|
|
11
|
-
export declare class
|
|
18
|
+
export declare class SizeTooLargeError extends ClientError {
|
|
12
19
|
constructor(message?: string);
|
|
13
20
|
}
|
|
14
21
|
export type RPCFactoryParams = {
|
|
@@ -18,13 +25,13 @@ export type RPCFactoryParams = {
|
|
|
18
25
|
};
|
|
19
26
|
providers: RpcProviders;
|
|
20
27
|
fetchRPC: TrackedFetchRPC;
|
|
21
|
-
/**
|
|
22
|
-
* @deprecated you should mask logs via pino & satanizer, see policies
|
|
23
|
-
* for additional details or reach out ui/secops
|
|
24
|
-
*/
|
|
25
|
-
serverLogger?: ServerLogger;
|
|
26
28
|
defaultChain: string | number;
|
|
27
29
|
allowedRPCMethods: string[];
|
|
30
|
+
allowedCallAddresses?: Record<number, string[]>;
|
|
31
|
+
allowedLogsAddresses?: Record<number, string[]>;
|
|
32
|
+
disallowEmptyAddressGetLogs?: boolean;
|
|
33
|
+
maxBatchCount?: number;
|
|
34
|
+
maxResponseSize?: number;
|
|
28
35
|
};
|
|
29
|
-
export declare const rpcFactory: ({ metrics: { prefix, registry }, providers, fetchRPC,
|
|
36
|
+
export declare const rpcFactory: ({ metrics: { prefix, registry }, providers, fetchRPC, defaultChain, allowedRPCMethods, allowedCallAddresses, allowedLogsAddresses, maxBatchCount, maxResponseSize, disallowEmptyAddressGetLogs, }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
30
37
|
//# sourceMappingURL=rpcFactory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcFactory.d.ts","sourceRoot":"","sources":["../../src/api/rpcFactory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"rpcFactory.d.ts","sourceRoot":"","sources":["../../src/api/rpcFactory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAI3D,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAEzE,eAAO,MAAM,yBAAyB,oDAAoD,CAAA;AAE1F,eAAO,MAAM,6BAA6B,mCAAmC,CAAA;AAE7E,qBAAa,WAAY,SAAQ,KAAK;CAAG;AACzC,qBAAa,uBAAwB,SAAQ,WAAW;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,0BAA2B,SAAQ,WAAW;gBAC7C,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,mBAAoB,SAAQ,WAAW;gBACtC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAoBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,QAAQ,CAAA;KACnB,CAAA;IACD,SAAS,EAAE,YAAY,CAAA;IACvB,QAAQ,EAAE,eAAe,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;IAI7B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAE3B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/C,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/C,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,eAAO,MAAM,UAAU,sMAWpB,gBAAgB,WAmBE,cAAc,OAAO,eAAe,KAAG,QAAQ,IAAI,CAuGvE,CAAA"}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/lidofinance/warehouse/issues"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.46.0",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
@@ -58,20 +58,20 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@lidofinance/api-logger": "~0.
|
|
62
|
-
"@lidofinance/api-rpc": "~0.
|
|
63
|
-
"@lidofinance/rpc": "~0.
|
|
64
|
-
"@lidofinance/ui-pages": "~0.
|
|
61
|
+
"@lidofinance/api-logger": "~0.46.0",
|
|
62
|
+
"@lidofinance/api-rpc": "~0.46.0",
|
|
63
|
+
"@lidofinance/rpc": "~0.46.0",
|
|
64
|
+
"@lidofinance/ui-pages": "~0.46.0",
|
|
65
65
|
"next": "12 || 13",
|
|
66
66
|
"prom-client": "^14.1.0",
|
|
67
67
|
"react": "17 || 18"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@lidofinance/api-logger": "~0.
|
|
71
|
-
"@lidofinance/api-rpc": "~0.
|
|
72
|
-
"@lidofinance/config-prettier": "~0.
|
|
73
|
-
"@lidofinance/rpc": "~0.
|
|
74
|
-
"@lidofinance/ui-pages": "~0.
|
|
70
|
+
"@lidofinance/api-logger": "~0.46.0",
|
|
71
|
+
"@lidofinance/api-rpc": "~0.46.0",
|
|
72
|
+
"@lidofinance/config-prettier": "~0.46.0",
|
|
73
|
+
"@lidofinance/rpc": "~0.46.0",
|
|
74
|
+
"@lidofinance/ui-pages": "~0.46.0",
|
|
75
75
|
"@types/react": "^18.0.25",
|
|
76
76
|
"next": "^12.3.1",
|
|
77
77
|
"prom-client": "^14.1.0"
|