stratum-pool-sha256
Version:
High performance SHA-256 Stratum poolserver in Node.js - zero dependencies, pure JavaScript
227 lines (195 loc) • 8.79 kB
JavaScript
var bignum = require('./bignum-compat');
var merkleTree = require('./merkleTree.js');
var transactions = require('./transactions.js');
var util = require('./util.js');
/**
* The BlockTemplate class holds a single mining job.
* It encapsulates block template data from the daemon and provides methods
* to serialize block data and validate share submissions.
*
* @class BlockTemplate
* @param {string} jobId - Unique identifier for this job
* @param {Object} rpcData - Block template data from getblocktemplate RPC
* @param {Buffer} poolAddressScript - Pool's address script for coinbase output
* @param {Buffer} extraNoncePlaceholder - Placeholder for extranonce in coinbase
* @param {string} reward - Reward type ('POW' or 'POS')
* @param {Array} txMessages - Additional messages to include in coinbase
* @param {Array} recipients - Fee recipients for coinbase outputs
*/
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, poolAddressScript, extraNoncePlaceholder, reward, txMessages, recipients){
//private members
var submits = [];
function getMerkleHashes(steps){
return steps.map(function(step){
return step.toString('hex');
});
}
function getTransactionBuffers(txs){
var txHashes = txs.map(function(tx){
if (tx.txid !== undefined) {
return util.uint256BufferFromHash(tx.txid);
}
return util.uint256BufferFromHash(tx.hash);
});
return [null].concat(txHashes);
}
function getVoteData(){
if (!rpcData.masternode_payments) return Buffer.alloc(0);
return Buffer.concat(
[util.varIntBuffer(rpcData.votes.length)].concat(
rpcData.votes.map(function (vt) {
return Buffer.from(vt, 'hex');
})
)
);
}
//public members
this.rpcData = rpcData;
this.jobId = jobId;
// Debug logging for version field
console.log('DEBUG: BlockTemplate constructor - rpcData.version = ' + rpcData.version);
console.log('DEBUG: BlockTemplate constructor - rpcData.version (hex) = 0x' + rpcData.version.toString(16));
console.log('DEBUG: BlockTemplate constructor - rpcData.version type = ' + typeof rpcData.version);
this.target = rpcData.target ?
bignum(rpcData.target, 16) :
util.bignumFromBitsHex(rpcData.bits);
// Calculate difficulty using BigInt to maintain precision
var diff1BigInt = BigInt('0x00000000ffff0000000000000000000000000000000000000000000000000000');
var targetBigInt = this.target.value; // Access the underlying BigInt directly
var precisionFactor = BigInt(1e9);
var difficultyBigInt = (diff1BigInt * precisionFactor) / targetBigInt;
this.difficulty = Number(difficultyBigInt) / 1e9;
this.prevHashReversed = util.reverseByteOrder(Buffer.from(rpcData.previousblockhash, 'hex')).toString('hex');
this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){
return Buffer.from(tx.data, 'hex');
}));
this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions));
this.merkleBranch = getMerkleHashes(this.merkleTree.steps);
this.generationTransaction = transactions.CreateGeneration(
rpcData,
poolAddressScript,
extraNoncePlaceholder,
reward,
txMessages,
recipients
);
/**
* Serializes the coinbase transaction with the provided extranonces.
*
* @method serializeCoinbase
* @param {Buffer} extraNonce1 - Worker's extranonce1
* @param {Buffer} extraNonce2 - Miner's extranonce2
* @returns {Buffer} Complete serialized coinbase transaction
*/
this.serializeCoinbase = function(extraNonce1, extraNonce2){
return Buffer.concat([
this.generationTransaction[0],
extraNonce1,
extraNonce2,
this.generationTransaction[1]
]);
};
/**
* Serializes a block header according to Bitcoin protocol specification.
* @see {@link https://en.bitcoin.it/wiki/Protocol_specification#Block_Headers}
*
* @method serializeHeader
* @param {string} merkleRoot - Merkle root hash (hex)
* @param {string} nTime - Block timestamp (hex)
* @param {string} nonce - Mining nonce (hex)
* @param {number} [version] - Block version for ASICBoost support
* @returns {Buffer} 80-byte serialized block header
*/
this.serializeHeader = function(merkleRoot, nTime, nonce, version){
var header = Buffer.alloc(80);
var position = 0;
header.write(nonce, position, 4, 'hex');
header.write(rpcData.bits, position += 4, 4, 'hex');
header.write(nTime, position += 4, 4, 'hex');
header.write(merkleRoot, position += 4, 32, 'hex');
header.write(rpcData.previousblockhash, position += 32, 32, 'hex');
// Debug logging for version being used
var actualVersion = version || rpcData.version;
console.log('DEBUG: serializeHeader - version parameter = ' + version);
console.log('DEBUG: serializeHeader - rpcData.version = ' + rpcData.version);
console.log('DEBUG: serializeHeader - actualVersion used = ' + actualVersion);
console.log('DEBUG: serializeHeader - actualVersion (hex) = 0x' + actualVersion.toString(16));
// Use provided version for ASICBoost support, or default to rpcData version
header.writeUInt32BE(actualVersion, position + 32);
var header = util.reverseBuffer(header);
return header;
};
/**
* Serializes a complete block including header and all transactions.
*
* @method serializeBlock
* @param {Buffer} header - Serialized block header
* @param {Buffer} coinbase - Serialized coinbase transaction
* @returns {Buffer} Complete serialized block ready for submission
*/
this.serializeBlock = function(header, coinbase){
return Buffer.concat([
header,
util.varIntBuffer(this.rpcData.transactions.length + 1),
coinbase,
this.transactionData,
getVoteData(),
//POS coins require a zero byte appended to block which the daemon replaces with the signature
reward === 'POS' ? Buffer.from([0]) : Buffer.alloc(0)
]);
};
/**
* Registers a share submission to prevent duplicate shares.
*
* @method registerSubmit
* @param {string} extraNonce1 - Worker's extranonce1
* @param {string} extraNonce2 - Miner's extranonce2
* @param {string} nTime - Block timestamp
* @param {string} nonce - Mining nonce
* @returns {boolean} True if this is a new submission, false if duplicate
*/
this.registerSubmit = function(extraNonce1, extraNonce2, nTime, nonce){
var submission = extraNonce1 + extraNonce2 + nTime + nonce;
if (submits.indexOf(submission) === -1){
submits.push(submission);
return true;
}
return false;
};
/**
* Gets the job parameters for Stratum mining.notify message.
*
* @method getJobParams
* @returns {Array} Job parameters array for Stratum protocol
* @returns {string} params[0] - Job ID
* @returns {string} params[1] - Previous block hash (reversed)
* @returns {string} params[2] - Coinbase part 1
* @returns {string} params[3] - Coinbase part 2
* @returns {Array} params[4] - Merkle branch
* @returns {string} params[5] - Block version
* @returns {string} params[6] - Encoded difficulty (bits)
* @returns {string} params[7] - Current time
* @returns {boolean} params[8] - Clean jobs flag
*/
this.getJobParams = function(){
if (!this.jobParams){
// Debug logging for version in job params
console.log('DEBUG: getJobParams - rpcData.version = ' + this.rpcData.version);
console.log('DEBUG: getJobParams - rpcData.version (hex) = 0x' + this.rpcData.version.toString(16));
var versionHex = util.packInt32BE(this.rpcData.version).toString('hex');
console.log('DEBUG: getJobParams - version packed as hex = ' + versionHex);
this.jobParams = [
this.jobId,
this.prevHashReversed,
this.generationTransaction[0].toString('hex'),
this.generationTransaction[1].toString('hex'),
this.merkleBranch,
versionHex,
this.rpcData.bits,
util.packUInt32BE(this.rpcData.curtime).toString('hex'),
true
];
}
return this.jobParams;
};
};