From 0d3be606a0ea4c44904f8fbc156cb64fdf6c961b Mon Sep 17 00:00:00 2001 From: pk910 Date: Fri, 20 Oct 2023 14:49:46 +0200 Subject: [PATCH] added `whitelist` module (skip some protections for whitelisted IPs) will be used to whitelist devconnect ip ranges during the event. otherwise the whole event with thousands of devs will be limited to 1 concurrent session --- src/modules/MODULE_HOOKS.md | 40 ++++++ .../ConcurrencyLimitModule.ts | 2 + src/modules/ethinfo/EthInfoModule.ts | 3 + src/modules/github/GithubModule.ts | 2 + src/modules/ipinfo/IPInfoModule.ts | 2 + .../mainnet-wallet/MainnetWalletModule.ts | 2 + src/modules/modules.ts | 2 + src/modules/passport/PassportModule.ts | 2 + src/modules/pow/PoWModule.ts | 3 + .../recurring-limits/RecurringLimitsModule.ts | 2 + src/modules/whitelist/WhitelistConfig.ts | 25 ++++ src/modules/whitelist/WhitelistModule.ts | 115 ++++++++++++++++++ src/session/FaucetSession.ts | 4 +- 13 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/modules/MODULE_HOOKS.md create mode 100644 src/modules/whitelist/WhitelistConfig.ts create mode 100644 src/modules/whitelist/WhitelistModule.ts diff --git a/src/modules/MODULE_HOOKS.md b/src/modules/MODULE_HOOKS.md new file mode 100644 index 000000000..d067232c2 --- /dev/null +++ b/src/modules/MODULE_HOOKS.md @@ -0,0 +1,40 @@ + +ClientConfig + prio 1: captcha, ensname, github, passport, pow + +SessionStart + prio 1: captcha, *maintenance_mode_check + prio 2: whitelist + prio 3: ensname + prio 5: *eth_address_check + prio 6: concurrency-limit, ethinfo, ipinfo, mainnet-wallet, passport, recurring-limits + prio 10: pow + +SessionRestore + prio 10: pow + +SessionInfo + prio 1: passport, pow + +SessionRewardFactor + prio 5: faucet-outflow, github, passport + prio 6: faucet-balance, ipinfo, whitelist + +SessionRewarded + prio 5: faucet-outflow + +SessionIpChange + prio 2: whitelist + prio 6: concurrency-limit, ipinfo + +SessionComplete + prio 5: github + prio 10: pow + +SessionClaim + prio 1: captcha + +SessionClaimed + +SessionClose + diff --git a/src/modules/concurrency-limit/ConcurrencyLimitModule.ts b/src/modules/concurrency-limit/ConcurrencyLimitModule.ts index 4bad28bdc..89f0bab96 100644 --- a/src/modules/concurrency-limit/ConcurrencyLimitModule.ts +++ b/src/modules/concurrency-limit/ConcurrencyLimitModule.ts @@ -29,6 +29,8 @@ export class ConcurrencyLimitModule extends BaseModule } private async processSessionStart(session: FaucetSession): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; this.checkLimit(session); } diff --git a/src/modules/ethinfo/EthInfoModule.ts b/src/modules/ethinfo/EthInfoModule.ts index 5cd4f13d3..1f58e760a 100644 --- a/src/modules/ethinfo/EthInfoModule.ts +++ b/src/modules/ethinfo/EthInfoModule.ts @@ -20,6 +20,9 @@ export class EthInfoModule extends BaseModule { } private async processSessionStart(session: FaucetSession, userInput: any): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; + let targetAddr = session.getTargetAddr(); let ethWalletManager = ServiceManager.GetService(EthWalletManager); diff --git a/src/modules/github/GithubModule.ts b/src/modules/github/GithubModule.ts index 70b4bcece..040ddb1a2 100644 --- a/src/modules/github/GithubModule.ts +++ b/src/modules/github/GithubModule.ts @@ -63,6 +63,8 @@ export class GithubModule extends BaseModule { } private async processSessionStart(session: FaucetSession, userInput: any): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; let infoOpts: IGithubInfoOpts = { loadOwnRepo: false, }; diff --git a/src/modules/ipinfo/IPInfoModule.ts b/src/modules/ipinfo/IPInfoModule.ts index 7e9af80f9..62b1d8513 100644 --- a/src/modules/ipinfo/IPInfoModule.ts +++ b/src/modules/ipinfo/IPInfoModule.ts @@ -58,6 +58,8 @@ export class IPInfoModule extends BaseModule { } private async processSessionStart(session: FaucetSession): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; let remoteIp = session.getRemoteIP(); let ipInfo: IIPInfo; try { diff --git a/src/modules/mainnet-wallet/MainnetWalletModule.ts b/src/modules/mainnet-wallet/MainnetWalletModule.ts index 5656044a8..7ee1618ae 100644 --- a/src/modules/mainnet-wallet/MainnetWalletModule.ts +++ b/src/modules/mainnet-wallet/MainnetWalletModule.ts @@ -27,6 +27,8 @@ export class MainnetWalletModule extends BaseModule { } private async processSessionStart(session: FaucetSession, userInput: any): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; let targetAddr = session.getTargetAddr(); if(this.moduleConfig.minBalance > 0) { diff --git a/src/modules/modules.ts b/src/modules/modules.ts index 0e5db057d..25863461c 100644 --- a/src/modules/modules.ts +++ b/src/modules/modules.ts @@ -10,6 +10,7 @@ import { MainnetWalletModule } from "./mainnet-wallet/MainnetWalletModule"; import { PassportModule } from "./passport/PassportModule"; import { PoWModule } from "./pow/PoWModule"; import { RecurringLimitsModule } from "./recurring-limits/RecurringLimitsModule"; +import { WhitelistModule } from "./whitelist/WhitelistModule"; export const MODULE_CLASSES = { "captcha": CaptchaModule, @@ -24,4 +25,5 @@ export const MODULE_CLASSES = { "passport": PassportModule, "pow": PoWModule, "recurring-limits": RecurringLimitsModule, + "whitelist": WhitelistModule, } diff --git a/src/modules/passport/PassportModule.ts b/src/modules/passport/PassportModule.ts index 589e426e1..a6b2cb8f4 100644 --- a/src/modules/passport/PassportModule.ts +++ b/src/modules/passport/PassportModule.ts @@ -69,6 +69,8 @@ export class PassportModule extends BaseModule { } private async processSessionStart(session: FaucetSession): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; let targetAddr = session.getTargetAddr(); let passportInfo = await this.passportResolver.getPassport(targetAddr); session.setSessionData("passport.refresh", Math.floor(new Date().getTime() / 1000)); diff --git a/src/modules/pow/PoWModule.ts b/src/modules/pow/PoWModule.ts index 65f12135d..918ecad4d 100644 --- a/src/modules/pow/PoWModule.ts +++ b/src/modules/pow/PoWModule.ts @@ -117,6 +117,9 @@ export class PoWModule extends BaseModule { } private async processSessionStart(session: FaucetSession): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; + session.addBlockingTask(this.moduleName, "mining", this.moduleConfig.powSessionTimeout); // this prevents the session from progressing to claimable before this module allows it session.setDropAmount(0n); diff --git a/src/modules/recurring-limits/RecurringLimitsModule.ts b/src/modules/recurring-limits/RecurringLimitsModule.ts index 47f4cce9c..f5b55d845 100644 --- a/src/modules/recurring-limits/RecurringLimitsModule.ts +++ b/src/modules/recurring-limits/RecurringLimitsModule.ts @@ -21,6 +21,8 @@ export class RecurringLimitsModule extends BaseModule { } private async processSessionStart(session: FaucetSession, userInput: any): Promise { + if(session.getSessionData>("skip.modules", []).indexOf(this.moduleName) !== -1) + return; await Promise.all(this.moduleConfig.limits.map((limit) => this.checkLimit(session, limit))); } diff --git a/src/modules/whitelist/WhitelistConfig.ts b/src/modules/whitelist/WhitelistConfig.ts new file mode 100644 index 000000000..5413874d2 --- /dev/null +++ b/src/modules/whitelist/WhitelistConfig.ts @@ -0,0 +1,25 @@ +import { IBaseModuleConfig } from "../BaseModule"; + +export interface IWhitelistConfig extends IBaseModuleConfig { + whitelistPattern: { // ip info pattern based restrictions + [pattern: string]: IWhitelistEntryConfig; // percentage of reward per share if IP info matches regex pattern + }; + whitelistFile: null | { // ip info pattern based restrictions from file + yaml: string|string[]; // path to yaml file (for more actions/kill messages/etc.) + refresh: number; // refresh interval + }; +} + +export interface IWhitelistEntryConfig { + reward: number; + skipModules?: string[]; + msgkey?: string; + message?: string; + notify?: boolean|string; +} + +export const defaultConfig: IWhitelistConfig = { + enabled: false, + whitelistPattern: {}, + whitelistFile: null, +} diff --git a/src/modules/whitelist/WhitelistModule.ts b/src/modules/whitelist/WhitelistModule.ts new file mode 100644 index 000000000..8161d58d8 --- /dev/null +++ b/src/modules/whitelist/WhitelistModule.ts @@ -0,0 +1,115 @@ +import * as fs from 'fs'; +import YAML from 'yaml' +import { ServiceManager } from "../../common/ServiceManager"; +import { FaucetSession } from "../../session/FaucetSession"; +import { BaseModule } from "../BaseModule"; +import { ModuleHookAction } from "../ModuleManager"; +import { FaucetError } from '../../common/FaucetError'; +import { defaultConfig, IWhitelistConfig, IWhitelistEntryConfig } from "./WhitelistConfig"; +import { resolveRelativePath } from "../../config/FaucetConfig"; +import { ISessionRewardFactor } from '../../session/SessionRewardFactor'; +import { FaucetLogLevel, FaucetProcess } from '../../common/FaucetProcess'; + +export class WhitelistModule extends BaseModule { + protected readonly moduleDefaultConfig = defaultConfig; + private cachedWhitelistEntries: [pattern: string, restriction: IWhitelistEntryConfig][]; + private cachedWhitelistRefresh: number; + + protected override async startModule(): Promise { + this.moduleManager.addActionHook( + this, ModuleHookAction.SessionStart, 2, "Whitelist check", + (session: FaucetSession) => this.processSessionStart(session) + ); + this.moduleManager.addActionHook( + this, ModuleHookAction.SessionIpChange, 2, "Whitelist check", + (session: FaucetSession) => this.processSessionStart(session) + ); + this.moduleManager.addActionHook( + this, ModuleHookAction.SessionRewardFactor, 6, "whitelist factor", + (session: FaucetSession, rewardFactors: ISessionRewardFactor[]) => this.processSessionRewardFactor(session, rewardFactors) + ); + } + + protected override stopModule(): Promise { + return Promise.resolve(); + } + + protected override onConfigReload(): void { + this.cachedWhitelistRefresh = 0; + } + + private async processSessionStart(session: FaucetSession): Promise { + let whitelistEntry = this.getSessionWhitelistEntry(session); + if(whitelistEntry) { + session.setSessionData("whitelist", true); + if(whitelistEntry.skipModules) + session.setSessionData("skip.modules", whitelistEntry.skipModules); + if(typeof whitelistEntry.reward === "number") + session.setSessionData("whitelist.factor", whitelistEntry.reward); + } + } + + private async processSessionRewardFactor(session: FaucetSession, rewardFactors: ISessionRewardFactor[]) { + let rewardPerc = session.getSessionData("whitelist.factor", 100); + if(rewardPerc !== 100) { + rewardFactors.push({ + factor: rewardPerc / 100, + module: this.moduleName, + }); + } + } + + public refreshCachedWhitelistEntries(force?: boolean) { + let now = Math.floor((new Date()).getTime() / 1000); + let refresh = this.moduleConfig.whitelistFile ? this.moduleConfig.whitelistFile.refresh : 30; + if(this.cachedWhitelistRefresh > now - refresh && !force) + return; + + this.cachedWhitelistRefresh = now; + this.cachedWhitelistEntries = []; + if(this.moduleConfig.whitelistPattern) { + Object.keys(this.moduleConfig.whitelistPattern).forEach((pattern) => { + let entry = this.moduleConfig.whitelistPattern[pattern]; + this.cachedWhitelistEntries.push([pattern, entry]); + }); + } + + if(this.moduleConfig.whitelistFile && this.moduleConfig.whitelistFile.yaml) { + // load yaml file + if(Array.isArray(this.moduleConfig.whitelistFile.yaml)) + this.moduleConfig.whitelistFile.yaml.forEach((file) => this.refreshCachedWhitelistEntriesFromYaml(resolveRelativePath(file))); + else + this.refreshCachedWhitelistEntriesFromYaml(resolveRelativePath(this.moduleConfig.whitelistFile.yaml)); + } + } + + private refreshCachedWhitelistEntriesFromYaml(yamlFile: string) { + if(!fs.existsSync(yamlFile)) + return; + + let yamlSrc = fs.readFileSync(yamlFile, "utf8"); + let yamlObj = YAML.parse(yamlSrc); + + if(Array.isArray(yamlObj.restrictions)) { + yamlObj.restrictions.forEach((entry) => { + let pattern = entry.pattern; + delete entry.pattern; + this.cachedWhitelistEntries.push([pattern, entry]); + }) + } + } + + private getSessionWhitelistEntry(session: FaucetSession): IWhitelistEntryConfig|null { + let remoteIp = session.getRemoteIP(); + this.refreshCachedWhitelistEntries(); + for(let i = 0; i < this.cachedWhitelistEntries.length; i++) { + let entry = this.cachedWhitelistEntries[i]; + if(remoteIp.match(new RegExp(entry[0], "mi"))) { + return entry[1]; + } + } + return null; + } + + +} diff --git a/src/session/FaucetSession.ts b/src/session/FaucetSession.ts index 1c07bac79..044cd84e1 100644 --- a/src/session/FaucetSession.ts +++ b/src/session/FaucetSession.ts @@ -300,8 +300,8 @@ export class FaucetSession { this.targetAddr = addr; } - public getSessionData(key: string): any { - return this.sessionDataDict[key]; + public getSessionData(key: string, defval?: T): T { + return key in this.sessionDataDict ? this.sessionDataDict[key] : defval; } public setSessionData(key: string, value: any) {