@lidofinance/next-pages 0.45.1 → 0.47.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/README.md CHANGED
@@ -4,7 +4,7 @@ Common API and UI next pages.
4
4
 
5
5
  ## Installation
6
6
 
7
- * React 17 || 18
7
+ - React 17 || 18
8
8
 
9
9
  ```bash
10
10
  yarn add @lidofinance/next-pages
@@ -20,47 +20,45 @@ yarn add next@^12.3.0 prom-client@^14.0.0 @lidofinance/api-logger@^0.36.0 @lidof
20
20
  #### Health
21
21
 
22
22
  ```ts
23
- import { health } from '@lidofinance/next-pages';
23
+ import { health } from '@lidofinance/next-pages'
24
24
  // or import { health } from '@lidofinance/next-pages/api';
25
25
 
26
- export default health;
26
+ export default health
27
27
  ```
28
28
 
29
29
  #### Metrics
30
30
 
31
31
  ```ts
32
- import { registry } from 'utilsApi/metrics';
33
- import { metricsFactory } from '@lidofinance/next-pages';
32
+ import { registry } from 'utilsApi/metrics'
33
+ import { metricsFactory } from '@lidofinance/next-pages'
34
34
  // or import { metricsFactory } from '@lidofinance/next-pages/api';
35
35
 
36
36
  const metrics = metricsFactory({
37
37
  registry,
38
- });
38
+ })
39
39
 
40
- export default metrics;
40
+ export default metrics
41
41
  ```
42
42
 
43
43
  #### RPC
44
44
 
45
45
  ```ts
46
- import getConfig from 'next/config';
47
- import { rpcFactory } from '@lidofinance/api-pages';
46
+ import getConfig from 'next/config'
47
+ import { rpcFactory } from '@lidofinance/api-pages'
48
48
  // or import { rpcFactory } from '@lidofinance/next-pages/api';
49
- import { fetchRPC, serverLogger } from 'utilsApi';
50
- import { registry } from 'utilsApi/metrics';
51
- import { METRICS_PREFIX } from '../../config';
49
+ import { fetchRPC, serverLogger } from 'utilsApi'
50
+ import { registry } from 'utilsApi/metrics'
51
+ import { METRICS_PREFIX } from '../../config'
52
52
 
53
- const { publicRuntimeConfig, serverRuntimeConfig } = getConfig();
54
- const { defaultChain } = publicRuntimeConfig;
55
- const { infuraApiKey, alchemyApiKey } = serverRuntimeConfig;
53
+ const { publicRuntimeConfig, serverRuntimeConfig } = getConfig()
54
+ const { defaultChain } = publicRuntimeConfig
55
+ const { infuraApiKey, alchemyApiKey } = serverRuntimeConfig
56
56
 
57
- // Should be a separate file
58
57
  export const enum CHAINS {
59
58
  Mainnet = 1,
60
59
  Goerli = 5,
61
60
  }
62
61
 
63
- // Should be a separate file
64
62
  export const providers: Record<CHAINS, [string, ...string[]]> = {
65
63
  [CHAINS.Mainnet]: [
66
64
  `https://mainnet.infura.io/v3/${infuraApiKey}`,
@@ -70,24 +68,87 @@ export const providers: Record<CHAINS, [string, ...string[]]> = {
70
68
  `https://goerli.infura.io/v3/${infuraApiKey}`,
71
69
  `https://eth-goerli.alchemyapi.io/v2/${alchemyApiKey}`,
72
70
  ],
73
- };
71
+ }
72
+
73
+ const allowedCallAddresses: Record<string, string[]> = {
74
+ /* Mapping of chainIds to array of addresses permitted for eth_getLogs */
75
+ }
76
+
77
+ const allowedLogsAddresses: Record<string, string[]> = {
78
+ /* Mapping of chainIds to array of addresses permitted for eth_call */
79
+ }
80
+
81
+ // example
82
+ const allowedRPCMethods = [
83
+ 'test',
84
+ 'eth_call',
85
+ 'eth_gasPrice',
86
+ 'eth_getCode',
87
+ 'eth_estimateGas',
88
+ 'eth_getBlockByNumber',
89
+ 'eth_feeHistory',
90
+ 'eth_maxPriorityFeePerGas',
91
+ 'eth_getBalance',
92
+ 'eth_blockNumber',
93
+ 'eth_getTransactionByHash',
94
+ 'eth_getTransactionReceipt',
95
+ 'eth_getTransactionCount',
96
+ 'eth_sendRawTransaction',
97
+ 'eth_getLogs',
98
+ 'eth_chainId',
99
+ 'net_version',
100
+ ]
74
101
 
75
102
  const rpc = rpcFactory({
76
- fetchRPC,
103
+ fetchRPC: trackedFetchRpcFactory({
104
+ registry: Metrics.registry,
105
+ prefix: METRICS_PREFIX,
106
+ }),
77
107
  metrics: {
78
108
  prefix: METRICS_PREFIX,
79
- registry,
109
+ registry: Metrics.registry,
80
110
  },
81
- allowedRPCMethods: [
82
- 'eth_call',
83
- 'eth_gasPrice',
84
- // ...
85
- ],
86
111
  defaultChain,
87
- providers,
88
- });
112
+ providers: {
113
+ [CHAINS.Mainnet]: secretConfig.rpcUrls_1,
114
+ [CHAINS.Holesky]: secretConfig.rpcUrls_17000,
115
+ },
116
+ // optional validation options to protect from misuse of public endpoints
117
+ // Strongly recommended to use all of them
118
+ validation: {
119
+ // !!! IMPORTANT, MUST HAVE !!!
120
+ // allowed JSON RPC methods
121
+ allowedRPCMethods,
122
+
123
+ // !!! IMPORTANT, MUST HAVE !!!
124
+ // mapping chainId - addresses fro eth_call, put only contracts you dapp interacts with
125
+ // no entry for chainId or empty array will prevent method for this chainId
126
+ allowedCallAddresses,
127
+
128
+ // !!! IMPORTANT, MUST HAVE !!!
129
+ // mapping chainId - addresses fro eth_getLogs put only contracts here if you need to fetch events from them
130
+ // no entry for chainId or empty array will prevent method for this chainId
131
+ allowedLogsAddresses,
132
+
133
+ // max JSON-RPC batch size
134
+ maxBatchCount: 20, // DEFAULT
135
+
136
+ // prevents empty `address` param for eth_getLogs, that can results in massive queries
137
+ blockEmptyAddressGetLogs: true, // DEFAULT
138
+
139
+ // only 20k blocks size historical queries for eth_getLogs
140
+ // fetches and caches current block to compare with ambiguous blockTags(e.g. "latest")
141
+ maxGetLogsRange: 20_000, // DEFAULT
142
+
143
+ /// cache TTL for maxGetLogsRange
144
+ currentBlockTTLms: 60_000; // DEFAULT
145
+
146
+ // 1mb max response after which data stream is interrupted and 413 is returned
147
+ maxResponseSize: 1_000_000, // DEFAULT
148
+ },
149
+ })
89
150
 
90
- export default rpc;
151
+ export default rpc
91
152
  ```
92
153
 
93
154
  ### UI pages
@@ -98,17 +159,19 @@ export default rpc;
98
159
  - **Page500**
99
160
 
100
161
  Example for `pages/404.tsx`
162
+
101
163
  ```tsx
102
- import { Page404 } from '@lidofinance/next-ui-pages/ui';
164
+ import { Page404 } from '@lidofinance/next-ui-pages/ui'
103
165
 
104
- export default Page404;
166
+ export default Page404
105
167
  ```
106
168
 
107
169
  Example for `pages/500.tsx`
170
+
108
171
  ```tsx
109
- import { Page500 } from '@lidofinance/next-ui-pages/ui';
172
+ import { Page500 } from '@lidofinance/next-ui-pages/ui'
110
173
 
111
- export default Page500;
174
+ export default Page500
112
175
  ```
113
176
 
114
177
  #### Error page as generic component
@@ -116,10 +179,10 @@ export default Page500;
116
179
  Example for `pages/404.tsx`
117
180
 
118
181
  ```tsx
119
- import { FC } from 'react';
120
- import { PageError } from '@lidofinance/next-ui-pages/ui';
182
+ import { FC } from 'react'
183
+ import { PageError } from '@lidofinance/next-ui-pages/ui'
121
184
 
122
- const Page404: FC = () => <PageError title="404" content="Page Not Found" />;
185
+ const Page404: FC = () => <PageError title="404" content="Page Not Found" />
123
186
 
124
- export default Page404;
187
+ export default Page404
125
188
  ```
@@ -1,4 +1,4 @@
1
1
  export { health } from './health';
2
2
  export { type MetricsFactoryParameters, metricsFactory } from './metricsFactory';
3
- export { type RpcProviders, DEFAULT_API_ERROR_MESSAGE, HEALTHY_RPC_SERVICES_ARE_OVER, UnsupportedChainIdError, UnsupportedHTTPMethodError, type RPCFactoryParams, rpcFactory, } from './rpcFactory';
3
+ export { type RPCFactoryParams, type RPCFactoryValidationParams, type RpcProviders, rpcFactory } from './rpcFactory';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,KAAK,wBAAwB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAChF,OAAO,EACL,KAAK,YAAY,EACjB,yBAAyB,EACzB,6BAA6B,EAC7B,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,UAAU,GACX,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,KAAK,wBAAwB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAChF,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,0BAA0B,EAAE,KAAK,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
package/dist/api/index.js CHANGED
@@ -1,6 +1,8 @@
1
- import {Readable as $FgOCV$Readable} from "node:stream";
1
+ import {Readable as $FgOCV$Readable, Transform as $FgOCV$Transform} from "node:stream";
2
+ import {pipeline as $FgOCV$pipeline} from "node:stream/promises";
2
3
  import {Counter as $FgOCV$Counter} from "prom-client";
3
4
  import {iterateUrls as $FgOCV$iterateUrls} from "@lidofinance/rpc";
5
+ import {Cache as $FgOCV$Cache} from "memory-cache";
4
6
 
5
7
  const $6ad1ea13f08e3d24$export$f0784a54fb0af903 = (_req, res)=>{
6
8
  res.status(200).send({
@@ -18,61 +20,230 @@ const $29c7c52f2735c037$export$fc20b1372f40c2c5 = ({ registry: registry })=>asyn
18
20
 
19
21
 
20
22
 
21
- const $7d34ac0315ec4c05$export$c982278a0ddc4dbe = "Something went wrong. Sorry, try again later :(";
22
- const $7d34ac0315ec4c05$export$56a189db5508cdcb = "Healthy RPC services are over!";
23
- class $7d34ac0315ec4c05$export$fc97cac2ba066ec7 extends Error {
23
+
24
+ const $09f9daeda4842558$export$c982278a0ddc4dbe = "Something went wrong. Sorry, try again later :(";
25
+ const $09f9daeda4842558$export$56a189db5508cdcb = "Healthy RPC services are over!";
26
+ class $09f9daeda4842558$export$bb8c263ad32caa8 extends Error {
27
+ }
28
+ class $09f9daeda4842558$export$fc97cac2ba066ec7 extends $09f9daeda4842558$export$bb8c263ad32caa8 {
24
29
  constructor(message){
25
30
  super(message || "Unsupported chainId");
26
31
  }
27
32
  }
28
- class $7d34ac0315ec4c05$export$2dfa2d8949b43fdc extends Error {
33
+ class $09f9daeda4842558$export$2dfa2d8949b43fdc extends $09f9daeda4842558$export$bb8c263ad32caa8 {
29
34
  constructor(message){
30
35
  super(message || "Unsupported HTTP method");
31
36
  }
32
37
  }
33
- const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix, registry: registry }, providers: providers, fetchRPC: fetchRPC, serverLogger: serverLogger = console, defaultChain: defaultChain, allowedRPCMethods: allowedRPCMethods })=>{
38
+ class $09f9daeda4842558$export$2adbfa76b71ba748 extends $09f9daeda4842558$export$bb8c263ad32caa8 {
39
+ constructor(message){
40
+ super(message || "Invalid Request");
41
+ }
42
+ }
43
+ class $09f9daeda4842558$export$e224c1080d950012 extends $09f9daeda4842558$export$bb8c263ad32caa8 {
44
+ constructor(message){
45
+ super(message || "Response size too large");
46
+ }
47
+ }
48
+
49
+
50
+
51
+
52
+
53
+ // validation factories
54
+ const $5a404da009e373d8$var$zeroBN = BigInt(0);
55
+ const $5a404da009e373d8$export$564dd6c0fb12e680 = (defaultChain, providers, maxBatchSize)=>{
56
+ return (req)=>{
57
+ if (req.method !== "POST") // We don't care about tracking blocked requests here
58
+ throw new (0, $09f9daeda4842558$export$2dfa2d8949b43fdc)();
59
+ const chainId = Number(req.query.chainId || defaultChain);
60
+ // Allow only chainId of specified chains
61
+ if (providers[chainId] == null) // We don't care about tracking blocked requests here
62
+ throw new (0, $09f9daeda4842558$export$fc97cac2ba066ec7)();
63
+ const requests = Array.isArray(req.body) ? req.body : [
64
+ req.body
65
+ ];
66
+ if (typeof maxBatchSize === "number" && requests.length > maxBatchSize) throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`Too many batched requests`);
67
+ return {
68
+ chainId: chainId,
69
+ requests: requests
70
+ };
71
+ };
72
+ };
73
+ const $5a404da009e373d8$export$bd57dbd3bc9a1cc6 = (allowedMethods)=>{
74
+ const methodsMap = new Set(allowedMethods ?? []);
75
+ return (request, context)=>{
76
+ const method = request.method;
77
+ if (typeof method !== "string") throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method isn't string`);
78
+ if (allowedMethods && !methodsMap.has(request.method)) {
79
+ context.rpcRequestBlocked.inc({
80
+ reason: "method not allowed"
81
+ });
82
+ throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method ${method} not allowed`);
83
+ }
84
+ };
85
+ };
86
+ const $5a404da009e373d8$export$b724143801441d66 = (allowedAddress)=>{
87
+ const allowedCallAddressMap = $5a404da009e373d8$var$createAllowedAddressMap(allowedAddress);
88
+ return (request, { chainId: chainId, rpcRequestBlocked: rpcRequestBlocked })=>{
89
+ const { params: params } = request;
90
+ if (Array.isArray(params) && params[0] && typeof params[0] === "object" && "to" in params[0] && typeof params[0].to === "string") {
91
+ if (!allowedCallAddressMap[chainId]?.has(params[0].to.toLowerCase())) {
92
+ rpcRequestBlocked.inc({
93
+ reason: "address not allowed for eth_call"
94
+ });
95
+ throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`Address not allowed for eth_call`);
96
+ }
97
+ } else throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_call is invalid`);
98
+ };
99
+ };
100
+ const $5a404da009e373d8$export$5d8ee2232a9cb21a = (allowedAddress, blockEmptyAddressGetLogs, maxBlockRange, currentBlockTTL = 60000)=>{
101
+ const allowedAddressMap = $5a404da009e373d8$var$createAllowedAddressMap(allowedAddress ?? {});
102
+ const maxBlockRangeBN = maxBlockRange ? BigInt(maxBlockRange) : $5a404da009e373d8$var$zeroBN;
103
+ const currentBlock = new (0, $FgOCV$Cache)();
104
+ return async (request, { chainId: chainId, rpcRequestBlocked: rpcRequestBlocked, requestRPC: requestRPC })=>{
105
+ try {
106
+ const params = request.params[0];
107
+ const fromBlock = params.fromBlock;
108
+ const toBlock = params.toBlock ?? "latest";
109
+ if (blockEmptyAddressGetLogs || allowedAddress) {
110
+ // address validation
111
+ const address = params.address;
112
+ if (blockEmptyAddressGetLogs && !address) throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs address is invalid`);
113
+ const addresses = Array.isArray(address) ? address : [
114
+ address
115
+ ];
116
+ if (blockEmptyAddressGetLogs && addresses.length === 0) throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs address is invalid`);
117
+ if (allowedAddress && addresses.some((eventAddress)=>// needs this check before toLowerCase
118
+ typeof eventAddress !== "string" || !allowedAddressMap[chainId]?.has(eventAddress.toLowerCase()))) {
119
+ rpcRequestBlocked.inc({
120
+ reason: "address not allowed for eth_getLogs"
121
+ });
122
+ throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`Address not allowed for eth_getLogs`);
123
+ }
124
+ }
125
+ // block range validation
126
+ if (maxBlockRange) {
127
+ if (fromBlock === "earliest") throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs fromBlock is invalid`);
128
+ const shouldValidateBlockDistance = fromBlock.startsWith("0x");
129
+ if (shouldValidateBlockDistance) {
130
+ const normalizedFromBlock = BigInt(fromBlock);
131
+ let normalizedToBlock;
132
+ if (toBlock.startsWith("0x")) normalizedToBlock = BigInt(toBlock);
133
+ else {
134
+ const cached = currentBlock.get(chainId);
135
+ if (cached) normalizedToBlock = cached;
136
+ else {
137
+ normalizedToBlock = await requestRPC(chainId, {
138
+ id: 1,
139
+ jsonrpc: "2.0",
140
+ method: "eth_blockNumber"
141
+ }).then((res)=>res.json()).then((res)=>BigInt(res.result));
142
+ currentBlock.put(chainId, normalizedToBlock, currentBlockTTL);
143
+ }
144
+ }
145
+ const range = normalizedToBlock - normalizedFromBlock;
146
+ if (range < $5a404da009e373d8$var$zeroBN || range > maxBlockRangeBN) {
147
+ rpcRequestBlocked.inc({
148
+ reason: "eth_getLogs range is invalid"
149
+ });
150
+ throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs range is invalid`);
151
+ }
152
+ }
153
+ }
154
+ } catch (error) {
155
+ if (error instanceof (0, $09f9daeda4842558$export$bb8c263ad32caa8)) throw error;
156
+ throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs is invalid`);
157
+ }
158
+ };
159
+ };
160
+ const $5a404da009e373d8$export$7c5a2292697deb4b = (MAX_SIZE)=>(context)=>{
161
+ let bytesWritten = 0;
162
+ return new (0, $FgOCV$Transform)({
163
+ transform (chunk, _encoding, callback) {
164
+ bytesWritten += chunk.length;
165
+ if (bytesWritten > MAX_SIZE) {
166
+ // Emit an error if size exceeds MAX_SIZE
167
+ context.rpcRequestBlocked.inc({
168
+ reason: "response too large"
169
+ });
170
+ return callback(new (0, $09f9daeda4842558$export$e224c1080d950012)(`Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`));
171
+ }
172
+ return callback(null, chunk) // Pass the chunk through
173
+ ;
174
+ },
175
+ flush (callback) {
176
+ callback();
177
+ }
178
+ });
179
+ };
180
+ const $5a404da009e373d8$export$e67f820dcb2fefda = (method, allowedRPCMethods)=>!allowedRPCMethods || allowedRPCMethods.includes(method);
181
+ const $5a404da009e373d8$var$createAllowedAddressMap = (allowedAddress)=>{
182
+ return Object.entries(allowedAddress).reduce((acc, [chainId, addresses])=>{
183
+ acc[chainId] = new Set(addresses.map((a)=>a.toLowerCase()));
184
+ return acc;
185
+ }, {});
186
+ };
187
+
188
+
189
+ const $3f76f1f6d2274759$export$9bfcc39f106ad871 = ({ metrics: metrics, providers: providers, fetchRPC: fetchRPC, defaultChain: defaultChain, validation: validation })=>{
190
+ const { allowedRPCMethods: allowedRPCMethods, allowedCallAddresses: allowedCallAddresses, allowedLogsAddresses: allowedLogsAddresses, maxResponseSize: maxResponseSize = 1000000, maxBatchCount: maxBatchCount = 20, currentBlockTTLms: currentBlockTTLms = 60000, maxGetLogsRange: maxGetLogsRange = 20000, blockEmptyAddressGetLogs: blockEmptyAddressGetLogs = true } = validation;
191
+ // optional metrics
192
+ const { registry: registry, prefix: prefix = "" } = metrics ?? {};
34
193
  const rpcRequestBlocked = new (0, $FgOCV$Counter)({
35
194
  name: prefix + "rpc_service_request_blocked",
36
195
  help: "RPC service request blocked",
37
- labelNames: [],
196
+ labelNames: [
197
+ "reason"
198
+ ],
38
199
  registers: []
39
200
  });
40
- registry.registerMetric(rpcRequestBlocked);
201
+ registry?.registerMetric(rpcRequestBlocked);
202
+ const validateBaseRequest = (0, $5a404da009e373d8$export$564dd6c0fb12e680)(defaultChain, providers, maxBatchCount);
203
+ const validateRpcMethod = (0, $5a404da009e373d8$export$bd57dbd3bc9a1cc6)(allowedRPCMethods);
204
+ const validateEthCall = allowedCallAddresses && (0, $5a404da009e373d8$export$e67f820dcb2fefda)("eth_call", allowedRPCMethods) ? (0, $5a404da009e373d8$export$b724143801441d66)(allowedCallAddresses) : undefined;
205
+ const validateEthGetLogs = (allowedLogsAddresses || blockEmptyAddressGetLogs) && (0, $5a404da009e373d8$export$e67f820dcb2fefda)("eth_getLogs", allowedRPCMethods) ? (0, $5a404da009e373d8$export$5d8ee2232a9cb21a)(allowedLogsAddresses, blockEmptyAddressGetLogs, maxGetLogsRange, currentBlockTTLms) : undefined;
206
+ const validateMaxSteamSize = (0, $5a404da009e373d8$export$7c5a2292697deb4b)(maxResponseSize);
207
+ const requestRPC = (chainId, body)=>(0, $FgOCV$iterateUrls)(providers[chainId], // TODO: consider adding verification that body is actually matches FetchRpcInitBody
208
+ (url)=>fetchRPC(url, {
209
+ body: body
210
+ }, {
211
+ chainId: chainId
212
+ }), // eslint-disable-next-line @typescript-eslint/unbound-method
213
+ console.error);
41
214
  return async (req, res)=>{
215
+ const abortController = new AbortController();
42
216
  try {
43
- // Accept only POST requests
44
- if (req.method !== "POST") // We don't care about tracking blocked requests here
45
- throw new $7d34ac0315ec4c05$export$2dfa2d8949b43fdc();
46
- const chainId = Number(req.query.chainId || defaultChain);
47
- // Allow only chainId of specified chains
48
- if (providers[chainId] == null) // We don't care about tracking blocked requests here
49
- throw new $7d34ac0315ec4c05$export$fc97cac2ba066ec7();
50
- // TODO: consider returning array of validators instead of throwing error right away
51
- // Check if provided methods are allowed
52
- for (const { method: method } of Array.isArray(req.body) ? req.body : [
53
- req.body
54
- ]){
55
- if (typeof method !== "string") throw new Error(`RPC method isn't string`);
56
- if (!allowedRPCMethods.includes(method)) {
57
- rpcRequestBlocked.inc();
58
- throw new Error(`RPC method ${method} isn't allowed`);
59
- }
217
+ const { chainId: chainId, requests: requests } = validateBaseRequest(req);
218
+ const validationContext = {
219
+ chainId: chainId,
220
+ rpcRequestBlocked: rpcRequestBlocked,
221
+ requestRPC: requestRPC
222
+ };
223
+ // We throw HTTP error for ANY invalid RPC request out of batch
224
+ // because we assume that frontend must not send invalid requests at all
225
+ for (const request of requests){
226
+ validateRpcMethod(request, validationContext);
227
+ const method = request.method;
228
+ if (method === "eth_call" && validateEthCall) validateEthCall(request, validationContext);
229
+ if (method === "eth_getLogs" && validateEthGetLogs) await validateEthGetLogs(request, validationContext);
60
230
  }
61
- const requested = await (0, $FgOCV$iterateUrls)(providers[chainId], // TODO: consider adding verification that body is actually matches FetchRpcInitBody
62
- (url)=>fetchRPC(url, {
63
- body: req.body
64
- }, {
65
- chainId: chainId
66
- }), serverLogger.error);
67
- res.setHeader("Content-Type", requested.headers.get("Content-Type") ?? "application/json");
68
- if (requested.body) (0, $FgOCV$Readable).fromWeb(requested.body).pipe(res);
69
- else res.status(requested.status).json("There are a problems with RPC provider");
231
+ const proxyedRPC = await requestRPC(chainId, req.body);
232
+ res.setHeader("Content-Type", proxyedRPC.headers.get("Content-Type") ?? "application/json");
233
+ if (!proxyedRPC.body) throw new Error("There are a problems with RPC provider");
234
+ // auto closes both Readable.fromWeb() and underlying proxyedRPC streams on error
235
+ await (0, $FgOCV$pipeline)((0, $FgOCV$Readable).fromWeb(proxyedRPC.body), validateMaxSteamSize(validationContext), res, {
236
+ signal: abortController.signal
237
+ });
70
238
  } catch (error) {
71
- if (error instanceof Error) {
72
- // TODO: check if there are errors duplication with iterateUrls
73
- serverLogger.error(error.message ?? $7d34ac0315ec4c05$export$c982278a0ddc4dbe);
74
- res.status(500).json(error.message ?? $7d34ac0315ec4c05$export$c982278a0ddc4dbe);
75
- } else res.status(500).json($7d34ac0315ec4c05$export$56a189db5508cdcb);
239
+ if (error instanceof (0, $09f9daeda4842558$export$bb8c263ad32caa8)) res.status(400).json(error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe));
240
+ else if (error instanceof Error) {
241
+ console.error("[rpcFactory]" + (error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe)));
242
+ if (!res.headersSent) res.status(500).json(error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe));
243
+ } else res.status(500).json((0, $09f9daeda4842558$export$56a189db5508cdcb));
244
+ } finally{
245
+ // forces pipeline closure in case of external error/abort
246
+ abortController.abort();
76
247
  }
77
248
  };
78
249
  };
@@ -80,5 +251,7 @@ const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix,
80
251
 
81
252
 
82
253
 
83
- export {$6ad1ea13f08e3d24$export$f0784a54fb0af903 as health, $29c7c52f2735c037$export$fc20b1372f40c2c5 as metricsFactory, $7d34ac0315ec4c05$export$c982278a0ddc4dbe as DEFAULT_API_ERROR_MESSAGE, $7d34ac0315ec4c05$export$56a189db5508cdcb as HEALTHY_RPC_SERVICES_ARE_OVER, $7d34ac0315ec4c05$export$fc97cac2ba066ec7 as UnsupportedChainIdError, $7d34ac0315ec4c05$export$2dfa2d8949b43fdc as UnsupportedHTTPMethodError, $7d34ac0315ec4c05$export$9bfcc39f106ad871 as rpcFactory};
254
+
255
+
256
+ export {$6ad1ea13f08e3d24$export$f0784a54fb0af903 as health, $29c7c52f2735c037$export$fc20b1372f40c2c5 as metricsFactory, $3f76f1f6d2274759$export$9bfcc39f106ad871 as rpcFactory};
84
257
  //# sourceMappingURL=index.js.map
@@ -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/index.ts","packages/next/pages/src/api/rpcFactory/rpc-factory.ts","packages/next/pages/src/api/rpcFactory/errors.ts","packages/next/pages/src/api/rpcFactory/validation.ts"],"sourcesContent":["export { health } from './health'\nexport { type MetricsFactoryParameters, metricsFactory } from './metricsFactory'\nexport { type RPCFactoryParams, type RPCFactoryValidationParams, type RpcProviders, rpcFactory } 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","export { rpcFactory } from './rpc-factory'\nexport type { RPCFactoryParams, RPCFactoryValidationParams, RpcProviders } from './types'\n","import { Readable } from 'node:stream'\nimport { ReadableStream } from 'node:stream/web'\nimport { pipeline } from 'node:stream/promises'\n\nimport { Counter } from 'prom-client'\nimport { iterateUrls } from '@lidofinance/rpc'\n\nimport { DEFAULT_API_ERROR_MESSAGE, HEALTHY_RPC_SERVICES_ARE_OVER, ClientError } from './errors'\nimport {\n baseRequestValidationFactory,\n ethCallValidationFactory,\n ethGetLogsValidationFactory,\n rpcMethodsValidationFactory,\n shouldValidateRpcMethod,\n streamMaxSizeValidationFactory,\n} from './validation'\n\nimport type { RPCFactoryParams } from './types'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport type { FetchRpcInitBody } from '@lidofinance/rpc'\n\nexport const rpcFactory = ({ metrics, providers, fetchRPC, defaultChain, validation }: RPCFactoryParams) => {\n const {\n allowedRPCMethods,\n allowedCallAddresses,\n allowedLogsAddresses,\n maxResponseSize = 1_000_000,\n maxBatchCount = 20,\n currentBlockTTLms = 60_000,\n maxGetLogsRange = 20_000,\n blockEmptyAddressGetLogs = true,\n } = validation\n\n // optional metrics\n const { registry, prefix = '' } = metrics ?? {}\n const rpcRequestBlocked = new Counter({\n name: prefix + 'rpc_service_request_blocked',\n help: 'RPC service request blocked',\n labelNames: ['reason'],\n registers: [],\n })\n registry?.registerMetric(rpcRequestBlocked)\n\n const validateBaseRequest = baseRequestValidationFactory(defaultChain, providers, maxBatchCount)\n\n const validateRpcMethod = rpcMethodsValidationFactory(allowedRPCMethods)\n\n const validateEthCall =\n allowedCallAddresses && shouldValidateRpcMethod('eth_call', allowedRPCMethods)\n ? ethCallValidationFactory(allowedCallAddresses)\n : undefined\n\n const validateEthGetLogs =\n (allowedLogsAddresses || blockEmptyAddressGetLogs) && shouldValidateRpcMethod('eth_getLogs', allowedRPCMethods)\n ? ethGetLogsValidationFactory(allowedLogsAddresses, blockEmptyAddressGetLogs, maxGetLogsRange, currentBlockTTLms)\n : undefined\n\n const validateMaxSteamSize = streamMaxSizeValidationFactory(maxResponseSize)\n\n const requestRPC = (chainId: number, body: FetchRpcInitBody) =>\n iterateUrls(\n providers[chainId],\n // TODO: consider adding verification that body is actually matches FetchRpcInitBody\n (url) => fetchRPC(url, { body }, { chainId }),\n // eslint-disable-next-line @typescript-eslint/unbound-method\n console.error,\n )\n\n return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {\n const abortController = new AbortController()\n try {\n const { chainId, requests } = validateBaseRequest(req)\n\n const validationContext = {\n chainId,\n rpcRequestBlocked,\n requestRPC,\n }\n\n // We throw HTTP error for ANY invalid RPC request out of batch\n // because we assume that frontend must not send invalid requests at all\n for (const request of requests) {\n validateRpcMethod(request, validationContext)\n\n const method = request.method\n\n if (method === 'eth_call' && validateEthCall) {\n validateEthCall(request, validationContext)\n }\n if (method === 'eth_getLogs' && validateEthGetLogs) {\n await validateEthGetLogs(request, validationContext)\n }\n }\n\n const proxyedRPC = await requestRPC(chainId, req.body as FetchRpcInitBody)\n\n res.setHeader('Content-Type', proxyedRPC.headers.get('Content-Type') ?? 'application/json')\n if (!proxyedRPC.body) {\n throw new Error('There are a problems with RPC provider')\n }\n\n // auto closes both Readable.fromWeb() and underlying proxyedRPC streams on error\n await pipeline(\n Readable.fromWeb(proxyedRPC.body as ReadableStream),\n validateMaxSteamSize(validationContext),\n res,\n {\n signal: abortController.signal,\n },\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 console.error('[rpcFactory]' + (error.message ?? DEFAULT_API_ERROR_MESSAGE))\n if (!res.headersSent) {\n res.status(500).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n }\n } else {\n res.status(500).json(HEALTHY_RPC_SERVICES_ARE_OVER)\n }\n } finally {\n // forces pipeline closure in case of external error/abort\n abortController.abort()\n }\n }\n}\n","export 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 {}\n\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 || 'Response size too large')\n }\n}\n","import { Transform } from 'node:stream'\n\nimport { Cache } from 'memory-cache'\n\nimport {\n ClientError,\n InvalidRequestError,\n SizeTooLargeError,\n UnsupportedChainIdError,\n UnsupportedHTTPMethodError,\n} from './errors'\n\nimport type { NextApiRequest } from 'next'\nimport type { RpcRequest, ValidationContext } from './types'\n\n// validation factories\n\nconst zeroBN = BigInt(0)\n\nexport const baseRequestValidationFactory = (\n defaultChain: number | string,\n providers: Record<string, unknown>,\n maxBatchSize?: number,\n) => {\n return (req: NextApiRequest) => {\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 maxBatchSize === 'number' && requests.length > maxBatchSize) {\n throw new InvalidRequestError(`Too many batched requests`)\n }\n return { chainId, requests }\n }\n}\n\nexport const rpcMethodsValidationFactory = (allowedMethods?: string[]) => {\n const methodsMap = new Set(allowedMethods ?? [])\n\n return (request: RpcRequest, context: ValidationContext) => {\n const method = request.method\n if (typeof method !== 'string') {\n throw new InvalidRequestError(`RPC method isn't string`)\n }\n if (allowedMethods && !methodsMap.has(request.method)) {\n context.rpcRequestBlocked.inc({ reason: 'method not allowed' })\n throw new InvalidRequestError(`RPC method ${method} not allowed`)\n }\n }\n}\n\nexport const ethCallValidationFactory = (allowedAddress: Record<number, string[]>) => {\n const allowedCallAddressMap = createAllowedAddressMap(allowedAddress)\n\n return (request: RpcRequest, { chainId, rpcRequestBlocked }: ValidationContext) => {\n const { params } = request\n if (\n Array.isArray(params) &&\n params[0] &&\n typeof params[0] === 'object' &&\n 'to' in params[0] &&\n typeof params[0].to === 'string'\n ) {\n if (!allowedCallAddressMap[chainId]?.has(params[0].to.toLowerCase())) {\n rpcRequestBlocked.inc({ reason: 'address not allowed for eth_call' })\n throw new InvalidRequestError(`Address not allowed for eth_call`)\n }\n } else throw new InvalidRequestError(`RPC method eth_call is invalid`)\n }\n}\n\nexport const ethGetLogsValidationFactory = (\n allowedAddress: Record<number, string[]> | undefined,\n blockEmptyAddressGetLogs: boolean | undefined,\n maxBlockRange: number | undefined,\n currentBlockTTL = 60_000,\n) => {\n const allowedAddressMap = createAllowedAddressMap(allowedAddress ?? {})\n\n const maxBlockRangeBN = maxBlockRange ? BigInt(maxBlockRange) : zeroBN\n\n const currentBlock = new Cache<number, bigint>()\n\n return async (request: RpcRequest, { chainId, rpcRequestBlocked, requestRPC }: ValidationContext) => {\n try {\n const params = request.params[0] as any\n const fromBlock: string = params.fromBlock\n const toBlock: string = params.toBlock ?? 'latest'\n\n if (blockEmptyAddressGetLogs || allowedAddress) {\n // address validation\n const address: string | string[] = params.address\n\n if (blockEmptyAddressGetLogs && !address)\n throw new InvalidRequestError(`RPC method eth_getLogs address is invalid`)\n\n const addresses = Array.isArray(address) ? address : [address]\n\n if (blockEmptyAddressGetLogs && addresses.length === 0) {\n throw new InvalidRequestError(`RPC method eth_getLogs address is invalid`)\n }\n if (\n allowedAddress &&\n addresses.some(\n (eventAddress) =>\n // needs this check before toLowerCase\n typeof eventAddress !== 'string' || !allowedAddressMap[chainId]?.has(eventAddress.toLowerCase()),\n )\n ) {\n rpcRequestBlocked.inc({\n reason: 'address not allowed for eth_getLogs',\n })\n throw new InvalidRequestError(`Address not allowed for eth_getLogs`)\n }\n }\n\n // block range validation\n if (maxBlockRange) {\n if (fromBlock === 'earliest') throw new InvalidRequestError(`RPC method eth_getLogs fromBlock is invalid`)\n\n const shouldValidateBlockDistance = fromBlock.startsWith('0x')\n\n if (shouldValidateBlockDistance) {\n const normalizedFromBlock = BigInt(fromBlock)\n let normalizedToBlock: bigint\n if (toBlock.startsWith('0x')) normalizedToBlock = BigInt(toBlock)\n else {\n const cached = currentBlock.get(chainId)\n if (cached) {\n normalizedToBlock = cached\n } else {\n normalizedToBlock = await requestRPC(chainId, {\n id: 1,\n jsonrpc: '2.0',\n method: 'eth_blockNumber',\n })\n .then((res) => res.json())\n .then((res) => BigInt(res.result))\n currentBlock.put(chainId, normalizedToBlock, currentBlockTTL)\n }\n }\n const range = normalizedToBlock - normalizedFromBlock\n\n if (range < zeroBN || range > maxBlockRangeBN) {\n rpcRequestBlocked.inc({\n reason: 'eth_getLogs range is invalid',\n })\n throw new InvalidRequestError(`RPC method eth_getLogs range is invalid`)\n }\n }\n }\n } catch (error) {\n if (error instanceof ClientError) throw error\n throw new InvalidRequestError(`RPC method eth_getLogs is invalid`)\n }\n }\n}\n\nexport const streamMaxSizeValidationFactory = (MAX_SIZE: number) => (context: ValidationContext) => {\n let bytesWritten = 0\n return 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 context.rpcRequestBlocked.inc({ reason: 'response too large' })\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}\n\n// utils\n\nexport const shouldValidateRpcMethod = (method: string, allowedRPCMethods?: string[]) =>\n !allowedRPCMethods || allowedRPCMethods.includes(method)\n\nconst createAllowedAddressMap = (allowedAddress: Record<number, string[]>) => {\n return Object.entries(allowedAddress).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"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../../../"}
@@ -0,0 +1,17 @@
1
+ export declare const DEFAULT_API_ERROR_MESSAGE = "Something went wrong. Sorry, try again later :(";
2
+ export declare const HEALTHY_RPC_SERVICES_ARE_OVER = "Healthy RPC services are over!";
3
+ export declare class ClientError extends Error {
4
+ }
5
+ export declare class UnsupportedChainIdError extends ClientError {
6
+ constructor(message?: string);
7
+ }
8
+ export declare class UnsupportedHTTPMethodError extends ClientError {
9
+ constructor(message?: string);
10
+ }
11
+ export declare class InvalidRequestError extends ClientError {
12
+ constructor(message?: string);
13
+ }
14
+ export declare class SizeTooLargeError extends ClientError {
15
+ constructor(message?: string);
16
+ }
17
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/errors.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,oDAAoD,CAAA;AAE1F,eAAO,MAAM,6BAA6B,mCAAmC,CAAA;AAE7E,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAEzC,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"}
@@ -0,0 +1,3 @@
1
+ export { rpcFactory } from './rpc-factory';
2
+ export type { RPCFactoryParams, RPCFactoryValidationParams, RpcProviders } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,YAAY,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { RPCFactoryParams } from './types';
2
+ import type { NextApiRequest, NextApiResponse } from 'next';
3
+ export declare const rpcFactory: ({ metrics, providers, fetchRPC, defaultChain, validation }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
4
+ //# sourceMappingURL=rpc-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-factory.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/rpc-factory.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAG3D,eAAO,MAAM,UAAU,+DAAgE,gBAAgB,WA+ClF,cAAc,OAAO,eAAe,KAAG,QAAQ,IAAI,CA0DvE,CAAA"}
@@ -0,0 +1,34 @@
1
+ import type { TrackedFetchRPC } from '@lidofinance/api-rpc';
2
+ import type { FetchRpcInitBody } from '@lidofinance/rpc';
3
+ import type { Counter, Registry } from 'prom-client';
4
+ export type RpcProviders = Record<string | number, [string, ...string[]]>;
5
+ export type RPCFactoryValidationParams = {
6
+ maxBatchCount?: number;
7
+ maxResponseSize?: number;
8
+ allowedRPCMethods?: string[];
9
+ allowedCallAddresses?: Record<number, string[]>;
10
+ allowedLogsAddresses?: Record<number, string[]>;
11
+ blockEmptyAddressGetLogs?: boolean;
12
+ maxGetLogsRange?: number;
13
+ currentBlockTTLms?: number;
14
+ };
15
+ export type RPCFactoryParams = {
16
+ metrics?: {
17
+ prefix: string;
18
+ registry: Registry;
19
+ };
20
+ providers: RpcProviders;
21
+ fetchRPC: TrackedFetchRPC;
22
+ defaultChain: string | number;
23
+ validation: RPCFactoryValidationParams;
24
+ };
25
+ export type RpcRequest = {
26
+ method: string;
27
+ params: unknown[];
28
+ };
29
+ export type ValidationContext = {
30
+ chainId: number;
31
+ rpcRequestBlocked: Counter<'reason'>;
32
+ requestRPC: (chainId: number, body: FetchRpcInitBody) => Promise<Response>;
33
+ };
34
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEpD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,0BAA0B,GAAG;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,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,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE;QACR,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;IAC7B,UAAU,EAAE,0BAA0B,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,EAAE,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3E,CAAA"}
@@ -0,0 +1,14 @@
1
+ /// <reference types="node" />
2
+ import { Transform } from 'node:stream';
3
+ import type { NextApiRequest } from 'next';
4
+ import type { RpcRequest, ValidationContext } from './types';
5
+ export declare const baseRequestValidationFactory: (defaultChain: number | string, providers: Record<string, unknown>, maxBatchSize?: number) => (req: NextApiRequest) => {
6
+ chainId: number;
7
+ requests: any[];
8
+ };
9
+ export declare const rpcMethodsValidationFactory: (allowedMethods?: string[]) => (request: RpcRequest, context: ValidationContext) => void;
10
+ export declare const ethCallValidationFactory: (allowedAddress: Record<number, string[]>) => (request: RpcRequest, { chainId, rpcRequestBlocked }: ValidationContext) => void;
11
+ export declare const ethGetLogsValidationFactory: (allowedAddress: Record<number, string[]> | undefined, blockEmptyAddressGetLogs: boolean | undefined, maxBlockRange: number | undefined, currentBlockTTL?: number) => (request: RpcRequest, { chainId, rpcRequestBlocked, requestRPC }: ValidationContext) => Promise<void>;
12
+ export declare const streamMaxSizeValidationFactory: (MAX_SIZE: number) => (context: ValidationContext) => Transform;
13
+ export declare const shouldValidateRpcMethod: (method: string, allowedRPCMethods?: string[]) => boolean;
14
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/validation.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAYvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAM5D,eAAO,MAAM,4BAA4B,iBACzB,MAAM,GAAG,MAAM,aAClB,OAAO,MAAM,EAAE,OAAO,CAAC,iBACnB,MAAM,WAER,cAAc;;;CAqB5B,CAAA;AAED,eAAO,MAAM,2BAA2B,oBAAqB,MAAM,EAAE,eAGlD,UAAU,WAAW,iBAAiB,SAUxD,CAAA;AAED,eAAO,MAAM,wBAAwB,mBAAoB,OAAO,MAAM,EAAE,MAAM,EAAE,CAAC,eAG9D,UAAU,kCAAkC,iBAAiB,SAe/E,CAAA;AAED,eAAO,MAAM,2BAA2B,mBACtB,OAAO,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,SAAS,4BAC1B,OAAO,GAAG,SAAS,iBAC9B,MAAM,GAAG,SAAS,yCASV,UAAU,8CAA8C,iBAAiB,kBAyEjG,CAAA;AAED,eAAO,MAAM,8BAA8B,aAAc,MAAM,eAAe,iBAAiB,cAgB9F,CAAA;AAID,eAAO,MAAM,uBAAuB,WAAY,MAAM,sBAAsB,MAAM,EAAE,YAC1B,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.45.1",
10
+ "version": "0.47.0",
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist"
@@ -58,22 +58,26 @@
58
58
  }
59
59
  },
60
60
  "peerDependencies": {
61
- "@lidofinance/api-logger": "~0.45.1",
62
- "@lidofinance/api-rpc": "~0.45.1",
63
- "@lidofinance/rpc": "~0.45.1",
64
- "@lidofinance/ui-pages": "~0.45.1",
61
+ "@lidofinance/api-logger": "~0.47.0",
62
+ "@lidofinance/api-rpc": "~0.47.0",
63
+ "@lidofinance/rpc": "~0.47.0",
64
+ "@lidofinance/ui-pages": "~0.47.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.45.1",
71
- "@lidofinance/api-rpc": "~0.45.1",
72
- "@lidofinance/config-prettier": "~0.45.1",
73
- "@lidofinance/rpc": "~0.45.1",
74
- "@lidofinance/ui-pages": "~0.45.1",
70
+ "@lidofinance/api-logger": "~0.47.0",
71
+ "@lidofinance/api-rpc": "~0.47.0",
72
+ "@lidofinance/config-prettier": "~0.47.0",
73
+ "@lidofinance/rpc": "~0.47.0",
74
+ "@lidofinance/ui-pages": "~0.47.0",
75
+ "@types/memory-cache": "^0.2.6",
75
76
  "@types/react": "^18.0.25",
76
77
  "next": "^12.3.1",
77
78
  "prom-client": "^14.1.0"
79
+ },
80
+ "dependencies": {
81
+ "memory-cache": "^0.2.0"
78
82
  }
79
83
  }
@@ -1,30 +0,0 @@
1
- import type { NextApiRequest, NextApiResponse } from 'next';
2
- import { Registry } from 'prom-client';
3
- import type { TrackedFetchRPC } from '@lidofinance/api-rpc';
4
- import type { ServerLogger } from '@lidofinance/api-logger';
5
- export type RpcProviders = Record<string | number, [string, ...string[]]>;
6
- export declare const DEFAULT_API_ERROR_MESSAGE = "Something went wrong. Sorry, try again later :(";
7
- export declare const HEALTHY_RPC_SERVICES_ARE_OVER = "Healthy RPC services are over!";
8
- export declare class UnsupportedChainIdError extends Error {
9
- constructor(message?: string);
10
- }
11
- export declare class UnsupportedHTTPMethodError extends Error {
12
- constructor(message?: string);
13
- }
14
- export type RPCFactoryParams = {
15
- metrics: {
16
- prefix: string;
17
- registry: Registry;
18
- };
19
- providers: RpcProviders;
20
- 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
- defaultChain: string | number;
27
- allowedRPCMethods: string[];
28
- };
29
- export declare const rpcFactory: ({ metrics: { prefix, registry }, providers, fetchRPC, serverLogger, defaultChain, allowedRPCMethods, }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
30
- //# sourceMappingURL=rpcFactory.d.ts.map
@@ -1 +0,0 @@
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;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,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,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,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;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;IAI7B,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B,CAAA;AAED,eAAO,MAAM,UAAU,2GAOpB,gBAAgB,WASE,cAAc,OAAO,eAAe,KAAG,QAAQ,IAAI,CAmDvE,CAAA"}