Added rewardRecipients config, moved peer magic to coin config

This commit is contained in:
Matt 2014-05-02 16:02:17 -06:00
parent 64d832e1a2
commit dfad9e58c6
9 changed files with 140 additions and 78 deletions

View File

@ -89,9 +89,16 @@ var myCoin = {
"name": "Dogecoin", "name": "Dogecoin",
"symbol": "DOGE", "symbol": "DOGE",
"algorithm": "scrypt", "algorithm": "scrypt",
"nValue": 1024, //optional. Defaults to 1024 "nValue": 1024, //optional - defaults to 1024
"rValue": 1, //optional. Defaults to 1 "rValue": 1, //optional - defaults to 1
"txMessages": false, //or true (not required, defaults to false) "txMessages": false, //optional - defaults to false,
/* Magic value only required for setting up p2p block notifications. It is found in the daemon
source code as the pchMessageStart variable.
For example, litecoin mainnet magic: http://git.io/Bi8YFw
And for litecoin testnet magic: http://git.io/NXBYJA */
"peerMagic": "fbc0b6db" //optional
"peerMagicTestnet": "fcc1b7dc" //optional
}; };
``` ```
@ -154,6 +161,18 @@ var pool = Stratum.createPool({
"coin": myCoin, "coin": myCoin,
"address": "mi4iBXbBsydtcc5yFmsff2zCFVX4XG7qJc", //Address to where block rewards are given "address": "mi4iBXbBsydtcc5yFmsff2zCFVX4XG7qJc", //Address to where block rewards are given
/* Block rewards go to the configured pool wallet address to later be paid out to miners,
except for a percentages that can go to pool operator(s) as pool fees or donations.
Addresses or hashed public keys can be used. */
"rewardRecipients": {
"n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, //1.5% goes to pool op
"mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, //0.5% goes to a pool co-owner
//0.1% donation to NOMP to help support development
"22851477d63a085dbc2398c8430af1c09e7343f6": 0.1
},
"blockRefreshInterval": 1000, //How often to poll RPC daemons for new blocks, in milliseconds "blockRefreshInterval": 1000, //How often to poll RPC daemons for new blocks, in milliseconds
/* How many milliseconds should have passed before new block transactions will trigger a new /* How many milliseconds should have passed before new block transactions will trigger a new
@ -220,7 +239,6 @@ var pool = Stratum.createPool({
} }
}, },
/* Recommended to have at least two daemon instances running in case one drops out-of-sync /* Recommended to have at least two daemon instances running in case one drops out-of-sync
or offline. For redundancy, all instances will be polled for block/transaction updates or offline. For redundancy, all instances will be polled for block/transaction updates
and be used for submitting blocks. Creating a backup daemon involves spawning a daemon and be used for submitting blocks. Creating a backup daemon involves spawning a daemon
@ -246,8 +264,8 @@ var pool = Stratum.createPool({
/* This allows the pool to connect to the daemon as a node peer to receive block updates. /* This allows the pool to connect to the daemon as a node peer to receive block updates.
It may be the most efficient way to get block updates (faster than polling, less It may be the most efficient way to get block updates (faster than polling, less
intensive than blocknotify script). It requires additional setup: the 'magic' field must intensive than blocknotify script). It requires the additional field "peerMagic" in
be exact (extracted from the coin source code). */ the coin config. */
"p2p": { "p2p": {
"enabled": false, "enabled": false,
@ -260,13 +278,8 @@ var pool = Stratum.createPool({
/* If your coin daemon is new enough (i.e. not a shitcoin) then it will support a p2p /* If your coin daemon is new enough (i.e. not a shitcoin) then it will support a p2p
feature that prevents the daemon from spamming our peer node with unnecessary feature that prevents the daemon from spamming our peer node with unnecessary
transaction data. Assume its supported but if you have problems try disabling it. */ transaction data. Assume its supported but if you have problems try disabling it. */
"disableTransactions": true, "disableTransactions": true
/* Magic value is different for main/testnet and for each coin. It is found in the daemon
source code as the pchMessageStart variable.
For example, litecoin mainnet magic: http://git.io/Bi8YFw
And for litecoin testnet magic: http://git.io/NXBYJA */
"magic": "fcc1b7dc"
} }
}, function(ip, workerName, password, callback){ //stratum authorization function }, function(ip, workerName, password, callback){ //stratum authorization function
@ -289,7 +302,8 @@ Listen to pool events
ip: '71.33.19.37', //ip address of client ip: '71.33.19.37', //ip address of client
worker: 'matt.worker1', //stratum worker name worker: 'matt.worker1', //stratum worker name
height: 443795, //block height height: 443795, //block height
reward: 5000000000, //the number of satoshis received as payment for solving this block poolReward: 4900000000, //the number of satoshis sent to the configured pool address
blockReward: 5000000000, //the number of satoshis received as payment for solving this block
difficulty: 64, //stratum worker difficulty difficulty: 64, //stratum worker difficulty
shareDiff: 78, //actual difficulty of the share shareDiff: 78, //actual difficulty of the share
blockDiff: 3349, //block difficulty adjusted for share padding blockDiff: 3349, //block difficulty adjusted for share padding

View File

@ -9,7 +9,7 @@ var util = require('./util.js');
* The BlockTemplate class holds a single job. * The BlockTemplate class holds a single job.
* and provides several methods to validate and submit it to the daemon coin * and provides several methods to validate and submit it to the daemon coin
**/ **/
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publicKey, extraNoncePlaceholder, reward, txMessages){ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, poolAddressScript, extraNoncePlaceholder, reward, txMessages, recipients){
//private members //private members
@ -34,6 +34,14 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ
this.rpcData = rpcData; this.rpcData = rpcData;
this.jobId = jobId; this.jobId = jobId;
this.poolReward = (function(){
var pReward = rpcData.coinbasevalue;
for (var i = 0; i < recipients.length; i++){
var recipientReward = Math.floor(recipients[i].percent * rpcData.coinbasevalue);
pReward -= recipientReward;
}
return pReward;
})();
//Use the 'target' field if available, but some daemons only return the 'bits' field //Use the 'target' field if available, but some daemons only return the 'bits' field
@ -55,10 +63,11 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ
this.merkleBranch = getMerkleHashes(this.merkleTree.steps); this.merkleBranch = getMerkleHashes(this.merkleTree.steps);
this.generationTransaction = transactions.CreateGeneration( this.generationTransaction = transactions.CreateGeneration(
rpcData, rpcData,
publicKey, poolAddressScript,
extraNoncePlaceholder, extraNoncePlaceholder,
reward, reward,
txMessages txMessages,
recipients
); );
this.serializeCoinbase = function(extraNonce1, extraNonce2){ this.serializeCoinbase = function(extraNonce1, extraNonce2){

View File

@ -81,7 +81,7 @@ function DaemonInterface(options){
} }
} }
if (typeof(dataJson) !== 'undefined'){ if (typeof(dataJson) !== 'undefined'){
callback(dataJson.error, dataJson); callback(dataJson.error, dataJson, data);
} }
else else
callback(parsingError); callback(parsingError);
@ -141,20 +141,20 @@ function DaemonInterface(options){
/* Sends a JSON RPC (http://json-rpc.org/wiki/specification) command to every configured daemon. /* Sends a JSON RPC (http://json-rpc.org/wiki/specification) command to every configured daemon.
The callback function is fired once with the result from each daemon unless streamResults is The callback function is fired once with the result from each daemon unless streamResults is
set to true. */ set to true. */
function cmd(method, params, callback, streamResults){ function cmd(method, params, callback, streamResults, returnRawData){
var results = []; var results = [];
async.each(instances, function(instance, eachCallback){ async.each(instances, function(instance, eachCallback){
var itemFinished = function(error, result){ var itemFinished = function(error, result, data){
var returnObj = { var returnObj = {
error: error, error: error,
response: (result || {}).result, response: (result || {}).result,
instance: instance instance: instance
}; };
if (returnRawData) returnObj.data = data;
if (streamResults) callback(returnObj); if (streamResults) callback(returnObj);
else results.push(returnObj); else results.push(returnObj);
eachCallback(); eachCallback();
@ -167,8 +167,8 @@ function DaemonInterface(options){
id: Date.now() + Math.floor(Math.random() * 10) id: Date.now() + Math.floor(Math.random() * 10)
}); });
performHttpRequest(instance, requestJson, function(error, result){ performHttpRequest(instance, requestJson, function(error, result, data){
itemFinished(error, result); itemFinished(error, result, data);
}); });

View File

@ -109,14 +109,15 @@ var JobManager = module.exports = function JobManager(options){
} }
})(); })();
this.updateCurrentJob = function(publicKey){ this.updateCurrentJob = function(){
var tmpBlockTemplate = new blockTemplate( var tmpBlockTemplate = new blockTemplate(
jobCounter.next(), jobCounter.next(),
_this.currentJob.rpcData, _this.currentJob.rpcData,
publicKey, options.poolAddressScript,
_this.extraNoncePlaceholder, _this.extraNoncePlaceholder,
options.coin.reward, options.coin.reward,
options.coin.txMessages options.coin.txMessages,
options.recipients
); );
_this.currentJob = tmpBlockTemplate; _this.currentJob = tmpBlockTemplate;
@ -128,7 +129,7 @@ var JobManager = module.exports = function JobManager(options){
}; };
//returns true if processed a new block //returns true if processed a new block
this.processTemplate = function(rpcData, publicKey){ this.processTemplate = function(rpcData){
/* Block is new if A) its the first block we have seen so far or B) the blockhash is different and the /* Block is new if A) its the first block we have seen so far or B) the blockhash is different and the
block height is greater than the one we have */ block height is greater than the one we have */
@ -159,10 +160,11 @@ var JobManager = module.exports = function JobManager(options){
var tmpBlockTemplate = new blockTemplate( var tmpBlockTemplate = new blockTemplate(
jobCounter.next(), jobCounter.next(),
rpcData, rpcData,
publicKey, options.poolAddressScript,
_this.extraNoncePlaceholder, _this.extraNoncePlaceholder,
options.coin.reward, options.coin.reward,
options.coin.txMessages options.coin.txMessages,
options.recipients
); );
this.currentJob = tmpBlockTemplate; this.currentJob = tmpBlockTemplate;
@ -283,7 +285,8 @@ var JobManager = module.exports = function JobManager(options){
ip: ipAddress, ip: ipAddress,
worker: workerName, worker: workerName,
height: job.rpcData.height, height: job.rpcData.height,
reward: job.rpcData.coinbasevalue, blockReward: job.rpcData.coinbasevalue,
poolReward: job.poolReward,
difficulty: difficulty, difficulty: difficulty,
shareDiff: shareDiff.toFixed(8), shareDiff: shareDiff.toFixed(8),
blockDiff : blockDiffAdjusted, blockDiff : blockDiffAdjusted,

View File

@ -46,7 +46,7 @@ var Peer = module.exports = function (options) {
var _this = this; var _this = this;
var client; var client;
var magic = new Buffer(options.p2p.magic, 'hex'); var magic = new Buffer(options.testnet ? options.coin.peerMagicTestnet : options.coin.peerMagic, 'hex');
var magicInt = magic.readUInt32LE(0); var magicInt = magic.readUInt32LE(0);
var verack = false; var verack = false;
var validConnectionConfig = true; var validConnectionConfig = true;

View File

@ -9,21 +9,6 @@ var jobManager = require('./jobManager.js');
var util = require('./util.js'); var util = require('./util.js');
var bignum = require('bignum');
/**
* Main pool object. It emits the following events:
* - started() - when the pool is effectively started
* - share(isValidShare, isValidBlock, shareData) - When a share is submitted
* - log(severity, key, text) - for debug, warning, and error messages
*
* It initializes and connects:
* - JobManager - for generating miner work, processing block templates and shares
* - DaemonInterface - for RPC communication with daemon
* - StratumServer - for TCP socket communication with miners
*
*/
var pool = module.exports = function pool(options, authorizeFn){ var pool = module.exports = function pool(options, authorizeFn){
this.options = options; this.options = options;
@ -44,13 +29,6 @@ var pool = module.exports = function pool(options, authorizeFn){
throw new Error(); throw new Error();
} }
//var diff1 = options.coin.diffShift ?
// util.getTruncatedDiff(options.coin.diffShift) :
// algos[options.coin.algorithm].diff;
//Which number to use as dividend when converting difficulty to target
//var maxDifficulty = algos[options.coin.algorithm].maxDiff;
this.start = function(){ this.start = function(){
@ -58,6 +36,7 @@ var pool = module.exports = function pool(options, authorizeFn){
SetupApi(); SetupApi();
SetupDaemonInterface(function(){ SetupDaemonInterface(function(){
DetectCoinData(function(){ DetectCoinData(function(){
SetupRecipients();
SetupJobManager(); SetupJobManager();
OnBlockchainSynced(function(){ OnBlockchainSynced(function(){
GetFirstJob(function(){ GetFirstJob(function(){
@ -122,7 +101,8 @@ var pool = module.exports = function pool(options, authorizeFn){
'Current Block Diff:\t' + _this.jobManager.currentJob.difficulty * algos[options.coin.algorithm].multiplier, 'Current Block Diff:\t' + _this.jobManager.currentJob.difficulty * algos[options.coin.algorithm].multiplier,
'Network Difficulty:\t' + options.initStats.difficulty, 'Network Difficulty:\t' + options.initStats.difficulty,
'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate), 'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate),
'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', ') 'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '),
'Pool Fee Percent:\t' + _this.options.feePercent + '%'
]; ];
if (typeof options.blockRefreshInterval === "number" && options.blockRefreshInterval > 0) if (typeof options.blockRefreshInterval === "number" && options.blockRefreshInterval > 0)
@ -198,6 +178,16 @@ var pool = module.exports = function pool(options, authorizeFn){
function SetupPeer(){ function SetupPeer(){
if (!options.p2p || !options.p2p.enabled) if (!options.p2p || !options.p2p.enabled)
return; return;
if (options.testnet && !options.coin.peerMagicTestnet){
emitErrorLog('p2p cannot be enabled in testnet without peerMagicTestnet set in coin configuration');
return;
}
else if (!options.coin.peerMagic){
emitErrorLog('p2p cannot be enabled without peerMagic set in coin configuration');
return;
}
_this.peer = new peer(options); _this.peer = new peer(options);
_this.peer.on('connected', function() { _this.peer.on('connected', function() {
emitLog('p2p connection successful'); emitLog('p2p connection successful');
@ -263,6 +253,33 @@ var pool = module.exports = function pool(options, authorizeFn){
} }
function SetupRecipients(){
var recipients = [];
options.feePercent = 0;
options.rewardRecipients = options.rewardRecipients || {};
for (var r in options.rewardRecipients){
var percent = options.rewardRecipients[r];
var rObj = {
percent: percent / 100
};
try {
if (r.length === 40)
rObj.script = util.miningKeyToScript(r);
else
rObj.script = util.addressToScript(r);
recipients.push(rObj);
options.feePercent += percent;
}
catch(e){
emitErrorLog('Error generating transaction output script for ' + r + ' in rewardRecipients');
}
}
if (recipients.length === 0){
emitErrorLog('No rewardRecipients have been setup which means no fees will be taken');
}
options.recipients = recipients;
}
function SetupJobManager(){ function SetupJobManager(){
_this.jobManager = new jobManager(options); _this.jobManager = new jobManager(options);
@ -385,7 +402,7 @@ var pool = module.exports = function pool(options, authorizeFn){
return; return;
} }
options.publicKeyBuffer = (function(){ options.poolAddressScript = (function(){
switch(options.coin.reward){ switch(options.coin.reward){
case 'POS': case 'POS':
return util.pubkeyToScript(rpcResults.validateaddress.pubkey); return util.pubkeyToScript(rpcResults.validateaddress.pubkey);
@ -433,7 +450,7 @@ var pool = module.exports = function pool(options, authorizeFn){
}).on('broadcastTimeout', function(){ }).on('broadcastTimeout', function(){
emitLog('No new work for ' + options.jobRebroadcastTimeout + ' seconds - updating & rebroadcasting current job'); emitLog('No new work for ' + options.jobRebroadcastTimeout + ' seconds - updating & rebroadcasting current job');
_this.jobManager.updateCurrentJob(options.publicKeyBuffer); _this.jobManager.updateCurrentJob();
}).on('client.connected', function(client){ }).on('client.connected', function(client){
if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') { if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') {
@ -541,16 +558,7 @@ var pool = module.exports = function pool(options, authorizeFn){
result.instance.index + ' with error ' + JSON.stringify(result.error)); result.instance.index + ' with error ' + JSON.stringify(result.error));
callback(result.error); callback(result.error);
} else { } else {
var processedNewBlock = _this.jobManager.processTemplate(result.response, options.publicKeyBuffer); var processedNewBlock = _this.jobManager.processTemplate(result.response);
if (processedNewBlock) {
Object.keys(_this.varDiff).forEach(function(port){
_this.varDiff[port].setNetworkDifficulty(_this.jobManager.currentJob.difficulty);
});
}
callback(null, result.response, processedNewBlock); callback(null, result.response, processedNewBlock);
callback = function(){}; callback = function(){};
} }

View File

@ -123,9 +123,42 @@ For some (probably outdated and incorrect) documentation about whats kinda going
see: https://en.bitcoin.it/wiki/Protocol_specification#tx see: https://en.bitcoin.it/wiki/Protocol_specification#tx
*/ */
var generateOutputTransactions = function(poolRecipient, recipients, reward){
var totalOutputs = 1;
var txOutputBuffers = [];
var totalToRecipients = 0;
for (var i = 0; i < recipients.length; i++){
var recipientReward = Math.floor(recipients[i].percent * reward);
totalToRecipients += recipientReward;
totalOutputs++;
txOutputBuffers.push(Buffer.concat([
util.packInt64LE(recipientReward),
util.varIntBuffer(recipients[i].script.length),
recipients[i].script
]));
}
var rewardToPool = reward - totalToRecipients;
txOutputBuffers.push(Buffer.concat([
util.packInt64LE(rewardToPool),
util.varIntBuffer(poolRecipient.length),
poolRecipient
]));
return Buffer.concat([
util.varIntBuffer(totalOutputs),
Buffer.concat(txOutputBuffers)
]);
};
exports.CreateGeneration = function(rpcData, publicKey, extraNoncePlaceholder, reward, txMessages){ exports.CreateGeneration = function(rpcData, publicKey, extraNoncePlaceholder, reward, txMessages, recipients){
var txInputsCount = 1; var txInputsCount = 1;
var txOutputsCount = 1; var txOutputsCount = 1;
@ -158,9 +191,9 @@ exports.CreateGeneration = function(rpcData, publicKey, extraNoncePlaceholder, r
var p1 = Buffer.concat([ var p1 = Buffer.concat([
util.packUInt32LE(txVersion), util.packUInt32LE(txVersion),
txTimestamp, txTimestamp,
util.varIntBuffer(txInputsCount),
//transaction input //transaction input
util.varIntBuffer(txInputsCount),
util.uint256BufferFromHash(txInPrevOutHash), util.uint256BufferFromHash(txInPrevOutHash),
util.packUInt32LE(txInPrevOutIndex), util.packUInt32LE(txInPrevOutIndex),
util.varIntBuffer(scriptSigPart1.length + extraNoncePlaceholder.length + scriptSigPart2.length), util.varIntBuffer(scriptSigPart1.length + extraNoncePlaceholder.length + scriptSigPart2.length),
@ -179,12 +212,8 @@ exports.CreateGeneration = function(rpcData, publicKey, extraNoncePlaceholder, r
util.packUInt32LE(txInSequence), util.packUInt32LE(txInSequence),
//end transaction input //end transaction input
util.varIntBuffer(txOutputsCount),
//transaction output //transaction output
util.packInt64LE(rpcData.coinbasevalue), generateOutputTransactions(publicKey, recipients, rpcData.coinbasevalue),
util.varIntBuffer(publicKey.length),
publicKey,
//end transaction ouput //end transaction ouput
util.packUInt32LE(txLockTime), util.packUInt32LE(txLockTime),

View File

@ -253,6 +253,11 @@ exports.pubkeyToScript = function(key){
}; };
exports.miningKeyToScript = function(key){
var keyBuffer = new Buffer(key, 'hex');
return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), keyBuffer, new Buffer([0x88, 0xac])]);
};
/* /*
For POW coins - used to format wallet address for use in generation transaction's output For POW coins - used to format wallet address for use in generation transaction's output
*/ */

View File

@ -48,8 +48,6 @@ function toFixed(num, len) {
var varDiff = module.exports = function varDiff(port, varDiffOptions){ var varDiff = module.exports = function varDiff(port, varDiffOptions){
var _this = this; var _this = this;
var networkDifficulty;
var bufferSize, tMin, tMax; var bufferSize, tMin, tMax;
//if (!varDiffOptions) return; //if (!varDiffOptions) return;
@ -61,9 +59,6 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
tMin = varDiffOptions.targetTime - variance; tMin = varDiffOptions.targetTime - variance;
tMax = varDiffOptions.targetTime + variance; tMax = varDiffOptions.targetTime + variance;
this.setNetworkDifficulty = function(diff){
networkDifficulty = diff;
};
this.manageClient = function(client){ this.manageClient = function(client){
@ -113,7 +108,6 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
if (options.x2mode) { if (options.x2mode) {
ddiff = 2; ddiff = 2;
} }
//var diffMax = networkDifficulty < options.maxDiff ? networkDifficulty : options.maxDiff;
var diffMax = options.maxDiff; var diffMax = options.maxDiff;
if (ddiff * client.difficulty > diffMax) { if (ddiff * client.difficulty > diffMax) {
ddiff = diffMax / client.difficulty; ddiff = diffMax / client.difficulty;