mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
Harden API process under production load
This commit is contained in:
@@ -19,6 +19,17 @@ API_PORT=3334
|
|||||||
# Keep API_BIND_HOST as 127.0.0.1 when a reverse proxy terminates public traffic.
|
# Keep API_BIND_HOST as 127.0.0.1 when a reverse proxy terminates public traffic.
|
||||||
API_BIND_HOST=127.0.0.1
|
API_BIND_HOST=127.0.0.1
|
||||||
API_PUBLIC_PORT=3334
|
API_PUBLIC_PORT=3334
|
||||||
|
API_MAX_CONNECTIONS=512
|
||||||
|
API_REQUEST_TIMEOUT_MS=15000
|
||||||
|
API_HEADERS_TIMEOUT_MS=10000
|
||||||
|
API_KEEP_ALIVE_TIMEOUT_MS=5000
|
||||||
|
API_SOCKET_TIMEOUT_MS=15000
|
||||||
|
API_TLS_HANDSHAKE_TIMEOUT_MS=3000
|
||||||
|
API_LISTEN_BACKLOG=1024
|
||||||
|
API_NODE_ARGS=--max-old-space-size=512
|
||||||
|
API_MAX_MEMORY_RESTART=768M
|
||||||
|
API_KILL_TIMEOUT_MS=5000
|
||||||
|
API_RESTART_DELAY_MS=2000
|
||||||
|
|
||||||
# Plain TCP Stratum ports accept both SV1 JSON-RPC and SV2 Noise/binary traffic.
|
# Plain TCP Stratum ports accept both SV1 JSON-RPC and SV2 Noise/binary traffic.
|
||||||
STRATUM_PORTS=3333,3332,3331,3330
|
STRATUM_PORTS=3333,3332,3331,3330
|
||||||
|
|||||||
@@ -50,6 +50,17 @@ services:
|
|||||||
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
||||||
API_PORT: ${API_PORT:-3334}
|
API_PORT: ${API_PORT:-3334}
|
||||||
API_SECURE: ${API_SECURE:-false}
|
API_SECURE: ${API_SECURE:-false}
|
||||||
|
API_MAX_CONNECTIONS: ${API_MAX_CONNECTIONS:-512}
|
||||||
|
API_REQUEST_TIMEOUT_MS: ${API_REQUEST_TIMEOUT_MS:-15000}
|
||||||
|
API_HEADERS_TIMEOUT_MS: ${API_HEADERS_TIMEOUT_MS:-10000}
|
||||||
|
API_KEEP_ALIVE_TIMEOUT_MS: ${API_KEEP_ALIVE_TIMEOUT_MS:-5000}
|
||||||
|
API_SOCKET_TIMEOUT_MS: ${API_SOCKET_TIMEOUT_MS:-15000}
|
||||||
|
API_TLS_HANDSHAKE_TIMEOUT_MS: ${API_TLS_HANDSHAKE_TIMEOUT_MS:-3000}
|
||||||
|
API_LISTEN_BACKLOG: ${API_LISTEN_BACKLOG:-1024}
|
||||||
|
API_NODE_ARGS: ${API_NODE_ARGS:---max-old-space-size=512}
|
||||||
|
API_MAX_MEMORY_RESTART: ${API_MAX_MEMORY_RESTART:-768M}
|
||||||
|
API_KILL_TIMEOUT_MS: ${API_KILL_TIMEOUT_MS:-5000}
|
||||||
|
API_RESTART_DELAY_MS: ${API_RESTART_DELAY_MS:-2000}
|
||||||
PM2_ENABLED: ${PM2_ENABLED:-true}
|
PM2_ENABLED: ${PM2_ENABLED:-true}
|
||||||
STRATUM_WORKERS: ${STRATUM_WORKERS:-2}
|
STRATUM_WORKERS: ${STRATUM_WORKERS:-2}
|
||||||
STRATUM_PORTS: ${STRATUM_PORTS:-3333,3332,3331,3330}
|
STRATUM_PORTS: ${STRATUM_PORTS:-3333,3332,3331,3330}
|
||||||
|
|||||||
@@ -79,6 +79,17 @@ services:
|
|||||||
REDIS_URL: redis://redis:6379
|
REDIS_URL: redis://redis:6379
|
||||||
API_PORT: ${API_PORT:-3334}
|
API_PORT: ${API_PORT:-3334}
|
||||||
API_SECURE: ${API_SECURE:-false}
|
API_SECURE: ${API_SECURE:-false}
|
||||||
|
API_MAX_CONNECTIONS: ${API_MAX_CONNECTIONS:-512}
|
||||||
|
API_REQUEST_TIMEOUT_MS: ${API_REQUEST_TIMEOUT_MS:-15000}
|
||||||
|
API_HEADERS_TIMEOUT_MS: ${API_HEADERS_TIMEOUT_MS:-10000}
|
||||||
|
API_KEEP_ALIVE_TIMEOUT_MS: ${API_KEEP_ALIVE_TIMEOUT_MS:-5000}
|
||||||
|
API_SOCKET_TIMEOUT_MS: ${API_SOCKET_TIMEOUT_MS:-15000}
|
||||||
|
API_TLS_HANDSHAKE_TIMEOUT_MS: ${API_TLS_HANDSHAKE_TIMEOUT_MS:-3000}
|
||||||
|
API_LISTEN_BACKLOG: ${API_LISTEN_BACKLOG:-1024}
|
||||||
|
API_NODE_ARGS: ${API_NODE_ARGS:---max-old-space-size=512}
|
||||||
|
API_MAX_MEMORY_RESTART: ${API_MAX_MEMORY_RESTART:-768M}
|
||||||
|
API_KILL_TIMEOUT_MS: ${API_KILL_TIMEOUT_MS:-5000}
|
||||||
|
API_RESTART_DELAY_MS: ${API_RESTART_DELAY_MS:-2000}
|
||||||
PM2_ENABLED: ${PM2_ENABLED:-true}
|
PM2_ENABLED: ${PM2_ENABLED:-true}
|
||||||
STRATUM_WORKERS: ${STRATUM_WORKERS:-2}
|
STRATUM_WORKERS: ${STRATUM_WORKERS:-2}
|
||||||
STRATUM_PORTS: ${STRATUM_PORTS:-3333,3332,3331,3330}
|
STRATUM_PORTS: ${STRATUM_PORTS:-3333,3332,3331,3330}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
const apiNodeArgs = (process.env.API_NODE_ARGS || '--max-old-space-size=512')
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(arg => arg.length > 0);
|
||||||
|
const apiMaxMemoryRestart = process.env.API_MAX_MEMORY_RESTART || '768M';
|
||||||
|
const apiKillTimeout = parseInt(process.env.API_KILL_TIMEOUT_MS || '5000', 10);
|
||||||
|
const apiRestartDelay = parseInt(process.env.API_RESTART_DELAY_MS || '2000', 10);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
apps: [
|
apps: [
|
||||||
// API instance
|
// API instance
|
||||||
@@ -6,6 +13,14 @@ module.exports = {
|
|||||||
script: './dist/main.js',
|
script: './dist/main.js',
|
||||||
instances: 1,
|
instances: 1,
|
||||||
exec_mode: 'fork',
|
exec_mode: 'fork',
|
||||||
|
node_args: apiNodeArgs,
|
||||||
|
max_memory_restart: apiMaxMemoryRestart,
|
||||||
|
kill_timeout: apiKillTimeout,
|
||||||
|
restart_delay: apiRestartDelay,
|
||||||
|
min_uptime: '10s',
|
||||||
|
max_restarts: 10,
|
||||||
|
pmx: false,
|
||||||
|
vizion: false,
|
||||||
env: {
|
env: {
|
||||||
MASTER: 'false',
|
MASTER: 'false',
|
||||||
API_ONLY: 'true',
|
API_ONLY: 'true',
|
||||||
|
|||||||
@@ -97,6 +97,19 @@ services:
|
|||||||
DB_PASSWORD: public_pool
|
DB_PASSWORD: public_pool
|
||||||
DB_DATABASE: public_pool_mainnet
|
DB_DATABASE: public_pool_mainnet
|
||||||
REDIS_URL: redis://redis:6379
|
REDIS_URL: redis://redis:6379
|
||||||
|
API_PORT: ${API_PORT:-3334}
|
||||||
|
API_SECURE: ${API_SECURE:-false}
|
||||||
|
API_MAX_CONNECTIONS: ${API_MAX_CONNECTIONS:-512}
|
||||||
|
API_REQUEST_TIMEOUT_MS: ${API_REQUEST_TIMEOUT_MS:-15000}
|
||||||
|
API_HEADERS_TIMEOUT_MS: ${API_HEADERS_TIMEOUT_MS:-10000}
|
||||||
|
API_KEEP_ALIVE_TIMEOUT_MS: ${API_KEEP_ALIVE_TIMEOUT_MS:-5000}
|
||||||
|
API_SOCKET_TIMEOUT_MS: ${API_SOCKET_TIMEOUT_MS:-15000}
|
||||||
|
API_TLS_HANDSHAKE_TIMEOUT_MS: ${API_TLS_HANDSHAKE_TIMEOUT_MS:-3000}
|
||||||
|
API_LISTEN_BACKLOG: ${API_LISTEN_BACKLOG:-1024}
|
||||||
|
API_NODE_ARGS: ${API_NODE_ARGS:---max-old-space-size=512}
|
||||||
|
API_MAX_MEMORY_RESTART: ${API_MAX_MEMORY_RESTART:-768M}
|
||||||
|
API_KILL_TIMEOUT_MS: ${API_KILL_TIMEOUT_MS:-5000}
|
||||||
|
API_RESTART_DELAY_MS: ${API_RESTART_DELAY_MS:-2000}
|
||||||
PM2_ENABLED: "true"
|
PM2_ENABLED: "true"
|
||||||
STRATUM_WORKERS: ${STRATUM_WORKERS:-2}
|
STRATUM_WORKERS: ${STRATUM_WORKERS:-2}
|
||||||
|
|
||||||
|
|||||||
+44
-6
@@ -9,6 +9,14 @@ import * as ecc from 'tiny-secp256k1';
|
|||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
const DEFAULT_API_MAX_CONNECTIONS = 512;
|
||||||
|
const DEFAULT_API_REQUEST_TIMEOUT_MS = 15000;
|
||||||
|
const DEFAULT_API_HEADERS_TIMEOUT_MS = 10000;
|
||||||
|
const DEFAULT_API_KEEP_ALIVE_TIMEOUT_MS = 5000;
|
||||||
|
const DEFAULT_API_SOCKET_TIMEOUT_MS = 15000;
|
||||||
|
const DEFAULT_API_TLS_HANDSHAKE_TIMEOUT_MS = 3000;
|
||||||
|
const DEFAULT_API_LISTEN_BACKLOG = 1024;
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
if (process.env.API_PORT == null) {
|
if (process.env.API_PORT == null) {
|
||||||
console.error('It appears your environment is not configured, create and populate an .env file.');
|
console.error('It appears your environment is not configured, create and populate an .env file.');
|
||||||
@@ -28,6 +36,7 @@ async function bootstrap() {
|
|||||||
https: {
|
https: {
|
||||||
key: readFileSync(keyPath),
|
key: readFileSync(keyPath),
|
||||||
cert: readFileSync(certPath),
|
cert: readFileSync(certPath),
|
||||||
|
handshakeTimeout: getPositiveIntegerEnv('API_TLS_HANDSHAKE_TIMEOUT_MS', DEFAULT_API_TLS_HANDSHAKE_TIMEOUT_MS),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -65,13 +74,20 @@ async function bootstrap() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await app.listen(process.env.API_PORT, '0.0.0.0', (err, address) => {
|
configureApiServer(app.getHttpServer());
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
try {
|
||||||
process.exit(1);
|
const address = await app.listen({
|
||||||
}
|
port: parseInt(process.env.API_PORT, 10),
|
||||||
|
host: '0.0.0.0',
|
||||||
|
backlog: getPositiveIntegerEnv('API_LISTEN_BACKLOG', DEFAULT_API_LISTEN_BACKLOG),
|
||||||
|
});
|
||||||
console.log(`API listening on ${address}`);
|
console.log(`API listening on ${address}`);
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('API listen failed:', error);
|
||||||
|
await app.close().catch(() => undefined);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Live-reload TLS certs/keys when they change on disk ---
|
// --- Live-reload TLS certs/keys when they change on disk ---
|
||||||
if (secure) {
|
if (secure) {
|
||||||
@@ -111,4 +127,26 @@ async function bootstrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configureApiServer(server: any) {
|
||||||
|
const socketTimeoutMs = getPositiveIntegerEnv('API_SOCKET_TIMEOUT_MS', DEFAULT_API_SOCKET_TIMEOUT_MS);
|
||||||
|
|
||||||
|
server.maxConnections = getPositiveIntegerEnv('API_MAX_CONNECTIONS', DEFAULT_API_MAX_CONNECTIONS);
|
||||||
|
server.requestTimeout = getPositiveIntegerEnv('API_REQUEST_TIMEOUT_MS', DEFAULT_API_REQUEST_TIMEOUT_MS);
|
||||||
|
server.headersTimeout = getPositiveIntegerEnv('API_HEADERS_TIMEOUT_MS', DEFAULT_API_HEADERS_TIMEOUT_MS);
|
||||||
|
server.keepAliveTimeout = getPositiveIntegerEnv('API_KEEP_ALIVE_TIMEOUT_MS', DEFAULT_API_KEEP_ALIVE_TIMEOUT_MS);
|
||||||
|
server.timeout = socketTimeoutMs;
|
||||||
|
|
||||||
|
server.on('connection', (socket: NodeJS.ReadWriteStream & { setTimeout?: (ms: number) => void; destroy?: () => void }) => {
|
||||||
|
socket.setTimeout?.(socketTimeoutMs);
|
||||||
|
socket.once?.('timeout', () => {
|
||||||
|
socket.destroy?.();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPositiveIntegerEnv(name: string, fallback: number) {
|
||||||
|
const value = Number(process.env[name]);
|
||||||
|
return Number.isInteger(value) && value > 0 ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user