mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
mining submit work
This commit is contained in:
Vendored
+1
@@ -9,6 +9,7 @@
|
|||||||
"nbits",
|
"nbits",
|
||||||
"ntime",
|
"ntime",
|
||||||
"prevhash",
|
"prevhash",
|
||||||
|
"Satoshis",
|
||||||
"submitblock",
|
"submitblock",
|
||||||
"Tempalte"
|
"Tempalte"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,25 +18,18 @@ export class BitcoinStratumProvider implements OnModuleInit {
|
|||||||
|
|
||||||
private interval: NodeJS.Timer;
|
private interval: NodeJS.Timer;
|
||||||
|
|
||||||
private newMiningJobEmitter: BehaviorSubject<string> = new BehaviorSubject(null);
|
private newMiningJobEmitter: BehaviorSubject<MiningJob> = new BehaviorSubject(null);
|
||||||
|
|
||||||
private latestJob: MiningJob;
|
private latestJob: MiningJob;
|
||||||
|
|
||||||
|
|
||||||
constructor(private readonly bitcoinRpcService: BitcoinRpcService) {
|
constructor(private readonly bitcoinRpcService: BitcoinRpcService) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async onModuleInit(): Promise<void> {
|
async onModuleInit(): Promise<void> {
|
||||||
console.log('onModuleInit');
|
console.log('onModuleInit');
|
||||||
|
|
||||||
this.blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
|
|
||||||
this.latestJob = new MiningJob(this.blockTemplate)
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -47,9 +40,9 @@ export class BitcoinStratumProvider implements OnModuleInit {
|
|||||||
if (this.latestJob == null) {
|
if (this.latestJob == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const job = this.latestJob.response();
|
|
||||||
const jobString = JSON.stringify(job);
|
this.latestJob.constructResponse();
|
||||||
client.localMiningJobEmitter.next(jobString);
|
client.localMiningJobEmitter.next(this.latestJob);
|
||||||
});
|
});
|
||||||
|
|
||||||
// this.clients.push(client);
|
// this.clients.push(client);
|
||||||
@@ -59,24 +52,28 @@ export class BitcoinStratumProvider implements OnModuleInit {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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, () => {
|
this.server.listen(3333, () => {
|
||||||
console.log(`Bitcoin Stratum server is listening on port ${3333}`);
|
console.log(`Bitcoin Stratum server is listening on port ${3333}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//clearInterval(this.interval);
|
|
||||||
|
|
||||||
this.newMiningJobEmitter.next(JSON.stringify(this.latestJob.response()));
|
|
||||||
|
|
||||||
this.interval = setInterval(async () => {
|
|
||||||
this.blockTemplate = await this.bitcoinRpcService.getBlockTemplate();
|
|
||||||
|
|
||||||
this.latestJob = new MiningJob(this.blockTemplate)
|
|
||||||
this.newMiningJobEmitter.next(JSON.stringify(this.latestJob.response()));
|
|
||||||
}, 60000);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+44
-14
@@ -5,11 +5,17 @@ import { IBlockTemplate, IBlockTemplateTx } from './bitcoin-rpc/IBlockTemplate';
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { MerkleTree } from 'merkletreejs'
|
import { MerkleTree } from 'merkletreejs'
|
||||||
|
|
||||||
|
interface AddressObject {
|
||||||
|
address: string;
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
export class MiningJob {
|
export class MiningJob {
|
||||||
public id: number;
|
public id: number;
|
||||||
public method: eResponseMethod.MINING_NOTIFY;
|
public method: eResponseMethod.MINING_NOTIFY;
|
||||||
public params: string[];
|
public params: string[];
|
||||||
|
|
||||||
|
public target: string;
|
||||||
|
public merkleRoot: Buffer;
|
||||||
|
|
||||||
public job_id: string; // 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.
|
||||||
@@ -21,9 +27,12 @@ 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.
|
||||||
|
|
||||||
|
public response: string;
|
||||||
|
|
||||||
constructor(blockTemplate: IBlockTemplate) {
|
constructor(blockTemplate: IBlockTemplate) {
|
||||||
|
|
||||||
this.job_id = randomUUID();
|
this.job_id = randomUUID();
|
||||||
|
this.target = blockTemplate.target;
|
||||||
this.prevhash = blockTemplate.previousblockhash;
|
this.prevhash = blockTemplate.previousblockhash;
|
||||||
|
|
||||||
this.version = blockTemplate.version.toString();
|
this.version = blockTemplate.version.toString();
|
||||||
@@ -39,7 +48,7 @@ export class MiningJob {
|
|||||||
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('', blockTemplate.height, transactionFees + miningReward);
|
const { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction([{ address: '', percentage: 100 }], blockTemplate.height, transactionFees + miningReward);
|
||||||
|
|
||||||
this.coinb1 = coinbasePart1;
|
this.coinb1 = coinbasePart1;
|
||||||
this.coinb2 = coinbasePart2;
|
this.coinb2 = coinbasePart2;
|
||||||
@@ -62,6 +71,8 @@ export class MiningJob {
|
|||||||
|
|
||||||
this.merkle_branch = branch;
|
this.merkle_branch = branch;
|
||||||
|
|
||||||
|
this.merkleRoot = tree.getRoot();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateMiningReward(blockHeight: number): number {
|
private calculateMiningReward(blockHeight: number): number {
|
||||||
@@ -81,26 +92,43 @@ export class MiningJob {
|
|||||||
return buffer.toString('hex');
|
return buffer.toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCoinbaseTransaction(address: string, blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } {
|
|
||||||
|
|
||||||
|
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } {
|
||||||
// Generate coinbase script
|
// Generate coinbase script
|
||||||
const coinbaseScript = `03${blockHeight.toString(16).padStart(8, '0')}54696d652026204865616c74682021`;
|
const coinbaseScript = `03${blockHeight.toString(16).padStart(8, '0')}54696d652026204865616c74682021`;
|
||||||
|
|
||||||
// Create coinbase transaction
|
// Create coinbase transaction
|
||||||
const version = '01000000';
|
const version = '01000000';
|
||||||
const inputs = '01' + '0000000000000000000000000000000000000000000000000000000000000000ffffffff';
|
const inputCount = '01';
|
||||||
|
const inputs = '0000000000000000000000000000000000000000000000000000000000000000ffffffff';
|
||||||
const coinbaseScriptSize = coinbaseScript.length / 2;
|
const coinbaseScriptSize = coinbaseScript.length / 2;
|
||||||
const coinbaseScriptBytes = coinbaseScriptSize.toString(16).padStart(2, '0');
|
const coinbaseScriptBytes = coinbaseScriptSize.toString(16).padStart(2, '0');
|
||||||
const coinbaseTransaction = inputs + coinbaseScriptBytes + coinbaseScript + '00000000';
|
const coinbaseTransaction = inputCount + inputs + coinbaseScriptBytes + coinbaseScript + '00000000';
|
||||||
|
|
||||||
// Create output
|
// Create outputs
|
||||||
const outputCount = '01';
|
const outputCount = addresses.length;
|
||||||
const satoshis = '0f4240'; // 6.25 BTC in satoshis (1 BTC = 100,000,000 satoshis)
|
const outputCountHex = outputCount.toString(16).padStart(2, '0');
|
||||||
const script = '1976a914' + address + '88ac'; // Change this to your desired output script
|
|
||||||
const locktime = '00000000';
|
let remainingPayout = reward;
|
||||||
|
const outputs = addresses
|
||||||
|
.map((addressObj) => {
|
||||||
|
const percentage = addressObj.percentage / 100;
|
||||||
|
const satoshis = Math.floor(reward * percentage).toString(16).padStart(16, '0');
|
||||||
|
remainingPayout -= parseInt(satoshis, 16);
|
||||||
|
const script = '1976a914' + Buffer.from(addressObj.address, 'hex').toString('hex') + '88ac'; // Convert address to hex
|
||||||
|
return satoshis + script;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
// Distribute any remaining satoshis to the first address
|
||||||
|
const firstAddressSatoshis = (parseInt(outputs.substring(0, 16), 16) + remainingPayout).toString(16).padStart(16, '0');
|
||||||
|
const firstAddressOutput = firstAddressSatoshis + outputs.substring(16);
|
||||||
|
const modifiedOutputs = firstAddressOutput + outputs.slice(16);
|
||||||
|
|
||||||
// Combine coinbasePart1 and coinbasePart2
|
// Combine coinbasePart1 and coinbasePart2
|
||||||
const coinbasePart1 = version + coinbaseTransaction + outputCount + satoshis;
|
const coinbasePart1 = version + coinbaseTransaction + outputCountHex;
|
||||||
const coinbasePart2 = script + locktime;
|
const coinbasePart2 = modifiedOutputs + '00000000';
|
||||||
|
|
||||||
return { coinbasePart1, coinbasePart2 };
|
return { coinbasePart1, coinbasePart2 };
|
||||||
}
|
}
|
||||||
@@ -110,9 +138,9 @@ export class MiningJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public response() {
|
public constructResponse() {
|
||||||
|
|
||||||
return {
|
const job = {
|
||||||
id: 0,
|
id: 0,
|
||||||
method: eResponseMethod.MINING_NOTIFY,
|
method: eResponseMethod.MINING_NOTIFY,
|
||||||
params: [
|
params: [
|
||||||
@@ -126,7 +154,9 @@ export class MiningJob {
|
|||||||
this.ntime,
|
this.ntime,
|
||||||
this.clean_jobs
|
this.clean_jobs
|
||||||
]
|
]
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.response = JSON.stringify(job);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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';
|
||||||
|
import { MiningJob } from './MiningJob';
|
||||||
|
|
||||||
export class StratumV1Client extends EasyUnsubscribe {
|
export class StratumV1Client extends EasyUnsubscribe {
|
||||||
|
|
||||||
@@ -22,12 +23,14 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
|
|
||||||
|
|
||||||
public onInitialized: BehaviorSubject<void> = new BehaviorSubject(null);
|
public onInitialized: BehaviorSubject<void> = new BehaviorSubject(null);
|
||||||
public localMiningJobEmitter: BehaviorSubject<string> = new BehaviorSubject(null);
|
public localMiningJobEmitter: BehaviorSubject<MiningJob> = new BehaviorSubject(null);
|
||||||
|
|
||||||
|
|
||||||
|
private currentJob: MiningJob;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly socket: Socket,
|
private readonly socket: Socket,
|
||||||
private readonly globalMiningJobEmitter: Observable<string>
|
private readonly globalMiningJobEmitter: Observable<MiningJob>
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -44,11 +47,12 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
console.error('Socket error:', error);
|
console.error('Socket error:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
merge(this.globalMiningJobEmitter, this.localMiningJobEmitter).pipe(takeUntil(this.easyUnsubscribe)).subscribe((job: string) => {
|
merge(this.globalMiningJobEmitter, this.localMiningJobEmitter).pipe(takeUntil(this.easyUnsubscribe)).subscribe((job: MiningJob) => {
|
||||||
|
this.currentJob = job;
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.socket.write(job + '\n');
|
this.socket.write(job.response + '\n');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -185,6 +189,8 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
//this.clientSuggestedDifficulty = miningSubmitMessage;
|
//this.clientSuggestedDifficulty = miningSubmitMessage;
|
||||||
|
miningSubmitMessage.parse();
|
||||||
|
this.handleMiningSubmission(miningSubmitMessage);
|
||||||
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
|
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
|
||||||
} else {
|
} else {
|
||||||
console.error(errors);
|
console.error(errors);
|
||||||
@@ -209,6 +215,10 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private handleMiningSubmission(submission: MiningSubmitMessage) {
|
||||||
|
const diff = submission.testNonceValue(this.currentJob, submission.nonce);
|
||||||
|
console.log(diff);
|
||||||
|
}
|
||||||
|
|
||||||
// private miningNotify() {
|
// private miningNotify() {
|
||||||
// const notification = {
|
// const notification = {
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { ArrayMaxSize, ArrayMinSize, IsArray } from 'class-validator';
|
|||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||||
|
import { MiningJob } from '../MiningJob';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
const trueDiffOne = Number('26959535291011309493156476344723991336010898738574164086137773096960');
|
||||||
export class MiningSubmitMessage extends StratumBaseMessage {
|
export class MiningSubmitMessage extends StratumBaseMessage {
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@@ -10,11 +13,26 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
@ArrayMaxSize(5)
|
@ArrayMaxSize(5)
|
||||||
params: string[];
|
params: string[];
|
||||||
|
|
||||||
|
public userId: string;
|
||||||
|
public jobId: string;
|
||||||
|
public extraNonce2: string;
|
||||||
|
public ntime: string;
|
||||||
|
public nonce: string
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.method = eRequestMethod.AUTHORIZE;
|
this.method = eRequestMethod.AUTHORIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public parse() {
|
||||||
|
this.userId = this.params[0];
|
||||||
|
this.jobId = this.params[1];
|
||||||
|
this.extraNonce2 = this.params[2];
|
||||||
|
this.ntime = this.params[3];
|
||||||
|
this.nonce = this.params[4];
|
||||||
|
}
|
||||||
|
|
||||||
public response() {
|
public response() {
|
||||||
return {
|
return {
|
||||||
id: null,
|
id: null,
|
||||||
@@ -22,4 +40,45 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
result: true
|
result: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
testNonceValue(job: MiningJob, nonce: string): number {
|
||||||
|
|
||||||
|
const header: Buffer = Buffer.alloc(80);
|
||||||
|
|
||||||
|
// TODO: use the midstate hash instead of hashing the whole header
|
||||||
|
|
||||||
|
|
||||||
|
header.set(Buffer.from(job.version).subarray(0, 4), 0);
|
||||||
|
header.set(Buffer.from(job.prevhash).subarray(0, 32), 4);
|
||||||
|
header.set(Buffer.from(job.merkleRoot).subarray(0, 32), 36);
|
||||||
|
header.set(Buffer.from(job.ntime).subarray(0, 4), 68);
|
||||||
|
header.set(Buffer.from(job.target).subarray(0, 4), 72);
|
||||||
|
header.set(Buffer.from(nonce).subarray(0, 4), 76);
|
||||||
|
|
||||||
|
|
||||||
|
const hashBuffer = crypto.createHash('sha256').update(header).digest();
|
||||||
|
const hashResult = crypto.createHash('sha256').update(hashBuffer).digest();
|
||||||
|
|
||||||
|
const s64 = this.le256toDouble(hashResult);
|
||||||
|
const ds = trueDiffOne / s64;
|
||||||
|
console.log(trueDiffOne + '/ ' + s64)
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private le256toDouble(target: Buffer): number {
|
||||||
|
const bits192 = 6277101735386680763835789423207666416102355444464034512896n; // Replace with the actual value of bits192
|
||||||
|
const bits128 = 340282366920938463463374607431768211456n; // Replace with the actual value of bits128
|
||||||
|
const bits64 = 18446744073709551616n; // Replace with the actual value of bits64
|
||||||
|
|
||||||
|
const data64 = target.readBigUInt64LE(24);
|
||||||
|
let dcut64 = Number(data64) * Number(bits192);
|
||||||
|
|
||||||
|
dcut64 += Number(target.readBigUInt64LE(16)) * Number(bits128);
|
||||||
|
dcut64 += Number(target.readBigUInt64LE(8)) * Number(bits64);
|
||||||
|
dcut64 += Number(target.readBigUInt64LE(0));
|
||||||
|
|
||||||
|
return dcut64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "es2017",
|
"target": "ES2020",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
|
|||||||
Reference in New Issue
Block a user