From 89b73437af79e63d2bd217bd3b3016c75efe5608 Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Mon, 14 Feb 2022 12:48:24 +0100 Subject: [PATCH 1/7] Faucet can only be used once a day per ip --- packages/faucet/src/api/webserver.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts index 4a000531..b1ed2fbb 100644 --- a/packages/faucet/src/api/webserver.ts +++ b/packages/faucet/src/api/webserver.ts @@ -14,8 +14,14 @@ export interface ChainConstants { readonly chainId: string; } +export interface IpEntry { + ip: string; + date: number; +} + export class Webserver { private readonly api = new Koa(); + private readonly ipCounter: IpEntry[] = []; public constructor(faucet: Faucet, chainConstants: ChainConstants) { this.api.use(cors()); @@ -47,6 +53,16 @@ export class Webserver { break; } case "/credit": { + const ipUsed = this.ipCounter.find((x) => x.ip === context.request.ip); + if (ipUsed !== undefined) { + if (ipUsed.date + 24 * 3600 > Date.now()) { + throw new HttpError( + 405, + "Too many request from the same IP. Blocked to prevent draining. Please wait 24h and try it again!", + ); + } + } + if (context.request.method !== "POST") { throw new HttpError(405, "This endpoint requires a POST request"); } @@ -73,6 +89,10 @@ export class Webserver { try { await faucet.credit(address, matchingDenom); + // Count IPs to prevent draining + if (context.request.ip) { + this.ipCounter.push({ ip: context.request.ip, date: Date.now() }); + } } catch (e) { console.error(e); throw new HttpError(500, "Sending tokens failed"); From f9bcecbe20960f08a679273ae819ea7cf6fdb3e6 Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Mon, 14 Feb 2022 12:54:59 +0100 Subject: [PATCH 2/7] Using address instead of ip to prevent draining --- packages/faucet/src/api/webserver.ts | 29 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts index b1ed2fbb..0fb54ef5 100644 --- a/packages/faucet/src/api/webserver.ts +++ b/packages/faucet/src/api/webserver.ts @@ -14,14 +14,14 @@ export interface ChainConstants { readonly chainId: string; } -export interface IpEntry { - ip: string; +export interface AddressEntry { + address: string; date: number; } export class Webserver { private readonly api = new Koa(); - private readonly ipCounter: IpEntry[] = []; + private readonly addressCounter: AddressEntry[] = []; public constructor(faucet: Faucet, chainConstants: ChainConstants) { this.api.use(cors()); @@ -53,16 +53,6 @@ export class Webserver { break; } case "/credit": { - const ipUsed = this.ipCounter.find((x) => x.ip === context.request.ip); - if (ipUsed !== undefined) { - if (ipUsed.date + 24 * 3600 > Date.now()) { - throw new HttpError( - 405, - "Too many request from the same IP. Blocked to prevent draining. Please wait 24h and try it again!", - ); - } - } - if (context.request.method !== "POST") { throw new HttpError(405, "This endpoint requires a POST request"); } @@ -74,13 +64,22 @@ export class Webserver { // context.request.body is set by the bodyParser() plugin const requestBody = context.request.body; const creditBody = RequestParser.parseCreditBody(requestBody); - const { address, denom } = creditBody; if (!isValidAddress(address, constants.addressPrefix)) { throw new HttpError(400, "Address is not in the expected format for this chain."); } + const addressUsed = this.addressCounter.find((x) => x.address === address); + if (addressUsed !== undefined) { + if (addressUsed.date + 24 * 3600 > Date.now()) { + throw new HttpError( + 405, + "Too many request from the same address. Blocked to prevent draining. Please wait 24h and try it again!", + ); + } + } + const availableTokens = await faucet.availableTokens(); const matchingDenom = availableTokens.find((availableDenom) => availableDenom === denom); if (matchingDenom === undefined) { @@ -91,7 +90,7 @@ export class Webserver { await faucet.credit(address, matchingDenom); // Count IPs to prevent draining if (context.request.ip) { - this.ipCounter.push({ ip: context.request.ip, date: Date.now() }); + this.addressCounter.push({ address: address, date: Date.now() }); } } catch (e) { console.error(e); From a525f5200d9785bcf9fdb3c49f9f85a9ac980088 Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Mon, 14 Feb 2022 12:56:00 +0100 Subject: [PATCH 3/7] Typo --- packages/faucet/src/api/webserver.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts index 0fb54ef5..6b7fe928 100644 --- a/packages/faucet/src/api/webserver.ts +++ b/packages/faucet/src/api/webserver.ts @@ -88,10 +88,8 @@ export class Webserver { try { await faucet.credit(address, matchingDenom); - // Count IPs to prevent draining - if (context.request.ip) { - this.addressCounter.push({ address: address, date: Date.now() }); - } + // Count addresses to prevent draining + this.addressCounter.push({ address: address, date: Date.now() }); } catch (e) { console.error(e); throw new HttpError(500, "Sending tokens failed"); From a9bc07d4be99f0b4d7ad3e135fbaa7c4d0c6f131 Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Wed, 16 Feb 2022 12:18:20 +0100 Subject: [PATCH 4/7] Using Map instead of an array --- packages/faucet/src/api/webserver.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts index 6b7fe928..86ef4750 100644 --- a/packages/faucet/src/api/webserver.ts +++ b/packages/faucet/src/api/webserver.ts @@ -14,14 +14,9 @@ export interface ChainConstants { readonly chainId: string; } -export interface AddressEntry { - address: string; - date: number; -} - export class Webserver { private readonly api = new Koa(); - private readonly addressCounter: AddressEntry[] = []; + private readonly addressCounter = new Map(); public constructor(faucet: Faucet, chainConstants: ChainConstants) { this.api.use(cors()); @@ -70,9 +65,9 @@ export class Webserver { throw new HttpError(400, "Address is not in the expected format for this chain."); } - const addressUsed = this.addressCounter.find((x) => x.address === address); - if (addressUsed !== undefined) { - if (addressUsed.date + 24 * 3600 > Date.now()) { + const entry: Date | undefined = this.addressCounter.get(address); + if (entry !== undefined) { + if (entry.getDate() + 24 * 3600 > Date.now()) { throw new HttpError( 405, "Too many request from the same address. Blocked to prevent draining. Please wait 24h and try it again!", @@ -80,7 +75,7 @@ export class Webserver { } } - const availableTokens = await faucet.availableTokens(); + const availableTokens: string[] = await faucet.availableTokens(); const matchingDenom = availableTokens.find((availableDenom) => availableDenom === denom); if (matchingDenom === undefined) { throw new HttpError(422, `Token is not available. Available tokens are: ${availableTokens}`); @@ -89,7 +84,7 @@ export class Webserver { try { await faucet.credit(address, matchingDenom); // Count addresses to prevent draining - this.addressCounter.push({ address: address, date: Date.now() }); + this.addressCounter.set(address, new Date()); } catch (e) { console.error(e); throw new HttpError(500, "Sending tokens failed"); From 5f0678764b77a5d1de32240c10bc661be42cee78 Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Wed, 16 Feb 2022 15:08:24 +0100 Subject: [PATCH 5/7] Date doesn't have to be type explicit --- packages/faucet/src/api/webserver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts index 86ef4750..f85b3d48 100644 --- a/packages/faucet/src/api/webserver.ts +++ b/packages/faucet/src/api/webserver.ts @@ -65,7 +65,7 @@ export class Webserver { throw new HttpError(400, "Address is not in the expected format for this chain."); } - const entry: Date | undefined = this.addressCounter.get(address); + const entry = this.addressCounter.get(address); if (entry !== undefined) { if (entry.getDate() + 24 * 3600 > Date.now()) { throw new HttpError( @@ -75,7 +75,7 @@ export class Webserver { } } - const availableTokens: string[] = await faucet.availableTokens(); + const availableTokens = await faucet.availableTokens(); const matchingDenom = availableTokens.find((availableDenom) => availableDenom === denom); if (matchingDenom === undefined) { throw new HttpError(422, `Token is not available. Available tokens are: ${availableTokens}`); From be22507caf86270d07a8ddb5dd18f1b71a35262e Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Wed, 16 Feb 2022 15:18:18 +0100 Subject: [PATCH 6/7] getTime() instead of getDate() --- packages/faucet/src/api/webserver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts index f85b3d48..ae17951f 100644 --- a/packages/faucet/src/api/webserver.ts +++ b/packages/faucet/src/api/webserver.ts @@ -67,7 +67,7 @@ export class Webserver { const entry = this.addressCounter.get(address); if (entry !== undefined) { - if (entry.getDate() + 24 * 3600 > Date.now()) { + if (entry.getTime() + 24 * 3600 > Date.now()) { throw new HttpError( 405, "Too many request from the same address. Blocked to prevent draining. Please wait 24h and try it again!", From 1b4958f5cac1d0188ad09e94e075f4cb352ac0b9 Mon Sep 17 00:00:00 2001 From: Milan Steiner Date: Wed, 16 Feb 2022 15:20:46 +0100 Subject: [PATCH 7/7] Added to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f50346f6..15b6551e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,7 +84,10 @@ and this project adheres to @cosmjs/launchpad. They are re-exported in @cosmjs/launchpad for backwards compatibility. - @cosmjs/stargate: Add `GasPrice.toString`. +- @cosmjs/faucet: Added a new functionality to faucet: Each address is only + allowed to get credits once every 24h to prevent draining. ([#962])) +[#962]: https://github.com/cosmos/cosmjs/issues/962 [#938]: https://github.com/cosmos/cosmjs/issues/938 [#932]: https://github.com/cosmos/cosmjs/issues/932 [#878]: https://github.com/cosmos/cosmjs/issues/878