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
+142 -1
View File
@@ -1,12 +1,153 @@
import { MiningJob } from '../models/MiningJob';
import * as bitcoinjs from 'bitcoinjs-lib';
import * as merkle from 'merkle-lib';
import * as merkleProof from 'merkle-lib/proof';
import { combineLatest, filter, from, interval, map, Observable, shareReplay, startWith, switchMap, tap } from 'rxjs';
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
import { MiningJob } from '../models/MiningJob';
import { BitcoinRpcService } from './bitcoin-rpc.service';
export interface IJobTemplate {
block: bitcoinjs.Block;
merkle_branch: string[];
blockData: {
id: string,
littleEndianBlockHeight: Buffer;
coinbasevalue: number;
networkDifficulty: number;
clearJobs: boolean;
};
}
export class StratumV1JobsService {
private lastIntervalCount: number;
private skipNext: boolean = false;
public newMiningJob$: Observable<IJobTemplate>;
public latestJobId: number = 1;
public jobs: MiningJob[] = [];
public blocks: { [id: number]: IJobTemplate } = {};
private currentBlockTemplate$: Observable<{ blockTemplate: IBlockTemplate }>;
constructor(
private readonly bitcoinRpcService: BitcoinRpcService
) {
this.currentBlockTemplate$ = this.bitcoinRpcService.newBlock$.pipe(
switchMap((miningInfo) => {
return from(this.bitcoinRpcService.getBlockTemplate())
.pipe(map(blockTemplate => { return { miningInfo, blockTemplate } }))
}
),
shareReplay({ refCount: true, bufferSize: 1 })
);
this.newMiningJob$ = combineLatest([this.currentBlockTemplate$, interval(60000).pipe(startWith(-1))]).pipe(
map(([{ blockTemplate }, interValCount]) => {
let clearJobs = false;
if (this.lastIntervalCount === interValCount) {
clearJobs = true;
this.skipNext = true;
console.log('new block')
}
if (this.skipNext == true && clearJobs == false) {
this.skipNext = false;
return null;
}
this.lastIntervalCount = interValCount;
return {
version: blockTemplate.version,
bits: parseInt(blockTemplate.bits, 16),
prevHash: this.convertToLittleEndian(blockTemplate.previousblockhash),
transactions: blockTemplate.transactions.map(t => bitcoinjs.Transaction.fromHex(t.data)),
littleEndianBlockHeight: this.convertToLittleEndian(blockTemplate.height.toString(16).padStart(6, '0')),
coinbasevalue: blockTemplate.coinbasevalue,
timestamp: Math.floor(new Date().getTime() / 1000),
networkDifficulty: this.calculateNetworkDifficulty(parseInt(blockTemplate.bits, 16)),
clearJobs
};
}),
filter(next => next != null),
map(({ version, bits, prevHash, transactions, timestamp, littleEndianBlockHeight, coinbasevalue, networkDifficulty, clearJobs }) => {
const block = new bitcoinjs.Block();
//create an empty coinbase tx
const tempCoinbaseTx = new bitcoinjs.Transaction();
tempCoinbaseTx.version = 2;
tempCoinbaseTx.addInput(Buffer.alloc(32, 0), 0xffffffff, 0xffffffff);
tempCoinbaseTx.ins[0].witness = [Buffer.alloc(32, 0)];
transactions.unshift(tempCoinbaseTx);
const transactionBuffers = transactions.map(tx => tx.getHash(false));
const merkleTree = merkle(transactionBuffers, bitcoinjs.crypto.hash256);
const merkleBranches: Buffer[] = merkleProof(merkleTree, transactionBuffers[0]).filter(h => h != null);
block.merkleRoot = merkleBranches.pop();
// remove the first (coinbase) and last (root) element from the branch
const merkle_branch = merkleBranches.slice(1, merkleBranches.length).map(b => b.toString('hex'))
block.prevHash = prevHash;
block.version = version;
block.bits = bits;
block.timestamp = timestamp;
block.transactions = transactions;
block.witnessCommit = bitcoinjs.Block.calculateMerkleRoot(transactions, true);
return {
block,
merkle_branch,
blockData: {
id: this.getNextId(),
littleEndianBlockHeight,
coinbasevalue,
networkDifficulty,
clearJobs
}
}
}),
tap((data) => {
if (data.blockData.clearJobs) {
this.blocks = {};
}
this.blocks[data.blockData.id] = data;
}),
shareReplay({ refCount: true, bufferSize: 1 })
)
}
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;
}
private convertToLittleEndian(hash: string): Buffer {
const bytes = Buffer.from(hash, 'hex');
Array.prototype.reverse.call(bytes);
return bytes;
}
public getJobTemplateById(jobTemplateId: string) {
return this.blocks[jobTemplateId];
}
public addJob(job: MiningJob, clearJobs: boolean) {
if (clearJobs) {