revert stale job retention

This commit is contained in:
Ben
2026-05-07 09:07:04 -04:00
parent 8424638081
commit 9878fa42b9
4 changed files with 28 additions and 142 deletions
-1
View File
@@ -25,7 +25,6 @@ export class MiningJob {
public jobTemplateId: string;
public networkDifficulty: number;
public creation: number;
public retiredAt?: number;
constructor(
private network: bitcoinjs.networks.Network,
-13
View File
@@ -550,19 +550,6 @@ export class StratumV1Client {
return false;
}
const classification = this.stratumV1JobsService.classifyJobForShare(job);
if (classification === 'stale-rejected') {
const err = new StratumErrorMessage(
submission.id,
eStratumErrorCode.JobNotFound,
'stale').response();
const success = await this.write(err);
if (!success) {
return false;
}
return false;
}
const submissionHash = [
submission.jobId,
submission.extraNonce2,
+11 -31
View File
@@ -47,7 +47,7 @@ describe('StratumV1JobsService', () => {
expect(service.getJobTemplateById('1')).toBe(jobTemplate);
});
it('should retire jobs when the block height changes', async () => {
it('should clear jobs when the block height changes', async () => {
const firstTemplate = await firstValueFrom(service.newMiningJob$);
service.addJob({ jobId: 'old-job', creation: Date.now() } as any);
@@ -57,27 +57,18 @@ describe('StratumV1JobsService', () => {
const jobTemplate = await nextTemplate;
expect(jobTemplate.blockData.clearJobs).toBe(true);
expect(service.getJobById('old-job')).toEqual(expect.objectContaining({
jobId: 'old-job',
retiredAt: Date.now()
}));
expect(service.getJobTemplateById(firstTemplate.blockData.id).blockData.retiredAt).toBe(Date.now());
expect(service.getJobById('old-job')).toBeUndefined();
expect(service.getJobTemplateById(firstTemplate.blockData.id)).toBeUndefined();
expect(service.getJobTemplateById(jobTemplate.blockData.id)).toBe(jobTemplate);
});
it('should age retired jobs and templates after the retention window', async () => {
const firstTemplate = await firstValueFrom(service.newMiningJob$);
it('should age old jobs and templates after five minutes', async () => {
await firstValueFrom(service.newMiningJob$);
const oldCreation = Date.now() - (1000 * 60 * 11);
const retiredAt = Date.now() - (1000 * 60 * 11);
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}`);
}
service.jobs['old-job'] = { jobId: 'old-job', creation: oldCreation } as any;
service.blocks['old-template'] = {
blockData: { creation: oldCreation }
} as any;
bitcoinRpcService.miningInfo.blocks = MockRecording1.BLOCK_TEMPLATE.height;
const nextTemplate = firstValueFrom(service.newMiningJob$.pipe(skip(1)));
@@ -85,22 +76,11 @@ describe('StratumV1JobsService', () => {
const jobTemplate = await nextTemplate;
expect(jobTemplate.blockData.clearJobs).toBe(false);
expect(service.getJobById('old-job-4')).toBeUndefined();
expect(service.getJobTemplateById('old-template-4')).toBeUndefined();
expect(service.getJobById('old-job')).toBeUndefined();
expect(service.getJobTemplateById('old-template')).toBeUndefined();
expect(service.getJobTemplateById(jobTemplate.blockData.id)).toBe(jobTemplate);
});
it('should classify retired jobs inside and outside the stale grace window', () => {
const job = { jobId: '1', creation: Date.now(), retiredAt: Date.now() - 1000 } as any;
expect(service.classifyJobForShare(job, Date.now())).toBe('stale-creditable');
job.retiredAt = Date.now() - 6000;
expect(service.classifyJobForShare(job, Date.now())).toBe('stale-rejected');
expect(service.classifyJobForShare({ jobId: '2', creation: Date.now() } as any, Date.now())).toBe('active');
});
it('should increment job ids when jobs are added', () => {
expect(service.getNextId()).toBe('1');
+17 -97
View File
@@ -18,14 +18,9 @@ export interface IJobTemplate {
networkDifficulty: number;
height: number;
clearJobs: boolean;
retiredAt?: number;
};
}
const STALE_GRACE_MS = parseInt(process.env.STRATUM_STALE_GRACE_MS) || 5000;
const MIN_RETAINED = 3;
export { STALE_GRACE_MS };
@Injectable()
export class StratumV1JobsService {
@@ -36,11 +31,6 @@ export class StratumV1JobsService {
public blocks: { [id: number]: IJobTemplate } = {};
private lastBlockHeight = 0;
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(
private readonly bitcoinRpcService: BitcoinRpcService
@@ -121,7 +111,6 @@ 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 })
)
@@ -153,78 +142,32 @@ export class StratumV1JobsService {
public cleanup(clearJobs: boolean, now: number = Date.now()) {
if (clearJobs) {
for (const id of this.blockOrder) {
const block = this.blocks[id];
if (block != null && block.blockData.retiredAt === undefined) {
block.blockData.retiredAt = now;
}
}
for (const jobId of this.jobOrder) {
const job = this.jobs[jobId];
if (job != null && job.retiredAt === undefined) {
job.retiredAt = now;
}
this.blocks = {};
this.jobs = {};
return;
}
let templatesDeleted = 0;
let jobsDeleted = 0;
for (const templateId in this.blocks) {
if (now - this.blocks[templateId].blockData.creation > (1000 * 60 * 5)) {
delete this.blocks[templateId];
templatesDeleted++;
}
}
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,
);
}
private ageEntries<T>(
map: Record<string, T>,
order: string[],
orderSet: Set<string>,
now: number,
getCreation: (entry: T) => number,
getRetiredAt: (entry: T) => number | undefined,
): void {
while (order.length > MIN_RETAINED) {
const id = order[0];
const entry = map[id];
if (entry == null) {
order.shift();
orderSet.delete(id);
continue;
for (const jobId in this.jobs) {
if (now - this.jobs[jobId].creation > (1000 * 60 * 5)) {
delete this.jobs[jobId];
jobsDeleted++;
}
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;
}
//console.log(`Deleted ${templatesDeleted} templates and ${jobsDeleted} jobs.`)
}
public addJob(job: MiningJob) {
this.jobs[job.jobId] = job;
this.trackJob(job.jobId);
this.latestJobId++;
}
@@ -232,13 +175,6 @@ export class StratumV1JobsService {
return this.jobs[jobId];
}
public classifyJobForShare(job: MiningJob, now: number = Date.now()): 'active' | 'stale-creditable' | 'stale-rejected' {
if (job.retiredAt === undefined) {
return 'active';
}
return (now - job.retiredAt) <= STALE_GRACE_MS ? 'stale-creditable' : 'stale-rejected';
}
public getNextTemplateId() {
return this.latestJobTemplateId.toString(16);
}
@@ -246,20 +182,4 @@ 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);
}
}