mining is submitting

This commit is contained in:
Ben Wilson
2023-06-10 13:58:27 -04:00
parent deb3aa30d4
commit 2aad736628
6 changed files with 164 additions and 80 deletions
+5 -11
View File
@@ -1,4 +1,4 @@
import { Injectable, OnModuleInit } 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';
@@ -6,7 +6,7 @@ import { IBlockTempalte } from './models/IBlockTempalte';
@Injectable() @Injectable()
export class BitcoinRpcService implements OnModuleInit { export class BitcoinRpcService {
private client: RPCClient; private client: RPCClient;
@@ -21,16 +21,10 @@ export class BitcoinRpcService implements OnModuleInit {
console.log('Bitcoin RPC connected'); console.log('Bitcoin RPC connected');
} }
async onModuleInit(): Promise<void> {
console.log('onModuleInit');
// Asynchronous initialization logic
//await this.getBlockTemplate();
return;
}
public async getBlockTemplate() {
public async getBlockTemplate(): Promise<IBlockTempalte> {
const result: IBlockTempalte = await this.client.getblocktemplate({ const result: IBlockTempalte = await this.client.getblocktemplate({
template_request: { template_request: {
@@ -40,7 +34,7 @@ export class BitcoinRpcService implements OnModuleInit {
} }
}); });
console.log('Block Template: ', JSON.stringify(result)); return result;
} }
} }
+46 -7
View File
@@ -1,34 +1,73 @@
import { Injectable } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import { Server, Socket } from 'net'; import { Server, Socket } from 'net';
import { BehaviorSubject, take } from 'rxjs';
import { CoinbaseConstructorService } from './coinbase-constructor.service'; import { BitcoinRpcService } from './bitcoin-rpc.service';
import { IBlockTempalte } from './models/IBlockTempalte';
import { MiningJob } from './models/MiningJob';
import { StratumV1Client } from './models/StratumV1Client'; import { StratumV1Client } from './models/StratumV1Client';
@Injectable() @Injectable()
export class BitcoinStratumProvider { export class BitcoinStratumProvider implements OnModuleInit {
public clients: StratumV1Client[] = []; public clients: StratumV1Client[] = [];
private server: Server; private server: Server;
constructor(private coinbaseConstructorService: CoinbaseConstructorService) { private blockTemplate: IBlockTempalte;
private interval: NodeJS.Timer;
private newMiningJobEmitter: BehaviorSubject<string> = new BehaviorSubject(null);
private latestJob: MiningJob;
constructor(private readonly bitcoinRpcService: BitcoinRpcService) {
this.server = new Server((socket: Socket) => { this.server = new Server((socket: Socket) => {
console.log('New client connected:', socket.remoteAddress); console.log('New client connected:', socket.remoteAddress);
const client = new StratumV1Client(socket); const client = new StratumV1Client(socket, this.newMiningJobEmitter.asObservable());
this.clients.push(client);
console.log('Number of Clients:', this.clients.length); client.onInitialized.pipe(take(1)).subscribe(() => {
if (this.latestJob == null) {
return;
}
const job = this.latestJob.response();
const jobString = JSON.stringify(job);
client.localMiningJobEmitter.next(jobString);
});
// this.clients.push(client);
// console.log('Number of Clients:', this.clients.length);
}); });
} }
async onModuleInit(): Promise<void> {
console.log('onModuleInit');
this.blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
clearInterval(this.interval);
const job = new MiningJob(this.blockTemplate).response();
this.newMiningJobEmitter.next(JSON.stringify(job));
this.interval = setInterval(() => {
this.latestJob = new MiningJob(this.blockTemplate);
const job = this.latestJob.response();
const jobString = JSON.stringify(job);
this.newMiningJobEmitter.next(jobString);
}, 60000);
return;
}
listen(port: number) { listen(port: number) {
+52 -17
View File
@@ -1,6 +1,8 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { eResponseMethod } from './enums/eResponseMethod'; import { eResponseMethod } from './enums/eResponseMethod';
import { IBlockTempalte } from './IBlockTempalte';
import { randomUUID } from 'crypto';
export class MiningJob { export class MiningJob {
public id: number; public id: number;
@@ -18,29 +20,38 @@ export class MiningJob {
public ntime: string; // Current ntime/ public ntime: string; // 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.
constructor() { constructor(blockTemplate: IBlockTempalte) {
} this.job_id = randomUUID();
this.prevhash = blockTemplate.previousblockhash;
public response() { this.version = blockTemplate.version.toString();
this.nbits = blockTemplate.bits;
this.job_id = null;
this.prevhash = null;
this.coinb1 = null;
this.coinb2 = null;
this.merkle_branch = null;
this.version = null;
this.nbits = null;
this.ntime = Math.floor(new Date().getTime() / 1000).toString(); this.ntime = Math.floor(new Date().getTime() / 1000).toString();
this.clean_jobs = false; this.clean_jobs = false;
// Construct coinbase transaction
const coinbaseTransaction = blockTemplate.transactions[0];
const coinbaseHashBin = this.buildCoinbaseHashBin(coinbaseTransaction.data);
this.coinb1 = this.coinbasePrefix(coinbaseTransaction.data, coinbaseHashBin);
this.coinb2 = ''; // Assuming no suffix is required
// Calculate merkle branch
const merkleBranch = blockTemplate.transactions.slice(1).map((transaction) => transaction.hash);
this.merkle_branch = this.buildMerkleBranch(merkleBranch, coinbaseHashBin);
}
public response() {
return { return {
id: this.id, id: 0,
method: this.method, method: eResponseMethod.MINING_NOTIFY,
params: [ params: [
this.job_id, '123',///this.job_id,
this.prevhash, this.prevhash,
this.coinb1, this.coinb1,
this.coinb2, this.coinb2,
@@ -55,7 +66,18 @@ export class MiningJob {
} }
public buildCoinbaseHashBin(coinbase: string): Buffer { private coinbasePrefix(coinbase: string, coinbaseHashBin: Buffer): string {
const coinbaseData = Buffer.from(coinbase, 'hex');
const coinbaseSize = Buffer.alloc(1, coinbaseData.length);
const extraNoncePlaceholder = Buffer.alloc(4); // Assuming 4 bytes for extra nonce
const concatenatedBuffer = Buffer.concat([coinbaseSize, coinbaseData, extraNoncePlaceholder]);
const merkleRoot = this.doubleSHA(Buffer.concat([coinbaseHashBin, concatenatedBuffer]));
return merkleRoot.toString('hex');
}
private buildCoinbaseHashBin(coinbase: string): Buffer {
const sha256 = crypto.createHash('sha256'); const sha256 = crypto.createHash('sha256');
const sha256Digest = sha256.update(Buffer.from(coinbase, 'hex')).digest(); const sha256Digest = sha256.update(Buffer.from(coinbase, 'hex')).digest();
@@ -65,15 +87,28 @@ export class MiningJob {
return coinbaseHash; return coinbaseHash;
} }
public buildMerkleRoot(merkleBranch: string[], coinbaseHashBin: Buffer): string { private buildMerkleBranch(merkleBranch: string[], coinbaseHashBin: Buffer): string[] {
const merkleRoots: string[] = [];
let merkleRoot = coinbaseHashBin; let merkleRoot = coinbaseHashBin;
for (const h of merkleBranch) { for (const h of merkleBranch) {
const concatenatedBuffer = Buffer.concat([merkleRoot, Buffer.from(h, 'hex')]); const concatenatedBuffer = Buffer.concat([merkleRoot, Buffer.from(h, 'hex')]);
merkleRoot = this.doubleSHA(concatenatedBuffer); merkleRoot = this.doubleSHA(concatenatedBuffer);
merkleRoots.push(merkleRoot.toString('hex'));
} }
return merkleRoot.toString('hex');
return merkleRoots.slice(0, 1);
} }
// private buildMerkleRoot(merkleBranch: string[], coinbaseHashBin: Buffer): string {
// let merkleRoot = coinbaseHashBin;
// for (const h of merkleBranch) {
// const concatenatedBuffer = Buffer.concat([merkleRoot, Buffer.from(h, 'hex')]);
// merkleRoot = this.doubleSHA(concatenatedBuffer);
// }
// return merkleRoot.toString('hex');
// }
private doubleSHA(data: Buffer): Buffer { private doubleSHA(data: Buffer): Buffer {
const sha256 = crypto.createHash('sha256'); const sha256 = crypto.createHash('sha256');
const sha256Digest = sha256.update(data).digest(); const sha256Digest = sha256.update(data).digest();
+50 -44
View File
@@ -1,17 +1,17 @@
import { plainToInstance } from 'class-transformer'; import { plainToInstance } from 'class-transformer';
import { validate, ValidatorOptions } from 'class-validator'; import { validate, ValidatorOptions } from 'class-validator';
import { Socket } from 'net'; import { Socket } from 'net';
import { interval } from 'rxjs'; import { BehaviorSubject, combineLatest, concat, merge, Observable, takeUntil } from 'rxjs';
import { EasyUnsubscribe } from '../utils/AutoUnsubscribe';
import { eRequestMethod } from './enums/eRequestMethod'; import { eRequestMethod } from './enums/eRequestMethod';
import { eResponseMethod } from './enums/eResponseMethod';
import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage'; import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage';
import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage'; import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage';
import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage'; 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 { export class StratumV1Client extends EasyUnsubscribe {
private clientSubscription: SubscriptionMessage; private clientSubscription: SubscriptionMessage;
private clientConfiguration: ConfigurationMessage; private clientConfiguration: ConfigurationMessage;
@@ -20,17 +20,23 @@ export class StratumV1Client {
public initialized = false; public initialized = false;
private interval: NodeJS.Timer;
public onInitialized: BehaviorSubject<void> = new BehaviorSubject(null);
public localMiningJobEmitter: BehaviorSubject<string> = new BehaviorSubject(null);
constructor(
constructor(private readonly socket: Socket) { private readonly socket: Socket,
private readonly globalMiningJobEmitter: Observable<string>
) {
super();
this.socket.on('data', this.handleData.bind(this, this.socket)); this.socket.on('data', this.handleData.bind(this, this.socket));
this.socket.on('end', () => { this.socket.on('end', () => {
// Handle socket disconnection // Handle socket disconnection
console.log('Client disconnected:', socket.remoteAddress); console.log('Client disconnected:', socket.remoteAddress);
this.unsubscribeAll();
}); });
this.socket.on('error', (error: Error) => { this.socket.on('error', (error: Error) => {
@@ -38,6 +44,13 @@ export class StratumV1Client {
console.error('Socket error:', error); console.error('Socket error:', error);
}); });
merge(this.globalMiningJobEmitter, this.localMiningJobEmitter).pipe(takeUntil(this.easyUnsubscribe)).subscribe((job: string) => {
if (!this.initialized) {
return;
}
this.socket.write(job + '\n');
})
} }
@@ -187,7 +200,7 @@ export class StratumV1Client {
&& this.clientSuggestedDifficulty != null) { && this.clientSuggestedDifficulty != null) {
this.initialized = true; this.initialized = true;
this.manualMiningNotify(); this.onInitialized.next();
} }
@@ -195,46 +208,39 @@ export class StratumV1Client {
} }
private manualMiningNotify() {
clearInterval(this.interval);
this.miningNotify();
this.interval = setInterval(() => {
this.miningNotify();
}, 60000);
}
private miningNotify() { // private miningNotify() {
const notification = { // const notification = {
id: null, // id: null,
method: eResponseMethod.MINING_NOTIFY, // method: eResponseMethod.MINING_NOTIFY,
params: [ // params: [
'64756fab0000442e', // '64756fab0000442e',
'39dbb5b4e173e1f9ac6f6ad92e9dde300effce6b0003ea860000000000000000', // '39dbb5b4e173e1f9ac6f6ad92e9dde300effce6b0003ea860000000000000000',
'01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff35033e1c0c00048fef83640447483e060c', // '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff35033e1c0c00048fef83640447483e060c',
'0a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff03e1c7872600000000160014876961d21eaba10f0701dcc78f6624a4c682270d384dc900000000001976a914f4cbe6c6bb3a8535c963169c22963d3a20e7686988ac0000000000000000266a24aa21a9ed751521cd94e7780a9a13ac8bb1da5410d30acdd2f6348876835623b04b2dc83b00000000', // '0a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff03e1c7872600000000160014876961d21eaba10f0701dcc78f6624a4c682270d384dc900000000001976a914f4cbe6c6bb3a8535c963169c22963d3a20e7686988ac0000000000000000266a24aa21a9ed751521cd94e7780a9a13ac8bb1da5410d30acdd2f6348876835623b04b2dc83b00000000',
[ // [
'c0c9d351b9e094dd85bc64c8909dece6226269ebfe173bb74ba2f89c51df7066', // 'c0c9d351b9e094dd85bc64c8909dece6226269ebfe173bb74ba2f89c51df7066',
'6c56f47cbfaef5688bb338bc56c4189530f12cdb98f8cc46b6a12053f1e69fdd', // '6c56f47cbfaef5688bb338bc56c4189530f12cdb98f8cc46b6a12053f1e69fdd',
'697cdaa8d15691f7b30dfe7f6c33957f04cfc8126fed16d2fe38c96adaa59c41', // '697cdaa8d15691f7b30dfe7f6c33957f04cfc8126fed16d2fe38c96adaa59c41',
'cbe8aa6e0343884a40b850df8fd3c2ffcc026acec392ce93f9e28619eb0d3dac', // 'cbe8aa6e0343884a40b850df8fd3c2ffcc026acec392ce93f9e28619eb0d3dac',
'e023091cf0fc02684c77730a34791181a44be92f966ba579aa4cf6e98d754548', // 'e023091cf0fc02684c77730a34791181a44be92f966ba579aa4cf6e98d754548',
'6b8fea64efe363e02ff42a4257d76a77381006eade804c8a8c9b96c9c98b1d9e', // '6b8fea64efe363e02ff42a4257d76a77381006eade804c8a8c9b96c9c98b1d9e',
'1c936bc5320cdbbe7348cdd4bf272529822e9d34dfa8e0ee0041eae635891cc3', // '1c936bc5320cdbbe7348cdd4bf272529822e9d34dfa8e0ee0041eae635891cc3',
'8fed2682a3c95863c6b9440b1a47abdd3d4230181f61edf0daa2e5f0befbcf65', // '8fed2682a3c95863c6b9440b1a47abdd3d4230181f61edf0daa2e5f0befbcf65',
'0837c4d162e1086ec553ea90af4be4f9747958e556598cc38cb08149e58227b1', // '0837c4d162e1086ec553ea90af4be4f9747958e556598cc38cb08149e58227b1',
'0b5287e647c7cb6f2fcdf13d5ef3bf091d1137773d7695405d0b2768f442ee78', // '0b5287e647c7cb6f2fcdf13d5ef3bf091d1137773d7695405d0b2768f442ee78',
'71eaff9247b5556fa88bca6da2e055d5db8aef2969a2d5c68f8e7efd7d39a283' // '71eaff9247b5556fa88bca6da2e055d5db8aef2969a2d5c68f8e7efd7d39a283'
], // ],
'20000000', // '20000000',
'17057e69', // '17057e69',
'6483ef8f', // '6483ef8f',
true // true
], // ],
}; // };
this.socket.write(JSON.stringify(notification) + '\n'); // this.socket.write(JSON.stringify(notification) + '\n');
} // }
} }
@@ -20,7 +20,7 @@ export class SuggestDifficulty extends StratumBaseMessage {
return { return {
id: null, id: null,
method: eResponseMethod.SET_DIFFICULTY, method: eResponseMethod.SET_DIFFICULTY,
params: [1024] params: [256]
} }
} }
} }
+10
View File
@@ -0,0 +1,10 @@
import { Subject } from 'rxjs';
export class EasyUnsubscribe {
protected easyUnsubscribe = new Subject<void>();
public unsubscribeAll(): void {
this.easyUnsubscribe.next();
this.easyUnsubscribe.complete();
}
}