From 821f908fdb3a83111b8c8cc51919eaaac049b5a1 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 8 Nov 2020 08:07:16 -0800 Subject: [PATCH] feat(rate limits): Bumping anonymous rate limits to 20 RPM --- src/middleware/route-ratelimit.js | 31 ++++++++++++++++++++-------- test/v3/rate-limits.js | 34 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/middleware/route-ratelimit.js b/src/middleware/route-ratelimit.js index 4ca8510..4467f9c 100644 --- a/src/middleware/route-ratelimit.js +++ b/src/middleware/route-ratelimit.js @@ -1,3 +1,15 @@ +/* + Sets the rate limits for the anonymous and paid tiers. Current rate limits: + - 1000 points in 60 seconds + - 10 points per call for paid tier (100 RPM) + - 50 points per call for anonymous tier (20 RPM) + + Background: + The rate limits below were originially coded with the idea of charging on a + per-resource basis. However, that was confusing to end users trying to purchase + a subscription. So everything was simplied to two tiers: paid and anonymous +*/ + 'use strict' const wlogger = require('../util/winston-logging') @@ -98,8 +110,8 @@ class RateLimits { wlogger.debug('No JWT token found!') } - // Used for displaying error message. Default value is 30. - let rateLimit = 30 + // Default value is 50 points per request = 20 RPM + let rateLimit = 50 // Code here for the rate limiter is adapted from this example: // https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users @@ -118,9 +130,12 @@ class RateLimits { // Retrieve the origin. let origin = req.get('origin') + + // Handle calls coming from the intranet. if (origin === undefined && key.indexOf('10.0.0.5') > -1) { origin = 'slp-api' } + wlogger.info(`origin: ${origin}`) // If the request originates from one of the approved wallet apps, then @@ -135,7 +150,7 @@ class RateLimits { res.locals.pointsToConsume = pointsToConsume // Feedback for tests. } - // Apply paid-access rate limits based on key/IP + // For internal calls, increase rate limits to as fast as possible. if ( key.toString().indexOf('172.17.') > -1 || key.toString().indexOf('::ffff:127.0.0.1') > -1 @@ -178,7 +193,7 @@ class RateLimits { // Calculates the points consumed, based on the jwt information and the route // requested. calcPoints (jwtInfo) { - let retVal = 300 // By default, use anonymous tier. + let retVal = 50 // By default, use anonymous tier. try { // console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`) @@ -197,12 +212,12 @@ class RateLimits { if (level40Routes.includes(resource)) { if (apiLevel >= 40) retVal = 10 // else if (apiLevel >= 10) retVal = 10 - else retVal = 100 + else retVal = 50 // Normal indexer routes } else if (level30Routes.includes(resource)) { if (apiLevel >= 30) retVal = 10 - else retVal = 100 + else retVal = 50 // Full node tier } else if (apiLevel >= 20) { @@ -210,7 +225,7 @@ class RateLimits { // Free tier, full node only. } else { - retVal = 100 + retVal = 50 } } @@ -218,7 +233,7 @@ class RateLimits { } catch (err) { wlogger.error('Error in route-ratelimit.js/calcPoints()') // throw err - retVal = 300 + retVal = 50 } return retVal diff --git a/test/v3/rate-limits.js b/test/v3/rate-limits.js index e47423d..c60473f 100644 --- a/test/v3/rate-limits.js +++ b/test/v3/rate-limits.js @@ -105,14 +105,14 @@ describe('#route-ratelimits & jwt-auth', () => { }) describe('#calcPoints', () => { - it('should return 300 points for anonymous user', () => { + it('should return 50 points for anonymous user', () => { const result = rateLimits.calcPoints() // console.log(`result: ${result}`) - assert.equal(result, 300) + assert.equal(result, 50) }) - it('should return 100 points for free tier requesting full node access', () => { + it('should return 50 points for free tier requesting full node access', () => { const jwtInfo = { apiLevel: 10, resource: 'blockchain', @@ -120,10 +120,10 @@ describe('#route-ratelimits & jwt-auth', () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 100) + assert.equal(result, 50) }) - it('should return 100 points for free tier requesting indexer access', () => { + it('should return 50 points for free tier requesting indexer access', () => { const jwtInfo = { apiLevel: 10, resource: 'blockbook', @@ -131,10 +131,10 @@ describe('#route-ratelimits & jwt-auth', () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 100) + assert.equal(result, 50) }) - it('should return 100 points for free tier requesting SLPDB access', () => { + it('should return 50 points for free tier requesting SLPDB access', () => { const jwtInfo = { apiLevel: 10, resource: 'slp', @@ -142,7 +142,7 @@ describe('#route-ratelimits & jwt-auth', () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 100) + assert.equal(result, 50) }) it('should return 10 points for full node tier requesting full node access', () => { @@ -156,7 +156,7 @@ describe('#route-ratelimits & jwt-auth', () => { assert.equal(result, 10) }) - it('should return 100 points for full-node tier requesting indexer access', () => { + it('should return 50 points for full-node tier requesting indexer access', () => { const jwtInfo = { apiLevel: 20, resource: 'blockbook', @@ -164,10 +164,10 @@ describe('#route-ratelimits & jwt-auth', () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 100) + assert.equal(result, 50) }) - it('should return 100 points for full node tier requesting SLPDB access', () => { + it('should return 50 points for full node tier requesting SLPDB access', () => { const jwtInfo = { apiLevel: 20, resource: 'slp', @@ -175,7 +175,7 @@ describe('#route-ratelimits & jwt-auth', () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 100) + assert.equal(result, 50) }) it('should return 10 point for indexer tier requesting full node access', () => { @@ -200,7 +200,7 @@ describe('#route-ratelimits & jwt-auth', () => { assert.equal(result, 10) }) - it('should return 100 points for indexer tier requesting SLPDB access', () => { + it('should return 50 points for indexer tier requesting SLPDB access', () => { const jwtInfo = { apiLevel: 30, resource: 'slp', @@ -208,7 +208,7 @@ describe('#route-ratelimits & jwt-auth', () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 100) + assert.equal(result, 50) }) it('should return 10 point for SLP tier requesting full node access', () => { @@ -319,7 +319,7 @@ describe('#route-ratelimits & jwt-auth', () => { ) }) - it('should trigger rate-limit for free tier after 10 RPM', async () => { + it('should trigger rate-limit for free tier after 20 RPM', async () => { // Create a new instance of the rate limit so we start with zeroed tracking. rateLimits = new RateLimits() @@ -338,7 +338,7 @@ describe('#route-ratelimits & jwt-auth', () => { // Mock the call to the jwt library. sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo) - for (let i = 0; i < 12; i++) { + for (let i = 0; i < 22; i++) { next.reset() // reset the stubbed next() function. await rateLimits.rateLimitByResource(req, res, next) @@ -442,7 +442,7 @@ describe('#route-ratelimits & jwt-auth', () => { // Issues with token secret should treat incoming requests as anonymous // calls with 30 points or 3 RPM. - assert.equal(res.locals.pointsToConsume, 300) + assert.equal(res.locals.pointsToConsume, 50) }) }) })