mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2025-03-27 02:02:10 +01:00
big refactor
This commit is contained in:
parent
9315d200ff
commit
9d4a1dda7f
@ -3,19 +3,18 @@ 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 }>;
|
||||
public currentBlockTemplate$: Observable<{ 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),
|
||||
tap(({ blockTemplate }) => this.currentBlockTemplate = blockTemplate),
|
||||
shareReplay({ refCount: true, bufferSize: 1 })
|
||||
);
|
||||
}
|
||||
|
@ -11,43 +11,32 @@ interface AddressObject {
|
||||
}
|
||||
export class MiningJob {
|
||||
|
||||
private merkle_branch: string[]; // List of hashes, will be used for calculation of merkle root. This is not a list of all transactions, it only contains prepared hashes of steps of merkle tree algorithm.
|
||||
|
||||
public jobId: 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 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 merkle_branch: string[]; // List of hashes, will be used for calculation of merkle root. This is not a list of all transactions, it only contains prepared hashes of steps of merkle tree algorithm.
|
||||
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 response: string;
|
||||
|
||||
|
||||
private tree: MerkleTree;
|
||||
|
||||
public coinbaseTransaction: bitcoinjs.Transaction;
|
||||
|
||||
public block: bitcoinjs.Block = new bitcoinjs.Block();
|
||||
public networkDifficulty: number;
|
||||
|
||||
constructor(id: string, payoutInformation: AddressObject[], public blockTemplate: IBlockTemplate, public networkDifficulty: number, public clean_jobs: boolean) {
|
||||
|
||||
constructor(id: string, payoutInformation: AddressObject[], public blockTemplate: IBlockTemplate, public clean_jobs: boolean) {
|
||||
|
||||
this.jobId = id;
|
||||
//this.target = blockTemplate.target;
|
||||
this.prevhash = this.convertToLittleEndian(blockTemplate.previousblockhash);
|
||||
this.block.prevHash = this.convertToLittleEndian(blockTemplate.previousblockhash);
|
||||
|
||||
this.version = blockTemplate.version;
|
||||
this.nbits = parseInt(blockTemplate.bits, 16);
|
||||
this.ntime = Math.floor(new Date().getTime() / 1000);
|
||||
this.block.version = blockTemplate.version;
|
||||
this.block.bits = parseInt(blockTemplate.bits, 16);
|
||||
this.networkDifficulty = this.calculateNetworkDifficulty(this.block.bits);
|
||||
this.block.timestamp = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
this.block.transactions = blockTemplate.transactions.map(t => bitcoinjs.Transaction.fromHex(t.data));
|
||||
|
||||
const allTransactions = blockTemplate.transactions.map(t => bitcoinjs.Transaction.fromHex(t.data));
|
||||
|
||||
this.coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, this.blockTemplate.height, this.blockTemplate.coinbasevalue);
|
||||
allTransactions.unshift(this.coinbaseTransaction);
|
||||
const witnessRootHash = bitcoinjs.Block.calculateMerkleRoot(allTransactions, true);
|
||||
const coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, this.blockTemplate.height, this.blockTemplate.coinbasevalue);
|
||||
this.block.transactions.unshift(coinbaseTransaction);
|
||||
|
||||
this.block.witnessCommit = bitcoinjs.Block.calculateMerkleRoot(this.block.transactions, true);
|
||||
|
||||
this.block.merkleRoot = bitcoinjs.Block.calculateMerkleRoot(this.block.transactions, false);
|
||||
|
||||
//The commitment is recorded in a scriptPubKey of the coinbase transaction. It must be at least 38 bytes, with the first 6-byte of 0x6a24aa21a9ed, that is:
|
||||
// 1-byte - OP_RETURN (0x6a)
|
||||
@ -55,34 +44,82 @@ export class MiningJob {
|
||||
// 4-byte - Commitment header (0xaa21a9ed)
|
||||
const segwitMagicBits = Buffer.from('aa21a9ed', 'hex');
|
||||
// 32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value)
|
||||
const commitmentHash = this.sha256(this.sha256(witnessRootHash));
|
||||
const commitmentHash = this.sha256(this.sha256(this.block.witnessCommit));
|
||||
// 39th byte onwards: Optional data with no consensus meaning
|
||||
this.coinbaseTransaction.outs[0].script = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, commitmentHash])]);
|
||||
coinbaseTransaction.outs[0].script = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, commitmentHash])]);
|
||||
|
||||
// get the non-witness coinbase tx
|
||||
//@ts-ignore
|
||||
const serializedTx = this.coinbaseTransaction.__toBuffer().toString('hex');
|
||||
const serializedCoinbaseTx = coinbaseTransaction.__toBuffer().toString('hex');
|
||||
|
||||
const blockHeightScript = `03${this.blockTemplate.height.toString(16).padStart(8, '0')}` + '00000000' + '00000000';
|
||||
const partOneIndex = serializedTx.indexOf(blockHeightScript) + blockHeightScript.length;
|
||||
const partOneIndex = serializedCoinbaseTx.indexOf(blockHeightScript) + blockHeightScript.length;
|
||||
|
||||
const coinbasePart1 = serializedTx.slice(0, partOneIndex);
|
||||
const coinbasePart2 = serializedTx.slice(partOneIndex);
|
||||
this.coinb1 = coinbasePart1.slice(0, coinbasePart1.length - 16);
|
||||
this.coinb2 = coinbasePart2;
|
||||
const coinbasePart1 = serializedCoinbaseTx.slice(0, partOneIndex);
|
||||
const coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex);
|
||||
const coinb1 = coinbasePart1.slice(0, coinbasePart1.length - 16);
|
||||
const coinb2 = coinbasePart2;
|
||||
|
||||
|
||||
// Calculate merkle branch
|
||||
const transactionBuffers = allTransactions.map(tx => tx.getHash(false));
|
||||
const transactionBuffers = this.block.transactions.map(tx => tx.getHash(false));
|
||||
|
||||
this.tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true });
|
||||
const tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true });
|
||||
this.merkle_branch = tree.getProof(coinbaseTransaction.getHash(false)).map(p => p.data.toString('hex'));
|
||||
|
||||
this.merkle_branch = this.tree.getProof(this.coinbaseTransaction.getHash(false)).map(p => p.data.toString('hex'));
|
||||
this.block.transactions[0] = coinbaseTransaction;
|
||||
|
||||
|
||||
this.constructResponse();
|
||||
this.constructResponse(coinb1, coinb2);
|
||||
|
||||
}
|
||||
|
||||
public tryBlock(versionMaskString: number, nonce: number, extraNonce: string, extraNonce2: string): bitcoinjs.Block {
|
||||
|
||||
const testBlock = bitcoinjs.Block.fromBuffer(this.block.toBuffer());
|
||||
|
||||
testBlock.nonce = nonce;
|
||||
|
||||
// recompute version mask
|
||||
const versionMask = versionMaskString;
|
||||
if (versionMask !== undefined && versionMask != 0) {
|
||||
testBlock.version = (testBlock.version ^ versionMask);
|
||||
}
|
||||
|
||||
// set the nonces
|
||||
const blockHeightScript = `03${this.blockTemplate.height.toString(16).padStart(8, '0')}${extraNonce}${extraNonce2}`;
|
||||
const inputScript = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.from(blockHeightScript, 'hex')]);
|
||||
testBlock.transactions[0].ins[0].script = inputScript;
|
||||
|
||||
//@ts-ignore
|
||||
// const test = testBlock.transactions[0].__toBuffer();
|
||||
// console.log(test.toString('hex'))
|
||||
|
||||
const newRoot = this.calculateMerkleRootHash(testBlock.transactions[0].__toBuffer(), this.merkle_branch);
|
||||
//recompute the root
|
||||
testBlock.merkleRoot = newRoot;
|
||||
|
||||
return testBlock;
|
||||
}
|
||||
|
||||
private calculateMerkleRootHash(coinbaseTx: string, merkleBranches: string[]): Buffer {
|
||||
|
||||
let coinbaseTxBuf = Buffer.from(coinbaseTx, 'hex');
|
||||
|
||||
const bothMerkles = Buffer.alloc(64);
|
||||
let test = this.sha256(coinbaseTxBuf)
|
||||
let newRoot = this.sha256(test);
|
||||
bothMerkles.set(newRoot);
|
||||
|
||||
for (let i = 0; i < merkleBranches.length; i++) {
|
||||
bothMerkles.set(Buffer.from(merkleBranches[i], 'hex'), 32);
|
||||
newRoot = this.sha256(this.sha256(bothMerkles));
|
||||
bothMerkles.set(newRoot);
|
||||
}
|
||||
|
||||
return bothMerkles.subarray(0, 32)
|
||||
}
|
||||
|
||||
|
||||
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): bitcoinjs.Transaction {
|
||||
// Part 1
|
||||
const coinbaseTransaction = new bitcoinjs.Transaction();
|
||||
@ -107,10 +144,11 @@ export class MiningJob {
|
||||
coinbaseTransaction.addOutput(scriptPubKey.output, amount);
|
||||
})
|
||||
|
||||
//Add any remaining sats from the Math.floor
|
||||
coinbaseTransaction.outs[0].value += rewardBalance;
|
||||
|
||||
const segwitWitnessReservedValue = Buffer.alloc(32, 0);
|
||||
|
||||
|
||||
//and the coinbase's input's witness must consist of a single 32-byte array for the witness reserved value
|
||||
coinbaseTransaction.ins[0].witness = [segwitWitnessReservedValue];
|
||||
|
||||
@ -122,36 +160,57 @@ export class MiningJob {
|
||||
}
|
||||
|
||||
|
||||
private constructResponse() {
|
||||
private constructResponse(coinb1: string, coinb2: string) {
|
||||
|
||||
const job = {
|
||||
id: null,
|
||||
method: eResponseMethod.MINING_NOTIFY,
|
||||
params: [
|
||||
this.jobId,
|
||||
this.prevhash,
|
||||
this.coinb1,
|
||||
this.coinb2,
|
||||
this.swapEndianWords(this.block.prevHash).toString('hex'),
|
||||
coinb1,
|
||||
coinb2,
|
||||
this.merkle_branch,
|
||||
this.version.toString(16),
|
||||
this.nbits.toString(16),
|
||||
this.ntime.toString(16),
|
||||
this.block.version.toString(16),
|
||||
this.block.bits.toString(16),
|
||||
this.block.timestamp.toString(16),
|
||||
this.clean_jobs
|
||||
]
|
||||
};
|
||||
|
||||
this.response = JSON.stringify(job);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private convertToLittleEndian(hash: string): string {
|
||||
private convertToLittleEndian(hash: string): Buffer {
|
||||
const bytes = Buffer.from(hash, 'hex');
|
||||
Array.prototype.reverse.call(bytes);
|
||||
return bytes.toString('hex');
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private swapEndianWords(buffer: Buffer): Buffer {
|
||||
const swappedBuffer = Buffer.alloc(buffer.length);
|
||||
|
||||
for (let i = 0; i < buffer.length; i += 4) {
|
||||
swappedBuffer[i] = buffer[i + 3];
|
||||
swappedBuffer[i + 1] = buffer[i + 2];
|
||||
swappedBuffer[i + 2] = buffer[i + 1];
|
||||
swappedBuffer[i + 3] = buffer[i];
|
||||
}
|
||||
|
||||
return swappedBuffer;
|
||||
}
|
||||
|
||||
|
||||
private calculateNetworkDifficulty(nBits: number) {
|
||||
const mantissa: number = nBits & 0x007fffff; // Extract the mantissa from nBits
|
||||
const exponent: number = (nBits >> 24) & 0xff; // Extract the exponent from nBits
|
||||
|
||||
const target: number = mantissa * Math.pow(256, (exponent - 3)); // Calculate the target value
|
||||
|
||||
const difficulty: number = (Math.pow(2, 208) * 65535) / target; // Calculate the difficulty
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { Block, Transaction } from 'bitcoinjs-lib';
|
||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
import Big from 'big.js';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validate, ValidatorOptions } from 'class-validator';
|
||||
import * as crypto from 'crypto';
|
||||
@ -25,26 +24,17 @@ import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
||||
|
||||
export class StratumV1Client extends EasyUnsubscribe {
|
||||
|
||||
public startTime: Date;
|
||||
private clientSubscription: SubscriptionMessage;
|
||||
private clientConfiguration: ConfigurationMessage;
|
||||
private clientAuthorization: AuthorizationMessage;
|
||||
private clientSuggestedDifficulty: SuggestDifficulty;
|
||||
|
||||
public clientSubscription: SubscriptionMessage;
|
||||
public clientConfiguration: ConfigurationMessage;
|
||||
public clientAuthorization: AuthorizationMessage;
|
||||
public clientSuggestedDifficulty: SuggestDifficulty;
|
||||
|
||||
public statistics: StratumV1ClientStatistics;
|
||||
private statistics: StratumV1ClientStatistics;
|
||||
private stratumInitialized = false;
|
||||
private clientDifficulty: number = 512;
|
||||
private entity: ClientEntity;
|
||||
|
||||
public extraNonce: string;
|
||||
public stratumInitialized = false;
|
||||
|
||||
public refreshInterval: NodeJS.Timer;
|
||||
|
||||
public clientDifficulty: number = 512;
|
||||
|
||||
public jobRefreshInterval: NodeJS.Timer;
|
||||
|
||||
public entity: ClientEntity;
|
||||
|
||||
|
||||
constructor(
|
||||
public readonly socket: Socket,
|
||||
@ -55,7 +45,7 @@ export class StratumV1Client extends EasyUnsubscribe {
|
||||
private readonly clientStatisticsService: ClientStatisticsService
|
||||
) {
|
||||
super();
|
||||
this.startTime = new Date();
|
||||
|
||||
this.statistics = new StratumV1ClientStatistics(this.clientStatisticsService);
|
||||
this.extraNonce = this.getRandomHexString();
|
||||
|
||||
@ -221,7 +211,6 @@ export class StratumV1Client extends EasyUnsubscribe {
|
||||
|
||||
this.stratumInitialized = true;
|
||||
|
||||
|
||||
this.entity = await this.clientService.save({
|
||||
sessionId: this.extraNonce,
|
||||
address: this.clientAuthorization.address,
|
||||
@ -229,26 +218,21 @@ export class StratumV1Client extends EasyUnsubscribe {
|
||||
startTime: new Date(),
|
||||
});
|
||||
|
||||
|
||||
|
||||
let lastIntervalCount = undefined;
|
||||
combineLatest([this.blockTemplateService.currentBlockTemplate$, interval(60000).pipe(startWith(-1))]).subscribe(([{ miningInfo, blockTemplate }, interValCount]) => {
|
||||
combineLatest([this.blockTemplateService.currentBlockTemplate$, interval(60000).pipe(startWith(-1))]).subscribe(([{ 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);
|
||||
const job = new MiningJob(this.stratumV1JobsService.getNextId(), [{ address: this.clientAuthorization.address, percent: 100 }], blockTemplate, clearJobs);
|
||||
|
||||
this.stratumV1JobsService.addJob(job, clearJobs);
|
||||
|
||||
this.socket.write(job.response + '\n');
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,74 +244,52 @@ export class StratumV1Client extends EasyUnsubscribe {
|
||||
if (job == null) {
|
||||
return;
|
||||
}
|
||||
const diff = submission.calculateDifficulty(this.extraNonce, job, submission);
|
||||
const updatedJobBlock = job.tryBlock(
|
||||
parseInt(submission.versionMask, 16),
|
||||
parseInt(submission.nonce, 16),
|
||||
this.extraNonce,
|
||||
submission.extraNonce2
|
||||
);
|
||||
const diff = this.calculateDifficulty(updatedJobBlock.toBuffer(true));
|
||||
console.log(`DIFF: ${diff}`);
|
||||
|
||||
if (diff >= this.clientDifficulty) {
|
||||
const networkDifficulty = this.calculateNetworkDifficulty(parseInt(job.blockTemplate.bits, 16));
|
||||
|
||||
await this.statistics.addSubmission(this.entity, this.clientDifficulty);
|
||||
if (diff > this.entity.bestDifficulty) {
|
||||
await this.clientService.updateBestDifficulty(this.extraNonce, diff);
|
||||
this.entity.bestDifficulty = diff;
|
||||
}
|
||||
if (diff >= (networkDifficulty / 2)) {
|
||||
if (diff >= (job.networkDifficulty / 2)) {
|
||||
console.log('!!! BOCK FOUND !!!');
|
||||
this.constructBlockAndBroadcast(job, submission);
|
||||
const blockHex = updatedJobBlock.toHex(false);
|
||||
this.bitcoinRpcService.SUBMIT_BLOCK(blockHex);
|
||||
}
|
||||
} else {
|
||||
console.log(`Difficulty too low`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private calculateNetworkDifficulty(nBits: number) {
|
||||
const mantissa: number = nBits & 0x007fffff; // Extract the mantissa from nBits
|
||||
const exponent: number = (nBits >> 24) & 0xff; // Extract the exponent from nBits
|
||||
public calculateDifficulty(header: Buffer): number {
|
||||
|
||||
const target: number = mantissa * Math.pow(256, (exponent - 3)); // Calculate the target value
|
||||
const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest();
|
||||
const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest();
|
||||
|
||||
const difficulty: number = (Math.pow(2, 208) * 65535) / target; // Calculate the difficulty
|
||||
let s64 = this.le256todouble(hashResult);
|
||||
|
||||
return difficulty;
|
||||
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
||||
return truediffone.div(s64.toString()).toNumber();
|
||||
}
|
||||
|
||||
private constructBlockAndBroadcast(job: MiningJob, submission: MiningSubmitMessage) {
|
||||
const block = new Block();
|
||||
|
||||
const blockHeightScript = `03${job.blockTemplate.height.toString(16).padStart(8, '0')}${this.extraNonce}${submission.extraNonce2}`;
|
||||
private le256todouble(target: Buffer): bigint {
|
||||
|
||||
const inputScript = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.from(blockHeightScript, 'hex')]);
|
||||
job.coinbaseTransaction.ins[0].script = inputScript;
|
||||
|
||||
|
||||
const versionMask = parseInt(submission.versionMask, 16);
|
||||
let version = job.version;
|
||||
if (versionMask !== undefined && versionMask != 0) {
|
||||
version = (version ^ versionMask);
|
||||
}
|
||||
|
||||
block.version = version;
|
||||
block.prevHash = Buffer.from(job.prevhash, 'hex');
|
||||
|
||||
block.timestamp = job.ntime;
|
||||
block.bits = job.nbits;
|
||||
block.nonce = parseInt(submission.nonce, 16);
|
||||
|
||||
block.transactions = job.blockTemplate.transactions.map(tx => {
|
||||
return Transaction.fromHex(tx.data);
|
||||
});
|
||||
block.transactions.unshift(job.coinbaseTransaction);
|
||||
|
||||
block.merkleRoot = bitcoinjs.Block.calculateMerkleRoot(block.transactions, false);
|
||||
block.witnessCommit = bitcoinjs.Block.calculateMerkleRoot(block.transactions, true);
|
||||
|
||||
// const test1 = block.getWitnessCommit();
|
||||
// const test2 = block.checkTxRoots();
|
||||
|
||||
|
||||
const blockHex = block.toHex(false);
|
||||
this.bitcoinRpcService.SUBMIT_BLOCK(blockHex);
|
||||
const number = target.reduceRight((acc, byte) => {
|
||||
// Shift the number 8 bits to the left and OR with the current byte
|
||||
return (acc << BigInt(8)) | BigInt(byte);
|
||||
}, BigInt(0));
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
import Big from 'big.js';
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from 'class-validator';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||
import { MiningJob } from '../MiningJob';
|
||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||
|
||||
export class MiningSubmitMessage extends StratumBaseMessage {
|
||||
@ -65,88 +62,8 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
||||
}
|
||||
|
||||
|
||||
public calculateDifficulty(clientId: string, job: MiningJob, submission: MiningSubmitMessage): number {
|
||||
|
||||
const nonce = parseInt(submission.nonce, 16);
|
||||
const versionMask = parseInt(submission.versionMask, 16);
|
||||
const extraNonce = clientId;
|
||||
const extraNonce2 = submission.extraNonce2;
|
||||
|
||||
const coinbaseTx = `${job.coinb1}${extraNonce}${extraNonce2}${job.coinb2}`;
|
||||
|
||||
|
||||
const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch)
|
||||
|
||||
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
||||
|
||||
const header = Buffer.alloc(80);
|
||||
|
||||
let version = job.version;
|
||||
if (versionMask !== undefined && versionMask != 0) {
|
||||
version = (version ^ versionMask);
|
||||
}
|
||||
|
||||
|
||||
header.writeUInt32LE(version, 0);
|
||||
|
||||
header.write(this.swapEndianWords(job.prevhash), 4, 'hex')
|
||||
newRoot.copy(header, 36, 0, 32)
|
||||
header.writeUInt32LE(job.ntime, 68);
|
||||
header.writeBigUint64LE(BigInt(job.nbits), 72);
|
||||
header.writeUInt32LE(nonce, 76);
|
||||
|
||||
const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest();
|
||||
const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest();
|
||||
|
||||
|
||||
let s64 = this.le256todouble(hashResult);
|
||||
|
||||
return truediffone.div(s64.toString()).toNumber();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private swapEndianWords(str: string) {
|
||||
const hexGroups = str.match(/.{1,8}/g);
|
||||
// Reverse each group and concatenate them
|
||||
const reversedHexString = hexGroups.reduce((pre, cur, indx, arr) => {
|
||||
const reversed = cur.match(/.{2}/g).reverse();
|
||||
return `${pre}${reversed.join('')}`;
|
||||
}, '');
|
||||
return reversedHexString;
|
||||
}
|
||||
|
||||
|
||||
private le256todouble(target: Buffer): bigint {
|
||||
|
||||
const number = target.reduceRight((acc, byte) => {
|
||||
// Shift the number 8 bits to the left and OR with the current byte
|
||||
return (acc << BigInt(8)) | BigInt(byte);
|
||||
}, BigInt(0));
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
private calculateMerkleRootHash(coinbaseTx: string, merkleBranches: string[]): Buffer {
|
||||
|
||||
let coinbaseTxBuf = Buffer.from(coinbaseTx, 'hex');
|
||||
|
||||
const bothMerkles = Buffer.alloc(64);
|
||||
let test = this.sha256(coinbaseTxBuf)
|
||||
let newRoot = this.sha256(test);
|
||||
bothMerkles.set(newRoot);
|
||||
|
||||
for (let i = 0; i < merkleBranches.length; i++) {
|
||||
bothMerkles.set(Buffer.from(merkleBranches[i], 'hex'), 32);
|
||||
newRoot = this.sha256(this.sha256(bothMerkles));
|
||||
bothMerkles.set(newRoot);
|
||||
}
|
||||
|
||||
return bothMerkles.subarray(0, 32)
|
||||
}
|
||||
|
||||
private sha256(data: Buffer) {
|
||||
return crypto.createHash('sha256').update(data).digest()
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user