From 703546719c8f2daeb013a0f20bbecb5a7d99230a Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 6 May 2026 21:24:04 -0400 Subject: [PATCH] optimizations --- src/models/MiningJob.ts | 33 +++++------ src/services/stratum-v1-jobs.service.spec.ts | 7 ++- src/services/stratum-v1-jobs.service.ts | 61 +++++++++++++++----- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index 3d67816..4565b4c 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -3,7 +3,6 @@ import * as bitcoinjs from 'bitcoinjs-lib'; import { IJobTemplate } from '../services/stratum-v1-jobs.service'; import { eResponseMethod } from './enums/eResponseMethod'; -import { IMiningNotify } from './stratum-messages/IMiningNotify'; import { TOTAL_EXTRANONCE_SIZE_BYTES } from './stratum.constants'; const MAX_BLOCK_WEIGHT = 4000000; @@ -21,6 +20,7 @@ export class MiningJob { private coinbasePart1Buffer: Buffer; private coinbasePart2Buffer: Buffer; private merkleBranchBuffers: Buffer[]; + private notifyStaticParams: string; public jobTemplateId: string; public networkDifficulty: number; @@ -89,7 +89,16 @@ export class MiningJob { this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex); this.coinbasePart1Buffer = Buffer.from(this.coinbasePart1, 'hex'); this.coinbasePart2Buffer = Buffer.from(this.coinbasePart2, 'hex'); - + this.notifyStaticParams = JSON.stringify([ + this.swapEndianWords(jobTemplate.block.prevHash).toString('hex'), + this.coinbasePart1, + this.coinbasePart2, + jobTemplate.merkle_branch, + jobTemplate.block.version.toString(16), + jobTemplate.block.bits.toString(16), + jobTemplate.block.timestamp.toString(16), + jobTemplate.blockData.clearJobs + ]).slice(1); } @@ -108,6 +117,7 @@ export class MiningJob { job.coinbasePart1Buffer = this.coinbasePart1Buffer; job.coinbasePart2Buffer = this.coinbasePart2Buffer; job.merkleBranchBuffers = this.merkleBranchBuffers; + job.notifyStaticParams = this.notifyStaticParams; job.jobTemplateId = this.jobTemplateId; job.networkDifficulty = this.networkDifficulty; job.creation = new Date().getTime(); @@ -254,24 +264,7 @@ export class MiningJob { } public response(jobTemplate: IJobTemplate): string { - - const job: IMiningNotify = { - id: null, - method: eResponseMethod.MINING_NOTIFY, - params: [ - this.jobId, - this.swapEndianWords(jobTemplate.block.prevHash).toString('hex'), - this.coinbasePart1, - this.coinbasePart2, - jobTemplate.merkle_branch, - jobTemplate.block.version.toString(16), - jobTemplate.block.bits.toString(16), - jobTemplate.block.timestamp.toString(16), - jobTemplate.blockData.clearJobs - ] - }; - - return JSON.stringify(job) + '\n'; + return `{"id":null,"method":"${eResponseMethod.MINING_NOTIFY}","params":[${JSON.stringify(this.jobId)},${this.notifyStaticParams}}\n`; } diff --git a/src/services/stratum-v1-jobs.service.spec.ts b/src/services/stratum-v1-jobs.service.spec.ts index de5cbfc..d5a78c9 100644 --- a/src/services/stratum-v1-jobs.service.spec.ts +++ b/src/services/stratum-v1-jobs.service.spec.ts @@ -66,14 +66,17 @@ describe('StratumV1JobsService', () => { }); it('should age retired jobs and templates after the retention window', async () => { - await firstValueFrom(service.newMiningJob$); + const firstTemplate = await firstValueFrom(service.newMiningJob$); const oldCreation = Date.now() - (1000 * 60 * 11); const retiredAt = Date.now() - (1000 * 60 * 11); - for (let i = 0; i < 5; i++) { + firstTemplate.blockData.retiredAt = retiredAt; + for (let i = 4; i >= 0; i--) { service.jobs[`old-job-${i}`] = { jobId: `old-job-${i}`, creation: oldCreation - i, retiredAt } as any; + (service as any).trackJob(`old-job-${i}`); service.blocks[`old-template-${i}`] = { blockData: { creation: oldCreation - i, retiredAt } } as any; + (service as any).trackBlock(`old-template-${i}`); } bitcoinRpcService.miningInfo.blocks = MockRecording1.BLOCK_TEMPLATE.height; diff --git a/src/services/stratum-v1-jobs.service.ts b/src/services/stratum-v1-jobs.service.ts index 40e5c29..b073d0d 100644 --- a/src/services/stratum-v1-jobs.service.ts +++ b/src/services/stratum-v1-jobs.service.ts @@ -37,6 +37,10 @@ export class StratumV1JobsService { private lastBlockHeight = 0; private jobRetentionMs = parseInt(process.env.JOB_RETENTION_MS) || 600000; + private jobOrder: string[] = []; + private blockOrder: string[] = []; + private jobOrderSet = new Set(); + private blockOrderSet = new Set(); constructor( private readonly bitcoinRpcService: BitcoinRpcService @@ -117,6 +121,7 @@ export class StratumV1JobsService { tap((data) => { this.cleanup(data.blockData.clearJobs); this.blocks[data.blockData.id] = data; + this.trackBlock(data.blockData.id); }), shareReplay({ refCount: true, bufferSize: 1 }) ) @@ -148,15 +153,15 @@ export class StratumV1JobsService { public cleanup(clearJobs: boolean, now: number = Date.now()) { if (clearJobs) { - for (const id in this.blocks) { + for (const id of this.blockOrder) { const block = this.blocks[id]; - if (block.blockData.retiredAt === undefined) { + if (block != null && block.blockData.retiredAt === undefined) { block.blockData.retiredAt = now; } } - for (const jobId in this.jobs) { + for (const jobId of this.jobOrder) { const job = this.jobs[jobId]; - if (job.retiredAt === undefined) { + if (job != null && job.retiredAt === undefined) { job.retiredAt = now; } } @@ -164,12 +169,16 @@ export class StratumV1JobsService { this.ageEntries( this.blocks, + this.blockOrder, + this.blockOrderSet, now, entry => entry.blockData.creation, entry => entry.blockData.retiredAt, ); this.ageEntries( this.jobs, + this.jobOrder, + this.jobOrderSet, now, entry => entry.creation, entry => entry.retiredAt, @@ -178,37 +187,44 @@ export class StratumV1JobsService { private ageEntries( map: Record, + order: string[], + orderSet: Set, now: number, getCreation: (entry: T) => number, getRetiredAt: (entry: T) => number | undefined, ): void { - const ids = Object.keys(map); - if (ids.length <= MIN_RETAINED) { - return; - } - - const candidatesForGC = ids - .slice() - .sort((a, b) => getCreation(map[b]) - getCreation(map[a])) - .slice(MIN_RETAINED); - - for (const id of candidatesForGC) { + while (order.length > MIN_RETAINED) { + const id = order[0]; const entry = map[id]; + if (entry == null) { + order.shift(); + orderSet.delete(id); + continue; + } + const retiredAt = getRetiredAt(entry); if (retiredAt !== undefined && now - retiredAt > this.jobRetentionMs) { delete map[id]; + order.shift(); + orderSet.delete(id); continue; } if (retiredAt === undefined && now - getCreation(entry) > this.jobRetentionMs * 2) { delete map[id]; + order.shift(); + orderSet.delete(id); + continue; } + + break; } } public addJob(job: MiningJob) { this.jobs[job.jobId] = job; + this.trackJob(job.jobId); this.latestJobId++; } @@ -230,5 +246,20 @@ export class StratumV1JobsService { return this.latestJobId.toString(16); } + private trackJob(jobId: string): void { + if (this.jobOrderSet.has(jobId)) { + return; + } + this.jobOrderSet.add(jobId); + this.jobOrder.push(jobId); + } + + private trackBlock(blockId: string): void { + if (this.blockOrderSet.has(blockId)) { + return; + } + this.blockOrderSet.add(blockId); + this.blockOrder.push(blockId); + } }