refactor project to use one singleton for blocks (memory optimization)

This commit is contained in:
Ben Wilson
2023-07-16 16:36:12 -04:00
parent c81b894b4c
commit 57b5b1ed53
8 changed files with 205 additions and 148 deletions
+31 -66
View File
@@ -1,51 +1,41 @@
import { AddressType, getAddressInfo } from 'bitcoin-address-validation';
import * as bitcoinjs from 'bitcoinjs-lib';
import * as merkle from 'merkle-lib';
import * as merkleProof from 'merkle-lib/proof';
import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate';
import { IJobTemplate } from '../services/stratum-v1-jobs.service';
import { eResponseMethod } from './enums/eResponseMethod';
interface AddressObject {
address: string;
percent: number;
}
export class MiningJob {
private coinbaseTransaction: bitcoinjs.Transaction;
private coinbasePart1: string;
private coinbasePart2: string;
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.
// 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 block: bitcoinjs.Block = new bitcoinjs.Block();
public jobTemplateId: string;
//public block: bitcoinjs.Block = new bitcoinjs.Block();
public networkDifficulty: number;
constructor(
private network: bitcoinjs.networks.Network,
id: string,
payoutInformation: AddressObject[],
public blockTemplate: IBlockTemplate,
public clean_jobs: boolean) {
jobTemplate: IJobTemplate
) {
this.jobId = id;
this.block.prevHash = this.convertToLittleEndian(blockTemplate.previousblockhash);
this.jobTemplateId = jobTemplate.blockData.id,
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 coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, this.blockTemplate.coinbasevalue);
this.block.transactions.unshift(coinbaseTransaction);
this.block.witnessCommit = bitcoinjs.Block.calculateMerkleRoot(this.block.transactions, true);
// https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki
const littleEndianBlockHeight = this.convertToLittleEndian(this.blockTemplate.height.toString(16).padStart(6, '0'))
this.coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, jobTemplate.blockData.coinbasevalue);
//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)
@@ -56,15 +46,16 @@ export class MiningJob {
// 39th byte onwards: Optional data with no consensus meaning
const extra = Buffer.from('\\public-pool\\');
coinbaseTransaction.ins[0].script = Buffer.concat([Buffer.from([littleEndianBlockHeight.byteLength]), littleEndianBlockHeight, extra, Buffer.alloc(8, 0)]);
// https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki
this.coinbaseTransaction.ins[0].script = Buffer.concat([Buffer.from([jobTemplate.blockData.littleEndianBlockHeight.byteLength]), jobTemplate.blockData.littleEndianBlockHeight, extra, Buffer.alloc(8, 0)]);
coinbaseTransaction.addOutput(bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, this.block.witnessCommit])]), 0);
this.coinbaseTransaction.addOutput(bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, jobTemplate.block.witnessCommit])]), 0);
// get the non-witness coinbase tx
//@ts-ignore
const serializedCoinbaseTx = coinbaseTransaction.__toBuffer().toString('hex');
const serializedCoinbaseTx = this.coinbaseTransaction.__toBuffer().toString('hex');
const inputScript = coinbaseTransaction.ins[0].script.toString('hex');
const inputScript = this.coinbaseTransaction.ins[0].script.toString('hex');
const partOneIndex = serializedCoinbaseTx.indexOf(inputScript) + inputScript.length;
@@ -72,23 +63,13 @@ export class MiningJob {
this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex);
// Calculate merkle branch
const transactionBuffers = this.block.transactions.map(tx => tx.getHash(false));
const merkleTree = merkle(transactionBuffers, bitcoinjs.crypto.hash256);
const merkleBranches: Buffer[] = merkleProof(merkleTree, transactionBuffers[0]).filter(h => h != null);
this.block.merkleRoot = merkleBranches.pop();
// remove the first (coinbase) and last (root) element from the branch
this.merkle_branch = merkleBranches.slice(1, merkleBranches.length).map(b => b.toString('hex'))
this.block.transactions[0] = coinbaseTransaction;
}
public copyAndUpdateBlock(versionMask: number, nonce: number, extraNonce: string, extraNonce2: string, timestamp: number): bitcoinjs.Block {
public copyAndUpdateBlock(jobTemplate: IJobTemplate, versionMask: number, nonce: number, extraNonce: string, extraNonce2: string, timestamp: number): bitcoinjs.Block {
const testBlock = bitcoinjs.Block.fromBuffer(this.block.toBuffer());
const testBlock = bitcoinjs.Block.fromBuffer(jobTemplate.block.toBuffer());
testBlock.transactions[0] = this.coinbaseTransaction;
testBlock.nonce = nonce;
@@ -103,7 +84,7 @@ export class MiningJob {
testBlock.transactions[0].ins[0].script = Buffer.from(`${nonceScript.substring(0, nonceScript.length - 16)}${extraNonce}${extraNonce2}`, 'hex');
//recompute the root since we updated the coinbase script with the nonces
testBlock.merkleRoot = this.calculateMerkleRootHash(testBlock.transactions[0].getHash(false), this.merkle_branch);
testBlock.merkleRoot = this.calculateMerkleRootHash(testBlock.transactions[0].getHash(false), jobTemplate.merkle_branch);
testBlock.timestamp = timestamp;
@@ -111,6 +92,7 @@ export class MiningJob {
return testBlock;
}
private calculateMerkleRootHash(newRoot: Buffer, merkleBranches: string[]): Buffer {
const bothMerkles = Buffer.alloc(64);
@@ -181,21 +163,21 @@ export class MiningJob {
}
}
public response(): string {
public response(jobTemplate: IJobTemplate): string {
const job = {
id: null,
method: eResponseMethod.MINING_NOTIFY,
params: [
this.jobId,
this.swapEndianWords(this.block.prevHash).toString('hex'),
this.swapEndianWords(jobTemplate.block.prevHash).toString('hex'),
this.coinbasePart1,
this.coinbasePart2,
this.merkle_branch,
this.block.version.toString(16),
this.block.bits.toString(16),
this.block.timestamp.toString(16),
this.clean_jobs
jobTemplate.merkle_branch,
jobTemplate.block.version.toString(16),
jobTemplate.block.bits.toString(16),
jobTemplate.block.timestamp.toString(16),
jobTemplate.blockData.clearJobs
]
};
@@ -203,12 +185,6 @@ export class MiningJob {
}
private convertToLittleEndian(hash: string): Buffer {
const bytes = Buffer.from(hash, 'hex');
Array.prototype.reverse.call(bytes);
return bytes;
}
private swapEndianWords(buffer: Buffer): Buffer {
const swappedBuffer = Buffer.alloc(buffer.length);
@@ -223,15 +199,4 @@ export class MiningJob {
}
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;
}
}