optimizations

This commit is contained in:
Ben
2026-05-06 21:24:04 -04:00
parent d9fa933825
commit 703546719c
3 changed files with 64 additions and 37 deletions
+13 -20
View File
@@ -3,7 +3,6 @@ import * as bitcoinjs from 'bitcoinjs-lib';
import { IJobTemplate } from '../services/stratum-v1-jobs.service'; import { IJobTemplate } from '../services/stratum-v1-jobs.service';
import { eResponseMethod } from './enums/eResponseMethod'; import { eResponseMethod } from './enums/eResponseMethod';
import { IMiningNotify } from './stratum-messages/IMiningNotify';
import { TOTAL_EXTRANONCE_SIZE_BYTES } from './stratum.constants'; import { TOTAL_EXTRANONCE_SIZE_BYTES } from './stratum.constants';
const MAX_BLOCK_WEIGHT = 4000000; const MAX_BLOCK_WEIGHT = 4000000;
@@ -21,6 +20,7 @@ export class MiningJob {
private coinbasePart1Buffer: Buffer; private coinbasePart1Buffer: Buffer;
private coinbasePart2Buffer: Buffer; private coinbasePart2Buffer: Buffer;
private merkleBranchBuffers: Buffer[]; private merkleBranchBuffers: Buffer[];
private notifyStaticParams: string;
public jobTemplateId: string; public jobTemplateId: string;
public networkDifficulty: number; public networkDifficulty: number;
@@ -89,7 +89,16 @@ export class MiningJob {
this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex); this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex);
this.coinbasePart1Buffer = Buffer.from(this.coinbasePart1, 'hex'); this.coinbasePart1Buffer = Buffer.from(this.coinbasePart1, 'hex');
this.coinbasePart2Buffer = Buffer.from(this.coinbasePart2, '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.coinbasePart1Buffer = this.coinbasePart1Buffer;
job.coinbasePart2Buffer = this.coinbasePart2Buffer; job.coinbasePart2Buffer = this.coinbasePart2Buffer;
job.merkleBranchBuffers = this.merkleBranchBuffers; job.merkleBranchBuffers = this.merkleBranchBuffers;
job.notifyStaticParams = this.notifyStaticParams;
job.jobTemplateId = this.jobTemplateId; job.jobTemplateId = this.jobTemplateId;
job.networkDifficulty = this.networkDifficulty; job.networkDifficulty = this.networkDifficulty;
job.creation = new Date().getTime(); job.creation = new Date().getTime();
@@ -254,24 +264,7 @@ export class MiningJob {
} }
public response(jobTemplate: IJobTemplate): string { public response(jobTemplate: IJobTemplate): string {
return `{"id":null,"method":"${eResponseMethod.MINING_NOTIFY}","params":[${JSON.stringify(this.jobId)},${this.notifyStaticParams}}\n`;
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';
} }
+5 -2
View File
@@ -66,14 +66,17 @@ describe('StratumV1JobsService', () => {
}); });
it('should age retired jobs and templates after the retention window', async () => { 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 oldCreation = Date.now() - (1000 * 60 * 11);
const retiredAt = 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.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}`] = { service.blocks[`old-template-${i}`] = {
blockData: { creation: oldCreation - i, retiredAt } blockData: { creation: oldCreation - i, retiredAt }
} as any; } as any;
(service as any).trackBlock(`old-template-${i}`);
} }
bitcoinRpcService.miningInfo.blocks = MockRecording1.BLOCK_TEMPLATE.height; bitcoinRpcService.miningInfo.blocks = MockRecording1.BLOCK_TEMPLATE.height;
+45 -14
View File
@@ -37,6 +37,10 @@ export class StratumV1JobsService {
private lastBlockHeight = 0; private lastBlockHeight = 0;
private jobRetentionMs = parseInt(process.env.JOB_RETENTION_MS) || 600000; private jobRetentionMs = parseInt(process.env.JOB_RETENTION_MS) || 600000;
private jobOrder: string[] = [];
private blockOrder: string[] = [];
private jobOrderSet = new Set<string>();
private blockOrderSet = new Set<string>();
constructor( constructor(
private readonly bitcoinRpcService: BitcoinRpcService private readonly bitcoinRpcService: BitcoinRpcService
@@ -117,6 +121,7 @@ export class StratumV1JobsService {
tap((data) => { tap((data) => {
this.cleanup(data.blockData.clearJobs); this.cleanup(data.blockData.clearJobs);
this.blocks[data.blockData.id] = data; this.blocks[data.blockData.id] = data;
this.trackBlock(data.blockData.id);
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 })
) )
@@ -148,15 +153,15 @@ export class StratumV1JobsService {
public cleanup(clearJobs: boolean, now: number = Date.now()) { public cleanup(clearJobs: boolean, now: number = Date.now()) {
if (clearJobs) { if (clearJobs) {
for (const id in this.blocks) { for (const id of this.blockOrder) {
const block = this.blocks[id]; const block = this.blocks[id];
if (block.blockData.retiredAt === undefined) { if (block != null && block.blockData.retiredAt === undefined) {
block.blockData.retiredAt = now; block.blockData.retiredAt = now;
} }
} }
for (const jobId in this.jobs) { for (const jobId of this.jobOrder) {
const job = this.jobs[jobId]; const job = this.jobs[jobId];
if (job.retiredAt === undefined) { if (job != null && job.retiredAt === undefined) {
job.retiredAt = now; job.retiredAt = now;
} }
} }
@@ -164,12 +169,16 @@ export class StratumV1JobsService {
this.ageEntries( this.ageEntries(
this.blocks, this.blocks,
this.blockOrder,
this.blockOrderSet,
now, now,
entry => entry.blockData.creation, entry => entry.blockData.creation,
entry => entry.blockData.retiredAt, entry => entry.blockData.retiredAt,
); );
this.ageEntries( this.ageEntries(
this.jobs, this.jobs,
this.jobOrder,
this.jobOrderSet,
now, now,
entry => entry.creation, entry => entry.creation,
entry => entry.retiredAt, entry => entry.retiredAt,
@@ -178,37 +187,44 @@ export class StratumV1JobsService {
private ageEntries<T>( private ageEntries<T>(
map: Record<string, T>, map: Record<string, T>,
order: string[],
orderSet: Set<string>,
now: number, now: number,
getCreation: (entry: T) => number, getCreation: (entry: T) => number,
getRetiredAt: (entry: T) => number | undefined, getRetiredAt: (entry: T) => number | undefined,
): void { ): void {
const ids = Object.keys(map); while (order.length > MIN_RETAINED) {
if (ids.length <= MIN_RETAINED) { const id = order[0];
return; const entry = map[id];
if (entry == null) {
order.shift();
orderSet.delete(id);
continue;
} }
const candidatesForGC = ids
.slice()
.sort((a, b) => getCreation(map[b]) - getCreation(map[a]))
.slice(MIN_RETAINED);
for (const id of candidatesForGC) {
const entry = map[id];
const retiredAt = getRetiredAt(entry); const retiredAt = getRetiredAt(entry);
if (retiredAt !== undefined && now - retiredAt > this.jobRetentionMs) { if (retiredAt !== undefined && now - retiredAt > this.jobRetentionMs) {
delete map[id]; delete map[id];
order.shift();
orderSet.delete(id);
continue; continue;
} }
if (retiredAt === undefined && now - getCreation(entry) > this.jobRetentionMs * 2) { if (retiredAt === undefined && now - getCreation(entry) > this.jobRetentionMs * 2) {
delete map[id]; delete map[id];
order.shift();
orderSet.delete(id);
continue;
} }
break;
} }
} }
public addJob(job: MiningJob) { public addJob(job: MiningJob) {
this.jobs[job.jobId] = job; this.jobs[job.jobId] = job;
this.trackJob(job.jobId);
this.latestJobId++; this.latestJobId++;
} }
@@ -230,5 +246,20 @@ export class StratumV1JobsService {
return this.latestJobId.toString(16); 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);
}
} }