mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
cleaning up
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { from, map, Observable, shareReplay, switchMap, tap } from 'rxjs';
|
||||||
|
|
||||||
|
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
||||||
|
import { IBlockTemplate } from './models/bitcoin-rpc/IBlockTemplate';
|
||||||
|
import { IMiningInfo } from './models/bitcoin-rpc/IMiningInfo';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BlockTemplateService {
|
||||||
|
|
||||||
|
public currentBlockTemplate: IBlockTemplate;
|
||||||
|
|
||||||
|
public currentBlockTemplate$: Observable<{ miningInfo: IMiningInfo, blockTemplate: IBlockTemplate }>;
|
||||||
|
|
||||||
|
constructor(private readonly bitcoinRpcService: BitcoinRpcService) {
|
||||||
|
this.currentBlockTemplate$ = this.bitcoinRpcService.newBlock$.pipe(
|
||||||
|
switchMap((miningInfo) => from(this.bitcoinRpcService.getBlockTemplate()).pipe(map(blockTemplate => { return { miningInfo, blockTemplate } }))),
|
||||||
|
tap(({ miningInfo, blockTemplate }) => this.currentBlockTemplate = blockTemplate),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-4
@@ -4,8 +4,7 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
||||||
import { CoinbaseConstructorService } from './coinbase-constructor.service';
|
import { BlockTemplateService } from './BlockTemplateService';
|
||||||
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
|
||||||
import { StratumV1Service } from './stratum-v1.service';
|
import { StratumV1Service } from './stratum-v1.service';
|
||||||
|
|
||||||
|
|
||||||
@@ -19,8 +18,7 @@ import { StratumV1Service } from './stratum-v1.service';
|
|||||||
AppService,
|
AppService,
|
||||||
StratumV1Service,
|
StratumV1Service,
|
||||||
BitcoinRpcService,
|
BitcoinRpcService,
|
||||||
CoinbaseConstructorService,
|
BlockTemplateService
|
||||||
StratumV1JobsService
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { RPCClient } from 'rpc-bitcoin';
|
import { RPCClient } from 'rpc-bitcoin';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, filter } from 'rxjs';
|
||||||
|
|
||||||
import { IBlockTemplate } from './models/bitcoin-rpc/IBlockTemplate';
|
import { IBlockTemplate } from './models/bitcoin-rpc/IBlockTemplate';
|
||||||
import { IMiningInfo } from './models/bitcoin-rpc/IMiningInfo';
|
import { IMiningInfo } from './models/bitcoin-rpc/IMiningInfo';
|
||||||
@@ -12,7 +12,8 @@ export class BitcoinRpcService {
|
|||||||
|
|
||||||
private blockHeight = 0;
|
private blockHeight = 0;
|
||||||
private client: RPCClient;
|
private client: RPCClient;
|
||||||
private newBlock$: BehaviorSubject<number> = new BehaviorSubject(0);
|
private _newBlock$: BehaviorSubject<IMiningInfo> = new BehaviorSubject(undefined);
|
||||||
|
public newBlock$ = this._newBlock$.pipe(filter(block => block != null));
|
||||||
|
|
||||||
constructor(configService: ConfigService) {
|
constructor(configService: ConfigService) {
|
||||||
const url = configService.get('BITCOIN_RPC_URL');
|
const url = configService.get('BITCOIN_RPC_URL');
|
||||||
@@ -26,14 +27,14 @@ export class BitcoinRpcService {
|
|||||||
|
|
||||||
console.log('Bitcoin RPC connected');
|
console.log('Bitcoin RPC connected');
|
||||||
|
|
||||||
|
|
||||||
// Maybe use ZeroMQ ?
|
// Maybe use ZeroMQ ?
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
const miningInfo = await this.getMiningInfo();
|
const miningInfo = await this.getMiningInfo();
|
||||||
if (miningInfo.blocks > this.blockHeight) {
|
if (miningInfo.blocks > this.blockHeight) {
|
||||||
// console.log(miningInfo);
|
|
||||||
if (this.blockHeight != 0) {
|
this._newBlock$.next(miningInfo);
|
||||||
this.newBlock$.next(miningInfo.blocks + 1);
|
|
||||||
}
|
|
||||||
this.blockHeight = miningInfo.blocks;
|
this.blockHeight = miningInfo.blocks;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -43,9 +44,6 @@ export class BitcoinRpcService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public newBlock(): Observable<any> {
|
|
||||||
return this.newBlock$.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async getBlockTemplate(): Promise<IBlockTemplate> {
|
public async getBlockTemplate(): Promise<IBlockTemplate> {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CoinbaseConstructorService {
|
|
||||||
|
|
||||||
constructor(private bitcoinRPCService: BitcoinRpcService) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
+16
-46
@@ -7,7 +7,7 @@ import { eResponseMethod } from './enums/eResponseMethod';
|
|||||||
|
|
||||||
interface AddressObject {
|
interface AddressObject {
|
||||||
address: string;
|
address: string;
|
||||||
percentage: number;
|
percent: number;
|
||||||
}
|
}
|
||||||
export class MiningJob {
|
export class MiningJob {
|
||||||
public id: number;
|
public id: number;
|
||||||
@@ -25,7 +25,7 @@ export class MiningJob {
|
|||||||
public version: number; // The hex-encoded block version.
|
public version: number; // The hex-encoded block version.
|
||||||
public nbits: number; // The hex-encoded network difficulty required for the block.
|
public nbits: number; // The hex-encoded network difficulty required for the block.
|
||||||
public ntime: number; // Current ntime/
|
public ntime: number; // Current ntime/
|
||||||
public clean_jobs: boolean; // When true, server indicates that submitting shares from previous jobs don't have a sense and such shares will be rejected. When this flag is set, miner should also drop all previous jobs too.
|
//public clean_jobs: boolean; // When true, server indicates that submitting shares from previous jobs don't have a sense and such shares will be rejected. When this flag is set, miner should also drop all previous jobs too.
|
||||||
|
|
||||||
public response: string;
|
public response: string;
|
||||||
|
|
||||||
@@ -33,9 +33,8 @@ export class MiningJob {
|
|||||||
|
|
||||||
public tree: MerkleTree;
|
public tree: MerkleTree;
|
||||||
|
|
||||||
constructor(id: string, public blockTemplate: IBlockTemplate, _cleanJobs: boolean) {
|
constructor(id: string, payoutInformation: AddressObject[], public blockTemplate: IBlockTemplate, public networkDifficulty: number, public clean_jobs: boolean) {
|
||||||
|
|
||||||
//console.log(blockTemplate);
|
|
||||||
|
|
||||||
this.job_id = id;
|
this.job_id = id;
|
||||||
this.target = blockTemplate.target;
|
this.target = blockTemplate.target;
|
||||||
@@ -44,17 +43,14 @@ export class MiningJob {
|
|||||||
this.version = blockTemplate.version;
|
this.version = blockTemplate.version;
|
||||||
this.nbits = parseInt(blockTemplate.bits, 16);
|
this.nbits = parseInt(blockTemplate.bits, 16);
|
||||||
this.ntime = Math.floor(new Date().getTime() / 1000);
|
this.ntime = Math.floor(new Date().getTime() / 1000);
|
||||||
this.clean_jobs = _cleanJobs;
|
|
||||||
|
|
||||||
|
|
||||||
const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => {
|
const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => {
|
||||||
return pre + cur.fee;
|
return pre + cur.fee;
|
||||||
}, 0);
|
}, 0);
|
||||||
const miningReward = this.calculateMiningReward(blockTemplate.height);
|
|
||||||
//console.log('TRANSACTION FEES', transactionFees);
|
|
||||||
//console.log('MINING REWARD', miningReward);
|
|
||||||
|
|
||||||
const { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction([{ address: 'bc1qsa5kr5s74wss7pcpmnrc7e3y5nrgyfcdzpwakk', percentage: 100 }], blockTemplate.height, transactionFees + miningReward);
|
|
||||||
|
const { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction(payoutInformation, blockTemplate.height, blockTemplate.coinbasevalue);
|
||||||
|
|
||||||
this.coinb1 = coinbasePart1;
|
this.coinb1 = coinbasePart1;
|
||||||
this.coinb2 = coinbasePart2;
|
this.coinb2 = coinbasePart2;
|
||||||
@@ -62,19 +58,12 @@ export class MiningJob {
|
|||||||
const coinbaseHash = this.sha256(this.coinb1 + this.coinb2).toString('hex');
|
const coinbaseHash = this.sha256(this.coinb1 + this.coinb2).toString('hex');
|
||||||
const coinbaseBuffer = Buffer.from(coinbaseHash, 'hex');
|
const coinbaseBuffer = Buffer.from(coinbaseHash, 'hex');
|
||||||
|
|
||||||
//transactions.unshift(coinbaseHash);
|
|
||||||
|
|
||||||
// Calculate merkle branch
|
// Calculate merkle branch
|
||||||
const transactionBuffers = blockTemplate.transactions.map(tx => Buffer.from(tx.hash, 'hex'));
|
const transactionBuffers = blockTemplate.transactions.map(tx => Buffer.from(tx.hash, 'hex'));
|
||||||
transactionBuffers.unshift(coinbaseBuffer);
|
transactionBuffers.unshift(coinbaseBuffer);
|
||||||
this.tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true });
|
this.tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true });
|
||||||
|
|
||||||
|
|
||||||
// // this.merkle_branch = tree.getProof(coinbaseBuffer).map(p => p.data.toString('hex'))
|
|
||||||
|
|
||||||
// this.merkle_branch = tree.getLayers().map(l => l.pop().toString('hex'));
|
|
||||||
// this.merkleRoot = this.merkle_branch.pop();
|
|
||||||
|
|
||||||
const rootBuffer = this.tree.getRoot();
|
const rootBuffer = this.tree.getRoot();
|
||||||
this.merkleRoot = rootBuffer.toString('hex');
|
this.merkleRoot = rootBuffer.toString('hex');
|
||||||
this.merkle_branch = this.tree.getProof(coinbaseBuffer).map(p => p.data.toString('hex'));
|
this.merkle_branch = this.tree.getProof(coinbaseBuffer).map(p => p.data.toString('hex'));
|
||||||
@@ -84,30 +73,12 @@ export class MiningJob {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateMiningReward(blockHeight: number): number {
|
|
||||||
const initialBlockReward = 50 * 1e8; // Initial block reward in satoshis (1 BTC = 100 million satoshis)
|
|
||||||
const halvingInterval = 210000; // Number of blocks after which the reward halves
|
|
||||||
|
|
||||||
// Calculate the number of times the reward has been halved
|
|
||||||
const halvingCount = Math.floor(blockHeight / halvingInterval);
|
|
||||||
|
|
||||||
// Calculate the current block reward in satoshis
|
|
||||||
const currentReward = initialBlockReward / Math.pow(2, halvingCount);
|
|
||||||
|
|
||||||
return currentReward;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } {
|
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } {
|
||||||
// Generate coinbase script
|
// Part 1
|
||||||
const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}`;
|
const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}`;
|
||||||
|
|
||||||
|
|
||||||
// Create coinbase transaction
|
|
||||||
const outputIndex = 'ffffffff';
|
const outputIndex = 'ffffffff';
|
||||||
const sequence = 'ffffffff';
|
|
||||||
const lockTime = '00000000';
|
|
||||||
const version = '01000000';
|
const version = '01000000';
|
||||||
const inputCount = '01';
|
const inputCount = '01';
|
||||||
const fakeCoinbaseInput = '0000000000000000000000000000000000000000000000000000000000000000';
|
const fakeCoinbaseInput = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
@@ -115,15 +86,14 @@ export class MiningJob {
|
|||||||
const inputScriptBytes = ((blockHeightScript.length + 16) / 2).toString(16).padStart(2, '0');
|
const inputScriptBytes = ((blockHeightScript.length + 16) / 2).toString(16).padStart(2, '0');
|
||||||
|
|
||||||
|
|
||||||
const inputTransaction = inputCount + fakeCoinbaseInput + outputIndex + inputScriptBytes + blockHeightScript;
|
const coinbasePart1 = version + inputCount + fakeCoinbaseInput + outputIndex + inputScriptBytes + blockHeightScript;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//let remainingPayout = reward;
|
// Part 2
|
||||||
|
|
||||||
const outputs = addresses
|
const outputs = addresses
|
||||||
.map((addressObj) => {
|
.map((addressObj) => {
|
||||||
const percentage = addressObj.percentage / 100;
|
const percentage = addressObj.percent / 100;
|
||||||
const satoshis = Math.floor(reward * percentage);
|
const satoshis = Math.floor(reward * percentage);
|
||||||
const satoshiBuff = Buffer.alloc(4);
|
const satoshiBuff = Buffer.alloc(4);
|
||||||
satoshiBuff.writeUInt32LE(satoshis);
|
satoshiBuff.writeUInt32LE(satoshis);
|
||||||
@@ -135,14 +105,13 @@ export class MiningJob {
|
|||||||
})
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
// // Distribute any remaining satoshis to the first address
|
|
||||||
// const firstAddressSatoshis = (parseInt(outputs.substring(0, 16), 16) + remainingPayout).toString(16).padStart(16, '0');
|
|
||||||
|
|
||||||
// Combine coinbasePart1 and coinbasePart2
|
|
||||||
// Create outputs
|
|
||||||
const outputCountHex = addresses.length.toString(16).padStart(2, '0');
|
const outputCountHex = addresses.length.toString(16).padStart(2, '0');
|
||||||
|
|
||||||
const coinbasePart1 = version + inputTransaction;
|
|
||||||
|
const sequence = 'ffffffff';
|
||||||
|
const lockTime = '00000000';
|
||||||
|
|
||||||
const coinbasePart2 = sequence + outputCountHex + outputs + lockTime;
|
const coinbasePart2 = sequence + outputCountHex + outputs + lockTime;
|
||||||
|
|
||||||
return { coinbasePart1, coinbasePart2 };
|
return { coinbasePart1, coinbasePart2 };
|
||||||
@@ -184,4 +153,5 @@ export class MiningJob {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,11 @@ import { plainToInstance } from 'class-transformer';
|
|||||||
import { validate, ValidatorOptions } from 'class-validator';
|
import { validate, ValidatorOptions } from 'class-validator';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { Socket } from 'net';
|
import { Socket } from 'net';
|
||||||
|
import { combineLatest, interval, startWith } from 'rxjs';
|
||||||
|
|
||||||
|
import { BlockTemplateService } from '../BlockTemplateService';
|
||||||
import { StratumV1JobsService } from '../stratum-v1-jobs.service';
|
import { StratumV1JobsService } from '../stratum-v1-jobs.service';
|
||||||
|
import { EasyUnsubscribe } from '../utils/AutoUnsubscribe';
|
||||||
import { eRequestMethod } from './enums/eRequestMethod';
|
import { eRequestMethod } from './enums/eRequestMethod';
|
||||||
import { MiningJob } from './MiningJob';
|
import { MiningJob } from './MiningJob';
|
||||||
import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage';
|
import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage';
|
||||||
@@ -13,7 +16,7 @@ import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
|||||||
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
||||||
|
|
||||||
|
|
||||||
export class StratumV1Client {
|
export class StratumV1Client extends EasyUnsubscribe {
|
||||||
|
|
||||||
private clientSubscription: SubscriptionMessage;
|
private clientSubscription: SubscriptionMessage;
|
||||||
private clientConfiguration: ConfigurationMessage;
|
private clientConfiguration: ConfigurationMessage;
|
||||||
@@ -29,11 +32,15 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
public clientDifficulty: number = 512;
|
public clientDifficulty: number = 512;
|
||||||
|
|
||||||
|
public jobRefreshInterval: NodeJS.Timer;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly socket: Socket,
|
public readonly socket: Socket,
|
||||||
private readonly stratumV1JobsService: StratumV1JobsService
|
private readonly stratumV1JobsService: StratumV1JobsService,
|
||||||
|
private readonly blockTemplateService: BlockTemplateService
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
this.id = this.getRandomHexString();
|
this.id = this.getRandomHexString();
|
||||||
|
|
||||||
console.log(`id: ${this.id}`);
|
console.log(`id: ${this.id}`);
|
||||||
@@ -181,7 +188,6 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
this.handleMiningSubmission(miningSubmitMessage);
|
this.handleMiningSubmission(miningSubmitMessage);
|
||||||
//this.clientSubmission.next(miningSubmitMessage)
|
|
||||||
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
|
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
|
||||||
} else {
|
} else {
|
||||||
console.error(errors);
|
console.error(errors);
|
||||||
@@ -199,35 +205,44 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
this.stratumInitialized = true;
|
this.stratumInitialized = true;
|
||||||
|
|
||||||
this.newBlock(this.stratumV1JobsService.getLatestJob());
|
|
||||||
|
|
||||||
}
|
let lastIntervalCount = undefined;
|
||||||
}
|
combineLatest([this.blockTemplateService.currentBlockTemplate$, interval(60000).pipe(startWith(-1))]).subscribe(([{ miningInfo, blockTemplate }, interValCount]) => {
|
||||||
|
let clearJobs = false;
|
||||||
|
if (lastIntervalCount === interValCount) {
|
||||||
|
clearJobs = true;
|
||||||
|
}
|
||||||
|
lastIntervalCount = interValCount;
|
||||||
|
|
||||||
|
const job = new MiningJob(this.stratumV1JobsService.getNextId(), [{ address: this.clientAuthorization.address, percent: 100 }], blockTemplate, miningInfo.difficulty, clearJobs);
|
||||||
|
|
||||||
|
this.stratumV1JobsService.addJob(job, clearJobs);
|
||||||
|
|
||||||
|
this.socket.write(job.response + '\n');
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
public newBlock(job: MiningJob) {
|
|
||||||
this.newJob(job);
|
|
||||||
|
|
||||||
clearInterval(this.refreshInterval);
|
|
||||||
this.refreshInterval = setInterval(async () => {
|
|
||||||
this.newJob(this.stratumV1JobsService.getLatestJob());
|
|
||||||
}, 60000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private newJob(job: MiningJob) {
|
|
||||||
if (this.stratumInitialized && job != null) {
|
|
||||||
this.socket.write(job.response + '\n');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private handleMiningSubmission(submission: MiningSubmitMessage) {
|
private handleMiningSubmission(submission: MiningSubmitMessage) {
|
||||||
const networkDifficulty = 0;
|
|
||||||
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
||||||
const diff = submission.testNonceValue(this.id, job, submission);
|
const diff = submission.calculateDifficulty(this.id, job, submission);
|
||||||
console.log(`DIFF: ${diff}`);
|
console.log(`DIFF: ${diff}`);
|
||||||
if (networkDifficulty < diff) {
|
if (diff >= this.clientDifficulty) {
|
||||||
//this.clientSubmission.next(true);
|
|
||||||
|
if (diff >= job.networkDifficulty) {
|
||||||
|
console.log('!!! BOCK FOUND !!!');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Difficulty too low`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,9 +14,17 @@ export class AuthorizationMessage extends StratumBaseMessage {
|
|||||||
@Expose()
|
@Expose()
|
||||||
@IsString()
|
@IsString()
|
||||||
@Transform(({ value, key, obj, type }) => {
|
@Transform(({ value, key, obj, type }) => {
|
||||||
return obj.params[0];
|
return obj.params[0].split('.')[0];
|
||||||
})
|
})
|
||||||
public username: string;
|
public address: string;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
@IsString()
|
||||||
|
@Transform(({ value, key, obj, type }) => {
|
||||||
|
return obj.params[0].split('.')[1];
|
||||||
|
})
|
||||||
|
public worker: string;
|
||||||
|
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public testNonceValue(clientId: string, job: MiningJob, submission: MiningSubmitMessage): number {
|
public calculateDifficulty(clientId: string, job: MiningJob, submission: MiningSubmitMessage): number {
|
||||||
|
|
||||||
const nonce = parseInt(submission.nonce, 16);
|
const nonce = parseInt(submission.nonce, 16);
|
||||||
const versionMask = parseInt(submission.versionMask, 16);
|
const versionMask = parseInt(submission.versionMask, 16);
|
||||||
@@ -76,13 +76,10 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
|
|
||||||
const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch)
|
const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch)
|
||||||
|
|
||||||
|
|
||||||
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
||||||
|
|
||||||
const header = Buffer.alloc(80);
|
const header = Buffer.alloc(80);
|
||||||
|
|
||||||
// TODO: Use the midstate hash instead of hashing the whole header
|
|
||||||
|
|
||||||
let version = job.version;
|
let version = job.version;
|
||||||
if (versionMask !== undefined && versionMask != 0) {
|
if (versionMask !== undefined && versionMask != 0) {
|
||||||
version = (version ^ versionMask);
|
version = (version ^ versionMask);
|
||||||
@@ -97,10 +94,6 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
header.writeBigUint64LE(BigInt(job.nbits), 72);
|
header.writeBigUint64LE(BigInt(job.nbits), 72);
|
||||||
header.writeUInt32LE(nonce, 76);
|
header.writeUInt32LE(nonce, 76);
|
||||||
|
|
||||||
// for (let i = 0; i < 80; i++) {
|
|
||||||
// console.log(header[i].toString(10));
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest();
|
const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest();
|
||||||
const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest();
|
const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest();
|
||||||
@@ -108,7 +101,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
|
|
||||||
let s64 = this.le256todouble(hashResult);
|
let s64 = this.le256todouble(hashResult);
|
||||||
|
|
||||||
return parseInt(truediffone.div(s64.toString()).toString());
|
return truediffone.div(s64.toString()).toNumber();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -132,7 +125,6 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
}, BigInt(0));
|
}, BigInt(0));
|
||||||
|
|
||||||
return number;
|
return number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateMerkleRootHash(coinbaseTx: string, merkleBranches: string[]): Buffer {
|
private calculateMerkleRootHash(coinbaseTx: string, merkleBranches: string[]): Buffer {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { MiningJob } from './models/MiningJob';
|
import { MiningJob } from './models/MiningJob';
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class StratumV1JobsService {
|
export class StratumV1JobsService {
|
||||||
|
|
||||||
public latestJobId: number = 1;
|
public latestJobId: number = 1;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable, OnModuleInit } from '@nestjs/common';
|
|||||||
import { Server, Socket } from 'net';
|
import { Server, Socket } from 'net';
|
||||||
|
|
||||||
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
||||||
import { MiningJob } from './models/MiningJob';
|
import { BlockTemplateService } from './BlockTemplateService';
|
||||||
import { StratumV1Client } from './models/StratumV1Client';
|
import { StratumV1Client } from './models/StratumV1Client';
|
||||||
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly bitcoinRpcService: BitcoinRpcService,
|
private readonly bitcoinRpcService: BitcoinRpcService,
|
||||||
private readonly stratumV1JobsService: StratumV1JobsService
|
private readonly blockTemplateService: BlockTemplateService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
|
|
||||||
this.startSocketServer();
|
this.startSocketServer();
|
||||||
|
|
||||||
this.listenForNewBlocks();
|
//this.listenForNewBlocks();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
|
|
||||||
console.log('New client connected:', socket.remoteAddress);
|
console.log('New client connected:', socket.remoteAddress);
|
||||||
|
|
||||||
const client = new StratumV1Client(socket, this.stratumV1JobsService);
|
const client = new StratumV1Client(socket, new StratumV1JobsService(), this.blockTemplateService);
|
||||||
this.clients.push(client);
|
this.clients.push(client);
|
||||||
|
|
||||||
|
|
||||||
@@ -59,37 +59,6 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private listenForNewBlocks() {
|
|
||||||
this.bitcoinRpcService.newBlock().subscribe(async () => {
|
|
||||||
console.log('NEW BLOCK')
|
|
||||||
this.resetMiningNotifyInterval();
|
|
||||||
|
|
||||||
const blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
|
|
||||||
const job = new MiningJob(this.stratumV1JobsService.getNextId(), blockTemplate, true);
|
|
||||||
this.stratumV1JobsService.addJob(job, true);
|
|
||||||
|
|
||||||
|
|
||||||
this.clients
|
|
||||||
.filter(client => client.stratumInitialized)
|
|
||||||
.forEach(client => {
|
|
||||||
client.newBlock(job);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private resetMiningNotifyInterval() {
|
|
||||||
clearInterval(this.miningNotifyInterval);
|
|
||||||
this.miningNotifyInterval = setInterval(async () => {
|
|
||||||
|
|
||||||
const blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
|
|
||||||
const job = new MiningJob(this.stratumV1JobsService.getNextId(), blockTemplate, false);
|
|
||||||
this.stratumV1JobsService.addJob(job, false);
|
|
||||||
|
|
||||||
}, 60000);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user