Add Sapling support (#22)

Thanks to VerusHash for the original code https://github.com/VerusCoin/node-stratum-pool
This commit is contained in:
Nate Smith 2018-10-29 07:03:30 -04:00 committed by Beshoy Girgis
parent 62f0706017
commit 3daad16084
9 changed files with 1367 additions and 1532 deletions

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v8.11

View File

@ -13,6 +13,16 @@ var algos = module.exports = global.algos = {
}
}
},
verushash: {
multiplier: 1,
diff: parseInt('0x0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
hashReserved: '0000000000000000000000000000000000000000000000000000000000000000',
hash: function(coinOptions) {
return function(){
return true;
}
}
},
'equihash': {
multiplier: 1,
diff: parseInt('0x0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),

View File

@ -92,6 +92,11 @@ var BlockTemplate = module.exports = function BlockTemplate(
// generate the merkle root
this.prevHashReversed = util.reverseBuffer(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex');
if (rpcData.finalsaplingroothash) {
this.hashReserved = util.reverseBuffer(new Buffer(rpcData.finalsaplingroothash, 'hex')).toString('hex');
} else {
this.hashReserved = '0000000000000000000000000000000000000000000000000000000000000000'; //hashReserved
}
this.merkleRoot = merkle.getRoot(rpcData, this.genTxHash);
this.txCount = this.rpcData.transactions.length + 1; // add total txs and new coinbase
this.merkleRootReversed = util.reverseBuffer(new Buffer(this.merkleRoot, 'hex')).toString('hex');
@ -106,15 +111,16 @@ var BlockTemplate = module.exports = function BlockTemplate(
console.log('nonce:' + nonce);
console.log('this.rpcData.bits: ' + this.rpcData.bits);
console.log('nTime: ' + nTime);
console.log('this.merkleRootReversed: ' + this.merkleRoot);
console.log('this.merkleRootReversed: ' + this.merkleRootReversed);
console.log('this.prevHashReversed: ' + this.prevHashReversed);
console.log('this.hashReserved: ' + this.hashReserved);
console.log('this.rpcData.version: ' + this.rpcData.version);
*/
header.writeUInt32LE(this.rpcData.version, position += 0, 4, 'hex');
header.write(this.prevHashReversed, position += 4, 32, 'hex');
header.write(this.merkleRootReversed, position += 32, 32, 'hex');
header.write('0000000000000000000000000000000000000000000000000000000000000000', position += 32, 32, 'hex'); //hashReserved
header.write(this.hashReserved, position += 32, 32, 'hex');
header.write(nTime, position += 32, 4, 'hex');
header.write(util.reverseBuffer(new Buffer(rpcData.bits, 'hex')).toString('hex'), position += 4, 4, 'hex');
header.write(nonce, position += 4, 32, 'hex');
@ -131,9 +137,11 @@ var BlockTemplate = module.exports = function BlockTemplate(
if (this.txCount <= 0x7f){
var varInt = new Buffer(txCount, 'hex');
}
else if (this.txCount <= 0x7fff){
var varInt = new Buffer.concat([Buffer('FD', 'hex'), new Buffer(txCount, 'hex')]);
} else if (this.txCount <= 0x7fff) {
if (txCount.length == 2) {
txCount = "00" + txCount;
}
var varInt = new Buffer.concat([Buffer('FD', 'hex'), util.reverseBuffer(new Buffer(txCount, 'hex'))]);
}
buf = new Buffer.concat([
@ -180,7 +188,7 @@ var BlockTemplate = module.exports = function BlockTemplate(
util.packUInt32LE(this.rpcData.version).toString('hex'),
this.prevHashReversed,
this.merkleRootReversed,
'0000000000000000000000000000000000000000000000000000000000000000', //hashReserved
this.hashReserved,
util.packUInt32LE(rpcData.curtime).toString('hex'),
util.reverseBuffer(new Buffer(this.rpcData.bits, 'hex')).toString('hex'),
true

View File

@ -6,6 +6,8 @@ var bignum = require('bignum');
var util = require('./util.js');
var blockTemplate = require('./blockTemplate.js');
var vh = require('verushash');
const EH_PARAMS_MAP = {
"144_5": {
SOLUTION_LENGTH: 202,
@ -185,24 +187,34 @@ var JobManager = module.exports = function JobManager(options) {
return {error: error, result: null};
};
//console.log('processShare ck1: ', jobId, previousDifficulty, difficulty, extraNonce1, extraNonce2, nTime, nonce, ipAddress, port, workerName, soln)
var submitTime = Date.now() / 1000 | 0;
var job = this.validJobs[jobId];
if (typeof job === 'undefined' || job.jobId != jobId) {
// console.log('job not found');
return shareError([21, 'job not found']);
}
if (nTime.length !== 8) {
// console.log('incorrect size of ntime');
return shareError([20, 'incorrect size of ntime']);
}
//console.log('processShare ck2')
var nTimeInt = parseInt(util.reverseBuffer(new Buffer(nTime, 'hex')), 16);
if (nTimeInt < job.rpcData.curtime || nTimeInt > submitTime + 7200) {
// console.log('ntime out of range');
return shareError([20, 'ntime out of range']);
}
//console.log('processShare ck3')
if (nonce.length !== 64) {
// console.log('incorrect size of nonce');
return shareError([20, 'incorrect size of nonce']);
}
@ -224,23 +236,45 @@ var JobManager = module.exports = function JobManager(options) {
let solutionSlice = EH_PARAMS_MAP[`${N}_${K}`].SOLUTION_SLICE || 0
if (soln.length !== expectedLength) {
// console.log('Error: Incorrect size of solution (' + soln.length + '), expected ' + expectedLength);
return shareError([20, 'Error: Incorrect size of solution (' + soln.length + '), expected ' + expectedLength]);
}
if (!isHexString(extraNonce2)) {
// console.log('invalid hex in extraNonce2');
return shareError([20, 'invalid hex in extraNonce2']);
}
if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) {
//console.log('processShare ck4')
if (!job.registerSubmit(nonce, soln)) {
return shareError([22, 'duplicate share']);
}
//console.log('processShare ck5')
var extraNonce1Buffer = new Buffer(extraNonce1, 'hex');
var extraNonce2Buffer = new Buffer(extraNonce2, 'hex');
var headerBuffer = job.serializeHeader(nTime, nonce); // 144 bytes (doesn't contain soln)
var headerSolnBuffer = new Buffer.concat([headerBuffer, new Buffer(soln, 'hex')]);
var headerHash = util.sha256d(headerSolnBuffer);
var headerHash;
//console.log('processShare ck6')
switch (options.coin.algorithm) {
case 'verushash':
//console.log('processShare ck6a, buffer length: ', headerSolnBuffer.length)
headerHash = vh.hash(headerSolnBuffer);
break;
default:
//console.log('processShare ck6b')
headerHash = util.sha256d(headerSolnBuffer);
break;
};
//console.log('processShare ck7')
var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32});
var blockHashInvalid;
@ -250,16 +284,22 @@ var JobManager = module.exports = function JobManager(options) {
var shareDiff = diff1 / headerBigNum.toNumber() * shareMultiplier;
var blockDiffAdjusted = job.difficulty * shareMultiplier;
//console.log('processShare ck8')
// check if valid solution
if (hashDigest(headerBuffer, new Buffer(soln.slice(solutionSlice), 'hex')) !== true) {
//console.log('invalid solution');
return shareError([20, 'invalid solution']);
}
//check if block candidate
if (headerBigNum.le(job.target)) {
//console.log('begin serialization');
blockHex = job.serializeBlock(headerBuffer, new Buffer(soln, 'hex')).toString('hex');
blockHash = util.reverseBuffer(headerHash).toString('hex');
//console.log('end serialization');
} else {
//console.log('low difficulty share');
if (options.emitInvalidBlockHashes)
blockHashInvalid = util.reverseBuffer(util.sha256d(headerSolnBuffer)).toString('hex');

View File

@ -124,8 +124,8 @@ var pool = module.exports = function pool(options, authorizeFn) {
function OnBlockchainSynced(syncedCallback) {
var callConfig = {
"capabilities": [
"coinbasetxn",
"workid",
"coinbasetxn",
"workid",
"coinbase/append"
]
};
@ -314,6 +314,7 @@ var pool = module.exports = function pool(options, authorizeFn) {
_this.stratumServer.broadcastMiningJobs(job);
}
}).on('share', function (shareData, blockHex) {
//console.log('share :', isValidShare, isValidBlock, shareData)
var isValidShare = !shareData.error;
var isValidBlock = !!blockHex;
var emitShare = function () {
@ -396,7 +397,7 @@ var pool = module.exports = function pool(options, authorizeFn) {
} else {
batchRpcCalls.push(['getblockchaininfo', []], ['getnetworkinfo', []]);
}
_this.daemon.batchCmd(batchRpcCalls, function (error, results) {
if (error || !results) {
emitErrorLog('Could not start pool, error with init batch RPC call: ' + JSON.stringify(error));
@ -583,22 +584,19 @@ var pool = module.exports = function pool(options, authorizeFn) {
function GetBlockTemplate(callback) {
function getBlockSubsidyandTemplate(callback) {
_this.daemon.cmd('getblocksubsidy',
function getBlockSubsidyandTemplate() {
_this.daemon.cmd(
'getblocksubsidy',
[],
function (result) {
callback = function (subsidy) {getBlockTemplate(subsidy)};
var subsidy = result[0].response;
callback(subsidy);
});
result => result.error ? callback(result.error) : getBlockTemplate(result[0].response)
)
}
function getBlockTemplate(subsidy) {
var callConfig = {
"capabilities": [
"coinbasetxn",
"workid",
"coinbasetxn",
"workid",
"coinbase/append"
]
};

View File

@ -160,19 +160,22 @@ var StratumClient = function(options){
}
function handleSubmit(message){
//console.log('share submitted: ', _this.workerName, message.params[1], message.params[2], message.params[3], message.params[4], _this.extraNonce1 + message.params[3])
if (!_this.workerName){
_this.workerName = getSafeWorkerString(message.params[0]);
}
if (_this.authorized === false){
//console.log('not authorized')
sendJson({
id : message.id,
result: null,
error : [24, "unauthorized worker", null]
error : [24, 'unauthorized worker', null]
});
considerBan(false);
return;
}
if (!_this.extraNonce1){
//console.log("not subscribed")
sendJson({
id : message.id,
result: null,

View File

@ -1,4 +1,4 @@
const bitcoin = require('bitcoinjs-lib-zcash')
const bitcoin = require('bitgo-utxo-lib')
const util = require('./util.js')
const scriptCompile = addrHash => bitcoin.script.compile([
@ -21,10 +21,21 @@ exports.txHash = () => txHash
exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAddress, poolHex, coin, masternodeReward, masternodePayee, masternodePayment) => {
let poolAddrHash = bitcoin.address.fromBase58Check(poolAddress).hash
let tx = new bitcoin.Transaction()
if (coin.overwinter) {
tx.setOverwinter()
let network = bitcoin.networks[coin.symbol]
//console.log('network: ', network)
let txb = new bitcoin.TransactionBuilder(network)
// Set sapling or overwinter to either true OR block height to activate.
// NOTE: if both are set, sapling will be used.
if (coin.sapling) {
if (coin.sapling === true || (typeof coin.sapling === 'number' && coin.sapling <= blockHeight)) {
txb.setVersion(bitcoin.Transaction.ZCASH_SAPLING_VERSION);
}
} else if (coin.overwinter) {
if (coin.overwinter === true || (typeof coin.overwinter === 'number' && coin.overwinter <= blockHeight)) {
txb.setVersion(bitcoin.Transaction.ZCASH_OVERWINTER_VERSION);
}
}
// input for coinbase tx
@ -43,7 +54,7 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
new Buffer('00', 'hex') // OP_0
])
tx.addInput(new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
txb.addInput(new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
4294967295,
4294967295,
new Buffer.concat([
@ -84,25 +95,25 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// console.log(`superNodesAddr: ${coin.vSuperNodesRewardAddress[indexXN]}`)
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (coin.percentTreasuryUpdateReward + coin.percentSecureNodesReward + coin.percentSuperNodesReward + feePercent) / 100)) + feeReward
)
// treasury t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(foundersAddrHash),
Math.round(blockReward.total * (coin.percentTreasuryUpdateReward / 100))
)
// Secure Nodes t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(secureNodesAddrHash),
Math.round(blockReward.total * (coin.percentSecureNodesReward / 100))
)
// Super Nodes t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(superNodesAddrHash),
Math.round(blockReward.total * (coin.percentSuperNodesReward / 100))
)
@ -117,13 +128,13 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// console.log(`treasuryAddr: ${coin.vTreasuryRewardAddress[index]}`)
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (coin.percentTreasuryReward + feePercent) / 100)) + feeReward
)
// treasury t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(foundersAddrHash),
Math.round(blockReward.total * (coin.percentTreasuryReward / 100))
)
@ -158,21 +169,21 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// console.log('Pool Fee Deduction Total Percent: ' + poolFeeDeductionTotalPercent);
// console.log('Pool Fee Deduction Total Amount: ' + poolDeductionAmount);
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
blockReward.miner - poolDeductionAmount + feeReward
);
// Add founders txs
tx.addOutput( scriptFoundersCompile(chrisAddrHash), blockReward.founderSplit);
tx.addOutput( scriptFoundersCompile(jimmyAddrHash), blockReward.founderSplit);
tx.addOutput( scriptFoundersCompile(scottAddrHash), blockReward.founderSplit);
tx.addOutput( scriptFoundersCompile(shelbyAddrHash), blockReward.founderSplit);
tx.addOutput( scriptFoundersCompile(lokiAddrHash), blockReward.founderSplit);
txb.addOutput( scriptFoundersCompile(chrisAddrHash), blockReward.founderSplit);
txb.addOutput( scriptFoundersCompile(jimmyAddrHash), blockReward.founderSplit);
txb.addOutput( scriptFoundersCompile(scottAddrHash), blockReward.founderSplit);
txb.addOutput( scriptFoundersCompile(shelbyAddrHash), blockReward.founderSplit);
txb.addOutput( scriptFoundersCompile(lokiAddrHash), blockReward.founderSplit);
// Infrastructure
tx.addOutput( scriptFoundersCompile(infrastructureAddrHash), blockReward.infrastructure);
txb.addOutput( scriptFoundersCompile(infrastructureAddrHash), blockReward.infrastructure);
// Giveaways
tx.addOutput( scriptFoundersCompile(giveawaysAddrHash), blockReward.giveaways);
txb.addOutput( scriptFoundersCompile(giveawaysAddrHash), blockReward.giveaways);
} else {
// founders reward
let index = parseInt(Math.floor(rpcData.height / coin.foundersRewardAddressChangeInterval))
@ -182,13 +193,13 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// console.log(`foundersAddr: ${coin.vFoundersRewardAddress[index]}`)
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (coin.percentFoundersReward + feePercent) / 100)) + feeReward
);
// founders t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(foundersAddrHash),
Math.round(blockReward.total * (coin.percentFoundersReward / 100))
)
@ -196,7 +207,7 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// no founders rewards :)
} else {
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (feePercent / 100))) + feeReward
)
@ -217,19 +228,19 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// console.log(`treasuryAddr: ${coin.vTreasuryRewardAddress[index]}`)
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (coin.percentTreasuryReward + feePercent) / 100)) + feeReward - masternodeReward
)
// treasury t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(foundersAddrHash),
Math.round(blockReward.total * (coin.percentTreasuryReward / 100))
)
//masternode reward
tx.addOutput(
txb.addOutput(
scriptCompile(masternodeAddrHash),
Math.round(masternodeReward)
)
@ -242,19 +253,19 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// console.log(`foundersAddr: ${coin.vFoundersRewardAddress[index]}`)
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (coin.percentFoundersReward + feePercent) / 100)) + feeReward - masternodeReward
)
// founders t-addr
tx.addOutput(
txb.addOutput(
scriptFoundersCompile(foundersAddrHash),
Math.round(blockReward.total * (coin.percentFoundersReward / 100))
)
//masternode reward
tx.addOutput(
txb.addOutput(
scriptCompile(masternodeAddrHash),
Math.round(masternodeReward)
)
@ -262,35 +273,41 @@ exports.createGeneration = (rpcData, blockReward, feeReward, recipients, poolAdd
// no founders rewards :)
} else {
// pool t-addr
tx.addOutput(
txb.addOutput(
scriptCompile(poolAddrHash),
Math.round(blockReward.total * (1 - (feePercent / 100))) + feeReward - masternodeReward
)
//masternode reward
tx.addOutput(
txb.addOutput(
scriptCompile(masternodeAddrHash),
Math.round(masternodeReward)
)
}
}
// pool fee recipients t-addr
//recipients.forEach(recipient => console.log(`recipient address: ${recipient.address}`));
if (recipients.length > 0 && recipients[0].address != '')
{
recipients.forEach(recipient => tx.addOutput(
scriptCompile(bitcoin.address.fromBase58Check(recipient.address).hash),
Math.round(blockReward.total * (recipient.percent / 100))
))
// Segwit support
if (rpcData.default_witness_commitment !== undefined) {
txb.addOutput(new Buffer(rpcData.default_witness_commitment, 'hex'), 0);
}
// Segwit support
if (rpcData.default_witness_commitment !== undefined) {
tx.addOutput(new Buffer(rpcData.default_witness_commitment, 'hex'), 0);
// pool fee recipients t-addr
if (coin.burnFees) {
let burn = feeReward
recipients.forEach(recipient => {
txb.addOutput(
scriptCompile(bitcoin.address.fromBase58Check(recipient.address).hash),
Math.round(blockReward * (recipient.percent / 100) - burn)
)
burn = 0
})
}
let tx = txb.build()
txHex = tx.toHex()
// console.log('hex coinbase transaction: ' + txHex)
// assign
txHash = tx.getHash().toString('hex')

2691
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,8 +32,9 @@
"async": "^2.6.1",
"base58-native": "^0.1.4",
"bignum": "^0.13.0",
"bitcoinjs-lib-zcash": "git+https://github.com/s-nomp/bitcoinjs-lib.git",
"bitgo-utxo-lib": "git+https://github.com/miketout/bitgo-utxo-lib.git",
"equihashverify": "git+https://github.com/s-nomp/equihashverify.git",
"verushash": "git+https://github.com/VerusCoin/verushash-node",
"merkle-bitcoin": "^1.0.2",
"promise": "^8.0.1"
},