Files
public-pool/src/services/stratum-v1-jobs.service.spec.ts
T

123 lines
5.2 KiB
TypeScript

import { BehaviorSubject, firstValueFrom, skip } from 'rxjs';
import { MockRecording1 } from '../../test/models/MockRecording1';
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
import { StratumV1JobsService } from './stratum-v1-jobs.service';
describe('StratumV1JobsService', () => {
let blockTemplate$: BehaviorSubject<IBlockTemplate>;
let bitcoinRpcService: { newBlockTemplate$: any, miningInfo: { blocks: number } };
let service: StratumV1JobsService;
let consoleLogSpy: jest.SpyInstance;
const createTemplate = (height = MockRecording1.BLOCK_TEMPLATE.height): IBlockTemplate => ({
...MockRecording1.BLOCK_TEMPLATE,
transactions: MockRecording1.BLOCK_TEMPLATE.transactions.map(tx => ({ ...tx })),
height
});
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date(parseInt(MockRecording1.TIME, 16) * 1000));
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
blockTemplate$ = new BehaviorSubject(createTemplate());
bitcoinRpcService = {
newBlockTemplate$: blockTemplate$.asObservable(),
miningInfo: { blocks: MockRecording1.BLOCK_TEMPLATE.height }
};
service = new StratumV1JobsService(bitcoinRpcService as any);
});
afterEach(() => {
consoleLogSpy.mockRestore();
jest.useRealTimers();
});
it('should create job templates from block templates', async () => {
const jobTemplate = await firstValueFrom(service.newMiningJob$);
expect(jobTemplate.blockData).toEqual(expect.objectContaining({
id: '1',
height: MockRecording1.BLOCK_TEMPLATE.height,
clearJobs: true,
coinbasevalue: MockRecording1.BLOCK_TEMPLATE.coinbasevalue
}));
expect(jobTemplate.merkle_branch.length).toBeGreaterThan(0);
expect(jobTemplate.block.transactions[0].ins[0].witness[0]).toHaveLength(32);
expect(service.getJobTemplateById('1')).toBe(jobTemplate);
});
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);
bitcoinRpcService.miningInfo.blocks = MockRecording1.BLOCK_TEMPLATE.height + 1;
const nextTemplate = firstValueFrom(service.newMiningJob$.pipe(skip(1)));
blockTemplate$.next(createTemplate(MockRecording1.BLOCK_TEMPLATE.height + 1));
const jobTemplate = await nextTemplate;
expect(jobTemplate.blockData.clearJobs).toBe(true);
expect(service.getJobById('old-job')).toBeUndefined();
expect(service.getJobTemplateById(firstTemplate.blockData.id)).toBeUndefined();
expect(service.getJobTemplateById(jobTemplate.blockData.id)).toBe(jobTemplate);
});
it('should skip identical non-clean template refreshes', async () => {
await firstValueFrom(service.newMiningJob$);
blockTemplate$.next(createTemplate());
expect(service.latestJobTemplateId).toBe(2);
expect(Object.keys(service.blocks)).toHaveLength(1);
});
it('should emit when transaction identity changes without changing transaction count', async () => {
await firstValueFrom(service.newMiningJob$);
const reorderedTemplate = createTemplate();
reorderedTemplate.transactions = [
reorderedTemplate.transactions[1],
reorderedTemplate.transactions[0],
...reorderedTemplate.transactions.slice(2)
];
const nextTemplate = firstValueFrom(service.newMiningJob$.pipe(skip(1)));
blockTemplate$.next(reorderedTemplate);
const jobTemplate = await nextTemplate;
expect(jobTemplate.blockData.clearJobs).toBe(false);
expect(jobTemplate.blockData.id).toBe('2');
expect(service.getJobTemplateById(jobTemplate.blockData.id)).toBe(jobTemplate);
});
it('should age old jobs and templates after five minutes', async () => {
await firstValueFrom(service.newMiningJob$);
const oldCreation = Date.now() - (1000 * 60 * 11);
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;
jest.setSystemTime(new Date(Date.now() + 1000));
const nextTemplate = firstValueFrom(service.newMiningJob$.pipe(skip(1)));
blockTemplate$.next(createTemplate());
const jobTemplate = await nextTemplate;
expect(jobTemplate.blockData.clearJobs).toBe(false);
expect(service.getJobById('old-job')).toBeUndefined();
expect(service.getJobTemplateById('old-template')).toBeUndefined();
expect(service.getJobTemplateById(jobTemplate.blockData.id)).toBe(jobTemplate);
});
it('should increment job ids when jobs are added', () => {
expect(service.getNextId()).toBe('1');
service.addJob({ jobId: '1', creation: Date.now() } as any);
expect(service.getNextId()).toBe('2');
expect(service.getJobById('1')).toEqual(expect.objectContaining({ jobId: '1' }));
});
});