mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
extranonce2 8 bytes
This commit is contained in:
@@ -4,6 +4,7 @@ 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';
|
||||
|
||||
|
||||
interface AddressObject {
|
||||
@@ -50,7 +51,7 @@ export class MiningJob {
|
||||
const blockHeightLengthByte = Buffer.from([blockHeightEncoded.length]);
|
||||
|
||||
// generate padding and take length of encode blockHeight into account
|
||||
const padding = Buffer.alloc(8 + (3 - blockHeightEncoded.length), 0)
|
||||
const padding = Buffer.alloc(TOTAL_EXTRANONCE_SIZE_BYTES + (3 - blockHeightEncoded.length), 0)
|
||||
|
||||
// build the script
|
||||
this.coinbaseTransaction.ins[0].script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, extra, padding])
|
||||
@@ -65,7 +66,7 @@ export class MiningJob {
|
||||
|
||||
const partOneIndex = serializedCoinbaseTx.indexOf(inputScript) + inputScript.length;
|
||||
|
||||
this.coinbasePart1 = serializedCoinbaseTx.slice(0, partOneIndex - 16);
|
||||
this.coinbasePart1 = serializedCoinbaseTx.slice(0, partOneIndex - (TOTAL_EXTRANONCE_SIZE_BYTES * 2));
|
||||
this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex);
|
||||
|
||||
|
||||
@@ -90,7 +91,7 @@ export class MiningJob {
|
||||
// set the nonces
|
||||
const nonceScript = testBlock.transactions[0].ins[0].script.toString('hex');
|
||||
|
||||
testBlock.transactions[0].ins[0].script = Buffer.from(`${nonceScript.substring(0, nonceScript.length - 16)}${extraNonce}${extraNonce2}`, 'hex');
|
||||
testBlock.transactions[0].ins[0].script = Buffer.from(`${nonceScript.substring(0, nonceScript.length - (TOTAL_EXTRANONCE_SIZE_BYTES * 2))}${extraNonce}${extraNonce2}`, 'hex');
|
||||
|
||||
//recompute the root since we updated the coinbase script with the nonces
|
||||
testBlock.merkleRoot = this.calculateMerkleRootHash(testBlock.transactions[0].getHash(false), jobTemplate.merkle_branch);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ClientService } from '../ORM/client/client.service';
|
||||
import { BitcoinRpcService as MockBitcoinRpcService } from '../services/bitcoin-rpc.service';
|
||||
import { NotificationService } from '../services/notification.service';
|
||||
import { StratumV1JobsService } from '../services/stratum-v1-jobs.service';
|
||||
import { IMiningInfo } from './bitcoin-rpc/IMiningInfo';
|
||||
import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate';
|
||||
import { StratumV1Client } from './StratumV1Client';
|
||||
|
||||
|
||||
@@ -50,8 +50,9 @@ describe('StratumV1Client', () => {
|
||||
let client: StratumV1Client;
|
||||
|
||||
let socketEmitter: (...args: any[]) => void;
|
||||
const emitMessage = (message: string) => socketEmitter(Buffer.from(`${message}\n`));
|
||||
|
||||
let newBlockEmitter: BehaviorSubject<IMiningInfo> = new BehaviorSubject(null);
|
||||
let newBlockEmitter: BehaviorSubject<IBlockTemplate> = new BehaviorSubject(MockRecording1.BLOCK_TEMPLATE);
|
||||
|
||||
let moduleRef: TestingModule;
|
||||
|
||||
@@ -60,7 +61,7 @@ describe('StratumV1Client', () => {
|
||||
imports: [
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'sqlite',
|
||||
database: './DB/public-pool.test.sqlite',
|
||||
database: ':memory:',
|
||||
synchronize: true,
|
||||
autoLoadEntities: true,
|
||||
cache: true,
|
||||
@@ -94,23 +95,26 @@ describe('StratumV1Client', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
console.log('NEW TEST')
|
||||
jest.useFakeTimers({ advanceTimers: true })
|
||||
jest.setSystemTime(new Date(parseInt(MockRecording1.TIME, 16) * 1000));
|
||||
|
||||
clientService = moduleRef.get<ClientService>(ClientService);
|
||||
|
||||
const dataSource = moduleRef.get<DataSource>(DataSource);
|
||||
|
||||
dataSource.getRepository(ClientEntity).delete({});
|
||||
dataSource.getRepository(ClientStatisticsEntity).delete({});
|
||||
await dataSource.getRepository(ClientStatisticsEntity).delete({});
|
||||
await dataSource.getRepository(ClientEntity).delete({});
|
||||
|
||||
|
||||
clientStatisticsService = moduleRef.get<ClientStatisticsService>(ClientStatisticsService);
|
||||
|
||||
configService = moduleRef.get<ConfigService>(ConfigService);
|
||||
|
||||
bitcoinRpcService = new MockBitcoinRpcService(null);
|
||||
jest.spyOn(bitcoinRpcService, 'getBlockTemplate').mockReturnValue(Promise.resolve(MockRecording1.BLOCK_TEMPLATE));
|
||||
bitcoinRpcService.newBlock$ = newBlockEmitter.asObservable();
|
||||
bitcoinRpcService = {
|
||||
newBlockTemplate$: newBlockEmitter.asObservable(),
|
||||
miningInfo: { blocks: MockRecording1.BLOCK_TEMPLATE.height },
|
||||
SUBMIT_BLOCK: jest.fn().mockResolvedValue(null)
|
||||
} as any;
|
||||
|
||||
|
||||
stratumV1JobsService = new StratumV1JobsService(bitcoinRpcService);
|
||||
@@ -143,8 +147,8 @@ describe('StratumV1Client', () => {
|
||||
);
|
||||
|
||||
client.extraNonceAndSessionId = MockRecording1.EXTRA_NONCE;
|
||||
jest.spyOn(client as any, 'getRandomHexString').mockReturnValue(MockRecording1.EXTRA_NONCE);
|
||||
|
||||
jest.useFakeTimers({ advanceTimers: true })
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -158,7 +162,7 @@ describe('StratumV1Client', () => {
|
||||
});
|
||||
|
||||
it('should close socket on invalid JSON', () => {
|
||||
socketEmitter(Buffer.from('INVALID'));
|
||||
emitMessage('INVALID');
|
||||
jest.spyOn(socket, 'destroy');
|
||||
expect(socket.on).toHaveBeenCalled();
|
||||
});
|
||||
@@ -167,11 +171,11 @@ describe('StratumV1Client', () => {
|
||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||
|
||||
expect(socket.on).toHaveBeenCalled();
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
||||
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(socket.write).toHaveBeenCalledWith(`{"id":1,"error":null,"result":[[["mining.notify","${client.extraNonceAndSessionId}"]],"${client.extraNonceAndSessionId}",4]}\n`, expect.any(Function));
|
||||
expect(socket.write).toHaveBeenCalledWith(`{"id":1,"error":null,"result":[[["mining.notify","${client.extraNonceAndSessionId}"]],"${client.extraNonceAndSessionId}",8]}\n`, expect.any(Function));
|
||||
|
||||
});
|
||||
|
||||
@@ -181,7 +185,7 @@ describe('StratumV1Client', () => {
|
||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||
|
||||
expect(socket.on).toHaveBeenCalled();
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_CONFIGURE));
|
||||
emitMessage(MockRecording1.MINING_CONFIGURE);
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
expect(socket.write).toHaveBeenCalledWith(`{"id":2,"error":null,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"}}\n`, expect.any(Function));
|
||||
});
|
||||
@@ -191,7 +195,7 @@ describe('StratumV1Client', () => {
|
||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||
|
||||
expect(socket.on).toHaveBeenCalled();
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
||||
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
expect(socket.write).toHaveBeenCalledWith('{"id":3,"error":null,"result":true}\n', expect.any(Function));
|
||||
});
|
||||
@@ -200,7 +204,7 @@ describe('StratumV1Client', () => {
|
||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||
|
||||
expect(socket.on).toHaveBeenCalled();
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY));
|
||||
emitMessage(MockRecording1.MINING_SUGGEST_DIFFICULTY);
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
expect(socket.write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[512]}\n`, expect.any(Function));
|
||||
});
|
||||
@@ -208,20 +212,21 @@ describe('StratumV1Client', () => {
|
||||
it('should set difficulty', async () => {
|
||||
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
||||
|
||||
console.log('should set difficulty')
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
||||
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
expect((client as any).write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[16384]}\n`);
|
||||
expect((client as any).write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[100000]}\n`);
|
||||
|
||||
});
|
||||
|
||||
it('should save client', async () => {
|
||||
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
||||
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
||||
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
emitMessage(MockRecording1.MINING_SUBMIT);
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
const clientCount = await clientService.connectedClientCount();
|
||||
@@ -244,9 +249,9 @@ describe('StratumV1Client', () => {
|
||||
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
||||
|
||||
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY));
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
||||
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||
emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`);
|
||||
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||
|
||||
|
||||
|
||||
@@ -255,10 +260,10 @@ describe('StratumV1Client', () => {
|
||||
|
||||
|
||||
|
||||
expect((client as any).write).lastCalledWith(`{"id":null,"method":"mining.notify","params":["1","171592f223740e92d223f6e68bff25279af7ac4f2246451e0000000200000000","02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1903c943255c7075626c69632d706f6f6c5c","ffffffff037a90000000000000160014e6f22ca44dc800e9d049621a3b9a42c509f1c4bc3b0f250000000000160014e6f22ca44dc800e9d049621a3b9a42c509f1c4bc0000000000000000266a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c00000000",["175335649d5e8746982969ec88f52e85ac9917106fba5468e699c8879ab974a1","d5644ab3e708c54cd68dc5aedc92b8d3037449687f92ec41ed6e37673d969d4a","5c9ec187517edc0698556cca5ce27e54c96acb014770599ed9df4d4937fbf2b0"],"20000000","192495f8","${MockRecording1.TIME}",false]}\n`);
|
||||
expect((client as any).write).lastCalledWith(`{"id":null,"method":"mining.notify","params":["1","171592f223740e92d223f6e68bff25279af7ac4f2246451e0000000200000000","02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1b03c943255075626c69632d506f6f6c","ffffffff02b59f250000000000160014e6f22ca44dc800e9d049621a3b9a42c509f1c4bc0000000000000000266a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c00000000",["175335649d5e8746982969ec88f52e85ac9917106fba5468e699c8879ab974a1","d5644ab3e708c54cd68dc5aedc92b8d3037449687f92ec41ed6e37673d969d4a","5c9ec187517edc0698556cca5ce27e54c96acb014770599ed9df4d4937fbf2b0"],"20000000","192495f8","${MockRecording1.TIME}",true]}\n`);
|
||||
|
||||
|
||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBMIT));
|
||||
emitMessage(MockRecording1.MINING_SUBMIT);
|
||||
|
||||
jest.useRealTimers();
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
@@ -270,4 +275,4 @@ describe('StratumV1Client', () => {
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage';
|
||||
import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage';
|
||||
import { StratumErrorMessage } from './stratum-messages/StratumErrorMessage';
|
||||
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
||||
import { EXTRANONCE1_SIZE_BYTES } from './stratum.constants';
|
||||
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
||||
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
||||
|
||||
@@ -103,10 +104,8 @@ export class StratumV1Client {
|
||||
}
|
||||
|
||||
private getRandomHexString() {
|
||||
const randomBytes = crypto.randomBytes(4); // 4 bytes = 32 bits
|
||||
const randomNumber = randomBytes.readUInt32BE(0); // Convert bytes to a 32-bit unsigned integer
|
||||
const hexString = randomNumber.toString(16).padStart(8, '0'); // Convert to hex and pad with zeros
|
||||
return hexString;
|
||||
const randomBytes = crypto.randomBytes(EXTRANONCE1_SIZE_BYTES);
|
||||
return randomBytes.toString('hex');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('MiningSubmitMessage', () => {
|
||||
|
||||
describe('test message parsing', () => {
|
||||
|
||||
const MINING_SUBMIT_MESSAGE = ' {"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "99020000", "64b1f10f", "2402812d", "00006000"]}'
|
||||
const MINING_SUBMIT_MESSAGE = ' {"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "9902000000000000", "64b1f10f", "2402812d", "00006000"]}'
|
||||
|
||||
const message = plainToInstance(
|
||||
MiningSubmitMessage,
|
||||
@@ -24,7 +24,7 @@ describe('MiningSubmitMessage', () => {
|
||||
expect(message.id).toEqual(5);
|
||||
expect(message.userId).toEqual('tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3');
|
||||
expect(message.jobId).toEqual('1');
|
||||
expect(message.extraNonce2).toEqual('99020000');
|
||||
expect(message.extraNonce2).toEqual('9902000000000000');
|
||||
expect(message.ntime).toEqual('64b1f10f');
|
||||
expect(message.nonce).toEqual('2402812d');
|
||||
expect(message.versionMask).toEqual('00006000');
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from 'class-validator';
|
||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString, Length } from 'class-validator';
|
||||
|
||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||
import { EXTRANONCE2_SIZE_BYTES } from '../stratum.constants';
|
||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
|
||||
@@ -27,6 +28,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
||||
public jobId: string;
|
||||
@Expose()
|
||||
@IsString()
|
||||
@Length(EXTRANONCE2_SIZE_BYTES * 2, EXTRANONCE2_SIZE_BYTES * 2)
|
||||
@Transform(({ value, key, obj, type }) => {
|
||||
return obj.params[2];
|
||||
})
|
||||
@@ -75,4 +77,4 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Expose, Transform } from 'class-transformer';
|
||||
import { IsArray, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||
import { EXTRANONCE2_SIZE_BYTES } from '../stratum.constants';
|
||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||
|
||||
export class SubscriptionMessage extends StratumBaseMessage {
|
||||
@@ -32,7 +33,7 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
||||
['mining.notify', clientId]
|
||||
],
|
||||
clientId, //Extranonce1 - Hex-encoded, per-connection unique string which will be used for coinbase serialization later. Keep it safe!
|
||||
4 //Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner.
|
||||
EXTRANONCE2_SIZE_BYTES //Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner.
|
||||
]
|
||||
}
|
||||
|
||||
@@ -50,4 +51,4 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
||||
}
|
||||
return userAgent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const EXTRANONCE1_SIZE_BYTES = 4;
|
||||
export const EXTRANONCE2_SIZE_BYTES = 8;
|
||||
export const TOTAL_EXTRANONCE_SIZE_BYTES = EXTRANONCE1_SIZE_BYTES + EXTRANONCE2_SIZE_BYTES;
|
||||
Reference in New Issue
Block a user