mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
increment ids
This commit is contained in:
+1
-1
@@ -73,4 +73,4 @@
|
|||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-3
@@ -4,8 +4,9 @@ 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 { BitcoinStratumProvider } from './bitcoin-stratum.provider';
|
|
||||||
import { CoinbaseConstructorService } from './coinbase-constructor.service';
|
import { CoinbaseConstructorService } from './coinbase-constructor.service';
|
||||||
|
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
||||||
|
import { StratumV1Service } from './stratum-v1.service';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -16,9 +17,10 @@ import { CoinbaseConstructorService } from './coinbase-constructor.service';
|
|||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
AppService,
|
AppService,
|
||||||
BitcoinStratumProvider,
|
StratumV1Service,
|
||||||
BitcoinRpcService,
|
BitcoinRpcService,
|
||||||
CoinbaseConstructorService
|
CoinbaseConstructorService,
|
||||||
|
StratumV1JobsService
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
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 { IBlockTemplate } from './models/bitcoin-rpc/IBlockTemplate';
|
import { IBlockTemplate } from './models/bitcoin-rpc/IBlockTemplate';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
|
||||||
import { IMiningInfo } from './models/bitcoin-rpc/IMiningInfo';
|
import { IMiningInfo } from './models/bitcoin-rpc/IMiningInfo';
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export class BitcoinRpcService {
|
|||||||
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);
|
// console.log(miningInfo);
|
||||||
if (this.blockHeight != 0) {
|
if (this.blockHeight != 0) {
|
||||||
this.newBlock$.next(miningInfo.blocks + 1);
|
this.newBlock$.next(miningInfo.blocks + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
|
||||||
import { Server, Socket } from 'net';
|
|
||||||
import { BehaviorSubject, skip, take } from 'rxjs';
|
|
||||||
|
|
||||||
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
|
||||||
import { IBlockTemplate } from './models/bitcoin-rpc/IBlockTemplate';
|
|
||||||
import { MiningJob } from './models/MiningJob';
|
|
||||||
import { StratumV1Client } from './models/StratumV1Client';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class BitcoinStratumProvider implements OnModuleInit {
|
|
||||||
|
|
||||||
public clients: StratumV1Client[] = [];
|
|
||||||
|
|
||||||
private server: Server;
|
|
||||||
|
|
||||||
private blockTemplate: IBlockTemplate;
|
|
||||||
|
|
||||||
private interval: NodeJS.Timer;
|
|
||||||
|
|
||||||
private newMiningJobEmitter: BehaviorSubject<MiningJob> = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
private latestJob: MiningJob;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(private readonly bitcoinRpcService: BitcoinRpcService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async onModuleInit(): Promise<void> {
|
|
||||||
console.log('onModuleInit');
|
|
||||||
|
|
||||||
this.server = new Server((socket: Socket) => {
|
|
||||||
console.log('New client connected:', socket.remoteAddress);
|
|
||||||
|
|
||||||
const client = new StratumV1Client(socket, this.newMiningJobEmitter.asObservable());
|
|
||||||
|
|
||||||
client.onInitialized.pipe(skip(1), take(1)).subscribe(() => {
|
|
||||||
console.log('Client Ready')
|
|
||||||
if (this.latestJob == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.latestJob.constructResponse();
|
|
||||||
client.localMiningJobEmitter.next(this.latestJob);
|
|
||||||
});
|
|
||||||
|
|
||||||
// this.clients.push(client);
|
|
||||||
|
|
||||||
// console.log('Number of Clients:', this.clients.length);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
this.bitcoinRpcService.newBlock().subscribe(async () => {
|
|
||||||
console.log('NEW BLOCK')
|
|
||||||
|
|
||||||
this.blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
|
|
||||||
this.latestJob = new MiningJob(this.blockTemplate)
|
|
||||||
|
|
||||||
this.latestJob.constructResponse();
|
|
||||||
this.newMiningJobEmitter.next(this.latestJob);
|
|
||||||
clearInterval(this.interval);
|
|
||||||
this.interval = setInterval(async () => {
|
|
||||||
this.blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
|
|
||||||
|
|
||||||
this.latestJob = new MiningJob(this.blockTemplate)
|
|
||||||
this.latestJob.constructResponse();
|
|
||||||
this.newMiningJobEmitter.next(this.latestJob);
|
|
||||||
}, 60000);
|
|
||||||
})
|
|
||||||
|
|
||||||
this.server.listen(3333, () => {
|
|
||||||
console.log(`Bitcoin Stratum server is listening on port ${3333}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
+9
-16
@@ -16,7 +16,7 @@ export class MiningJob {
|
|||||||
public target: string;
|
public target: string;
|
||||||
public merkleRoot: string;
|
public merkleRoot: string;
|
||||||
|
|
||||||
public job_id: number; // ID of the job. Use this ID while submitting share generated from this job.
|
public job_id: string; // ID of the job. Use this ID while submitting share generated from this job.
|
||||||
public prevhash: string; // The hex-encoded previous block hash.
|
public prevhash: string; // The hex-encoded previous block hash.
|
||||||
public coinb1: string; // The hex-encoded prefix of the coinbase transaction (to precede extra nonce 2).
|
public coinb1: string; // The hex-encoded prefix of the coinbase transaction (to precede extra nonce 2).
|
||||||
public coinb2: string; //The hex-encoded suffix of the coinbase transaction (to follow extra nonce 2).
|
public coinb2: string; //The hex-encoded suffix of the coinbase transaction (to follow extra nonce 2).
|
||||||
@@ -32,26 +32,26 @@ export class MiningJob {
|
|||||||
|
|
||||||
public tree: MerkleTree;
|
public tree: MerkleTree;
|
||||||
|
|
||||||
constructor(blockTemplate: IBlockTemplate) {
|
constructor(id: string, blockTemplate: IBlockTemplate, _cleanJobs: boolean) {
|
||||||
|
|
||||||
//console.log(blockTemplate);
|
//console.log(blockTemplate);
|
||||||
|
|
||||||
this.job_id = 1;
|
this.job_id = id;
|
||||||
this.target = blockTemplate.target;
|
this.target = blockTemplate.target;
|
||||||
this.prevhash = this.convertToLittleEndian(blockTemplate.previousblockhash);
|
this.prevhash = this.convertToLittleEndian(blockTemplate.previousblockhash);
|
||||||
|
|
||||||
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 = false;
|
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);
|
const miningReward = this.calculateMiningReward(blockTemplate.height);
|
||||||
console.log('TRANSACTION FEES', transactionFees);
|
//console.log('TRANSACTION FEES', transactionFees);
|
||||||
console.log('MINING REWARD', miningReward);
|
//console.log('MINING REWARD', miningReward);
|
||||||
|
|
||||||
const { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction([{ address: '', percentage: 100 }], blockTemplate.height, transactionFees + miningReward);
|
const { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction([{ address: '', percentage: 100 }], blockTemplate.height, transactionFees + miningReward);
|
||||||
|
|
||||||
@@ -79,10 +79,7 @@ export class MiningJob {
|
|||||||
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'));
|
||||||
|
|
||||||
|
|
||||||
// let test = this.tree.getRoot();
|
this.constructResponse();
|
||||||
// for (let i = 0; i < test.length; i++) {
|
|
||||||
// console.log(test[i])
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +96,6 @@ export class MiningJob {
|
|||||||
return currentReward;
|
return currentReward;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bufferToHex(buffer: Buffer): string {
|
|
||||||
return buffer.toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } {
|
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } {
|
||||||
@@ -149,13 +142,13 @@ export class MiningJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public constructResponse() {
|
private constructResponse() {
|
||||||
|
|
||||||
const job = {
|
const job = {
|
||||||
id: 0,
|
id: 0,
|
||||||
method: eResponseMethod.MINING_NOTIFY,
|
method: eResponseMethod.MINING_NOTIFY,
|
||||||
params: [
|
params: [
|
||||||
this.job_id.toString(16),
|
this.job_id,
|
||||||
this.prevhash,
|
this.prevhash,
|
||||||
this.coinb1,
|
this.coinb1,
|
||||||
this.coinb2,
|
this.coinb2,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { validate, ValidatorOptions } from 'class-validator';
|
import { validate, ValidatorOptions } from 'class-validator';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
import { Socket } from 'net';
|
import { Socket } from 'net';
|
||||||
import { BehaviorSubject, merge, Observable, takeUntil } from 'rxjs';
|
|
||||||
|
|
||||||
import { EasyUnsubscribe } from '../utils/AutoUnsubscribe';
|
import { StratumV1JobsService } from '../stratum-v1-jobs.service';
|
||||||
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';
|
||||||
@@ -12,53 +12,40 @@ import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage';
|
|||||||
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
||||||
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
||||||
|
|
||||||
export class StratumV1Client extends EasyUnsubscribe {
|
|
||||||
|
export class StratumV1Client {
|
||||||
|
|
||||||
private clientSubscription: SubscriptionMessage;
|
private clientSubscription: SubscriptionMessage;
|
||||||
private clientConfiguration: ConfigurationMessage;
|
private clientConfiguration: ConfigurationMessage;
|
||||||
private clientAuthorization: AuthorizationMessage;
|
private clientAuthorization: AuthorizationMessage;
|
||||||
private clientSuggestedDifficulty: SuggestDifficulty;
|
private clientSuggestedDifficulty: SuggestDifficulty;
|
||||||
|
|
||||||
public initialized = false;
|
// public clientSubmission: BehaviorSubject<any> = new BehaviorSubject(null);
|
||||||
|
|
||||||
|
public id: string;
|
||||||
|
public stratumInitialized = false;
|
||||||
|
|
||||||
public onInitialized: BehaviorSubject<void> = new BehaviorSubject(null);
|
public refreshInterval: NodeJS.Timer;
|
||||||
public localMiningJobEmitter: BehaviorSubject<MiningJob> = new BehaviorSubject(null);
|
|
||||||
public blockFoundEmitter: BehaviorSubject<any> = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
|
|
||||||
private currentJob: MiningJob;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly socket: Socket,
|
public readonly socket: Socket,
|
||||||
private readonly globalMiningJobEmitter: Observable<MiningJob>
|
private readonly stratumV1JobsService: StratumV1JobsService
|
||||||
) {
|
) {
|
||||||
super();
|
|
||||||
|
this.id = this.getRandomHexString();
|
||||||
|
|
||||||
|
console.log(`id: ${this.id}`);
|
||||||
|
|
||||||
this.socket.on('data', this.handleData.bind(this, this.socket));
|
this.socket.on('data', this.handleData.bind(this, this.socket));
|
||||||
|
|
||||||
this.socket.on('end', () => {
|
|
||||||
// Handle socket disconnection
|
|
||||||
console.log('Client disconnected:', socket.remoteAddress);
|
|
||||||
this.unsubscribeAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('error', (error: Error) => {
|
|
||||||
// Handle socket error
|
|
||||||
console.error('Socket error:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
merge(this.globalMiningJobEmitter, this.localMiningJobEmitter).pipe(takeUntil(this.easyUnsubscribe)).subscribe((job: MiningJob) => {
|
|
||||||
this.currentJob = job;
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.socket.write(job.response + '\n');
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
private async handleData(socket: Socket, data: Buffer) {
|
private async handleData(socket: Socket, data: Buffer) {
|
||||||
const message = data.toString();
|
const message = data.toString();
|
||||||
@@ -98,7 +85,7 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
this.clientSubscription = subscriptionMessage;
|
this.clientSubscription = subscriptionMessage;
|
||||||
|
|
||||||
socket.write(JSON.stringify(this.clientSubscription.response()) + '\n');
|
socket.write(JSON.stringify(this.clientSubscription.response(this.id)) + '\n');
|
||||||
} else {
|
} else {
|
||||||
console.error(errors);
|
console.error(errors);
|
||||||
}
|
}
|
||||||
@@ -189,9 +176,8 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
const errors = await validate(miningSubmitMessage, validatorOptions);
|
const errors = await validate(miningSubmitMessage, validatorOptions);
|
||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
//this.clientSuggestedDifficulty = miningSubmitMessage;
|
|
||||||
miningSubmitMessage.parse();
|
|
||||||
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);
|
||||||
@@ -200,63 +186,45 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.initialized) {
|
|
||||||
if (this.clientSubscription != null
|
|
||||||
&& this.clientConfiguration != null
|
|
||||||
&& this.clientAuthorization != null
|
|
||||||
&& this.clientSuggestedDifficulty != null) {
|
|
||||||
this.initialized = true;
|
|
||||||
|
|
||||||
this.onInitialized.next();
|
if (this.clientSubscription != null
|
||||||
|
&& this.clientConfiguration != null
|
||||||
|
&& this.clientAuthorization != null
|
||||||
|
&& this.clientSuggestedDifficulty != null
|
||||||
|
&& this.stratumInitialized == false) {
|
||||||
|
|
||||||
}
|
this.stratumInitialized = true;
|
||||||
|
|
||||||
|
this.newBlock(this.stratumV1JobsService.getLatestJob());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
submission.parse();
|
||||||
const networkDifficulty = 0;
|
const networkDifficulty = 0;
|
||||||
const diff = submission.testNonceValue(this.currentJob, submission);
|
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
||||||
console.log('DIFF');
|
const diff = submission.testNonceValue(this.id, job, submission);
|
||||||
console.log(diff);
|
console.log(`DIFF: ${diff}`);
|
||||||
if (networkDifficulty < diff) {
|
if (networkDifficulty < diff) {
|
||||||
this.blockFoundEmitter.next(true);
|
//this.clientSubmission.next(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private miningNotify() {
|
|
||||||
// const notification = {
|
|
||||||
// id: null,
|
|
||||||
// method: eResponseMethod.MINING_NOTIFY,
|
|
||||||
// params: [
|
|
||||||
// '64756fab0000442e',
|
|
||||||
// '39dbb5b4e173e1f9ac6f6ad92e9dde300effce6b0003ea860000000000000000',
|
|
||||||
// '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff35033e1c0c00048fef83640447483e060c',
|
|
||||||
// '0a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff03e1c7872600000000160014876961d21eaba10f0701dcc78f6624a4c682270d384dc900000000001976a914f4cbe6c6bb3a8535c963169c22963d3a20e7686988ac0000000000000000266a24aa21a9ed751521cd94e7780a9a13ac8bb1da5410d30acdd2f6348876835623b04b2dc83b00000000',
|
|
||||||
// [
|
|
||||||
// 'c0c9d351b9e094dd85bc64c8909dece6226269ebfe173bb74ba2f89c51df7066',
|
|
||||||
// '6c56f47cbfaef5688bb338bc56c4189530f12cdb98f8cc46b6a12053f1e69fdd',
|
|
||||||
// '697cdaa8d15691f7b30dfe7f6c33957f04cfc8126fed16d2fe38c96adaa59c41',
|
|
||||||
// 'cbe8aa6e0343884a40b850df8fd3c2ffcc026acec392ce93f9e28619eb0d3dac',
|
|
||||||
// 'e023091cf0fc02684c77730a34791181a44be92f966ba579aa4cf6e98d754548',
|
|
||||||
// '6b8fea64efe363e02ff42a4257d76a77381006eade804c8a8c9b96c9c98b1d9e',
|
|
||||||
// '1c936bc5320cdbbe7348cdd4bf272529822e9d34dfa8e0ee0041eae635891cc3',
|
|
||||||
// '8fed2682a3c95863c6b9440b1a47abdd3d4230181f61edf0daa2e5f0befbcf65',
|
|
||||||
// '0837c4d162e1086ec553ea90af4be4f9747958e556598cc38cb08149e58227b1',
|
|
||||||
// '0b5287e647c7cb6f2fcdf13d5ef3bf091d1137773d7695405d0b2768f442ee78',
|
|
||||||
// '71eaff9247b5556fa88bca6da2e055d5db8aef2969a2d5c68f8e7efd7d39a283'
|
|
||||||
// ],
|
|
||||||
// '20000000',
|
|
||||||
// '17057e69',
|
|
||||||
// '6483ef8f',
|
|
||||||
// true
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// };
|
|
||||||
|
|
||||||
// this.socket.write(JSON.stringify(notification) + '\n');
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -43,11 +43,11 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public testNonceValue(job: MiningJob, submission: MiningSubmitMessage): number {
|
public testNonceValue(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);
|
||||||
const extraNonce = 'ccc5d664';
|
const extraNonce = clientId;
|
||||||
const extraNonce2 = submission.extraNonce2;
|
const extraNonce2 = submission.extraNonce2;
|
||||||
|
|
||||||
const coinbaseTx = `${job.coinb1}${extraNonce}${extraNonce2}${job.coinb2}`;
|
const coinbaseTx = `${job.coinb1}${extraNonce}${extraNonce2}${job.coinb2}`;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
|||||||
console.log('constructor SubscriptionMessage');
|
console.log('constructor SubscriptionMessage');
|
||||||
}
|
}
|
||||||
|
|
||||||
public response() {
|
public response(clientId: string) {
|
||||||
return {
|
return {
|
||||||
id: null,
|
id: null,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -24,7 +24,7 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
|||||||
[
|
[
|
||||||
['mining.notify', '64d8c004']
|
['mining.notify', '64d8c004']
|
||||||
], //subscription details
|
], //subscription details
|
||||||
'ccc5d664', //Extranonce1 - Hex-encoded, per-connection unique string which will be used for coinbase serialization later. Keep it safe!
|
clientId, //Extranonce1 - Hex-encoded, per-connection unique string which will be used for coinbase serialization later. Keep it safe!
|
||||||
8 //Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner.
|
8 //Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner.
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { MiningJob } from './models/MiningJob';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StratumV1JobsService {
|
||||||
|
|
||||||
|
public latestJobId: number = 1;
|
||||||
|
|
||||||
|
public jobs: MiningJob[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
public addJob(job: MiningJob, clearJobs: boolean) {
|
||||||
|
if (clearJobs) {
|
||||||
|
this.jobs = [];
|
||||||
|
}
|
||||||
|
this.jobs.push(job);
|
||||||
|
this.latestJobId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLatestJob() {
|
||||||
|
return this.jobs[this.jobs.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getJobById(jobId: string) {
|
||||||
|
return this.jobs.find(job => job.job_id == jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNextId() {
|
||||||
|
return this.latestJobId.toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { Server, Socket } from 'net';
|
||||||
|
|
||||||
|
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
||||||
|
import { MiningJob } from './models/MiningJob';
|
||||||
|
import { StratumV1Client } from './models/StratumV1Client';
|
||||||
|
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StratumV1Service implements OnModuleInit {
|
||||||
|
|
||||||
|
private miningNotifyInterval: NodeJS.Timer;
|
||||||
|
|
||||||
|
|
||||||
|
public clients: StratumV1Client[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly bitcoinRpcService: BitcoinRpcService,
|
||||||
|
private readonly stratumV1JobsService: StratumV1JobsService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async onModuleInit(): Promise<void> {
|
||||||
|
|
||||||
|
this.startSocketServer();
|
||||||
|
|
||||||
|
this.listenForNewBlocks();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private startSocketServer() {
|
||||||
|
new Server((socket: Socket) => {
|
||||||
|
|
||||||
|
console.log('New client connected:', socket.remoteAddress);
|
||||||
|
|
||||||
|
const client = new StratumV1Client(socket, this.stratumV1JobsService);
|
||||||
|
this.clients.push(client);
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('end', () => {
|
||||||
|
// Handle socket disconnection
|
||||||
|
console.log('Client disconnected:', socket.remoteAddress);
|
||||||
|
this.clients = this.clients.filter(c => c.id == client.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', (error: Error) => {
|
||||||
|
// Handle socket error
|
||||||
|
console.error('Socket error:', error);
|
||||||
|
this.clients = this.clients.filter(c => c.id == client.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
}).listen(3333, () => {
|
||||||
|
console.log(`Bitcoin Stratum server is listening on port ${3333}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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