cleaning up

This commit is contained in:
Ben Wilson
2023-06-17 23:36:14 -04:00
parent c167fe9517
commit 916d5f20e6
10 changed files with 101 additions and 144 deletions
+16 -46
View File
@@ -7,7 +7,7 @@ import { eResponseMethod } from './enums/eResponseMethod';
interface AddressObject {
address: string;
percentage: number;
percent: number;
}
export class MiningJob {
public id: number;
@@ -25,7 +25,7 @@ export class MiningJob {
public version: number; // The hex-encoded block version.
public nbits: number; // The hex-encoded network difficulty required for the block.
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;
@@ -33,9 +33,8 @@ export class MiningJob {
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.target = blockTemplate.target;
@@ -44,17 +43,14 @@ export class MiningJob {
this.version = blockTemplate.version;
this.nbits = parseInt(blockTemplate.bits, 16);
this.ntime = Math.floor(new Date().getTime() / 1000);
this.clean_jobs = _cleanJobs;
const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => {
return pre + cur.fee;
}, 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.coinb2 = coinbasePart2;
@@ -62,19 +58,12 @@ export class MiningJob {
const coinbaseHash = this.sha256(this.coinb1 + this.coinb2).toString('hex');
const coinbaseBuffer = Buffer.from(coinbaseHash, 'hex');
//transactions.unshift(coinbaseHash);
// Calculate merkle branch
const transactionBuffers = blockTemplate.transactions.map(tx => Buffer.from(tx.hash, 'hex'));
transactionBuffers.unshift(coinbaseBuffer);
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();
this.merkleRoot = rootBuffer.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 } {
// Generate coinbase script
// Part 1
const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}`;
// Create coinbase transaction
const outputIndex = 'ffffffff';
const sequence = 'ffffffff';
const lockTime = '00000000';
const version = '01000000';
const inputCount = '01';
const fakeCoinbaseInput = '0000000000000000000000000000000000000000000000000000000000000000';
@@ -115,15 +86,14 @@ export class MiningJob {
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
.map((addressObj) => {
const percentage = addressObj.percentage / 100;
const percentage = addressObj.percent / 100;
const satoshis = Math.floor(reward * percentage);
const satoshiBuff = Buffer.alloc(4);
satoshiBuff.writeUInt32LE(satoshis);
@@ -135,14 +105,13 @@ export class MiningJob {
})
.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 coinbasePart1 = version + inputTransaction;
const sequence = 'ffffffff';
const lockTime = '00000000';
const coinbasePart2 = sequence + outputCountHex + outputs + lockTime;
return { coinbasePart1, coinbasePart2 };
@@ -184,4 +153,5 @@ export class MiningJob {
}
+36 -21
View File
@@ -2,8 +2,11 @@ import { plainToInstance } from 'class-transformer';
import { validate, ValidatorOptions } from 'class-validator';
import * as crypto from 'crypto';
import { Socket } from 'net';
import { combineLatest, interval, startWith } from 'rxjs';
import { BlockTemplateService } from '../BlockTemplateService';
import { StratumV1JobsService } from '../stratum-v1-jobs.service';
import { EasyUnsubscribe } from '../utils/AutoUnsubscribe';
import { eRequestMethod } from './enums/eRequestMethod';
import { MiningJob } from './MiningJob';
import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage';
@@ -13,7 +16,7 @@ import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
export class StratumV1Client {
export class StratumV1Client extends EasyUnsubscribe {
private clientSubscription: SubscriptionMessage;
private clientConfiguration: ConfigurationMessage;
@@ -29,11 +32,15 @@ export class StratumV1Client {
public clientDifficulty: number = 512;
public jobRefreshInterval: NodeJS.Timer;
constructor(
public readonly socket: Socket,
private readonly stratumV1JobsService: StratumV1JobsService
private readonly stratumV1JobsService: StratumV1JobsService,
private readonly blockTemplateService: BlockTemplateService
) {
super();
this.id = this.getRandomHexString();
console.log(`id: ${this.id}`);
@@ -181,7 +188,6 @@ export class StratumV1Client {
if (errors.length === 0) {
this.handleMiningSubmission(miningSubmitMessage);
//this.clientSubmission.next(miningSubmitMessage)
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
} else {
console.error(errors);
@@ -199,35 +205,44 @@ export class StratumV1Client {
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) {
const networkDifficulty = 0;
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}`);
if (networkDifficulty < diff) {
//this.clientSubmission.next(true);
if (diff >= this.clientDifficulty) {
if (diff >= job.networkDifficulty) {
console.log('!!! BOCK FOUND !!!');
}
} else {
console.log(`Difficulty too low`);
}
}
}
@@ -14,9 +14,17 @@ export class AuthorizationMessage extends StratumBaseMessage {
@Expose()
@IsString()
@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()
@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 versionMask = parseInt(submission.versionMask, 16);
@@ -76,13 +76,10 @@ export class MiningSubmitMessage extends StratumBaseMessage {
const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch)
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
const header = Buffer.alloc(80);
// TODO: Use the midstate hash instead of hashing the whole header
let version = job.version;
if (versionMask !== undefined && versionMask != 0) {
version = (version ^ versionMask);
@@ -97,10 +94,6 @@ export class MiningSubmitMessage extends StratumBaseMessage {
header.writeBigUint64LE(BigInt(job.nbits), 72);
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 hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest();
@@ -108,7 +101,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
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));
return number;
}
private calculateMerkleRootHash(coinbaseTx: string, merkleBranches: string[]): Buffer {