Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

Commit

Permalink
Merge pull request #96 from nervosnetwork/restrict-access
Browse files Browse the repository at this point in the history
fix: restrict access for poly_executeRawL2Transaction
  • Loading branch information
RetricSu authored Dec 3, 2021
2 parents 355bc65 + 38460b4 commit de1e7b4
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 0 deletions.
91 changes: 91 additions & 0 deletions packages/api-server/src/cache/guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Store } from "./store";
import { HexString } from "@ckb-lumos/base";
import { envConfig } from "../base/env-config";

const RedisPrefixName = "access";
export const CACHE_EXPIRED_TIME_MILSECS = 1 * 60 * 1000; // milsec, default 1 minutes

export interface MaxRpmMap {
[reqRouter: string]: number;
}

export class AccessGuard {
public store: Store;
public maxRpmMap: MaxRpmMap;

constructor(
enableExpired = true,
expiredTimeMilsecs = CACHE_EXPIRED_TIME_MILSECS, // milsec, default 1 minutes
store?: Store
) {
this.store =
store || new Store(envConfig.redisUrl, enableExpired, expiredTimeMilsecs);
this.maxRpmMap = {};
}

isConnected() {
return this.store.client.isOpen;
}

async connect() {
if (!this.isConnected()) {
await this.store.client.connect();
}
}

async setMaxRpm(rpcRouter: string, maxRpm: number) {
this.maxRpmMap[rpcRouter] = maxRpm;
}

async getCount(rpcRouter: string, reqId: string) {
const id = getId(rpcRouter, reqId);
const count = await this.store.get(id);
if (count == null) {
return null;
}
return +count;
}

async add(rpcRouter: string, reqId: string): Promise<HexString | undefined> {
const isExist = await this.isExist(rpcRouter, reqId);
if (!isExist) {
const id = getId(rpcRouter, reqId);
await this.store.insert(id, 0);
return id;
}
}

async updateCount(rpcRouter: string, reqId: string) {
const preCount = await this.getCount(rpcRouter, reqId);
if (preCount != null) {
const afterCount = preCount + 1;
const id = getId(rpcRouter, reqId);
await this.store.insert(id, afterCount);
}
}

async isExist(rpcRouter: string, reqId: string) {
const id = getId(rpcRouter, reqId);
const data = await this.store.get(id);
if (data == null) return false;
return true;
}

async isOverRate(rpcRouter: string, reqId: string): Promise<boolean> {
const id = getId(rpcRouter, reqId);
const data = await this.store.get(id);
if (data == null) return false;
if (this.maxRpmMap[rpcRouter] == null) return false;

const count = +data;
const maxNumber = this.maxRpmMap[rpcRouter];
if (count > maxNumber) {
return true;
}
return false;
}
}

export function getId(rpcRouter: string, reqUniqueId: string): HexString {
return `${RedisPrefixName}.${rpcRouter}.${reqUniqueId}`;
}
29 changes: 29 additions & 0 deletions packages/api-server/src/methods/modules/poly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,26 @@ import {
} from "@polyjuice-provider/godwoken/lib/addressTypes";
import { GodwokenClient } from "@godwoken-web3/godwoken";
import { parseGwRpcError } from "../gw-error";
import { AccessGuard } from "../../cache/guard";

const MAX_RPM = {
poly_executeRawL2Transaction: 30, // max: 0.5 req/s = 30 req/m
};

export class Poly {
private query: Query;
private rpc: GodwokenClient;
private accessGuard: AccessGuard;

constructor() {
this.query = new Query();
this.rpc = new GodwokenClient(envConfig.godwokenJsonRpc);
this.accessGuard = new AccessGuard();
this.accessGuard.connect();
this.accessGuard.setMaxRpm(
"poly_executeRawL2Transaction",
MAX_RPM.poly_executeRawL2Transaction
);

this.getEthAddressByGodwokenShortAddress = middleware(
this.getEthAddressByGodwokenShortAddress.bind(this),
Expand Down Expand Up @@ -88,6 +100,23 @@ export class Poly {
const txWithAddressMapping: RawL2TransactionWithAddressMapping =
deserializeRawL2TransactionWithAddressMapping(data);
const rawL2Tx = txWithAddressMapping.raw_tx;

// access control
const rpcRouter = "poly_executeRawL2Transaction";
const reqId = rawL2Tx.from_id;
const isExist = await this.accessGuard.isExist(rpcRouter, reqId);
if (!isExist) {
await this.accessGuard.add(rpcRouter, reqId);
}
const isOverRate = await this.accessGuard.isOverRate(rpcRouter, reqId);
if (isOverRate) {
throw new Error(
"you are temporally restrict to the service, please wait."
);
}
await this.accessGuard.updateCount(rpcRouter, reqId);
// end of access control

const result = await this.rpc.executeRawL2Transaction(rawL2Tx);
// if result is fine, then tx is legal, we can start thinking to store the address mapping
await saveAddressMapping(this.query, this.rpc, txWithAddressMapping);
Expand Down

0 comments on commit de1e7b4

Please sign in to comment.