@@ -6,7 +6,47 @@ import { JSONRPCError } from "jayson";
66
77export const accessGuard = new AccessGuard ( ) ;
88
9- export async function wsApplyRateLimitByIp ( req : Request , method : string ) {
9+ export async function wsApplyBatchRateLimitByIp (
10+ req : Request ,
11+ objs : any [ ]
12+ ) : Promise < JSONRPCError [ ] | undefined > {
13+ const ip = getIp ( req ) ;
14+ const methods = Object . keys ( accessGuard . rpcMethods ) ;
15+ for ( const targetMethod of methods ) {
16+ const count = calcMethodCount ( objs , targetMethod ) ;
17+ if ( count > 0 && ip != null ) {
18+ const isExist = await accessGuard . isExist ( targetMethod , ip ) ;
19+ if ( ! isExist ) {
20+ await accessGuard . add ( targetMethod , ip ) ;
21+ }
22+
23+ const isOverRate = await accessGuard . isOverRate ( targetMethod , ip , count ) ;
24+ if ( isOverRate ) {
25+ const remainSecs = await accessGuard . getKeyTTL ( targetMethod , ip ) ;
26+ const message = `Too Many Requests, IP: ${ ip } , please wait ${ remainSecs } s and retry. RPC method: ${ targetMethod } .` ;
27+ const error : JSONRPCError = {
28+ code : LIMIT_EXCEEDED ,
29+ message : message ,
30+ } ;
31+
32+ logger . debug (
33+ `Rate Limit Exceed, ip: ${ ip } , method: ${ targetMethod } , ttl: ${ remainSecs } s`
34+ ) ;
35+
36+ return new Array ( objs . length ) . fill ( error ) ;
37+ } else {
38+ await accessGuard . updateCount ( targetMethod , ip , count ) ;
39+ }
40+ }
41+
42+ return undefined ;
43+ }
44+ }
45+
46+ export async function wsApplyRateLimitByIp (
47+ req : Request ,
48+ method : string
49+ ) : Promise < JSONRPCError | undefined > {
1050 const ip = getIp ( req ) ;
1151 const methods = Object . keys ( accessGuard . rpcMethods ) ;
1252 if ( methods . includes ( method ) && ip != null ) {
@@ -23,6 +63,11 @@ export async function applyRateLimitByIp(
2363 res : Response ,
2464 next : NextFunction
2565) {
66+ // check batch limit
67+ if ( batchLimit ( req , res ) ) {
68+ return ;
69+ }
70+
2671 const methods = Object . keys ( accessGuard . rpcMethods ) ;
2772 if ( methods . length === 0 ) {
2873 return next ( ) ;
@@ -45,20 +90,69 @@ export async function applyRateLimitByIp(
4590 }
4691}
4792
93+ export function batchLimit ( req : Request , res : Response ) {
94+ let isBan = false ;
95+ if ( isBatchLimit ( req . body ) ) {
96+ isBan = true ;
97+ // if reach batch limit, we reject the whole req with error
98+ const message = `Too Many Batch Requests ${ req . body . length } , limit: ${ accessGuard . batchLimit } .` ;
99+ const error = {
100+ code : LIMIT_EXCEEDED ,
101+ message : message ,
102+ } ;
103+
104+ logger . debug (
105+ `Batch Limit Exceed, ${ req . body . length } , limit: ${ accessGuard . batchLimit } `
106+ ) ;
107+
108+ const content = req . body . map ( ( b : any ) => {
109+ return {
110+ jsonrpc : "2.0" ,
111+ id : b . id ,
112+ error : error ,
113+ } ;
114+ } ) ;
115+
116+ const httpRateLimitCode = 429 ;
117+ res . status ( httpRateLimitCode ) . send ( content ) ;
118+ }
119+ return isBan ;
120+ }
121+
122+ export function wsBatchLimit ( body : any ) : JSONRPCError [ ] | undefined {
123+ if ( isBatchLimit ( body ) ) {
124+ // if reach batch limit, we reject the whole req with error
125+ const message = `Too Many Batch Requests ${ body . length } , limit: ${ accessGuard . batchLimit } .` ;
126+ const error : JSONRPCError = {
127+ code : LIMIT_EXCEEDED ,
128+ message : message ,
129+ } ;
130+
131+ logger . debug (
132+ `WS Batch Limit Exceed, ${ body . length } , limit: ${ accessGuard . batchLimit } `
133+ ) ;
134+
135+ return new Array ( body . length ) . fill ( error ) ;
136+ }
137+
138+ return undefined ;
139+ }
140+
48141export async function rateLimit (
49142 req : Request ,
50143 res : Response ,
51144 rpcMethod : string ,
52145 reqId : string | undefined
53146) {
54147 let isBan = false ;
55- if ( hasMethod ( req . body , rpcMethod ) && reqId != null ) {
148+ const count = calcMethodCount ( req . body , rpcMethod ) ;
149+ if ( count > 0 && reqId != null ) {
56150 const isExist = await accessGuard . isExist ( rpcMethod , reqId ) ;
57151 if ( ! isExist ) {
58152 await accessGuard . add ( rpcMethod , reqId ) ;
59153 }
60154
61- const isOverRate = await accessGuard . isOverRate ( rpcMethod , reqId ) ;
155+ const isOverRate = await accessGuard . isOverRate ( rpcMethod , reqId , count ) ;
62156 if ( isOverRate ) {
63157 isBan = true ;
64158
@@ -94,7 +188,7 @@ export async function rateLimit(
94188 } ;
95189 res . status ( httpRateLimitCode ) . header ( httpRateLimitHeader ) . send ( content ) ;
96190 } else {
97- await accessGuard . updateCount ( rpcMethod , reqId ) ;
191+ await accessGuard . updateCount ( rpcMethod , reqId , count ) ;
98192 }
99193 }
100194 return isBan ;
@@ -120,7 +214,7 @@ export async function wsRateLimit(
120214 } ;
121215
122216 logger . debug (
123- `Rate Limit Exceed, ip: ${ reqId } , method: ${ rpcMethod } , ttl: ${ remainSecs } s`
217+ `WS Rate Limit Exceed, ip: ${ reqId } , method: ${ rpcMethod } , ttl: ${ remainSecs } s`
124218 ) ;
125219 return { error, remainSecs } ;
126220 } else {
@@ -129,12 +223,19 @@ export async function wsRateLimit(
129223 return undefined ;
130224}
131225
132- export function hasMethod ( body : any , name : string ) {
226+ export function isBatchLimit ( body : any ) {
227+ if ( Array . isArray ( body ) ) {
228+ return body . length >= accessGuard . batchLimit ;
229+ }
230+ return false ;
231+ }
232+
233+ export function calcMethodCount ( body : any , targetMethod : string ) : number {
133234 if ( Array . isArray ( body ) ) {
134- return body . map ( ( b ) => b . method ) . includes ( name ) ;
235+ return body . filter ( ( b ) => b . method === targetMethod ) . length ;
135236 }
136237
137- return body . method === name ;
238+ return body . method === targetMethod ? 1 : 0 ;
138239}
139240
140241export function getIp ( req : Request ) {
0 commit comments