mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
141 lines
5.5 KiB
TypeScript
141 lines
5.5 KiB
TypeScript
import * as bitcoinjs from 'bitcoinjs-lib';
|
|
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
|
|
|
import { MockRecording1 } from '../../test/models/MockRecording1';
|
|
import { StratumV1JobsService } from '../services/stratum-v1-jobs.service';
|
|
import { MiningJob } from './MiningJob';
|
|
|
|
describe('MiningJob', () => {
|
|
let jobTemplate;
|
|
let job: MiningJob;
|
|
|
|
beforeEach(async () => {
|
|
jest.useFakeTimers();
|
|
jest.setSystemTime(new Date(parseInt(MockRecording1.TIME, 16) * 1000));
|
|
|
|
const blockTemplate$ = new BehaviorSubject(MockRecording1.BLOCK_TEMPLATE);
|
|
const bitcoinRpcService = {
|
|
newBlockTemplate$: blockTemplate$.asObservable(),
|
|
miningInfo: { blocks: MockRecording1.BLOCK_TEMPLATE.height }
|
|
};
|
|
jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
|
|
const jobsService = new StratumV1JobsService(bitcoinRpcService as any);
|
|
jobTemplate = await firstValueFrom(jobsService.newMiningJob$);
|
|
job = new MiningJob(
|
|
bitcoinjs.networks.testnet,
|
|
'1',
|
|
[{ address: 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4', percent: 100 }],
|
|
jobTemplate
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('should split coinbase around 12 bytes of extranonce space', () => {
|
|
const notify = JSON.parse(job.response(jobTemplate));
|
|
const coinbasePart1 = notify.params[2];
|
|
const coinbasePart2 = notify.params[3];
|
|
const extraNonce1 = '57a6f098';
|
|
const extraNonce2 = 'c708000000000000';
|
|
const coinbase = bitcoinjs.Transaction.fromHex(`${coinbasePart1}${extraNonce1}${extraNonce2}${coinbasePart2}`);
|
|
|
|
expect(Buffer.byteLength(extraNonce1 + extraNonce2, 'hex')).toBe(12);
|
|
expect(coinbase.ins[0].script.toString('hex')).toContain(`${extraNonce1}${extraNonce2}`);
|
|
expect(coinbase.ins[0].script.toString('hex').endsWith(`${extraNonce1}${extraNonce2}`)).toBe(true);
|
|
});
|
|
|
|
it('should update block nonce, timestamp, version mask, and coinbase script', () => {
|
|
const extraNonce1 = '57a6f098';
|
|
const extraNonce2 = 'c708000000000000';
|
|
const timestamp = parseInt(MockRecording1.TIME, 16);
|
|
const originalMerkleRoot = Buffer.from(jobTemplate.block.merkleRoot);
|
|
|
|
const updatedBlock = job.copyAndUpdateBlock(
|
|
jobTemplate,
|
|
parseInt('00002000', 16),
|
|
parseInt('ed460d91', 16),
|
|
extraNonce1,
|
|
extraNonce2,
|
|
timestamp
|
|
);
|
|
|
|
expect(updatedBlock.nonce).toBe(parseInt('ed460d91', 16));
|
|
expect(updatedBlock.timestamp).toBe(timestamp);
|
|
expect(updatedBlock.version).toBe(jobTemplate.block.version ^ parseInt('00002000', 16));
|
|
expect(updatedBlock.transactions[0].ins[0].script.toString('hex').endsWith(`${extraNonce1}${extraNonce2}`)).toBe(true);
|
|
expect(updatedBlock.merkleRoot.equals(originalMerkleRoot)).toBe(false);
|
|
});
|
|
|
|
it('should leave block version unchanged without a version mask', () => {
|
|
const updatedBlock = job.copyAndUpdateBlock(
|
|
jobTemplate,
|
|
0,
|
|
parseInt('ed460d91', 16),
|
|
'57a6f098',
|
|
'c708000000000000',
|
|
parseInt(MockRecording1.TIME, 16)
|
|
);
|
|
|
|
expect(updatedBlock.version).toBe(jobTemplate.block.version);
|
|
});
|
|
|
|
it('should build the same header as the full block update path', () => {
|
|
const versionMask = parseInt('00002000', 16);
|
|
const nonce = parseInt('ed460d91', 16);
|
|
const extraNonce1 = '57a6f098';
|
|
const extraNonce2 = 'c708000000000000';
|
|
const timestamp = parseInt(MockRecording1.TIME, 16);
|
|
|
|
const updatedBlock = job.copyAndUpdateBlock(
|
|
jobTemplate,
|
|
versionMask,
|
|
nonce,
|
|
extraNonce1,
|
|
extraNonce2,
|
|
timestamp
|
|
);
|
|
const fastHeader = job.buildHeaderBuffer(
|
|
jobTemplate,
|
|
versionMask,
|
|
nonce,
|
|
extraNonce1,
|
|
extraNonce2,
|
|
timestamp
|
|
);
|
|
|
|
expect(fastHeader.equals(updatedBlock.toBuffer(true))).toBe(true);
|
|
});
|
|
|
|
it('should expose coinbase helpers for downstream validation', () => {
|
|
const notify = JSON.parse(job.response(jobTemplate));
|
|
|
|
expect(job.getCoinbaseTxHex()).toContain(notify.params[2]);
|
|
expect(job.getCoinbasePrefixBuffer().toString('hex')).toBe(notify.params[2]);
|
|
expect(job.getCoinbaseSuffixBuffer().toString('hex')).toBe(notify.params[3]);
|
|
const clone = job.cloneCoinbaseTransaction();
|
|
const nonWitnessCoinbase = bitcoinjs.Transaction.fromHex(job.getCoinbaseTxHex());
|
|
expect(clone.ins[0].script.toString('hex')).toBe(nonWitnessCoinbase.ins[0].script.toString('hex'));
|
|
expect(clone.outs).toHaveLength(nonWitnessCoinbase.outs.length);
|
|
expect(clone.ins[0].witness[0]).toHaveLength(32);
|
|
});
|
|
|
|
it('should omit oversized pool identifiers from the coinbase script', () => {
|
|
jest.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
const oversizedIdentifier = 'x'.repeat(120);
|
|
const oversizedJob = new MiningJob(
|
|
bitcoinjs.networks.testnet,
|
|
'2',
|
|
[{ address: 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4', percent: 100 }],
|
|
jobTemplate,
|
|
oversizedIdentifier
|
|
);
|
|
const coinbase = bitcoinjs.Transaction.fromHex(oversizedJob.getCoinbaseTxHex());
|
|
|
|
expect(coinbase.ins[0].script.toString()).not.toContain(oversizedIdentifier);
|
|
});
|
|
});
|