This commit is contained in:
Ben Wilson 2023-06-23 13:55:18 -04:00
parent 88f56d68b1
commit 7b77bf8006
3 changed files with 77 additions and 43 deletions

View File

@ -32,6 +32,7 @@ export class MiningJob {
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) {
@ -45,76 +46,87 @@ export class MiningJob {
this.ntime = Math.floor(new Date().getTime() / 1000);
const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => {
return pre + cur.fee;
}, 0);
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 { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction(payoutInformation, blockTemplate.height, blockTemplate.coinbasevalue);
this.coinb1 = coinbasePart1;
//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;
const coinbaseHash = this.sha256(this.coinb1 + this.coinb2).toString('hex');
const coinbaseBuffer = Buffer.from(coinbaseHash, 'hex');
// Calculate merkle branch
const transactionBuffers = blockTemplate.transactions.map(tx => Buffer.from(tx.hash, 'hex'));
transactionBuffers.unshift(coinbaseBuffer);
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(coinbaseBuffer).map(p => p.data.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): { coinbasePart1: string, coinbasePart2: string } {
private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): bitcoinjs.Transaction {
// Part 1
const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}`;
const outputIndex = 'ffffffff';
const tx = new bitcoinjs.Transaction();
// Set the version of the transaction
tx.version = 2;
const version = '01000000';
const inputCount = '01';
const fakeCoinbaseInput = '0000000000000000000000000000000000000000000000000000000000000000';
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 inputScriptBytes = ((blockHeightScript.length + 16) / 2).toString(16).padStart(2, '0');
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);
const coinbasePart1 = version + inputCount + fakeCoinbaseInput + outputIndex + inputScriptBytes + blockHeightScript;
// Add an output
const recipientAddress = addresses[0].address;
const scriptPubKey = bitcoinjs.payments.p2wpkh({ address: recipientAddress, network: bitcoinjs.networks.testnet });
tx.addOutput(scriptPubKey.output, reward);
// Part 2
const outputs = addresses
.map((addressObj) => {
const percentage = addressObj.percent / 100;
const satoshis = Math.floor(reward * percentage);
const satoshiBuff = Buffer.alloc(4);
satoshiBuff.writeUInt32LE(satoshis);
const littleEndianSatoshis = satoshiBuff.toString('hex').padEnd(16, '0');
const script = bitcoinjs.payments.p2wpkh({ address: addressObj.address }).output.toString('hex') // Convert address to hex
const scriptBytes = (script.length / 2).toString(16).padStart(2, '0');
return littleEndianSatoshis + scriptBytes + script;
})
.join('');
const outputCountHex = addresses.length.toString(16).padStart(2, '0');
const segwitWitnessReservedValue = Buffer.alloc(32, 0);
const sequence = 'ffffffff';
const lockTime = '00000000';
//and the coinbase's input's witness must consist of a single 32-byte array for the witness reserved value
const coinbasePart2 = sequence + outputCountHex + outputs + lockTime;
tx.ins[0].witness = [segwitWitnessReservedValue];
return { coinbasePart1, coinbasePart2 };
return tx;
}
private sha256(data) {

View File

@ -1,4 +1,5 @@
import { Block, Transaction } from 'bitcoinjs-lib';
import * as bitcoinjs from 'bitcoinjs-lib';
import { plainToInstance } from 'class-transformer';
import { validate, ValidatorOptions } from 'class-validator';
import * as crypto from 'crypto';
@ -271,18 +272,36 @@ export class StratumV1Client extends EasyUnsubscribe {
private constructBlockAndBroadcast(job: MiningJob, submission: MiningSubmitMessage) {
const block = new Block();
block.version = job.blockTemplate.version;
const blockHeightScript = `03${job.blockTemplate.height.toString(16).padStart(8, '0')}${job.id}${submission.extraNonce2}`;
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.merkleRoot = Buffer.from(job.merkleRoot, '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 coinbaseTx = `${job.coinb1}${this.id}${submission.extraNonce2}${job.coinb2}`;
block.transactions.unshift(Transaction.fromHex(coinbaseTx));
const blockHex = block.toHex(false);
this.bitcoinRpcService.SUBMIT_BLOCK(blockHex);

View File

@ -74,6 +74,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
const coinbaseTx = `${job.coinb1}${extraNonce}${extraNonce2}${job.coinb2}`;
const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch)
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
@ -94,6 +95,8 @@ export class MiningSubmitMessage extends StratumBaseMessage {
header.writeBigUint64LE(BigInt(job.nbits), 72);
header.writeUInt32LE(nonce, 76);
console.log(header.toString('hex'))
const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest();
const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest();
@ -142,7 +145,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
bothMerkles.set(newRoot);
}
return bothMerkles;
return bothMerkles.subarray(0, 32)
}
private sha256(data: Buffer) {