From 8b2bea702a746a59be12b3df416e59e89c5d11cc Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 29 Nov 2025 11:45:44 -0500 Subject: [PATCH] enable stratum TLS --- .env.example | 3 + src/services/stratum-v1.service.ts | 257 +++++++++++++++++++---------- 2 files changed, 172 insertions(+), 88 deletions(-) diff --git a/.env.example b/.env.example index c5b8633..d8a70c2 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,9 @@ BITCOIN_RPC_TIMEOUT=10000 API_PORT=3334 STRATUM_PORTS=3333,3332,3331,3330 +STRATUM_SECURE=true +SECURE_STRATUM_PORTS=4333,4332,4331,4330 + #optional telegram bot #TELEGRAM_BOT_TOKEN= diff --git a/src/services/stratum-v1.service.ts b/src/services/stratum-v1.service.ts index 883a43e..aec393b 100644 --- a/src/services/stratum-v1.service.ts +++ b/src/services/stratum-v1.service.ts @@ -11,6 +11,11 @@ import { BitcoinRpcService } from './bitcoin-rpc.service'; import { NotificationService } from './notification.service'; import { StratumV1JobsService } from './stratum-v1-jobs.service'; +import { readFileSync } from 'fs'; +import { TlsOptions, TLSSocket, createServer } from 'tls'; +import * as path from 'path'; + + @Injectable() export class StratumV1Service implements OnModuleInit { @@ -20,111 +25,187 @@ export class StratumV1Service implements OnModuleInit { private normalClosure = 0; private errorClosure = 0; - constructor( - private readonly bitcoinRpcService: BitcoinRpcService, - private readonly clientService: ClientService, - private readonly clientStatisticsService: ClientStatisticsService, - private readonly notificationService: NotificationService, - private readonly blocksService: BlocksService, - private readonly configService: ConfigService, - private readonly stratumV1JobsService: StratumV1JobsService, - private readonly addressSettingsService: AddressSettingsService - ) { + constructor( + private readonly bitcoinRpcService: BitcoinRpcService, + private readonly clientService: ClientService, + private readonly clientStatisticsService: ClientStatisticsService, + private readonly notificationService: NotificationService, + private readonly blocksService: BlocksService, + private readonly configService: ConfigService, + private readonly stratumV1JobsService: StratumV1JobsService, + private readonly addressSettingsService: AddressSettingsService + ) { - } - - async onModuleInit(): Promise { - - if (process.env.MASTER == 'true') { - await this.clientService.deleteAll(); } - // wait for all the other processes to init for an even connection distribution - setTimeout(() => { - process.env.STRATUM_PORTS.split(',').forEach(port =>{ - this.startSocketServer(parseInt(port)); - }); - }, (10000)); + async onModuleInit(): Promise { - setInterval(() => { - console.log(`Socket stats: ${this.emptySocket} empty, ${this.socketTimeout} timeouts, ${this.normalClosure} normal closure, ${this.errorClosure} error closure`); - this.emptySocket = 0; - this.socketTimeout = 0; - this.normalClosure = 0; - this.errorClosure = 0; - }, 1000 * 60); + if (process.env.MASTER == 'true') { + await this.clientService.deleteAll(); + } - } + // wait for all the other processes to init for an even connection distribution + setTimeout(() => { + process.env.STRATUM_PORTS.split(',').forEach(port => { + this.startSocketServer(parseInt(port)); + }); + if (process.env.STRATUM_SECURE?.toLowerCase() === 'true') { + process.env.SECURE_STRATUM_PORTS.split(',').forEach(port => { + this.startSecureSocketServer(parseInt(port)); + }); + } + }, (10000)); - private startSocketServer(port: number) { - const server = new Server(async (socket: Socket) => { - // Set 15-minute timeout - socket.setTimeout(1000 * 60 * 15); + setInterval(() => { + console.log(`Socket stats: ${this.emptySocket} empty, ${this.socketTimeout} timeouts, ${this.normalClosure} normal closure, ${this.errorClosure} error closure`); + this.emptySocket = 0; + this.socketTimeout = 0; + this.normalClosure = 0; + this.errorClosure = 0; + }, 1000 * 60); - const client = new StratumV1Client( - socket, - this.stratumV1JobsService, - this.bitcoinRpcService, - this.clientService, - this.clientStatisticsService, - this.notificationService, - this.blocksService, - this.configService, - this.addressSettingsService - ); + } - // Unified cleanup function - const cleanup = async (reason: string) => { - if (client.extraNonceAndSessionId != null) { - await client.destroy(); - if(reason == 'Error'){ - this.errorClosure++; - }else{ - this.normalClosure++; + private startSocketServer(port: number) { + const server = new Server(async (socket: Socket) => { + // Set 15-minute timeout + socket.setTimeout(1000 * 60 * 15); + + const client = new StratumV1Client( + socket, + this.stratumV1JobsService, + this.bitcoinRpcService, + this.clientService, + this.clientStatisticsService, + this.notificationService, + this.blocksService, + this.configService, + this.addressSettingsService + ); + + // Unified cleanup function + const cleanup = async (reason: string) => { + if (client.extraNonceAndSessionId != null) { + await client.destroy(); + if (reason == 'Error') { + this.errorClosure++; + } else { + this.normalClosure++; + } } - } - if (!socket.destroyed) { - socket.end(); - socket.destroy(); - } + if (!socket.destroyed) { + socket.end(); + socket.destroy(); + } + }; + + // Handle client disconnection + socket.on('close', async (hadError: boolean) => { + await cleanup(hadError ? "Error" : "Normal Closure"); + }); + + // Handle socket timeouts + socket.on('timeout', async () => { + if (socket.bytesRead == 0 || socket.bytesWritten == 0) { + this.emptySocket++; + } else { + this.socketTimeout++; + } + await cleanup("Timeout"); + }); + + // Handle errors properly + socket.on('error', async (error: Error) => { + await cleanup("Error"); + }); + + // + + + }); + + // Ensure server itself handles errors + server.on('error', (err) => { + console.error(`Server error: ${err.message}`); + }); + + server.listen(port, () => { + console.log(`Stratum server is listening on port ${port}`); + }); + + } + + private startSecureSocketServer(port: number) { + + const currentDirectory = process.cwd(); + const keyPath = path.join(currentDirectory, 'secrets', 'key.pem'); + const certPath = path.join(currentDirectory, 'secrets', 'cert.pem'); + + const tlsOptions: TlsOptions = { + key: readFileSync(keyPath), + cert: readFileSync(certPath), }; - // Handle client disconnection - socket.on('close', async (hadError: boolean) => { - await cleanup(hadError ? "Error" : "Normal Closure"); + const server = createServer(tlsOptions, async (socket: TLSSocket) => { + // Set 15-minute timeout + socket.setTimeout(1000 * 60 * 15); + + const client = new StratumV1Client( + socket, + this.stratumV1JobsService, + this.bitcoinRpcService, + this.clientService, + this.clientStatisticsService, + this.notificationService, + this.blocksService, + this.configService, + this.addressSettingsService + ); + + const cleanup = async (reason: string) => { + if (client.extraNonceAndSessionId != null) { + await client.destroy(); + if (reason === 'Error') { + this.errorClosure++; + } else { + this.normalClosure++; + } + } + if (!socket.destroyed) { + socket.end(); + socket.destroy(); + } + }; + + socket.on('close', async (hadError: boolean) => { + await cleanup(hadError ? 'Error' : 'Normal Closure'); + }); + + socket.on('timeout', async () => { + if (socket.bytesRead === 0 || socket.bytesWritten === 0) { + this.emptySocket++; + } else { + this.socketTimeout++; + } + await cleanup('Timeout'); + }); + + socket.on('error', async (error: Error) => { + await cleanup('Error'); + }); + + // your protocol handling stays the same }); - // Handle socket timeouts - socket.on('timeout', async () => { - if(socket.bytesRead == 0 || socket.bytesWritten == 0){ - this.emptySocket++; - }else{ - this.socketTimeout++; - } - await cleanup("Timeout"); + server.on('error', (err) => { + console.error(`Server error: ${err.message}`); }); - // Handle errors properly - socket.on('error', async (error: Error) => { - await cleanup("Error"); + server.listen(port, () => { + console.log(`Stratum TLS server is listening on port ${port}`); }); - // - - - }); - - // Ensure server itself handles errors - server.on('error', (err) => { - console.error(`Server error: ${err.message}`); - }); - - server.listen(port, () => { - console.log(`Stratum server is listening on port ${port}`); - }); - - -} + } } \ No newline at end of file