Harden accounting and stratum production settings

This commit is contained in:
Ben
2026-06-08 13:27:24 -04:00
parent deaf3e56bd
commit 8252111c32
27 changed files with 414 additions and 183 deletions
-9
View File
@@ -4,7 +4,6 @@ import { createClient, RedisClientType } from 'redis';
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
import { IMiningInfo } from '../models/bitcoin-rpc/IMiningInfo';
import { logTiming, timingStart } from '../utils/timing.utils';
const MINING_INFO_CHANNEL = 'mining-info.updated';
const MINING_INFO_KEY = 'mining-info:latest';
@@ -264,10 +263,8 @@ export class RedisMessagingService implements OnModuleInit, OnModuleDestroy {
}
private async getPresenceFromSet(setKey: string): Promise<ClientPresence[]> {
const start = timingStart();
const clientIds = await this.publisher.sMembers(setKey);
if (clientIds.length === 0) {
logTiming('redis presence set load', start, { setKey, clientIds: 0, presences: 0, staleClientIds: 0 });
return [];
}
@@ -297,12 +294,6 @@ export class RedisMessagingService implements OnModuleInit, OnModuleDestroy {
}
}
logTiming('redis presence set load', start, {
setKey,
clientIds: clientIds.length,
presences: presences.length,
staleClientIds: staleClientIds.length,
});
return presences;
}
+28
View File
@@ -8,6 +8,8 @@ describe('StratumV1Service', () => {
const originalBackpressureEnabled = process.env.STRATUM_BACKPRESSURE_ENABLED;
const originalMaxConnectionsPerListener = process.env.STRATUM_MAX_CONNECTIONS_PER_LISTENER;
const originalTlsHandshakeTimeoutMs = process.env.STRATUM_TLS_HANDSHAKE_TIMEOUT_MS;
const originalSocketTimeoutMs = process.env.STRATUM_SOCKET_TIMEOUT_MS;
const originalTcpKeepAliveInitialDelayMs = process.env.STRATUM_TCP_KEEPALIVE_INITIAL_DELAY_MS;
let service: StratumV1Service;
let clientService;
@@ -57,6 +59,8 @@ describe('StratumV1Service', () => {
restoreEnv('STRATUM_BACKPRESSURE_ENABLED', originalBackpressureEnabled);
restoreEnv('STRATUM_MAX_CONNECTIONS_PER_LISTENER', originalMaxConnectionsPerListener);
restoreEnv('STRATUM_TLS_HANDSHAKE_TIMEOUT_MS', originalTlsHandshakeTimeoutMs);
restoreEnv('STRATUM_SOCKET_TIMEOUT_MS', originalSocketTimeoutMs);
restoreEnv('STRATUM_TCP_KEEPALIVE_INITIAL_DELAY_MS', originalTcpKeepAliveInitialDelayMs);
consoleLogSpy.mockRestore();
consoleWarnSpy.mockRestore();
jest.useRealTimers();
@@ -164,6 +168,30 @@ describe('StratumV1Service', () => {
expect((service as any).getTlsHandshakeTimeoutMs()).toBe(5000);
});
it('should keep quiet miners connected for one hour by default', () => {
delete process.env.STRATUM_SOCKET_TIMEOUT_MS;
expect((service as any).getSocketTimeoutMs()).toBe(1000 * 60 * 60);
});
it('should allow configuring Stratum socket idle timeout', () => {
process.env.STRATUM_SOCKET_TIMEOUT_MS = '7200000';
expect((service as any).getSocketTimeoutMs()).toBe(7200000);
});
it('should enable TCP keepalive quickly by default', () => {
delete process.env.STRATUM_TCP_KEEPALIVE_INITIAL_DELAY_MS;
expect((service as any).getTcpKeepAliveInitialDelayMs()).toBe(60000);
});
it('should allow configuring TCP keepalive initial delay', () => {
process.env.STRATUM_TCP_KEEPALIVE_INITIAL_DELAY_MS = '30000';
expect((service as any).getTcpKeepAliveInitialDelayMs()).toBe(30000);
});
it('should detect JSON-RPC as Stratum V1', () => {
const firstChunk = Buffer.from('{"id":1,"method":"mining.subscribe","params":[]}\n');
+14 -4
View File
@@ -35,6 +35,8 @@ const DEFAULT_BACKPRESSURE_RESUME_RSS_MB = 2000;
const DEFAULT_BACKPRESSURE_HEALTHY_CHECKS = 3;
const DEFAULT_MAX_CONNECTIONS_PER_LISTENER = 10000;
const DEFAULT_TLS_HANDSHAKE_TIMEOUT_MS = 10000;
const DEFAULT_SOCKET_TIMEOUT_MS = 1000 * 60 * 60;
const DEFAULT_TCP_KEEPALIVE_INITIAL_DELAY_MS = 1000 * 60;
@@ -118,8 +120,8 @@ export class StratumV1Service implements OnModuleInit {
private createSocketServer(): Server {
const server = new Server(async (socket: Socket) => {
// Set 15-minute timeout
socket.setTimeout(1000 * 60 * 15);
socket.setTimeout(this.getSocketTimeoutMs());
socket.setKeepAlive(true, this.getTcpKeepAliveInitialDelayMs());
let client: StratumV1Client | StratumV2Client = null;
let protocol: 'v1' | 'v2' | null = null;
@@ -235,8 +237,8 @@ export class StratumV1Service implements OnModuleInit {
};
const server = createServer(tlsOptions, async (socket: TLSSocket) => {
// Set 15-minute timeout
socket.setTimeout(1000 * 60 * 15);
socket.setTimeout(this.getSocketTimeoutMs());
socket.setKeepAlive(true, this.getTcpKeepAliveInitialDelayMs());
const client = this.createV1Client(socket);
@@ -421,6 +423,14 @@ export class StratumV1Service implements OnModuleInit {
return this.getPositiveIntegerEnv('STRATUM_TLS_HANDSHAKE_TIMEOUT_MS', DEFAULT_TLS_HANDSHAKE_TIMEOUT_MS);
}
private getSocketTimeoutMs() {
return this.getPositiveIntegerEnv('STRATUM_SOCKET_TIMEOUT_MS', DEFAULT_SOCKET_TIMEOUT_MS);
}
private getTcpKeepAliveInitialDelayMs() {
return this.getPositiveIntegerEnv('STRATUM_TCP_KEEPALIVE_INITIAL_DELAY_MS', DEFAULT_TCP_KEEPALIVE_INITIAL_DELAY_MS);
}
private detectProtocol(firstChunk: Buffer): 'v1' | 'v2' | null {
if (firstChunk.length === 0) {
return null;
+20 -2
View File
@@ -22,6 +22,9 @@ import { NotificationService } from './notification.service';
import { RedisMessagingService } from './redis-messaging.service';
import { StratumV1JobsService } from './stratum-v1-jobs.service';
const DEFAULT_SOCKET_TIMEOUT_MS = 1000 * 60 * 60;
const DEFAULT_TCP_KEEPALIVE_INITIAL_DELAY_MS = 1000 * 60;
@Injectable()
export class StratumV2Service implements OnModuleInit {
private readonly servers: Server[] = [];
@@ -171,6 +174,7 @@ export class StratumV2Service implements OnModuleInit {
private startSocketServer(port: number): void {
const server = new Server((socket: Socket) => {
socket.setTimeout(this.getSocketTimeoutMs());
socket.setKeepAlive(true, this.getTcpKeepAliveInitialDelayMs());
socket.setNoDelay(true);
let client: StratumV2Client = null;
@@ -213,7 +217,21 @@ export class StratumV2Service implements OnModuleInit {
}
private getSocketTimeoutMs(): number {
const configured = parseInt(this.configService.get<string>('STRATUM_V2_SOCKET_TIMEOUT_MS') ?? '', 10);
return Number.isFinite(configured) && configured > 0 ? configured : 1000 * 60 * 15;
const configured = parseInt(
this.configService.get<string>('STRATUM_V2_SOCKET_TIMEOUT_MS')
?? this.configService.get<string>('STRATUM_SOCKET_TIMEOUT_MS')
?? '',
10,
);
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_SOCKET_TIMEOUT_MS;
}
private getTcpKeepAliveInitialDelayMs(): number {
const configured = parseInt(
this.configService.get<string>('STRATUM_TCP_KEEPALIVE_INITIAL_DELAY_MS')
?? '',
10,
);
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_TCP_KEEPALIVE_INITIAL_DELAY_MS;
}
}