1818 *
1919 */
2020
21- import { methodConfiguration } from './lib/methodConfiguration' ;
21+ import { IMethodRateLimitConfiguration , methodConfiguration } from './lib/methodConfiguration' ;
2222import jsonResp from './lib/RpcResponse' ;
2323import RateLimit from '../rateLimit' ;
2424import parse from 'co-body' ;
@@ -27,104 +27,84 @@ import path from 'path';
2727import { Logger } from 'pino' ;
2828
2929import {
30- ParseError ,
31- InvalidRequest ,
3230 InternalError ,
31+ InvalidRequest ,
3332 IPRateLimitExceeded ,
34- MethodNotFound ,
35- Unauthorized ,
3633 JsonRpcError as JsonRpcErrorServer ,
34+ MethodNotFound ,
35+ ParseError ,
3736} from './lib/RpcError' ;
3837import Koa from 'koa' ;
3938import { Histogram , Registry } from 'prom-client' ;
4039import { JsonRpcError , predefined } from '@hashgraph/json-rpc-relay' ;
41- import { RpcErrorCodeToStatusMap , HttpStatusCodeAndMessage } from './lib/HttpStatusCodeAndMessage' ;
40+ import { RpcErrorCodeToStatusMap } from './lib/HttpStatusCodeAndMessage' ;
41+ import {
42+ getBatchRequestsEnabled ,
43+ getBatchRequestsMaxSize ,
44+ getDefaultRateLimit ,
45+ getLimitDuration ,
46+ getRequestIdIsOptional ,
47+ hasOwnProperty ,
48+ } from './lib/utils' ;
49+ import { IJsonRpcRequest } from './lib/IJsonRpcRequest' ;
4250
43- const hasOwnProperty = ( obj , prop ) => Object . prototype . hasOwnProperty . call ( obj , prop ) ;
4451dotenv . config ( { path : path . resolve ( __dirname , '../../../../../.env' ) } ) ;
4552
46- import constants from '@hashgraph/json-rpc-relay/dist/lib/constants' ;
47-
48- const INTERNAL_ERROR = 'INTERNAL ERROR' ;
49- const INVALID_PARAMS_ERROR = 'INVALID PARAMS ERROR' ;
5053const INVALID_REQUEST = 'INVALID REQUEST' ;
51- const IP_RATE_LIMIT_EXCEEDED = 'IP RATE LIMIT EXCEEDED' ;
52- const JSON_RPC_ERROR = 'JSON RPC ERROR' ;
53- const CONTRACT_REVERT = 'CONTRACT REVERT' ;
54- const METHOD_NOT_FOUND = 'METHOD NOT FOUND' ;
5554const REQUEST_ID_HEADER_NAME = 'X-Request-Id' ;
5655const responseSuccessStatusCode = '200' ;
56+ const METRIC_HISTOGRAM_NAME = 'rpc_relay_method_result' ;
5757const BATCH_REQUEST_METHOD_NAME = 'batch_request' ;
5858
5959export default class KoaJsonRpc {
60- private registry : any ;
61- private registryTotal : any ;
62- private token : any ;
63- private methodConfig : any ;
64- private duration : number ;
65- private limit : string ;
66- private rateLimit : RateLimit ;
67- private metricsRegistry : any ;
68- private koaApp : Koa < Koa . DefaultState , Koa . DefaultContext > ;
60+ private readonly registry : { [ key : string ] : ( params ?: any ) => Promise < any > } ;
61+ private readonly registryTotal : { [ key : string ] : number } ;
62+ private readonly methodConfig : IMethodRateLimitConfiguration ;
63+ private readonly duration : number = getLimitDuration ( ) ;
64+ private readonly defaultRateLimit : number = getDefaultRateLimit ( ) ;
65+ private readonly limit : string ;
66+ private readonly rateLimit : RateLimit ;
67+ private readonly metricsRegistry : Registry ;
68+ private readonly koaApp : Koa < Koa . DefaultState , Koa . DefaultContext > ;
69+ private readonly logger : Logger ;
70+ private readonly requestIdIsOptional : boolean = getRequestIdIsOptional ( ) ; // default to false
71+ private readonly batchRequestsMaxSize : number = getBatchRequestsMaxSize ( ) ; // default to 100
72+ private readonly methodResponseHistogram : Histogram ;
73+
6974 private requestId : string ;
70- private logger : Logger ;
71- private startTimestamp ! : number ;
72- private readonly requestIdIsOptional = process . env . REQUEST_ID_IS_OPTIONAL == 'true' ; // default to false
73- private readonly batchRequestsMaxSize : number = process . env . BATCH_REQUESTS_MAX_SIZE
74- ? parseInt ( process . env . BATCH_REQUESTS_MAX_SIZE )
75- : 100 ; // default to 100
76- private methodResponseHistogram : Histogram | undefined ;
77-
78- constructor ( logger : Logger , register : Registry , opts ?) {
75+
76+ constructor ( logger : Logger , register : Registry , opts ?: { limit : string | null } ) {
7977 this . koaApp = new Koa ( ) ;
8078 this . requestId = '' ;
81- this . limit = '1mb' ;
82- this . duration = process . env . LIMIT_DURATION
83- ? parseInt ( process . env . LIMIT_DURATION )
84- : constants . DEFAULT_RATE_LIMIT . DURATION ;
8579 this . registry = Object . create ( null ) ;
8680 this . registryTotal = Object . create ( null ) ;
8781 this . methodConfig = methodConfiguration ;
88- if ( opts ) {
89- this . limit = opts . limit || this . limit ;
90- }
82+ this . limit = opts ?. limit ?? '1mb' ;
9183 this . logger = logger ;
9284 this . rateLimit = new RateLimit ( logger . child ( { name : 'ip-rate-limit' } ) , register , this . duration ) ;
9385 this . metricsRegistry = register ;
94- this . initMetrics ( ) ;
95- }
96-
97- private initMetrics ( ) {
9886 // clear and create metric in registry
99- const metricHistogramName = 'rpc_relay_method_result' ;
100- this . metricsRegistry . removeSingleMetric ( metricHistogramName ) ;
87+ this . metricsRegistry . removeSingleMetric ( METRIC_HISTOGRAM_NAME ) ;
10188 this . methodResponseHistogram = new Histogram ( {
102- name : metricHistogramName ,
89+ name : METRIC_HISTOGRAM_NAME ,
10390 help : 'JSON RPC method statusCode latency histogram' ,
10491 labelNames : [ 'method' , 'statusCode' , 'isPartOfBatch' ] ,
10592 registers : [ this . metricsRegistry ] ,
10693 buckets : [ 5 , 10 , 25 , 50 , 100 , 250 , 500 , 1000 , 2500 , 5000 , 10000 , 20000 , 30000 , 40000 , 50000 , 60000 ] , // ms (milliseconds)
10794 } ) ;
10895 }
10996
110- // we do it as a method so we can mock it in tests, since by default is false, but we need to test it as true
111- private getBatchRequestsEnabled ( ) : boolean {
112- return process . env . BATCH_REQUESTS_ENABLED == 'true' ; // default to false
113- }
114-
115- useRpc ( name , func ) {
97+ useRpc ( name : string , func : ( params ?: any ) => Promise < any > ) : void {
11698 this . registry [ name ] = func ;
117- this . registryTotal [ name ] = this . methodConfig [ name ] . total ;
99+ this . registryTotal [ name ] = this . methodConfig [ name ] ? .total ;
118100
119101 if ( ! this . registryTotal [ name ] ) {
120- this . registryTotal [ name ] = process . env . DEFAULT_RATE_LIMIT || 200 ;
102+ this . registryTotal [ name ] = this . defaultRateLimit ;
121103 }
122104 }
123105
124- rpcApp ( ) {
125- return async ( ctx , next ) => {
126- this . startTimestamp = ctx . state . start ;
127-
106+ rpcApp ( ) : ( ctx : Koa . Context , _next : Koa . Next ) => Promise < void > {
107+ return async ( ctx : Koa . Context , _next : Koa . Next ) => {
128108 this . requestId = ctx . state . reqId ;
129109 ctx . set ( REQUEST_ID_HEADER_NAME , this . requestId ) ;
130110
@@ -135,20 +115,11 @@ export default class KoaJsonRpc {
135115 return ;
136116 }
137117
138- if ( this . token ) {
139- const headerToken = ctx . get ( 'authorization' ) . split ( ' ' ) . pop ( ) ;
140- if ( headerToken !== this . token ) {
141- ctx . body = jsonResp ( null , new Unauthorized ( ) , undefined ) ;
142- return ;
143- }
144- }
145-
146118 let body : any ;
147119 try {
148120 body = await parse . json ( ctx , { limit : this . limit } ) ;
149121 } catch ( err ) {
150- const errBody = jsonResp ( null , new ParseError ( ) , undefined ) ;
151- ctx . body = errBody ;
122+ ctx . body = jsonResp ( null , new ParseError ( ) , undefined ) ;
152123 return ;
153124 }
154125 //check if body is array or object
@@ -160,7 +131,7 @@ export default class KoaJsonRpc {
160131 } ;
161132 }
162133
163- private async handleSingleRequest ( ctx , body : any ) : Promise < void > {
134+ private async handleSingleRequest ( ctx : Koa . Context , body : any ) : Promise < void > {
164135 ctx . state . methodName = body . method ;
165136 const response = await this . getRequestResult ( body , ctx . ip ) ;
166137 ctx . body = response ;
@@ -175,9 +146,9 @@ export default class KoaJsonRpc {
175146 }
176147 }
177148
178- private async handleMultipleRequest ( ctx , body : any ) : Promise < void > {
149+ private async handleMultipleRequest ( ctx : Koa . Context , body : any [ ] ) : Promise < void > {
179150 // verify that batch requests are enabled
180- if ( ! this . getBatchRequestsEnabled ( ) ) {
151+ if ( ! getBatchRequestsEnabled ( ) ) {
181152 ctx . body = jsonResp ( null , predefined . BATCH_REQUESTS_DISABLED , undefined ) ;
182153 ctx . status = 400 ;
183154 ctx . state . status = `${ ctx . status } (${ INVALID_REQUEST } )` ;
@@ -196,26 +167,17 @@ export default class KoaJsonRpc {
196167 return ;
197168 }
198169
199- // verify rate limit for batch request
200- const batchRequestTotalLimit = this . methodConfig [ BATCH_REQUEST_METHOD_NAME ] . total ;
201- // check rate limit for method and ip
202- if ( this . rateLimit . shouldRateLimit ( ctx . ip , BATCH_REQUEST_METHOD_NAME , batchRequestTotalLimit , this . requestId ) ) {
203- return jsonResp ( null , new IPRateLimitExceeded ( BATCH_REQUEST_METHOD_NAME ) , undefined ) ;
204- }
205-
206170 const response : any [ ] = [ ] ;
207171 ctx . state . methodName = BATCH_REQUEST_METHOD_NAME ;
208172
209173 // we do the requests in parallel to save time, but we need to keep track of the order of the responses (since the id might be optional)
210- const promises = body . map ( ( item : any ) => {
174+ const promises : Promise < any > [ ] = body . map ( async ( item : any ) => {
211175 const startTime = Date . now ( ) ;
212- const result = this . getRequestResult ( item , ctx . ip ) . then ( ( res ) => {
176+ return this . getRequestResult ( item , ctx . ip ) . then ( ( res ) => {
213177 const ms = Date . now ( ) - startTime ;
214178 this . methodResponseHistogram ?. labels ( item . method , `${ res . error ? res . error . code : 200 } ` , 'true' ) . observe ( ms ) ;
215179 return res ;
216180 } ) ;
217-
218- return result ;
219181 } ) ;
220182 const results = await Promise . all ( promises ) ;
221183 response . push ( ...results ) ;
@@ -226,7 +188,7 @@ export default class KoaJsonRpc {
226188 ctx . state . status = responseSuccessStatusCode ;
227189 }
228190
229- async getRequestResult ( request : any , ip : any ) : Promise < any > {
191+ async getRequestResult ( request : any , ip : string ) : Promise < any > {
230192 try {
231193 const methodName = request . method ;
232194
@@ -259,12 +221,12 @@ export default class KoaJsonRpc {
259221 }
260222 }
261223
262- validateJsonRpcRequest ( body ) : boolean {
224+ validateJsonRpcRequest ( body : IJsonRpcRequest ) : boolean {
263225 // validate it has the correct jsonrpc version, method, and id
264226 if (
265227 body . jsonrpc !== '2.0' ||
266228 ! hasOwnProperty ( body , 'method' ) ||
267- this . hasInvalidReqestId ( body ) ||
229+ this . hasInvalidRequestId ( body ) ||
268230 ! hasOwnProperty ( body , 'id' )
269231 ) {
270232 this . logger . warn (
@@ -295,7 +257,7 @@ export default class KoaJsonRpc {
295257 return this . requestId ;
296258 }
297259
298- hasInvalidReqestId ( body ) : boolean {
260+ hasInvalidRequestId ( body : IJsonRpcRequest ) : boolean {
299261 const hasId = hasOwnProperty ( body , 'id' ) ;
300262 if ( this . requestIdIsOptional && ! hasId ) {
301263 // If the request is invalid, we still want to return a valid JSON-RPC response, default id to 0
0 commit comments