Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion __tests__/integration/core-p2p/socket-server/peer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Managers.configManager.setFromPreset("unitnet");

let server: SocketCluster;
let socket;
let connect;
let emit;
let send;

const headers = {
version: "2.1.0",
Expand All @@ -32,7 +34,7 @@ beforeAll(async () => {

const { service, processor } = createPeerService();

server = await startSocketServer(service, { server: { port: 4007 } });
server = await startSocketServer(service, { server: { port: 4007, workers: 1 } });
await delay(1000);

socket = socketCluster.create({
Expand All @@ -43,11 +45,15 @@ beforeAll(async () => {
//
});

connect = () => socket.connect();

emit = (event, data) =>
new Promise((resolve, reject) => {
socket.emit(event, data, (err, val) => (err ? reject(err) : resolve(val)));
});

send = data => socket.send(data);

jest.spyOn(processor, "validateAndAcceptPeer").mockImplementation(jest.fn());
});

Expand Down Expand Up @@ -127,6 +133,41 @@ describe("Peer socket endpoint", () => {
}),
).toResolve();
});

it("should disconnect the client if it sends an invalid message payload", async () => {
await delay(1000);

expect(socket.state).toBe("open");

send('{"event": "#handshake", "data": {}, "cid": 1}');
await delay(500);

send("Invalid payload");
await delay(1000);

expect(socket.state).toBe("closed");
});

it("should disconnect the client if it sends too many pongs too quickly", async () => {
connect();
await delay(1000);

expect(socket.state).toBe("open");

send('{"event": "#handshake", "data": {}, "cid": 1}');
await delay(500);

send("#2");
await delay(1000);

expect(socket.state).toBe("open");

send("#2");
send("#2");
await delay(1000);

expect(socket.state).toBe("closed");
});
});
});

Expand Down Expand Up @@ -229,5 +270,30 @@ describe("Peer socket endpoint", () => {
}),
).rejects.toHaveProperty("name", "BadConnectionError");
});

it("should close the connection and prevent reconnection if blocked", async () => {
await delay(1000);

await emit("p2p.peer.getPeers", {
headers,
});

expect(socket.state).toBe("open");

for (let i = 0; i < 100; i++) {
await expect(
emit("p2p.peer.getPeers", {
headers,
}),
).rejects.toContainAnyEntries([["name", "CoreRateLimitExceededError"], ["name", "BadConnectionError"]]);
}

expect(socket.state).not.toBe("open");

socket.connect();
await delay(1000);

expect(socket.state).not.toBe("open");
});
});
});
72 changes: 72 additions & 0 deletions __tests__/unit/core-webhooks/conditions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ describe("Conditions - between", () => {
max: 2,
}),
).toBeTrue();
expect(
between("1.5", {
min: "1",
max: "2",
}),
).toBeTrue();
});

it("should be false", () => {
Expand All @@ -31,6 +37,12 @@ describe("Conditions - between", () => {
max: 2,
}),
).toBeFalse();
expect(
between("3", {
min: "1",
max: "2",
}),
).toBeFalse();
});
});

Expand All @@ -47,72 +59,116 @@ describe("Conditions - contains", () => {
describe("Conditions - equal", () => {
it("should be true", () => {
expect(eq(1, 1)).toBeTrue();
expect(eq("1", "1")).toBeTrue();
});

it("should be false", () => {
expect(eq(1, 2)).toBeFalse();
expect(eq("1", "2")).toBeFalse();
});
});

describe("Conditions - falsy", () => {
it("should be true", () => {
expect(falsy(false)).toBeTrue();
expect(falsy("false")).toBeTrue();
expect(falsy("FaLsE")).toBeTrue();
});

it("should be false", () => {
expect(falsy(true)).toBeFalse();
expect(falsy("true")).toBeFalse();
expect(falsy("TrUe")).toBeFalse();
});
});

describe("Conditions - greater than", () => {
it("should be true", () => {
expect(gt(2, 1)).toBeTrue();
expect(gt("2", "1")).toBeTrue();
expect(gt("10", "2")).toBeTrue();
});

it("should be false", () => {
expect(gt(1, 2)).toBeFalse();
expect(gt("1", "2")).toBeFalse();
expect(gt("2", "10")).toBeFalse();
expect(gt(undefined, NaN)).toBeFalse();
expect(gt(1, NaN)).toBeFalse();
expect(gt(undefined, 1)).toBeFalse();
expect(gt("null", "NaN")).toBeFalse();
expect(gt("1", "NaN")).toBeFalse();
expect(gt("null", "1")).toBeFalse();
});
});

describe("Conditions - greater than or equal", () => {
it("should be true", () => {
expect(gte(2, 1)).toBeTrue();
expect(gte(2, 2)).toBeTrue();
expect(gte("2", "1")).toBeTrue();
expect(gte("2", "2")).toBeTrue();
});

it("should be false", () => {
expect(gte(1, 2)).toBeFalse();
expect(gte("1", "2")).toBeFalse();
expect(gt(undefined, NaN)).toBeFalse();
expect(gt(1, NaN)).toBeFalse();
expect(gt(undefined, 1)).toBeFalse();
expect(gt("null", "NaN")).toBeFalse();
expect(gt("1", "NaN")).toBeFalse();
expect(gt("null", "1")).toBeFalse();
});
});

describe("Conditions - less than", () => {
it("should be true", () => {
expect(lt(1, 2)).toBeTrue();
expect(lt("1", "2")).toBeTrue();
});

it("should be false", () => {
expect(lt(2, 1)).toBeFalse();
expect(lt("2", "1")).toBeFalse();
expect(gt(undefined, NaN)).toBeFalse();
expect(gt(1, NaN)).toBeFalse();
expect(gt(undefined, 1)).toBeFalse();
expect(gt("null", "NaN")).toBeFalse();
expect(gt("1", "NaN")).toBeFalse();
expect(gt("null", "1")).toBeFalse();
});
});

describe("Conditions - less than or equal", () => {
it("should be true", () => {
expect(lte(1, 2)).toBeTrue();
expect(lte(1, 1)).toBeTrue();
expect(lte("1", "2")).toBeTrue();
expect(lte("1", "1")).toBeTrue();
});

it("should be false", () => {
expect(lte(2, 1)).toBeFalse();
expect(lte("2", "1")).toBeFalse();
expect(gt(undefined, NaN)).toBeFalse();
expect(gt(1, NaN)).toBeFalse();
expect(gt(undefined, 1)).toBeFalse();
expect(gt("null", "NaN")).toBeFalse();
expect(gt("1", "NaN")).toBeFalse();
expect(gt("null", "1")).toBeFalse();
});
});

describe("Conditions - not equal", () => {
it("should be true", () => {
expect(ne(1, 2)).toBeTrue();
expect(ne("1", "2")).toBeTrue();
});

it("should be false", () => {
expect(ne(1, 1)).toBeFalse();
expect(ne("1", "1")).toBeFalse();
});
});

Expand All @@ -124,6 +180,12 @@ describe("Conditions - not-between", () => {
max: 2,
}),
).toBeTrue();
expect(
notBetween("3", {
min: "1",
max: "2",
}),
).toBeTrue();
});

it("should be false", () => {
Expand All @@ -133,6 +195,12 @@ describe("Conditions - not-between", () => {
max: 2,
}),
).toBeFalse();
expect(
notBetween("1.5", {
min: "1",
max: "2",
}),
).toBeFalse();
});
});

Expand All @@ -149,9 +217,13 @@ describe("Conditions - regexp", () => {
describe("Conditions - truthy", () => {
it("should be true", () => {
expect(truthy(true)).toBeTrue();
expect(truthy("true")).toBeTrue();
expect(truthy("TrUe")).toBeTrue();
});

it("should be false", () => {
expect(truthy(false)).toBeFalse();
expect(truthy("false")).toBeFalse();
expect(truthy("FaLsE")).toBeFalse();
});
});
1 change: 1 addition & 0 deletions packages/core-p2p/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export enum SocketErrors {
Validation = "CoreValidationError",
RateLimitExceeded = "CoreRateLimitExceededError",
Forbidden = "CoreForbiddenError",
InvalidMessagePayload = "CoreInvalidMessagePayloadError",
}
1 change: 1 addition & 0 deletions packages/core-p2p/src/peer-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class PeerConnector implements P2P.IPeerConnector {
connection = create({
port: peer.port,
hostname: peer.ip,
perMessageDeflate: true,
});

this.connections.set(peer.ip, connection);
Expand Down
1 change: 1 addition & 0 deletions packages/core-p2p/src/socket-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const startSocketServer = async (service: P2P.IPeerService, config: Recor
workerController: __dirname + `${relativeSocketPath}/worker.js`,
workers: 2,
wsEngine: "ws",
perMessageDeflate: true,
},
...config.server,
});
Expand Down
34 changes: 33 additions & 1 deletion packages/core-p2p/src/socket-server/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export class Worker extends SCWorker {

await this.loadConfiguration();

// @ts-ignore
this.scServer.wsServer.on("connection", (ws, req) => this.handlePayload(ws, req));
this.scServer.on("connection", socket => this.handleConnection(socket));
this.scServer.addMiddleware(this.scServer.MIDDLEWARE_HANDSHAKE_WS, (req, next) =>
this.handleHandshake(req, next),
Expand Down Expand Up @@ -55,6 +57,36 @@ export class Worker extends SCWorker {
});
}

private handlePayload(ws, req) {
ws.on("message", message => {
try {
const InvalidMessagePayloadError: Error = this.createError(
SocketErrors.InvalidMessagePayload,
"The message contained an invalid payload",
);
if (message === "#2") {
const timeNow: number = new Date().getTime() / 1000;
if (ws._lastPingTime && timeNow - ws._lastPingTime < 1) {
throw InvalidMessagePayloadError;
}
ws._lastPingTime = timeNow;
} else {
const parsed = JSON.parse(message);
if (
typeof parsed.event !== "string" ||
typeof parsed.data !== "object" ||
(typeof parsed.cid !== "number" &&
(parsed.event === "#disconnect" && typeof parsed.cid !== "undefined"))
) {
throw InvalidMessagePayloadError;
}
}
} catch (error) {
ws.terminate();
}
});
}

private async handleConnection(socket): Promise<void> {
const { data } = await this.sendToMasterAsync("p2p.utils.getHandlers");

Expand Down Expand Up @@ -86,7 +118,7 @@ export class Worker extends SCWorker {
private async handleEmit(req, next): Promise<void> {
if (await this.rateLimiter.hasExceededRateLimit(req.socket.remoteAddress, req.event)) {
if (await this.rateLimiter.isBlocked(req.socket.remoteAddress)) {
req.socket.disconnect(4403, "Forbidden");
req.socket.terminate();
return;
}

Expand Down
22 changes: 13 additions & 9 deletions packages/core-webhooks/src/conditions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
export const between = (actual, expected): boolean => actual > expected.min && actual < expected.max;
import { Utils } from "@arkecosystem/crypto";

const toBoolean = (value): boolean => value.toString().toLowerCase().trim() === "true" ? true : false;

export const between = (actual, expected): boolean => gt(actual, expected.min) && lt(actual, expected.max);
export const contains = (actual, expected): boolean => actual.includes(expected);
export const eq = (actual, expected): boolean => actual === expected;
export const falsy = (actual): boolean => actual === false;
export const gt = (actual, expected): boolean => actual > expected;
export const gte = (actual, expected): boolean => actual >= expected;
export const lt = (actual, expected): boolean => actual < expected;
export const lte = (actual, expected): boolean => actual <= expected;
export const ne = (actual, expected): boolean => actual !== expected;
export const eq = (actual, expected): boolean => JSON.stringify(actual) === JSON.stringify(expected);
export const falsy = (actual): boolean => actual === false || !toBoolean(actual);
export const gt = (actual, expected): boolean => Utils.BigNumber.make(actual).gt(expected);
export const gte = (actual, expected): boolean => Utils.BigNumber.make(actual).gte(expected);
export const lt = (actual, expected): boolean => Utils.BigNumber.make(actual).lt(expected);
export const lte = (actual, expected): boolean => Utils.BigNumber.make(actual).lte(expected);
export const ne = (actual, expected): boolean => !eq(actual, expected);
export const notBetween = (actual, expected): boolean => !between(actual, expected);
export const regexp = (actual, expected): boolean => new RegExp(expected).test(actual);
export const truthy = (actual): boolean => actual === true;
export const truthy = (actual): boolean => actual === true || toBoolean(actual);
Loading