mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
169 lines
6.6 KiB
TypeScript
169 lines
6.6 KiB
TypeScript
import * as bitcoinjs from 'bitcoinjs-lib';
|
|
import * as crypto from 'crypto';
|
|
import { MerkleTree } from 'merkletreejs';
|
|
|
|
import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate';
|
|
import { eResponseMethod } from './enums/eResponseMethod';
|
|
|
|
interface AddressObject {
|
|
address: string;
|
|
percent: number;
|
|
}
|
|
export class MiningJob {
|
|
public id: number;
|
|
public method: eResponseMethod.MINING_NOTIFY;
|
|
public params: string[];
|
|
|
|
public target: string;
|
|
public merkleRoot: string;
|
|
|
|
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 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 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;
|
|
|
|
public versionMask: string;
|
|
|
|
public tree: MerkleTree;
|
|
public coinbaseTransaction: bitcoinjs.Transaction;
|
|
|
|
constructor(id: string, payoutInformation: AddressObject[], public blockTemplate: IBlockTemplate, public networkDifficulty: number, public clean_jobs: boolean) {
|
|
|
|
|
|
this.job_id = id;
|
|
this.target = blockTemplate.target;
|
|
this.prevhash = this.convertToLittleEndian(blockTemplate.previousblockhash);
|
|
|
|
this.version = blockTemplate.version;
|
|
this.nbits = parseInt(blockTemplate.bits, 16);
|
|
this.ntime = Math.floor(new Date().getTime() / 1000);
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
//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)
|
|
// 1-byte - Push the following 36 bytes (0x24)
|
|
// 4-byte - Commitment header (0xaa21a9ed)
|
|
const segwitMagicBits = Buffer.from('aa21a9ed', 'hex');
|
|
// 32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value)
|
|
const merkleRoot = this.sha256(this.sha256(witnessRootHash));
|
|
// 39th byte onwards: Optional data with no consensus meaning
|
|
this.coinbaseTransaction.outs[0].script = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, merkleRoot])]);
|
|
|
|
//@ts-ignore
|
|
const serializedTx = this.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 coinbasePart1 = serializedTx.slice(0, partOneIndex);
|
|
const coinbasePart2 = serializedTx.slice(partOneIndex);
|
|
this.coinb1 = coinbasePart1.slice(0, coinbasePart1.length - 16);
|
|
this.coinb2 = coinbasePart2;
|
|
|
|
|
|
// Calculate merkle branch
|
|
const transactionBuffers = allTransactions.map(tx => tx.getHash(false));
|
|
|
|
this.tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true });
|
|
|
|
const rootBuffer = this.tree.getRoot();
|
|
this.merkleRoot = rootBuffer.toString('hex');
|
|
this.merkle_branch = this.tree.getProof(this.coinbaseTransaction.getHash(false)).map(p => p.data.toString('hex'));
|
|
|
|
|
|
this.constructResponse();
|
|
|
|
}
|
|
|
|
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): bitcoinjs.Transaction {
|
|
// Part 1
|
|
const tx = new bitcoinjs.Transaction();
|
|
|
|
// Set the version of the transaction
|
|
tx.version = 2;
|
|
|
|
const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}` + '00000000' + '00000000';
|
|
// const inputScriptBytes = ((blockHeightScript.length + 16) / 2).toString(16).padStart(2, '0');
|
|
// const OP_RETURN = '6a';
|
|
// const inputScript = `${OP_RETURN}${inputScriptBytes}${blockHeightScript}`
|
|
|
|
const inputScript = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.from(blockHeightScript, 'hex')])
|
|
|
|
// Add the coinbase input (input with no previous output)
|
|
tx.addInput(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), 0xffffffff, 0xffffffff, inputScript);
|
|
|
|
// Add an output
|
|
const recipientAddress = addresses[0].address;
|
|
|
|
const scriptPubKey = bitcoinjs.payments.p2wpkh({ address: recipientAddress, network: bitcoinjs.networks.testnet });
|
|
tx.addOutput(scriptPubKey.output, reward);
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
tx.ins[0].witness = [segwitWitnessReservedValue];
|
|
|
|
|
|
|
|
return tx;
|
|
}
|
|
|
|
private sha256(data) {
|
|
return crypto.createHash('sha256').update(data).digest()
|
|
}
|
|
|
|
|
|
private constructResponse() {
|
|
|
|
const job = {
|
|
id: null,
|
|
method: eResponseMethod.MINING_NOTIFY,
|
|
params: [
|
|
this.job_id,
|
|
this.prevhash,
|
|
this.coinb1,
|
|
this.coinb2,
|
|
this.merkle_branch,
|
|
this.version.toString(16),
|
|
this.nbits.toString(16),
|
|
this.ntime.toString(16),
|
|
this.clean_jobs
|
|
]
|
|
};
|
|
|
|
this.response = JSON.stringify(job);
|
|
|
|
}
|
|
|
|
|
|
private convertToLittleEndian(hash: string): string {
|
|
const bytes = Buffer.from(hash, 'hex');
|
|
Array.prototype.reverse.call(bytes);
|
|
return bytes.toString('hex');
|
|
}
|
|
|
|
|
|
|
|
|
|
} |