coinbase calculations

This commit is contained in:
Ben Wilson
2023-06-10 19:39:39 -04:00
parent 2aad736628
commit 15f481f61a
10 changed files with 1142 additions and 145 deletions
-41
View File
@@ -1,41 +0,0 @@
export interface IBlockTempalte {
// (json object)
version: number; // (numeric) The preferred block version
rules: string[]; // (json array) specific block rules that are to be enforced // (string) name of a rule the client must understand to some extent; see BIP 9 for format
vbavailable: { // (json object) set of pending, supported versionbit (BIP 9) softfork deployments
rulename: number, // (numeric) identifies the bit number as indicating acceptance and readiness for the named softfork rule
},
vbrequired: number, // (numeric) bit mask of versionbits the server requires set in submissions
previousblockhash: string, // (string) The hash of current highest block
transactions: [ // (json array) contents of non-coinbase transactions that should be included in the next block
{ // (json object)
data: string; //'hex', // (string) transaction data encoded in hexadecimal (byte-for-byte)
txid: string; //'hex', // (string) transaction id encoded in little-endian hexadecimal
hash: string; //'hex', // (string) hash encoded in little-endian hexadecimal (including witness data)
depends: number[]; // (json array) array of numbers // (numeric) transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is
fee: number, // (numeric) difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one
sigops: number, // (numeric) total SigOps cost, as counted for purposes of block limits; if key is not present, sigop cost is unknown and clients MUST NOT assume it is zero
weight: number // (numeric) total transaction weight, as counted for purposes of block limits
}
],
coinbaseaux: { // (json object) data that should be included in the coinbase's scriptSig content
key: string; //'hex', // (string) values must be in the coinbase (keys may be ignored)
},
coinbasevalue: number, // (numeric) maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)
longpollid: string, // (string) an id to include with a request to longpoll on an update to this template
target: string, // (string) The hash target
mintime: number, // (numeric) The minimum timestamp appropriate for the next block time, expressed in UNIX epoch time
mutable: string[]; // (json array) list of ways the block template may be changed // (string) A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'
noncerange: string; // 'hex', // (string) A range of valid nonces
sigoplimit: number, // (numeric) limit of sigops in blocks
sizelimit: number, // (numeric) limit of block size
weightlimit: number, // (numeric) limit of block weight
curtime: number, // (numeric) current timestamp in UNIX epoch time
bits: string, // (string) compressed target of next block
height: number, // (numeric) The height of the next block
default_witness_commitment: string // (string, optional) a valid witness commitment for the unmodified block template
}
+81 -62
View File
@@ -1,8 +1,9 @@
import * as crypto from 'crypto';
import { eResponseMethod } from './enums/eResponseMethod';
import { IBlockTempalte } from './IBlockTempalte';
import { IBlockTemplate, IBlockTemplateTx } from './bitcoin-rpc/IBlockTemplate';
import { randomUUID } from 'crypto';
import { MerkleTree } from 'merkletreejs'
export class MiningJob {
public id: number;
@@ -20,7 +21,7 @@ export class MiningJob {
public ntime: string; // 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.
constructor(blockTemplate: IBlockTempalte) {
constructor(blockTemplate: IBlockTemplate) {
this.job_id = randomUUID();
this.prevhash = blockTemplate.previousblockhash;
@@ -31,27 +32,97 @@ export class MiningJob {
this.clean_jobs = false;
// Construct coinbase transaction
const coinbaseTransaction = blockTemplate.transactions[0];
const coinbaseHashBin = this.buildCoinbaseHashBin(coinbaseTransaction.data);
this.coinb1 = this.coinbasePrefix(coinbaseTransaction.data, coinbaseHashBin);
this.coinb2 = ''; // Assuming no suffix is required
const { coinb1, coinb2, coinbaseData } = this.createCoinbaseTransaction('', blockTemplate.height);
console.log(coinbaseData);
const coinbaseHash = this.bufferToHex(this.sha256(coinbaseData));
this.coinb1 = coinb1;
this.coinb2 = coinb2;
const transactions = blockTemplate.transactions.map(tx => tx.hash);
const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => {
return pre + cur.fee;
}, 0);
console.log('TRANSACTION FEES', transactionFees);
console.log('MINING REWARD', this.calculateMiningReward(blockTemplate.height));
transactions.unshift(coinbaseHash);
// Calculate merkle branch
const merkleBranch = blockTemplate.transactions.slice(1).map((transaction) => transaction.hash);
this.merkle_branch = this.buildMerkleBranch(merkleBranch, coinbaseHashBin);
const tree = new MerkleTree(transactions, this.sha256, { isBitcoinTree: true });
const layers = tree.getLayers();
const branch = [];
for (const layer of layers) {
branch.push(this.bufferToHex(layer[0]));
}
//console.log(branch);
this.merkle_branch = branch;
}
private calculateMiningReward(blockHeight: number): number {
const initialBlockReward = 50 * 1e8; // Initial block reward in satoshis (1 BTC = 100 million satoshis)
const halvingInterval = 210000; // Number of blocks after which the reward halves
// Calculate the number of times the reward has been halved
const halvingCount = Math.floor(blockHeight / halvingInterval);
// Calculate the current block reward in satoshis
const currentReward = initialBlockReward / Math.pow(2, halvingCount);
return currentReward;
}
private bufferToHex(buffer: Buffer): string {
return buffer.toString('hex');
}
private createCoinbaseTransaction(address: string, blockHeight: number): { coinb1: string, coinb2: string, coinbaseData: any } {
// Generate coinbase script
const coinbaseScript = `03${blockHeight.toString(16).padStart(8, '0')}54696d652026204865616c74682021`;
// Create coinbase transaction
const version = '01000000';
const inputs = '01' + '0000000000000000000000000000000000000000000000000000000000000000ffffffff';
const coinbaseScriptSize = coinbaseScript.length / 2;
const coinbaseScriptBytes = coinbaseScriptSize.toString(16).padStart(2, '0');
const coinbaseTransaction = inputs + coinbaseScriptBytes + coinbaseScript + '00000000';
// Create output
const outputCount = '01';
const satoshis = '0f4240'; // 6.25 BTC in satoshis (1 BTC = 100,000,000 satoshis)
const script = '1976a914' + address + '88ac'; // Change this to your desired output script
const locktime = '00000000';
// Combine coinb1 and coinb2
const coinb1 = version + coinbaseTransaction + outputCount + satoshis;
const coinb2 = script + locktime;
const coinbaseData = version + coinbaseTransaction + outputCount + satoshis + script + locktime;
return { coinb1, coinb2, coinbaseData };
}
private sha256(data) {
return crypto.createHash('sha256').update(data).digest()
}
public response() {
return {
id: 0,
method: eResponseMethod.MINING_NOTIFY,
params: [
'123',///this.job_id,
this.job_id,
this.prevhash,
this.coinb1,
this.coinb2,
@@ -66,58 +137,6 @@ export class MiningJob {
}
private coinbasePrefix(coinbase: string, coinbaseHashBin: Buffer): string {
const coinbaseData = Buffer.from(coinbase, 'hex');
const coinbaseSize = Buffer.alloc(1, coinbaseData.length);
const extraNoncePlaceholder = Buffer.alloc(4); // Assuming 4 bytes for extra nonce
const concatenatedBuffer = Buffer.concat([coinbaseSize, coinbaseData, extraNoncePlaceholder]);
const merkleRoot = this.doubleSHA(Buffer.concat([coinbaseHashBin, concatenatedBuffer]));
return merkleRoot.toString('hex');
}
private buildCoinbaseHashBin(coinbase: string): Buffer {
const sha256 = crypto.createHash('sha256');
const sha256Digest = sha256.update(Buffer.from(coinbase, 'hex')).digest();
const coinbaseHashSha256 = crypto.createHash('sha256');
const coinbaseHash = coinbaseHashSha256.update(sha256Digest).digest();
return coinbaseHash;
}
private buildMerkleBranch(merkleBranch: string[], coinbaseHashBin: Buffer): string[] {
const merkleRoots: string[] = [];
let merkleRoot = coinbaseHashBin;
for (const h of merkleBranch) {
const concatenatedBuffer = Buffer.concat([merkleRoot, Buffer.from(h, 'hex')]);
merkleRoot = this.doubleSHA(concatenatedBuffer);
merkleRoots.push(merkleRoot.toString('hex'));
}
return merkleRoots.slice(0, 1);
}
// private buildMerkleRoot(merkleBranch: string[], coinbaseHashBin: Buffer): string {
// let merkleRoot = coinbaseHashBin;
// for (const h of merkleBranch) {
// const concatenatedBuffer = Buffer.concat([merkleRoot, Buffer.from(h, 'hex')]);
// merkleRoot = this.doubleSHA(concatenatedBuffer);
// }
// return merkleRoot.toString('hex');
// }
private doubleSHA(data: Buffer): Buffer {
const sha256 = crypto.createHash('sha256');
const sha256Digest = sha256.update(data).digest();
const doubleSha256 = crypto.createHash('sha256');
const doubleSha256Digest = doubleSha256.update(sha256Digest).digest();
return doubleSha256Digest;
}
}
+39
View File
@@ -0,0 +1,39 @@
export interface IBlockTemplateTx {
data: string; //'hex', // (string) transaction data encoded in hexadecimal (byte-for-byte)
txid: string; //'hex', // (string) transaction id encoded in little-endian hexadecimal
hash: string; //'hex', // (string) hash encoded in little-endian hexadecimal (including witness data)
depends: number[]; // (json array) array of numbers // (numeric) transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is
fee: number, // (numeric) difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one
sigops: number, // (numeric) total SigOps cost, as counted for purposes of block limits; if key is not present, sigop cost is unknown and clients MUST NOT assume it is zero
weight: number // (numeric) total transaction weight, as counted for purposes of block limits
}
export interface IBlockTemplate {
// (json object)
version: number; // (numeric) The preferred block version
rules: string[]; // (json array) specific block rules that are to be enforced // (string) name of a rule the client must understand to some extent; see BIP 9 for format
vbavailable: { // (json object) set of pending, supported versionbit (BIP 9) softfork deployments
rulename: number, // (numeric) identifies the bit number as indicating acceptance and readiness for the named softfork rule
},
vbrequired: number, // (numeric) bit mask of versionbits the server requires set in submissions
previousblockhash: string, // (string) The hash of current highest block
transactions: IBlockTemplateTx[], // (json array) contents of non-coinbase transactions that should be included in the next block
coinbaseaux: { // (json object) data that should be included in the coinbase's scriptSig content
key: string; //'hex', // (string) values must be in the coinbase (keys may be ignored)
},
coinbasevalue: number, // (numeric) maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)
longpollid: string, // (string) an id to include with a request to longpoll on an update to this template
target: string, // (string) The hash target
mintime: number, // (numeric) The minimum timestamp appropriate for the next block time, expressed in UNIX epoch time
mutable: string[]; // (json array) list of ways the block template may be changed // (string) A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'
noncerange: string; // 'hex', // (string) A range of valid nonces
sigoplimit: number, // (numeric) limit of sigops in blocks
sizelimit: number, // (numeric) limit of block size
weightlimit: number, // (numeric) limit of block weight
curtime: number, // (numeric) current timestamp in UNIX epoch time
bits: string, // (string) compressed target of next block
height: number, // (numeric) The height of the next block
default_witness_commitment: string // (string, optional) a valid witness commitment for the unmodified block template
}
+11
View File
@@ -0,0 +1,11 @@
export interface IMiningInfo {
blocks: number,
currentblockweight: number,
currentblocktx: number,
difficulty: number,
networkhashps: number,
pooledtx: number,
chain: 'main' | 'test' | 'regtest',
warnings: string
}