From 9486c6f75cff42cb0695e017b6e75ee1e4141d31 Mon Sep 17 00:00:00 2001 From: JCThePants Date: Tue, 12 May 2020 11:25:51 -0700 Subject: [PATCH] Initial Zcoin commit --- README.md | 88 ++++++++++++++++++-- config.example.json | 4 +- libs/class.ClientReader.js | 78 +++++++++++------- libs/class.ClientWriter.js | 36 ++++---- libs/class.Coinbase.js | 43 +++++++++- libs/class.Job.js | 50 +++++------- libs/class.JobManager.js | 18 ++-- libs/class.Server.js | 1 + libs/class.Share.js | 163 +++++++++++++++++++++++++++++-------- libs/class.Socket.js | 67 ++++++++------- libs/class.Stratum.js | 4 +- libs/service.algorithm.js | 13 ++- package-lock.json | 39 ++++++++- package.json | 12 +-- start.js | 20 +++-- 15 files changed, 454 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index a11ac9b..3676c19 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -ref-stratum -=========== +ref-stratum-zcoin +================= This Reference Stratum is a simple implementation used as a basis for testing, experimentation, and demonstration purposes. It is not intended for production use. +This project has been developed and tested on [Node v10](https://nodejs.org/) and [Ubuntu 16.04](http://releases.ubuntu.com/16.04/) + ## Install ## __Dependencies__ ```bash @@ -15,9 +17,85 @@ npm config set //npm.pkg.github.com/:_authToken __Download from Github__ ```bash -git clone https://github.com/MintPond/ref-stratum +git clone https://github.com/MintPond/ref-stratum-zcoin # install -cd ref-stratum +cd ref-stratum-zcoin npm install -``` \ No newline at end of file +``` + +__Install in a Project__ +```bash + +npm config set @mintpond:registry https://npm.pkg.github.com/mintpond +npm config set //npm.pkg.github.com/:_authToken + +npm install @mintpond/ref-stratum-zcoin --save +``` + +## Usage ## +The stratum can be used as a module in a pool: +```javascript +const Stratum = require('@mintpond/ref-stratum-zcoin').Stratum; + +class MyStratum extends Stratum { + /* Override */ + canAuthorizeWorker(client, callback) { + // implement your own logic + if (client.minerAddress === 'bad') { + // do not authorize worker + callback(null/*error*/, false/*isAuthorized*/); + } + else { + // authorize worker + callback(null/*error*/, true/*isAuthorized*/); + } + } +} + +const stratum = new MyStratum({ + coinbaseAddress: 'TC6qME2GhepR7656DgsR72pkQDmhfTDbtV', // address that receives block reward + blockBrand: '/@mintpond/ref-stratum/', // Branding string added to every block found + host: "0.0.0.0", // address the stratum will listen on + port: { + number: 3000, // port the stratum will listen on + diff: 1024 // stratum difficulty + }, + rpc: { + host: '172.16.3.102', // Zcoin daemon RPC host + port: 17001, // Zcoin daemon RPC port + user: 'rpcuser', // Zcoin daemon RPC user + password: "x" // Zcoin daemon RPC password + }, + jobUpdateInterval: 55, // Broadcast job updates every n seconds + blockPollIntervalMs: 250 // Check for new blocks every n milliseconds +}); + +stratum.on(Stratum.EVENT_SHARE_SUBMITTED, ev => { + console.log(ev.share); +}); + +stratum.init(); +``` + +### Start Script ### +There is a start script (`start.js`) included which contains further +examples. It can also be run in order to get a Stratum going for test +purposes. You will need to open and modify the config inside before +running it. +``` +> node start +``` + +## Areas of Interest ## +- [ClientReader](libs/class.ClientReader.js) - Handles messages received from a client. +- [ClientWriter](libs/class.ClientWriter.js) - Handles sending messages to a client. +- [Coinbase](libs/class.Coinbase.js) - Creates coinbase transaction and includes founder rewards. +- [Share](libs/class.Share.js) - Processes shares, validates proofs, creates blocks. +- [Socket](libs/class.Socket.js) - Handles binary BOS serialization and deserialization. +- [algorithm](libs/service.algorithm.js) - Contains MTP constants and hash verification. + + +## Resources ## +- [Zcoin](https://zcoin.io/) - The first cryptocurrency to implement the Merkle Tree Proof POW algorithm. +- [MintPond Mining Pool](https://mintpond.com) - Zcoin mining pool. \ No newline at end of file diff --git a/config.example.json b/config.example.json index 8330f56..cdfc6b8 100644 --- a/config.example.json +++ b/config.example.json @@ -1,9 +1,9 @@ { - "coinbaseAddress": "mpN96WyD5Xb8q66aVsfd7P6P2WJDdqapKf", + "coinbaseAddress": "TC6qME2GhepR7656DgsR72pkQDmhfTDbtV", "blockBrand": "/@mintpond/ref-stratum/", "host": "0.0.0.0", "port": { - "number": 3020, + "number": 3000, "diff": 1024 }, "rpc": { diff --git a/libs/class.ClientReader.js b/libs/class.ClientReader.js index 0644491..e46a605 100644 --- a/libs/class.ClientReader.js +++ b/libs/class.ClientReader.js @@ -3,6 +3,7 @@ const precon = require('@mintpond/mint-precon'), mu = require('@mintpond/mint-utils'), + buffers = require('@mintpond/mint-utils').buffers, Share = require('./class.Share'), StratumError = require('./class.StratumError'); @@ -184,29 +185,65 @@ class ClientReader { } if (!Array.isArray(message.params)) { - _._client.emit(Client.EVENT_MALFORMED_MESSAGE, { message: message }); + _._client.disconnect('Malformed message: params is not an array'); return true/*isHandled*/; } const workerName = message.params[0]; - if (!workerName || !mu.isString(workerName)) { - _._client.emit(Client.EVENT_MALFORMED_MESSAGE, { message: message }); + const jobIdBuf = message.params[1]; + const extraNonce2Buf = message.params[2]; + const nTimeBuf = message.params[3]; + const nonceBuf = message.params[4]; + const mtpHashRootBuf = message.params[5]; + const mtpBlockBuf = message.params[6]; + const mtpProofBuf = message.params[7]; + + if (!Buffer.isBuffer(jobIdBuf)) { + _._client.disconnect('Malformed message: jobIdBuf is not a Buffer'); + return true/*isHandled*/; + } + + if (!Buffer.isBuffer(extraNonce2Buf)) { + _._client.disconnect('Malformed message: extraNonce2Buf is not a Buffer'); return true/*isHandled*/; } - const jobIdHex = _._hex(message.params[1]); - const extraNonce2Hex = _._hex(message.params[2]); - const nTimeHex = _._hex(message.params[3]); - const nonceHex = _._hex(message.params[4]); + if (!Buffer.isBuffer(nTimeBuf)) { + _._client.disconnect('Malformed message: nTimeBuf is not a Buffer'); + return true/*isHandled*/; + } + + if (!Buffer.isBuffer(nonceBuf)) { + _._client.disconnect('Malformed message: nonceBuf is not a Buffer'); + return true/*isHandled*/; + } + + if (!Buffer.isBuffer(mtpHashRootBuf)) { + _._client.disconnect('Malformed message: mtpHashRootBuf is not a Buffer'); + return true/*isHandled*/; + } + + if (!Buffer.isBuffer(mtpBlockBuf)) { + _._client.disconnect('Malformed message: mtpBlockBuf is not a Buffer'); + return true/*isHandled*/; + } + + if (!Buffer.isBuffer(mtpProofBuf)) { + _._client.disconnect('Malformed message: mtpProofBuf is not a Buffer'); + return true/*isHandled*/; + } const share = new Share({ client: _._client, stratum: _._stratum, workerName: workerName, - jobIdHex: jobIdHex, - extraNonce2Hex: extraNonce2Hex, - nTimeHex: nTimeHex, - nonceHex: nonceHex + jobIdHex: buffers.leToHex(jobIdBuf), + extraNonce2Buf: extraNonce2Buf, + nTimeBuf: nTimeBuf, + nonceBuf: nonceBuf, + mtpHashRootBuf: mtpHashRootBuf, + mtpBlockBuf: mtpBlockBuf, + mtpProofBuf: mtpProofBuf }); const isValid = share.validate(); @@ -221,25 +258,6 @@ class ClientReader { return true/*isHandled*/; } - - - _hex(val) { - - let value; - - if (mu.isString(val)) { - - if (val.startsWith('0x')) - val = val.substr(2); - - value = val.toLowerCase(); - } - else { - value = ''; - } - - return value; - } } module.exports = ClientReader; \ No newline at end of file diff --git a/libs/class.ClientWriter.js b/libs/class.ClientWriter.js index eacc1ca..7090fda 100644 --- a/libs/class.ClientWriter.js +++ b/libs/class.ClientWriter.js @@ -3,6 +3,8 @@ const precon = require('@mintpond/mint-precon'), mu = require('@mintpond/mint-utils'), + buffers = require('@mintpond/mint-utils').buffers, + algorithm = require('./service.algorithm'), Job = require('./class.Job'), StratumError = require('./class.StratumError'); @@ -52,18 +54,10 @@ class ClientWriter { const replyId = args.replyId; const subscriptionIdHex = _._client.subscriptionIdHex; const extraNonce1Hex = _._client.extraNonce1Hex; - const extraNonce2Size = _._client.extraNonce2Size; _._socket.send({ id: replyId, - result: [ - [ - ['mining.set_difficulty', subscriptionIdHex], - ['mining.notify', subscriptionIdHex] - ], - extraNonce1Hex, - extraNonce2Size - ], + result: [buffers.hexToLE(subscriptionIdHex), buffers.hexToLE(extraNonce1Hex)], error: null }); } @@ -81,10 +75,14 @@ class ClientWriter { const diff = args.diff; if (mu.isNumber(diff)) { + + const nDiff = diff / algorithm.multiplier; + const targetBuf = buffers.packUInt256LE(algorithm.diff1 / nDiff); + _._socket.send({ id: null, - method: 'mining.set_difficulty', - params: [diff] + method: 'mining.set_target', + params: [targetBuf] }); } @@ -92,14 +90,14 @@ class ClientWriter { id: null, method: 'mining.notify', params: [ - /* 0 Job Id */ job.idHex, - /* 1 prevhash */ job.prevBlockId, - /* 2 coinb1 */ job.coinbase.coinbase1Buf.toString('hex'), - /* 3 coinb2 */ job.coinbase.coinbase2Buf.toString('hex'), - /* 4 merkle_branch */ job.merkleTree.branchHexArr, - /* 5 version */ job.versionHex, - /* 6 nbits (diff) */ job.bitsHex, - /* 7 ntime */ job.curTimeHex, + /* 0 Job Id */ buffers.hexToLE(job.idHex), + /* 1 prevhash */ buffers.hexToLE(job.prevBlockId), + /* 2 coinb1 */ job.coinbase.coinbase1Buf, + /* 3 coinb2 */ job.coinbase.coinbase2Buf, + /* 4 merkle_branch */ job.merkleTree.branchBufArr, + /* 5 version */ job.versionBuf, + /* 6 nbits (diff) */ job.bitsBuf, + /* 7 ntime */ job.curTimeBuf, /* 8 clean_jobs */ cleanJobs ] }); diff --git a/libs/class.Coinbase.js b/libs/class.Coinbase.js index fccb6c8..6164646 100644 --- a/libs/class.Coinbase.js +++ b/libs/class.Coinbase.js @@ -8,7 +8,7 @@ const Share = require('./class.Share'); const - EXTRANONCE_SIZE = 8, + EXTRANONCE_SIZE = 16, BUFFER_U32_ZERO = buffers.packUInt32LE(0), BUFFER_VAR_ONE = buffers.packVarInt(1), BUFFER_32_MAX = Buffer.from('FFFFFFFF', 'hex'), @@ -72,8 +72,8 @@ class Coinbase { const coinbase2Buf = _.coinbase2Buf; return Buffer.concat([ coinbase1Buf, - Buffer.from(share.extraNonce1Hex, 'hex'), - Buffer.from(share.extraNonce2Hex, 'hex'), + share.extraNonce1Buf, + share.extraNonce2Buf, coinbase2Buf ]); } @@ -138,11 +138,46 @@ class Coinbase { const outputTxsArr = []; const blockTemplate = _._blockTemplate; const poolAddressScript = scripts.makeAddressScript(_._coinbaseAddress) + const isTestnet = _._coinbaseAddress[0] === 'T'; let poolRewardSt = blockTemplate.coinbasevalue; + + const feeRewardSt = Math.round(poolRewardSt * 0.0025); + poolRewardSt -= feeRewardSt; + _._txCount = 0; - _._addTransaction(outputTxsArr, poolRewardSt, poolAddressScript, true); + const founder1RewardSt = 50000000; + const founder2RewardSt = 50000000; + const founder3RewardSt = 50000000; + const founder4RewardSt = 150000000; + const founder5RewardSt = 50000000; + + const founder1Script = scripts.makeAddressScript( + isTestnet ? 'TDk19wPKYq91i18qmY6U9FeTdTxwPeSveo' : 'aCAgTPgtYcA4EysU4UKC86EQd5cTtHtCcr'); + + const founder2Script = scripts.makeAddressScript( + isTestnet ? 'TWZZcDGkNixTAMtRBqzZkkMHbq1G6vUTk5' : 'aHu897ivzmeFuLNB6956X6gyGeVNHUBRgD'); + + const founder3Script = scripts.makeAddressScript( + isTestnet ? 'TRZTFdNCKCKbLMQV8cZDkQN9Vwuuq4gDzT' : 'aQ18FBVFtnueucZKeVg4srhmzbpAeb1KoN'); + + const founder4Script = scripts.makeAddressScript( + isTestnet ? 'TG2ruj59E5b1u9G3F7HQVs6pCcVDBxrQve' : 'a1HwTdCmQV3NspP2QqCGpehoFpi8NY4Zg3'); + + const founder5Script = scripts.makeAddressScript( + isTestnet ? 'TCsTzQZKVn4fao8jDmB9zQBk9YQNEZ3XfS' : 'a1kCCGddf5pMXSipLVD9hBG2MGGVNaJ15U'); + + const feeScript = scripts.makeAddressScript( + isTestnet ? 'TC6qME2GhepR7656DgsR72pkQDmhfTDbtV' : 'aMaQErBviQDyXBPuh4cq6FBCnXhpVWiXT4'); + + _._addTransaction(outputTxsArr, founder1RewardSt, founder1Script); + _._addTransaction(outputTxsArr, founder2RewardSt, founder2Script); + _._addTransaction(outputTxsArr, founder3RewardSt, founder3Script); + _._addTransaction(outputTxsArr, founder4RewardSt, founder4Script); + _._addTransaction(outputTxsArr, founder5RewardSt, founder5Script); + _._addTransaction(outputTxsArr, feeRewardSt, feeScript); + _._addTransaction(outputTxsArr, poolRewardSt, poolAddressScript); const default_witness_commitment = blockTemplate.default_witness_commitment; if (default_witness_commitment) { diff --git a/libs/class.Job.js b/libs/class.Job.js index 66d1026..36e3914 100644 --- a/libs/class.Job.js +++ b/libs/class.Job.js @@ -32,7 +32,7 @@ class Job { _._stratum = args.stratum; _._time = mu.now(); - _._prevBlockId = _._getPrevBlockId(); + _._prevBlockId = _._blockTemplate.previousblockhash; _._height = _._blockTemplate.height; _._targetBi = _._blockTemplate.target @@ -42,10 +42,10 @@ class Job { _._nDiff = algorithm.diff1 / Number(_._targetBi); _._pDiff = _._nDiff * algorithm.multiplier; - _._versionHex = _._blockTemplate.version; - _._curTimeHex = buffers.packUInt32LE(_._blockTemplate.curtime).toString('hex'); - _._bitsHex = _._blockTemplate.bits; - _._prevHashHex = _._blockTemplate.previousblockhash; + _._versionBuf = buffers.packUInt32LE(_._blockTemplate.version); + _._curTimeBuf = buffers.packUInt32LE(_._blockTemplate.curtime); + _._bitsBuf = buffers.hexToLE(_._blockTemplate.bits); + _._prevHashBuf = buffers.hexToLE(_._blockTemplate.previousblockhash); _._coinbase = _._createCoinbase(); _._merkleTree = _._createMerkleTree(); @@ -111,28 +111,28 @@ class Job { get merkleTree() { return this._merkleTree; } /** - * Get the block template version in hex. + * Get the block template version in LE Buffer. * @returns {string} */ - get versionHex() { return this._versionHex; } + get versionBuf() { return this._versionBuf; } /** - * Get the block template curtime in hex. + * Get the block template curtime in LE Buffer. * @returns {string} */ - get curTimeHex() { return this._curTimeHex; } + get curTimeBuf() { return this._curTimeBuf; } /** - * Get the block template bits in hex. + * Get the block template bits in LE Buffer. * @returns {string} */ - get bitsHex() { return this._bitsHex; } + get bitsBuf() { return this._bitsBuf; } /** - * Get the block template previous block hash in hex. - * @returns {string} + * Get the block template previous block hash in LE Buffer. + * @returns {Buffer} */ - get prevHashHex() { return this._prevHashHex; } + get prevHashBuf() { return this._prevHashBuf; } /** * Get the block target @@ -155,16 +155,16 @@ class Job { */ registerShare(share) { precon.instanceOf(share, Share, 'share'); - precon.string(share.extraNonce1Hex, 'extraNonce1Hex'); - precon.string(share.extraNonce2Hex, 'extraNonce2Hex'); - precon.string(share.nTimeHex, 'nTimeHex'); - precon.string(share.nonceHex, 'nonceHex'); + precon.buffer(share.extraNonce1Buf, 'extraNonce1Buf'); + precon.buffer(share.extraNonce2Buf, 'extraNonce2Buf'); + precon.buffer(share.nTimeBuf, 'nTimeBuf'); + precon.buffer(share.nonceBuf, 'nonceBuf'); const _ = this; - const extraNonce1Hex = share.extraNonce1Hex; - const extraNonce2Hex = share.extraNonce2Hex; - const nTimeHex = share.nTimeHex; - const nonceHex = share.nonceHex; + const extraNonce1Hex = share.extraNonce1Buf.toString('hex'); + const extraNonce2Hex = share.extraNonce2Buf.toString('hex'); + const nTimeHex = share.nTimeBuf.toString('hex'); + const nonceHex = share.nonceBuf.toString('hex'); const submitId = `${nonceHex}:${nTimeHex}${extraNonce1Hex}:${extraNonce2Hex}`; @@ -194,12 +194,6 @@ class Job { blockBrand: _._stratum.config.blockBrand }); } - - - _getPrevBlockId() { - const _ = this; - return buffers.leToHex(buffers.reverseDWords(Buffer.from(_._blockTemplate.previousblockhash, 'hex'))); - } } module.exports = Job; \ No newline at end of file diff --git a/libs/class.JobManager.js b/libs/class.JobManager.js index 6f5d09a..6a393e9 100644 --- a/libs/class.JobManager.js +++ b/libs/class.JobManager.js @@ -71,7 +71,7 @@ class JobManager extends EventEmitter { destroy() { const _ = this; clearInterval(_._jobInterval); - clearInterval(_._blockInterval); + clearTimeout(_._blockInterval); } @@ -196,8 +196,8 @@ class JobManager extends EventEmitter { if (!blockPollIntervalMs) return; - clearInterval(_._blockInterval); - _._blockInterval = setInterval(_._pollBlockTemplate.bind(_), blockPollIntervalMs); + clearTimeout(_._blockInterval); + _._blockInterval = setTimeout(_._pollBlockTemplate.bind(_), blockPollIntervalMs); } @@ -205,12 +205,14 @@ class JobManager extends EventEmitter { const _ = this; _._getBlockTemplate((err, blockTemplate) => { - if (!blockTemplate) - return; + if (blockTemplate) { - const isNewBlock = !_.currentJob || _.currentJob.prevBlockId !== blockTemplate.previousblockhash; - if (isNewBlock) - _._processTemplate(blockTemplate); + const isNewBlock = !_.currentJob || _.currentJob.blockTemplate.previousblockhash !== blockTemplate.previousblockhash; + if (isNewBlock) + _._processTemplate(blockTemplate); + } + + _._scheduleBlockPolling(); }); } } diff --git a/libs/class.Server.js b/libs/class.Server.js index c58eecb..725eee6 100644 --- a/libs/class.Server.js +++ b/libs/class.Server.js @@ -179,6 +179,7 @@ class Server extends EventEmitter { } let extraNonce1Hex = _._extraNonceCounter.nextHex32(); + extraNonce1Hex = extraNonce1Hex + extraNonce1Hex; // create 8 byte extranonce const socket = new Socket(netSocket); diff --git a/libs/class.Share.js b/libs/class.Share.js index f490dcb..aa38a5a 100644 --- a/libs/class.Share.js +++ b/libs/class.Share.js @@ -9,6 +9,13 @@ const StratumError = require('./class.StratumError'); +const MTP_HASH_VALUE_BUFFER = Buffer.alloc(32, 0); + +const MTP_VERIFY_FAILED = StratumError.custom('MTP verify failed'); +const INCORRECT_HASH_ROOT_SIZE = StratumError.custom('Incorrect size of MTP hash root'); +const INCORRECT_BLOCK_SIZE = StratumError.custom('Incorrect size of MTP block'); + + class Share { /** @@ -19,27 +26,36 @@ class Share { * @param args.stratum {Stratum} * @param args.workerName {string} * @param args.jobIdHex {string} - * @param args.extraNonce2Hex {string} - * @param args.nTimeHex {string} - * @param args.nonceHex {string} + * @param args.extraNonce2Buf {string} + * @param args.nTimeBuf {string} + * @param args.nonceBuf {string} + * @param args.mtpHashRootBuf {Buffer} + * @param args.mtpBlockBuf {Buffer} + * @param args.mtpProofBuf {Buffer} */ constructor(args) { precon.notNull(args.client, 'client'); precon.notNull(args.stratum, 'stratum'); precon.string(args.workerName, 'workerName'); precon.string(args.jobIdHex, 'jobIdHex'); - precon.string(args.extraNonce2Hex, 'extraNonce2Hex'); - precon.string(args.nTimeHex, 'nTimeHex'); - precon.string(args.nonceHex, 'nonceHex'); + precon.buffer(args.extraNonce2Buf, 'extraNonce2Buf'); + precon.buffer(args.nTimeBuf, 'nTimeBuf'); + precon.buffer(args.nonceBuf, 'nonceBuf'); + precon.buffer(args.mtpHashRootBuf, 'mtpHashRootBuf'); + precon.buffer(args.mtpBlockBuf, 'mtpBlockBuf'); + precon.buffer(args.mtpProofBuf, 'mtpProofBuf'); const _ = this; _._client = args.client; _._stratum = args.stratum; _._workerName = args.workerName; _._jobIdHex = args.jobIdHex; - _._extraNonce2Hex = args.extraNonce2Hex; - _._nTimeHex = args.nTimeHex; - _._nonceHex = args.nonceHex; + _._extraNonce2Buf = args.extraNonce2Buf; + _._nTimeBuf = args.nTimeBuf; + _._nonceBuf = args.nonceBuf; + _._mtpHashRootBuf = args.mtpHashRootBuf; + _._mtpBlockBuf = args.mtpBlockBuf; + _._mtpProofBuf = args.mtpProofBuf; _._stratumDiff = _._client.diff; _._shareDiff = 0; @@ -93,7 +109,7 @@ class Share { * Get the mining address of the client that submitted the share. * @returns {string} */ - get minerAddress() { return this._client.worker.minerAddress; } + get minerAddress() { return this._client.minerAddress; } /** * Get the share difficulty. @@ -179,25 +195,25 @@ class Share { * Get share nTime as a Buffer * @returns {Buffer} */ - get nTimeHex() { return this._nTimeHex; } + get nTimeBuf() { return this._nTimeBuf; } /** * Get share nonce as a Buffer * @returns {Buffer} */ - get nonceHex() { return this._nonceHex; } + get nonceBuf() { return this._nonceBuf; } /** * Get client extraNonce1 as a Buffer * @returns {Buffer} */ - get extraNonce1Hex() { return this._client.extraNonce1Hex; } + get extraNonce1Buf() { return buffers.hexToLE(this._client.extraNonce1Hex); } /** * Get share extraNonce2 as a Buffer * @returns {Buffer} */ - get extraNonce2Hex() { return this._extraNonce2Hex; } + get extraNonce2Buf() { return this._extraNonce2Buf; } /** @@ -241,13 +257,41 @@ class Share { if (_._isDuplicateShare()) return false; + /* check MTP hash root size */ + if (_._mtpHashRootBuf.length !== algorithm.MTP_HASH_ROOT_SIZE) { + _._setError(INCORRECT_HASH_ROOT_SIZE); + return true; + } + + /* check MTP block size */ + if (_._mtpBlockBuf.length !== algorithm.MTP_BLOCK_SIZE) { + _._setError(INCORRECT_BLOCK_SIZE); + return true; + } + + /* Validate MTP proofs */ + const mtpHeaderBuf = _._serializeMtpHeader(); + + const isValidProof = algorithm.verify( + /* header */ mtpHeaderBuf, + /* nonce */ _._nonceBuf, + /* hash root */ _._mtpHashRootBuf, + /* mtp block */ _._mtpBlockBuf, + /* mtp proof */ _._mtpProofBuf, + /* hash out */ MTP_HASH_VALUE_BUFFER); + + if (!isValidProof) { + _._setError(MTP_VERIFY_FAILED); + return true; + } + // check valid block - const header = _._validateBlock(); + const header = _._validateBlock(MTP_HASH_VALUE_BUFFER); if (_._isValidBlock) { _._blockHex = _._serializeBlock(header).toString('hex'); - _._blockId = header.hash; + _._blockId = header.hash.toString('hex'); console.log(`Winning nonce submitted: ${_._blockId}`); } @@ -297,7 +341,7 @@ class Share { _isInvalidNonceSize() { const _ = this; - if (_._nonceHex.length !== 4) { + if (_._nonceBuf.length !== 4) { _._setError(StratumError.INCORRECT_NONCE_SIZE); return true; } @@ -307,7 +351,7 @@ class Share { _isInvalidExtraNonce2Size() { const _ = this; - if (_._extraNonce2Hex.length !== 4) { + if (_._extraNonce2Buf.length !== 8) { _._setError(StratumError.INCORRECT_EXTRANONCE2_SIZE); return true; } @@ -317,7 +361,7 @@ class Share { _isInvalidTimeSize() { const _ = this; - if (_._nTimeHex.length !== 4) { + if (_._nTimeBuf.length !== 4) { _._setError(StratumError.INCORRECT_TIME_SIZE); return true; } @@ -327,7 +371,7 @@ class Share { _isInvalidTimeRange() { const _ = this; - const nTimeInt = _._nTimeHex.readUInt32LE(0); + const nTimeInt = _._nTimeBuf.readUInt32LE(0); if (nTimeInt < _._job.blockTemplate.curtime || nTimeInt > _._submitTime + 7200) { _._setError(StratumError.TIME_OUT_OF_RANGE); return true; @@ -366,19 +410,19 @@ class Share { } - _validateBlock() { + _validateBlock(mtpHashValueBuf) { const _ = this; - const header = _._serializeHeader(); - const headerBi = bi.fromBufferLE(header.hash); + const headerBi = bi.fromBufferLE(mtpHashValueBuf); + _._shareDiff = algorithm.diff1 / Number(headerBi) * algorithm.multiplier; _._isValidBlock = _._job.targetBi >= headerBi; - return header; + return _._isValidBlock ? _._serializeHeader(mtpHashValueBuf) : null; } - _serializeHeader() { + _serializeHeader(mtpHashValueBuf) { const _ = this; const coinbaseBuf = _._job.coinbase.serialize(_); @@ -386,15 +430,15 @@ class Share { const merkleRootBuf = _._job.merkleTree.withFirstHash(coinbaseHashBuf); - const headerBuf = Buffer.alloc(80); + const headerBuf = Buffer.alloc(180); let position = 0; /* version */ - buffers.hexToLE(_._job.versionHex).copy(headerBuf, position); + _._job.versionBuf.copy(headerBuf, position); position += 4; /* prev block */ - buffers.hexToLE(_._job.prevHashHex).copy(headerBuf, position); + _._job.prevHashBuf.copy(headerBuf, position); position += 32; /* merkle */ @@ -402,30 +446,83 @@ class Share { position += 32; /* time */ - buffers.hexToLE(_._nTimeHex).copy(headerBuf, position); + _._nTimeBuf.copy(headerBuf, position); position += 4; /* bits */ - buffers.hexToLE(_._job.bitsHex).copy(headerBuf, position); + _._job.bitsBuf.copy(headerBuf, position); position += 4; /* nonce */ - buffers.hexToLE(_._nonceHex).copy(headerBuf, position); - //position += 4; + _._nonceBuf.copy(headerBuf, position); + position += 4; + + /* MTP version */ + headerBuf.writeUInt32BE(algorithm.MTP_VERSION, position); + position += 4; + + /* MTP hash value */ + mtpHashValueBuf.copy(headerBuf, position); + /* +32 bytes */ + + /* +32 bytes - MTP reserved[0] */ + /* +32 bytes - MTP reserved[1] */ return { - hash: algorithm.hash(headerBuf), + hash: buffers.sha256d(headerBuf), buffer: headerBuf, coinbaseBuf: coinbaseBuf }; } + _serializeMtpHeader() { + + const _ = this; + + const coinbaseBuf = _._job.coinbase.serialize(_); + const coinbaseHashBuf = buffers.sha256d(coinbaseBuf); + + const merkleRootBuf = _._job.merkleTree.withFirstHash(coinbaseHashBuf); + + const headerBuf = Buffer.alloc(80); + let position = 0; + + /* version */ + _._job.versionBuf.copy(headerBuf, position); + position += 4; + + /* prev block */ + _._job.prevHashBuf.copy(headerBuf, position); + position += 32; + + /* merkle */ + merkleRootBuf.copy(headerBuf, position); + position += 32; + + /* time */ + _._nTimeBuf.copy(headerBuf, position); + position += 4; + + /* bits */ + _._job.bitsBuf.copy(headerBuf, position); + position += 4; + + /* mtp version */ + headerBuf.writeUInt32BE(algorithm.MTP_VERSION, position); + + return headerBuf; + } + + _serializeBlock(header) { const _ = this; return Buffer.concat([ /* header */ header.buffer, + /* mtp hash root */ _._mtpHashRootBuf, + /* mtp block */ _._mtpBlockBuf, + /* mtp proof */ _._mtpProofBuf, /* transaction len */ buffers.packVarInt(_._job.blockTemplate.transactions.length + 1/* +coinbase */), /* coinbase tx */ header.coinbaseBuf, /* transactions */ _._job.txDataBuf diff --git a/libs/class.Socket.js b/libs/class.Socket.js index 324b086..59e426e 100644 --- a/libs/class.Socket.js +++ b/libs/class.Socket.js @@ -3,7 +3,10 @@ const EventEmitter = require('events'), NetSocket = require('net').Socket, - precon = require('@mintpond/mint-precon'); + bos = require('@mintpond/mint-bos'), + BosDeserializeBuffer = require('@mintpond/mint-bos').BosDeserializeBuffer, + precon = require('@mintpond/mint-precon'), + mu = require('@mintpond/mint-utils'); /** @@ -29,10 +32,11 @@ class Socket extends EventEmitter { _._localAddress = _._socket.localAddress; _._localPort = _._socket.localPort; - _._socket.setEncoding('utf8'); + _._bosErrorsArr = []; + _._socket.setKeepAlive(true); _._socket.setNoDelay(true); - _._socket.on('data', _._jsonReader.bind(_)); + _._socket.on('data', _._bosReader.bind(_)); _._socket.on('close', _._onSocketClose.bind(_)); _._socket.on('error', _._onSocketError.bind(_)); } @@ -91,7 +95,7 @@ class Socket extends EventEmitter { /** * Write raw data to the socket. * - * @param serializedData {string|Buffer} The data to write. + * @param serializedData {Buffer} The data to write. * @param originalMessage {object} The unserialized message so it can be included in event arguments. */ write(serializedData, originalMessage) { @@ -107,16 +111,17 @@ class Socket extends EventEmitter { * Serialize and write a stratum message to the socket. * * @param message {object} The object to write. - * @returns {{data:string|Buffer,message:object}} An object containing the data data written to the socket and the original message. + * @returns {{data:Buffer,message:object}} An object containing the data data written to the socket and the original message. */ send(message) { precon.obj(message, 'message'); const _ = this; + const serializedBuf = bos.serialize(message); + + _.write(serializedBuf, message); - const jsonString = JSON.stringify(message) + '\n'; - _.write(jsonString, message); - return { data: jsonString, message: message }; + return { data: serializedBuf, message: message }; } @@ -142,44 +147,38 @@ class Socket extends EventEmitter { } - _jsonReader(dataBuffer) { + _bosReader(dataBuf) { const _ = this; - const dataBufferString = dataBuffer.toString(); - _._jsonBuffer += dataBufferString; + if (!_._bosBuffer) + _._bosBuffer = new BosDeserializeBuffer(300000); - if (dataBufferString.lastIndexOf('\n') !== -1) { + if (!_._bosBuffer.append(dataBuf)) { + _.emit(Socket.EVENT_MALFORMED_MESSAGE, 'Failed to read data'); + return; + } - const messages = _._jsonBuffer.split('\n'); - const incomplete = _._jsonBuffer.slice(-1) === '\n' - ? '' - : messages.pop(); + const messagesArr = []; - messages.forEach(strMessage => { + const totalRead = _._bosBuffer.deserialize(messagesArr, _._bosErrorsArr); + if (totalRead === undefined) { + _._bosBuffer.clear(); + _.emit(Socket.EVENT_MALFORMED_MESSAGE, `BOS Failed to parse: ${_._bosErrorsArr.pop()}`); + return; + } - if (!strMessage) - return; + if (totalRead) { - const message = Socket._parseJson(strMessage); - if (!message) { - _.emit(Socket.EVENT_MALFORMED_MESSAGE, strMessage); + for (let i = 0; i < totalRead; i++) { + const message = messagesArr[i]; + if (!message || !mu.isObject(message)) { + _.emit(Socket.EVENT_MALFORMED_MESSAGE, 'Invalid message object'); return; } _.emit(Socket.EVENT_MESSAGE_IN, { message: message }); - }); - _._jsonBuffer = incomplete; - } - } - - - static _parseJson(json) { - try { - return JSON.parse(json); - } - catch (e) { - return false; + } } } } diff --git a/libs/class.Stratum.js b/libs/class.Stratum.js index 55e5292..a6c7f29 100644 --- a/libs/class.Stratum.js +++ b/libs/class.Stratum.js @@ -197,10 +197,10 @@ class Stratum extends EventEmitter { * Determine if a worker can be authorized or should be rejected. * * @param client {Client} - * @param callback {function(err:*,isAuthorized:boolean,message:string)} + * @param callback {function(err:*,isAuthorized:boolean)} */ canAuthorizeWorker(client, callback) { - callback(null, true, ''); + callback(null, true); } diff --git a/libs/service.algorithm.js b/libs/service.algorithm.js index cc3d40f..5b8994a 100644 --- a/libs/service.algorithm.js +++ b/libs/service.algorithm.js @@ -1,11 +1,16 @@ 'use strict'; -const buffers = require('@mintpond/mint-utils').buffers; +const mtp = require('@mintpond/hasher-mtp'); + +const MTP_L = 64; module.exports = { + MTP_VERSION: 0x1000, + MTP_HASH_ROOT_SIZE: 16, + MTP_BLOCK_SIZE: 8 * MTP_L * 2 * 128, diff1: 0x00000000ffff0000000000000000000000000000000000000000000000000000, - multiplier: Math.pow(2, 8), - hash: inputBuf => { - return buffers.sha256d(inputBuf); + multiplier: Math.pow(2, 16), + verify: (headerBuf, nonceBuf, mtpHashRootBuf, mtpBlockBuf, mtpProofBuf, hashValueOutBuf) => { + return mtp.verify(headerBuf, nonceBuf, mtpHashRootBuf, mtpBlockBuf, mtpProofBuf, hashValueOutBuf); } }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4280a88..9253994 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,18 @@ { - "name": "ref-stratum", - "version": "1.0.0", + "name": "@mintpond/ref-stratum", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@mintpond/hasher-mtp": { + "version": "2.0.0", + "resolved": "https://npm.pkg.github.com/download/@mintpond/hasher-mtp/2.0.0/cf748cf77edde0efbe692cba5689b0e8bddbbb6ae1ea6cbc5fa69f9f9b203892", + "integrity": "sha512-CO8N6CTHkH18i7qYru2t1kIHxJ+cG15m3vjRzJ/EkC7TqGJJNhchKO1flclVG4I/097dpSgLl+4Rnau+LTchrg==", + "requires": { + "bindings": "^1.3.0", + "nan": "^2.6.2" + } + }, "@mintpond/mint-bitcoin-script": { "version": "1.0.1", "resolved": "https://npm.pkg.github.com/download/@mintpond/mint-bitcoin-script/1.0.1/3ecbe9d4a975bb22ff9af668381fede7eb27308d94aad10bfde758150d590b24", @@ -13,6 +22,14 @@ "bs58": "^4.0.1" } }, + "@mintpond/mint-bos": { + "version": "2.0.1", + "resolved": "https://npm.pkg.github.com/download/@mintpond/mint-bos/2.0.1/1f1e591d2e6eb10db7622a33e7072f5fae1804333c6608741ad9add4c42e1365", + "integrity": "sha512-sP4sB9Ov53BfztYaAYDoGDo590AK7mBkDsq+QAU3L/dUgG7Q9dNEWHj2dwhtgLqGGyDw03b4iYpqTOjincpXfw==", + "requires": { + "@mintpond/mint-precon": "^1.0.0" + } + }, "@mintpond/mint-merkle": { "version": "1.0.0", "resolved": "https://npm.pkg.github.com/download/@mintpond/mint-merkle/1.0.0/cfe04a57ffd39db35e4cd92575d39502533a1bc6a43b4597f68d756c1774097f", @@ -70,6 +87,14 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -86,6 +111,11 @@ "moment-timezone": "^0.5.x" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "moment": { "version": "2.25.3", "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz", @@ -99,6 +129,11 @@ "moment": ">= 2.9.0" } }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index 8adfe30..eaf8985 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,25 @@ { - "name": "@mintpond/ref-stratum", + "name": "@mintpond/ref-stratum-zcoin", "version": "0.1.0", - "description": "Generic reference stratum for testing, experimentation, and demonstration.", + "description": "Zcoin reference stratum for testing, experimentation, and demonstration.", "main": "index.js", "author": "JCThePants", "license": "MIT", "dependencies": { + "@mintpond/hasher-mtp": "^2.0.0", + "@mintpond/mint-bos": "^2.0.1", "@mintpond/mint-bitcoin-script": "^1.0.1", "@mintpond/mint-merkle": "^1.0.0", "@mintpond/mint-precon": "^1.0.0", "@mintpond/mint-utils": "^2.0.0" }, - "homepage": "https://github.com/MintPond/ref-stratum", + "homepage": "https://github.com/MintPond/ref-stratum-zcoin", "bugs": { - "url": "https://github.com/MintPond/ref-stratum/issues" + "url": "https://github.com/MintPond/ref-stratum-zcoin/issues" }, "repository": { "type": "git", - "url": "https://github.com/MintPond/ref-stratum.git" + "url": "https://github.com/MintPond/ref-stratum-zcoin.git" }, "publishConfig": { "registry": "https://npm.pkg.github.com/" diff --git a/start.js b/start.js index 255e5e2..c187970 100644 --- a/start.js +++ b/start.js @@ -3,16 +3,16 @@ const Stratum = require('./libs/class.Stratum'); const stratum = new Stratum({ - coinbaseAddress: 'mpN96WyD5Xb8q66aVsfd7P6P2WJDdqapKf', + coinbaseAddress: 'TC6qME2GhepR7656DgsR72pkQDmhfTDbtV', blockBrand: '/@mintpond/ref-stratum/', host: "0.0.0.0", port: { - number: 3020, - diff: 1024 + number: 3000, + diff: 512 }, rpc: { - host: '172.0.0.1', - port: 8888, + host: '172.16.3.102', + port: 17001, user: 'rpcuser', password: "x" }, @@ -31,5 +31,13 @@ stratum.on(Stratum.EVENT_CLIENT_DISCONNECT, ev => { }); stratum.on(Stratum.EVENT_SHARE_SUBMITTED, ev => { - console.log(JSON.stringify(ev.share)); + if (ev.share.isValidBlock) { + console.log(`Valid block submitted by ${ev.share.client.workerName}`) + } + else if (ev.share.isValidShare) { + console.log(`Valid share submitted by ${ev.share.client.workerName}`) + } + else { + console.log(`Valid share submitted by ${ev.share.client.workerName} ${ev.share.error.message}`) + } }); \ No newline at end of file