From be37d9c0ee21f986d6db043c3630cdaf2d9b43aa Mon Sep 17 00:00:00 2001 From: Alejandro Reyero Date: Fri, 11 Apr 2014 11:32:33 +0200 Subject: [PATCH 001/150] Update blocknotify.c Changed sendto to send using TCP so we can control if the block notify produces a connection error. Added a note about "localhost" usage an possible error 13. --- scripts/blocknotify.c | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/scripts/blocknotify.c b/scripts/blocknotify.c index 74c455a..c0622bd 100644 --- a/scripts/blocknotify.c +++ b/scripts/blocknotify.c @@ -9,6 +9,7 @@ /* Contributed by Alex Petrov aka SysMan at sysman.net +Updated by Alejandro Reyero - TodoJuegos.com Part of NOMP project Simple lightweight & fast - a more efficient block notify script in pure C. @@ -22,8 +23,9 @@ Build with: Usage in daemon coin.conf - blocknotify="/bin/blocknotify localhost:8117 mySuperSecurePassword dogecoin %s" + blocknotify="/bin/blocknotify 127.0.01:8117 mySuperSecurePassword dogecoin %s" +*NOTE* If you use "localhost" as hostname you may get a "13" error (socket / connect / send may consider "localhost" as a broadcast address) // {"password":"notepas","coin":"Xcoin","hash":"d2191a8b644c9cd903439edf1d89ee060e196b3e116e0d48a3f11e5e3987a03b"} // simplest connect + send json string to server @@ -49,37 +51,41 @@ int main(int argc, char **argv) exit(1); } - strncpy(host,argv[1],(sizeof(host)-1)); - p=host; + strncpy(host,argv[1],(sizeof(host)-1)); + p=host; -if ( (arg=strchr(p,':')) ) - { *arg='\0'; + if ( (arg=strchr(p,':')) ) + { + *arg='\0'; - errno=0; // reset errno - port=strtol(++arg,&errptr,10); + errno=0; // reset errno + port=strtol(++arg,&errptr,10); -if ( (errno != 0) || (errptr == arg) ) { fprintf(stderr, "port number fail [%s]\n",errptr); } -// if(strlen(arg) > (errptr-arg) ) also fail, but we ignore it for now -// printf("host %s:%d\n",host,port); -} + if ( (errno != 0) || (errptr == arg) ) { fprintf(stderr, "port number fail [%s]\n",errptr); } + // if(strlen(arg) > (errptr-arg) ) also fail, but we ignore it for now + // printf("host %s:%d\n",host,port); + } -// printf("pass: %s coin: %s block:[%s]\n",argv[2],argv[3],argv[4]); -snprintf(sendline,sizeof(sendline)-1, - "{\"password\":\"%s\",\"coin\":\"%s\",\"hash\":\"%s\"}\n", - argv[2], argv[3], argv[4]); + // printf("pass: %s coin: %s block:[%s]\n",argv[2],argv[3],argv[4]); + snprintf(sendline,sizeof(sendline)-1, + "{\"password\":\"%s\",\"coin\":\"%s\",\"hash\":\"%s\"}\n", + argv[2], argv[3], argv[4]); -// printf("sendline:[%s]",sendline); - - sockfd=socket(AF_INET,SOCK_STREAM,0); + // printf("sendline:[%s]",sendline); + sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr=inet_addr(host); servaddr.sin_port=htons(port); - connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); - sendto(sockfd,sendline,strlen(sendline),0, - (struct sockaddr *)&servaddr,sizeof(servaddr)); -exit(0); + int result = send(sockfd,sendline,strlen(sendline),0); + close(sockfd); + + if(result == -1) { + printf("Error sending: %i\n",errno); + exit(-1); + } + exit(0); } From 7fdfea8abff482b809848b41314370faed0b1884 Mon Sep 17 00:00:00 2001 From: Alejandro Reyero Date: Fri, 11 Apr 2014 14:23:11 +0200 Subject: [PATCH 002/150] Update blocknotify.c --- scripts/blocknotify.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/blocknotify.c b/scripts/blocknotify.c index c0622bd..78c9db7 100644 --- a/scripts/blocknotify.c +++ b/scripts/blocknotify.c @@ -23,7 +23,7 @@ Build with: Usage in daemon coin.conf - blocknotify="/bin/blocknotify 127.0.01:8117 mySuperSecurePassword dogecoin %s" + blocknotify="/bin/blocknotify 127.0.0.1:8117 mySuperSecurePassword dogecoin %s" *NOTE* If you use "localhost" as hostname you may get a "13" error (socket / connect / send may consider "localhost" as a broadcast address) From b9891c86240023b332570984fb0c4bb598a5c1dc Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Fri, 11 Apr 2014 13:24:12 +0000 Subject: [PATCH 003/150] fix proxy state loading bug when key not present --- libs/poolWorker.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 4855507..3dbfe86 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -210,11 +210,15 @@ module.exports = function(logger){ redisClient.on('ready', function(){ redisClient.hgetall("proxyState", function(error, obj) { if (error) { + proxyState = {}; logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); } else { - proxyState = obj; - logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); + if (obj != null) + { + proxyState = obj; + logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); + } } // From 5f6b09a877f689bea16bbdea2972da36619453ab Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Fri, 11 Apr 2014 13:26:42 +0000 Subject: [PATCH 004/150] fix proxy state loading bug when key not present --- libs/poolWorker.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 3dbfe86..71da314 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -209,16 +209,12 @@ module.exports = function(logger){ var redisClient = redis.createClient(6379, "localhost") redisClient.on('ready', function(){ redisClient.hgetall("proxyState", function(error, obj) { - if (error) { - proxyState = {}; + if (error || obj == null) { logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); } else { - if (obj != null) - { - proxyState = obj; - logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); - } + proxyState = obj; + logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); } // From cf74cd41196a1f324778a8bf970eb5ec2a4eceb0 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 11 Apr 2014 17:26:45 -0600 Subject: [PATCH 005/150] Fixed stat page erroring when coins were added. Changed default stat update interval to 60 seconds. --- config_example.json | 2 +- libs/api.js | 3 +- libs/stats.js | 12 +--- website/pages/stats.html | 32 +++++---- website/static/stats.js | 138 ++++++++++++++++++++++++++------------- 5 files changed, 119 insertions(+), 68 deletions(-) diff --git a/config_example.json b/config_example.json index 8759fcf..8cd417c 100644 --- a/config_example.json +++ b/config_example.json @@ -11,7 +11,7 @@ "port": 80, "stratumHost": "cryppit.com", "stats": { - "updateInterval": 15, + "updateInterval": 60, "historicalRetention": 43200, "hashrateWindow": 300, "redis": { diff --git a/libs/api.js b/libs/api.js index 53ebd70..3a433ab 100644 --- a/libs/api.js +++ b/libs/api.js @@ -18,8 +18,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ res.end(portalStats.statsString); return; case 'pool_stats': - res.writeHead(200, {'content-encoding': 'gzip'}); - res.end(portalStats.statPoolHistoryBuffer); + res.end(JSON.stringify(portalStats.statPoolHistory)); return; case 'live_stats': res.writeHead(200, { diff --git a/libs/stats.js b/libs/stats.js index 459ff14..62b7f00 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -20,7 +20,6 @@ module.exports = function(logger, portalConfig, poolConfigs){ this.statHistory = []; this.statPoolHistory = []; - this.statPoolHistoryBuffer; this.stats = {}; this.statsString = ''; @@ -84,7 +83,6 @@ module.exports = function(logger, portalConfig, poolConfigs){ _this.statHistory.forEach(function(stats){ addStatPoolHistory(stats); }); - deflateStatPoolHistory(); }); } @@ -96,7 +94,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ for (var pool in stats.pools){ data.pools[pool] = { hashrate: stats.pools[pool].hashrate, - workers: stats.pools[pool].workerCount, + workerCount: stats.pools[pool].workerCount, blocks: stats.pools[pool].blocks } } @@ -104,11 +102,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ } - function deflateStatPoolHistory(){ - zlib.gzip(JSON.stringify(_this.statPoolHistory), function(err, buffer){ - _this.statPoolHistoryBuffer = buffer; - }); - } + this.getGlobalStats = function(callback){ @@ -245,8 +239,6 @@ module.exports = function(logger, portalConfig, poolConfigs){ } } - deflateStatPoolHistory(); - redisStats.multi([ ['zadd', 'statHistory', statGatherTime, _this.statsString], ['zremrangebyscore', 'statHistory', '-inf', '(' + retentionTime] diff --git a/website/pages/stats.html b/website/pages/stats.html index d7193b3..435a28f 100644 --- a/website/pages/stats.html +++ b/website/pages/stats.html @@ -4,11 +4,18 @@ padding: 18px; } - #topCharts > div > svg{ + #topCharts > div > div > svg{ display: block; height: 280px; } + .chartWrapper{ + border: solid 1px #c7c7c7; + border-radius: 5px; + padding: 5px; + margin-bottom: 18px; + } + .chartLabel{ font-size: 1.2em; text-align: center; @@ -16,24 +23,27 @@ } .chartHolder{ - border: solid 1px #c7c7c7; - border-radius: 5px; - padding: 5px; - margin-bottom: 18px; + }
-
Workers Per Pool
-
+
+
Workers Per Pool
+
+
-
Hashrate Per Pool
-
+
+
Hashrate Per Pool
+
+
-
Blocks Pending Per Pool
-
+
+
Blocks Pending Per Pool
+
+
diff --git a/website/static/stats.js b/website/static/stats.js index 4d5b885..10e43d6 100644 --- a/website/static/stats.js +++ b/website/static/stats.js @@ -1,29 +1,57 @@ -var poolWorkerData = []; -var poolHashrateData = []; -var poolBlockData = []; +var poolWorkerData; +var poolHashrateData; +var poolBlockData; var poolWorkerChart; var poolHashrateChart; var poolBlockChart; -function buildChartData(data){ +var statData; +var poolKeys; + +function buildChartData(){ var pools = {}; - for (var i = 0; i < data.length; i++){ - var time = data[i].time * 1000; - for (var pool in data[i].pools){ - var a = pools[pool] = (pools[pool] || { + poolKeys = []; + for (var i = 0; i < statData.length; i++){ + for (var pool in statData[i].pools){ + if (poolKeys.indexOf(pool) === -1) + poolKeys.push(pool); + } + } + + + for (var i = 0; i < statData.length; i++){ + + var time = statData[i].time * 1000; + + for (var f = 0; f < poolKeys.length; f++){ + var pName = poolKeys[f]; + var a = pools[pName] = (pools[pName] || { hashrate: [], workers: [], blocks: [] }); - a.hashrate.push([time, data[i].pools[pool].hashrate || 0]); - a.workers.push([time, data[i].pools[pool].workers || 0]); - a.blocks.push([time, data[i].pools[pool].blocks.pending]) + if (pName in statData[i].pools){ + a.hashrate.push([time, statData[i].pools[pName].hashrate]); + a.workers.push([time, statData[i].pools[pName].workerCount]); + a.blocks.push([time, statData[i].pools[pName].blocks.pending]) + } + else{ + a.hashrate.push([time, 0]); + a.workers.push([time, 0]); + a.blocks.push([time, 0]) + } + } + } + poolWorkerData = []; + poolHashrateData = []; + poolBlockData = []; + for (var pool in pools){ poolWorkerData.push({ key: pool, @@ -47,21 +75,26 @@ function getReadableHashRateString(hashrate){ hashrate = hashrate / 1024; i++; } while (hashrate > 1024); - return hashrate.toFixed(2) + byteUnits[i]; + return Math.round(hashrate) + byteUnits[i]; +} + +function timeOfDayFormat(timestamp){ + var dStr = d3.time.format('%I:%M %p')(new Date(timestamp)); + if (dStr.indexOf('0') === 0) dStr = dStr.slice(1); + return dStr; } function displayCharts(){ nv.addGraph(function() { poolWorkerChart = nv.models.stackedAreaChart() + .margin({left: 40, right: 40}) .x(function(d){ return d[0] }) .y(function(d){ return d[1] }) .useInteractiveGuideline(true) .clipEdge(true); - poolWorkerChart.xAxis.tickFormat(function(d) { - return d3.time.format('%X')(new Date(d)) - }); + poolWorkerChart.xAxis.tickFormat(timeOfDayFormat); poolWorkerChart.yAxis.tickFormat(d3.format('d')); @@ -73,13 +106,12 @@ function displayCharts(){ nv.addGraph(function() { poolHashrateChart = nv.models.lineChart() + .margin({left: 60, right: 40}) .x(function(d){ return d[0] }) .y(function(d){ return d[1] }) .useInteractiveGuideline(true); - poolHashrateChart.xAxis.tickFormat(function(d) { - return d3.time.format('%X')(new Date(d)) - }); + poolHashrateChart.xAxis.tickFormat(timeOfDayFormat); poolHashrateChart.yAxis.tickFormat(function(d){ return getReadableHashRateString(d); @@ -96,9 +128,7 @@ function displayCharts(){ .x(function(d){ return d[0] }) .y(function(d){ return d[1] }); - poolBlockChart.xAxis.tickFormat(function(d) { - return d3.time.format('%X')(new Date(d)) - }); + poolBlockChart.xAxis.tickFormat(timeOfDayFormat); d3.select('#poolBlocks').datum(poolBlockData).call(poolBlockChart); @@ -115,36 +145,56 @@ function TriggerChartUpdates(){ nv.utils.windowResize(TriggerChartUpdates); $.getJSON('/api/pool_stats', function(data){ - buildChartData(data); + statData = data; + buildChartData(); displayCharts(); }); statsSource.addEventListener('message', function(e){ var stats = JSON.parse(e.data); - var time = stats.time * 1000; - for (var pool in stats.pools){ - for (var i = 0; i < poolWorkerData.length; i++){ - if (poolWorkerData[i].key === pool){ - poolWorkerData[i].values.shift(); - poolWorkerData[i].values.push([time, stats.pools[pool].workerCount]); - break; - } - } - for (var i = 0; i < poolHashrateData.length; i++){ - if (poolHashrateData[i].key === pool){ - poolHashrateData[i].values.shift(); - poolHashrateData[i].values.push([time, stats.pools[pool].hashrate]); - break; - } - } - for (var i = 0; i < poolBlockData.length; i++){ - if (poolBlockData[i].key === pool){ - poolBlockData[i].values.shift(); - poolBlockData[i].values.push([time, stats.pools[pool].blocks.pending]); - break; + statData.push(stats); + + + var newPoolAdded = (function(){ + for (var p in stats.pools){ + if (poolKeys.indexOf(p) === -1) + return true; + } + return false; + })(); + + if (newPoolAdded || Object.keys(stats.pools).length > poolKeys.length){ + buildChartData(); + displayCharts(); + } + else { + var time = stats.time * 1000; + for (var f = 0; f < poolKeys.length; f++) { + var pool = poolKeys[f]; + for (var i = 0; i < poolWorkerData.length; i++) { + if (poolWorkerData[i].key === pool) { + poolWorkerData[i].values.shift(); + poolWorkerData[i].values.push([time, pool in stats.pools ? stats.pools[pool].workerCount : 0]); + break; + } + } + for (var i = 0; i < poolHashrateData.length; i++) { + if (poolHashrateData[i].key === pool) { + poolHashrateData[i].values.shift(); + poolHashrateData[i].values.push([time, pool in stats.pools ? stats.pools[pool].hashrate : 0]); + break; + } + } + for (var i = 0; i < poolBlockData.length; i++) { + if (poolBlockData[i].key === pool) { + poolBlockData[i].values.shift(); + poolBlockData[i].values.push([time, pool in stats.pools ? stats.pools[pool].blocks.pending : 0]); + break; + } } } + TriggerChartUpdates(); } - TriggerChartUpdates(); + }); \ No newline at end of file From 2c7c1a19c00b9adcdc5e1f408e1a00e1f7655599 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 11 Apr 2014 17:38:40 -0600 Subject: [PATCH 006/150] Integers only for yaxis on blocks chart --- website/static/stats.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/static/stats.js b/website/static/stats.js index 10e43d6..843720f 100644 --- a/website/static/stats.js +++ b/website/static/stats.js @@ -130,6 +130,8 @@ function displayCharts(){ poolBlockChart.xAxis.tickFormat(timeOfDayFormat); + poolBlockChart.yAxis.tickFormat(d3.format('d')); + d3.select('#poolBlocks').datum(poolBlockData).call(poolBlockChart); return poolBlockChart; From da22dad4825a808e06dcd07747e27d0bab4f994a Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 12 Apr 2014 16:06:32 -0600 Subject: [PATCH 007/150] Added coin-switching proxy ports to getting started menu. Fixed bug with coin stats on home page not live-updating. Switched zepto to jquery --- website/index.html | 2 +- website/pages/getting_started.html | 68 ++++++++++++++++++++++++------ website/pages/home.html | 25 ++++++++++- website/static/main.js | 8 ---- website/static/style.css | 1 + 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/website/index.html b/website/index.html index e613e65..0542569 100644 --- a/website/index.html +++ b/website/index.html @@ -12,7 +12,7 @@ - + diff --git a/website/pages/getting_started.html b/website/pages/getting_started.html index 37e8dad..c624952 100644 --- a/website/pages/getting_started.html +++ b/website/pages/getting_started.html @@ -17,26 +17,31 @@ min-width: 170px; } - #menu > div:first-child{ + #menu > .menuHeader{ color: #e3f7ff; border-bottom: 1px solid #7f878b; font-size: 1.2em; - padding: 16px 0 4px 15px; + padding: 16px 16px 4px 15px; } - #coinList{ - padding: 20px; + .menuList{ transition-duration: 200ms; } - #coinList > a{ + .menuList > a:first-child{ + margin-top: 10px; + } + + .menuList > a{ display: block; color: #e3f7ff; text-decoration: none; - padding: 5px; + padding: 7px; + padding-left: 25px; + text-transform: capitalize; } - #coinList > a:hover{ + .menuList > a:hover{ color: #f69b3a; } @@ -125,6 +130,9 @@ position: absolute; background-color: #f06350; } + #coinInfo .coinInfoName{ + text-transform: capitalize; + } #coinInfo > div:first-of-type{ font-size: 1.8em; text-align: center; @@ -175,11 +183,40 @@
- \ No newline at end of file + + + \ No newline at end of file diff --git a/website/static/main.js b/website/static/main.js index 23a88ef..3f8a05c 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -1,4 +1,3 @@ - $(function(){ var hotSwap = function(page, pushSate){ @@ -27,12 +26,5 @@ $(function(){ }); window.statsSource = new EventSource("/api/live_stats"); - statsSource.addEventListener('message', function(e){ - var stats = JSON.parse(e.data); - for (algo in stats.algos) { - $('#statsMiners'+algo).text(stats.algos[algo].workers); - $('#statsHashrate'+algo).text(stats.algos[algo].hashrateString); - } - }); }); diff --git a/website/static/style.css b/website/static/style.css index 7133db2..4b7b03b 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -4,6 +4,7 @@ html, button, input, select, textarea, .pure-g [class *= "pure-u"], .pure-g-r [c html{ background: #2d2d2d; + overflow-y: scroll; } body{ From 473322c72f2d27fe27e7f9bd173d9f16cf51f7cf Mon Sep 17 00:00:00 2001 From: Matt Culpepper Date: Sat, 12 Apr 2014 23:29:57 -0500 Subject: [PATCH 008/150] handle x11 difficulty for mpos compatibility --- libs/mposCompatibility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 26b30a2..bc2ae02 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -66,9 +66,9 @@ module.exports = function(logger, poolConfig){ var dbData = [ shareData.ip, shareData.worker, - isValidShare ? 'Y' : 'N', + isValidShare ? 'Y' : 'N', isValidBlock ? 'Y' : 'N', - shareData.difficulty, + poolConfig.coin.algorithm === 'x11' ? shareData.difficulty * 256 : shareData.difficulty, typeof(shareData.error) === 'undefined' ? null : shareData.error, shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '') ]; From 35ab95608a3ca6346b95ea8872405ecf8d168613 Mon Sep 17 00:00:00 2001 From: Alex Petrov Date: Sun, 13 Apr 2014 08:48:09 -0400 Subject: [PATCH 009/150] new coins trc,zet,bte,uno --- coins/bytecoin.json | 5 +++++ coins/terracoin.json | 5 +++++ coins/unobtanium.json | 5 +++++ coins/zetacoin.json | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 coins/bytecoin.json create mode 100644 coins/terracoin.json create mode 100644 coins/unobtanium.json create mode 100644 coins/zetacoin.json diff --git a/coins/bytecoin.json b/coins/bytecoin.json new file mode 100644 index 0000000..45f1b18 --- /dev/null +++ b/coins/bytecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Bytecoin", + "symbol": "BTE", + "algorithm": "sha256" +} diff --git a/coins/terracoin.json b/coins/terracoin.json new file mode 100644 index 0000000..2a72866 --- /dev/null +++ b/coins/terracoin.json @@ -0,0 +1,5 @@ +{ + "name": "Terracoin", + "symbol": "TRC", + "algorithm": "sha256" +} diff --git a/coins/unobtanium.json b/coins/unobtanium.json new file mode 100644 index 0000000..13b4de8 --- /dev/null +++ b/coins/unobtanium.json @@ -0,0 +1,5 @@ +{ + "name": "Unobtanium", + "symbol": "UNO", + "algorithm": "sha256" +} diff --git a/coins/zetacoin.json b/coins/zetacoin.json new file mode 100644 index 0000000..e7515af --- /dev/null +++ b/coins/zetacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Zetacoin", + "symbol": "ZTC", + "algorithm": "sha256" +} From 0af27316d3abb08b70f1e85ec3bac596dea7af1a Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Sun, 13 Apr 2014 16:44:12 +0000 Subject: [PATCH 010/150] profitability data collection from Poloniex --- config_example.json | 8 +- init.js | 40 ++++++-- libs/apiPoloniex.js | 212 ++++++++++++++++++++++++++++++++++++++++ libs/profitSwitch.js | 227 +++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 5 files changed, 482 insertions(+), 11 deletions(-) create mode 100644 libs/apiPoloniex.js create mode 100644 libs/profitSwitch.js diff --git a/config_example.json b/config_example.json index 8cd417c..d08d140 100644 --- a/config_example.json +++ b/config_example.json @@ -68,10 +68,16 @@ } }, + "profitSwitch": { + "enabled": false, + "updateInterval": 60, + "depth": 0.80 + }, + "redisBlockNotifyListener": { "enabled": false, "redisPort": 6379, "redisHost": "hostname", "psubscribeKey": "newblocks:*" } -} \ No newline at end of file +} diff --git a/init.js b/init.js index 0daf3fd..95f5a99 100644 --- a/init.js +++ b/init.js @@ -12,6 +12,7 @@ var WorkerListener = require('./libs/workerListener.js'); var PoolWorker = require('./libs/poolWorker.js'); var PaymentProcessor = require('./libs/paymentProcessor.js'); var Website = require('./libs/website.js'); +var ProfitSwitch = require('./libs/profitSwitch.js'); var algos = require('stratum-pool/lib/algoProperties.js'); @@ -58,7 +59,7 @@ catch(e){ if (cluster.isWorker){ - + switch(process.env.workerType){ case 'pool': new PoolWorker(logger); @@ -69,6 +70,9 @@ if (cluster.isWorker){ case 'website': new Website(logger); break; + case 'profitSwitch': + new ProfitSwitch(logger); + break; } return; @@ -203,23 +207,19 @@ var startCoinswitchListener = function(portalConfig){ logger.debug('Master', 'Coinswitch', text); }); listener.on('switchcoin', function(message){ - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; Object.keys(cluster.workers).forEach(function(id) { cluster.workers[id].send(ipcMessage); }); var ipcMessage = { - type:'switch', - coin: message.coin - }; + type:'switch', + coin: message.coin + }; Object.keys(cluster.workers).forEach(function(id) { cluster.workers[id].send(ipcMessage); }); - }); listener.start(); - - }; var startRedisBlockListener = function(portalConfig){ @@ -287,6 +287,28 @@ var startWebsite = function(portalConfig, poolConfigs){ }; +var startProfitSwitch = function(portalConfig, poolConfigs){ + + if (!portalConfig.profitSwitch.enabled){ + logger.error('Master', 'Profit', 'Profit auto switching disabled'); + return; + } + + var worker = cluster.fork({ + workerType: 'profitSwitch', + pools: JSON.stringify(poolConfigs), + portalConfig: JSON.stringify(portalConfig) + }); + worker.on('exit', function(code, signal){ + logger.error('Master', 'Profit', 'Profit switching process died, spawning replacement...'); + setTimeout(function(){ + startWebsite(portalConfig, poolConfigs); + }, 2000); + }); +}; + + + (function init(){ var poolConfigs = buildPoolConfigs(); @@ -305,4 +327,6 @@ var startWebsite = function(portalConfig, poolConfigs){ startWebsite(portalConfig, poolConfigs); + startProfitSwitch(portalConfig, poolConfigs); + })(); diff --git a/libs/apiPoloniex.js b/libs/apiPoloniex.js new file mode 100644 index 0000000..35f330c --- /dev/null +++ b/libs/apiPoloniex.js @@ -0,0 +1,212 @@ +var request = require('request'); +var nonce = require('nonce'); + +module.exports = function() { + 'use strict'; + + // Module dependencies + + // Constants + var version = '0.1.0', + PUBLIC_API_URL = 'http://poloniex.com/public', + PRIVATE_API_URL = 'https://poloniex.com/tradingApi', + USER_AGENT = 'npm-crypto-apis/' + version + + // Constructor + function Poloniex(key, secret){ + // Generate headers signed by this user's key and secret. + // The secret is encapsulated and never exposed + this._getPrivateHeaders = function(parameters){ + var paramString, signature; + + if (!key || !secret){ + throw 'Poloniex: Error. API key and secret required'; + } + + // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` + paramString = Object.keys(parameters).sort().map(function(param){ + return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); + }).join('&'); + + signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); + + return { + Key: key, + Sign: signature + }; + }; + } + + // If a site uses non-trusted SSL certificates, set this value to false + Poloniex.STRICT_SSL = true; + + // Helper methods + function joinCurrencies(currencyA, currencyB){ + return currencyA + '_' + currencyB; + } + + // Prototype + Poloniex.prototype = { + constructor: Poloniex, + + // Make an API request + _request: function(options, callback){ + if (!('headers' in options)){ + options.headers = {}; + } + + options.headers['User-Agent'] = USER_AGENT; + options.json = true; + options.strictSSL = Poloniex.STRICT_SSL; + + request(options, function(err, response, body) { + callback(err, body); + }); + + return this; + }, + + // Make a public API request + _public: function(parameters, callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL, + qs: parameters + }; + + return this._request(options, callback); + }, + + // Make a private API request + _private: function(parameters, callback){ + var options; + + parameters.nonce = nonce(); + options = { + method: 'POST', + url: PRIVATE_API_URL, + form: parameters, + headers: this._getPrivateHeaders(parameters) + }; + + return this._request(options, callback); + }, + + + ///// + + + // PUBLIC METHODS + + getTicker: function(callback){ + var parameters = { + command: 'returnTicker' + }; + + return this._public(parameters, callback); + }, + + get24hVolume: function(callback){ + var parameters = { + command: 'return24hVolume' + }; + + return this._public(parameters, callback); + }, + + getOrderBook: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOrderBook', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + getTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + + ///// + + + // PRIVATE METHODS + + myBalances: function(callback){ + var parameters = { + command: 'returnBalances' + }; + + return this._private(parameters, callback); + }, + + myOpenOrders: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOpenOrders', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + myTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + buy: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'buy', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + sell: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'sell', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + cancelOrder: function(currencyA, currencyB, orderNumber, callback){ + var parameters = { + command: 'cancelOrder', + currencyPair: joinCurrencies(currencyA, currencyB), + orderNumber: orderNumber + }; + + return this._private(parameters, callback); + }, + + withdraw: function(currency, amount, address, callback){ + var parameters = { + command: 'withdraw', + currency: currency, + amount: amount, + address: address + }; + + return this._private(parameters, callback); + } + }; + + return Poloniex; +}(); diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js new file mode 100644 index 0000000..be743c9 --- /dev/null +++ b/libs/profitSwitch.js @@ -0,0 +1,227 @@ +var async = require('async'); + +var Poloniex = require('./apiPoloniex.js'); + +module.exports = function(logger){ + + var _this = this; + + var portalConfig = JSON.parse(process.env.portalConfig); + var poolConfigs = JSON.parse(process.env.pools); + + var logSystem = 'Profit'; + + // + // build status tracker for collecting coin market information + // + var profitStatus = {}; + var profitSymbols = {}; + Object.keys(poolConfigs).forEach(function(coin){ + + var poolConfig = poolConfigs[coin]; + var algo = poolConfig.coin.algorithm; + + if (!profitStatus.hasOwnProperty(algo)) { + profitStatus[algo] = {}; + } + var coinStatus = { + name: poolConfig.coin.name, + symbol: poolConfig.coin.symbol, + difficulty: 0, + reward: 0, + avgPrice: { BTC: 0, LTC: 0 }, + avgDepth: { BTC: 0, LTC: 0 }, + avgVolume: { BTC: 0, LTC: 0 }, + prices: {}, + depths: {}, + volumes: {}, + }; + profitStatus[algo][poolConfig.coin.symbol] = coinStatus; + profitSymbols[poolConfig.coin.symbol] = algo; + }); + + + // + // ensure we have something to switch + // + var isMoreThanOneCoin = false; + Object.keys(profitStatus).forEach(function(algo){ + if (Object.keys(profitStatus[algo]).length > 1) { + isMoreThanOneCoin = true; + } + }); + if (!isMoreThanOneCoin){ + logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); + return; + } + logger.debug(logSystem, 'profitStatus', JSON.stringify(profitStatus)); + logger.debug(logSystem, 'profitStatus', JSON.stringify(profitSymbols)); + + + // + // setup APIs + // + var poloApi = new Poloniex( + // 'API_KEY', + // 'API_SECRET' + ); + + // + // market data collection from Poloniex + // + this.getProfitDataPoloniex = function(callback){ + async.series([ + function(taskCallback){ + poloApi.getTicker(function(err, data){ + if (err){ + taskCallback(err); + return; + } + Object.keys(profitSymbols).forEach(function(symbol){ + var btcPrice = new Number(0); + var ltcPrice = new Number(0); + + if (data.hasOwnProperty('BTC_' + symbol)) { + btcPrice = new Number(data['BTC_' + symbol]); + } + if (data.hasOwnProperty('LTC_' + symbol)) { + ltcPrice = new Number(data['LTC_' + symbol]); + } + + if (btcPrice > 0 || ltcPrice > 0) { + var prices = { + BTC: btcPrice, + LTC: ltcPrice + }; + profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices; + } + }); + taskCallback(); + }); + }, + function(taskCallback){ + poloApi.get24hVolume(function(err, data){ + if (err){ + taskCallback(err); + return; + } + Object.keys(profitSymbols).forEach(function(symbol){ + var btcVolume = new Number(0); + var ltcVolume = new Number(0); + + if (data.hasOwnProperty('BTC_' + symbol)) { + btcVolume = new Number(data['BTC_' + symbol].BTC); + } + if (data.hasOwnProperty('LTC_' + symbol)) { + ltcVolume = new Number(data['LTC_' + symbol].LTC); + } + + if (btcVolume > 0 || ltcVolume > 0) { + var volumes = { + BTC: btcVolume, + LTC: ltcVolume + }; + profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes; + } + }); + taskCallback(); + }); + }, + function(taskCallback){ + var depthTasks = []; + Object.keys(profitSymbols).forEach(function(symbol){ + var coinVolumes = profitStatus[profitSymbols[symbol]][symbol].volumes; + var coinPrices = profitStatus[profitSymbols[symbol]][symbol].prices; + + if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){ + var btcDepth = new Number(0); + var ltcDepth = new Number(0); + + if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){ + var coinPrice = new Number(coinPrices['Poloniex']['BTC']); + depthTasks.push(function(callback){ + _this.getMarketDepthFromPoloniex('BTC', symbol, coinPrice, callback) + }); + } + if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){ + var coinPrice = new Number(coinPrices['Poloniex']['LTC']); + depthTasks.push(function(callback){ + _this.getMarketDepthFromPoloniex('LTC', symbol, coinPrice, callback) + }); + } + } + }); + + if (depthTasks.length == 0){ + taskCallback; + return; + } + async.parallel(depthTasks, function(err){ + if (err){ + logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); + return; + } + taskCallback(); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + + }; + this.getMarketDepthFromPoloniex = function(symbolA, symbolB, coinPrice, callback){ + poloApi.getOrderBook(symbolA, symbolB, function(err, data){ + if (err){ + callback(err); + return; + } + var depth = new Number(0); + if (data.hasOwnProperty('bids')){ + data['bids'].forEach(function(order){ + var price = new Number(order[0]); + var qty = new Number(order[1]); + // only measure the depth down to configured depth + if (price >= coinPrice * portalConfig.profitSwitch.depth){ + depth += (qty * price); + } + }); + } + + if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){ + profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = { + BTC: 0, + LTC: 0 + }; + } + profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth; + callback(); + }); + }; + + // TODO + this.getProfitDataCryptsy = function(callback){ + callback(null); + }; + + + var checkProfitability = function(){ + logger.debug(logSystem, 'Check', 'Running profitability checks.'); + + async.parallel([ + _this.getProfitDataPoloniex, + _this.getProfitDataCryptsy + ], function(err){ + if (err){ + logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); + return; + } + logger.debug(logSystem, 'Check', JSON.stringify(profitStatus)); + }); + }; + setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000); + +}; diff --git a/package.json b/package.json index c1a28a3..2140f2c 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,11 @@ "compression": "*", "dot": "*", "colors": "*", - "node-watch": "*" + "node-watch": "*", + "request": "*", + "nonce": "*" }, "engines": { "node": ">=0.10" } -} \ No newline at end of file +} From 550b09767e779d06995268664295316af44fb4b7 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Sun, 13 Apr 2014 16:46:06 +0000 Subject: [PATCH 011/150] whitespace formatting --- config_example.json | 8 +- libs/profitSwitch.js | 354 +++++++++++++++++++++---------------------- 2 files changed, 181 insertions(+), 181 deletions(-) diff --git a/config_example.json b/config_example.json index d08d140..0ddaa74 100644 --- a/config_example.json +++ b/config_example.json @@ -68,11 +68,11 @@ } }, - "profitSwitch": { - "enabled": false, + "profitSwitch": { + "enabled": false, "updateInterval": 60, - "depth": 0.80 - }, + "depth": 0.80 + }, "redisBlockNotifyListener": { "enabled": false, diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index be743c9..3de8b9c 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -11,217 +11,217 @@ module.exports = function(logger){ var logSystem = 'Profit'; - // - // build status tracker for collecting coin market information - // - var profitStatus = {}; - var profitSymbols = {}; + // + // build status tracker for collecting coin market information + // + var profitStatus = {}; + var profitSymbols = {}; Object.keys(poolConfigs).forEach(function(coin){ var poolConfig = poolConfigs[coin]; - var algo = poolConfig.coin.algorithm; + var algo = poolConfig.coin.algorithm; - if (!profitStatus.hasOwnProperty(algo)) { - profitStatus[algo] = {}; - } - var coinStatus = { - name: poolConfig.coin.name, - symbol: poolConfig.coin.symbol, - difficulty: 0, - reward: 0, - avgPrice: { BTC: 0, LTC: 0 }, - avgDepth: { BTC: 0, LTC: 0 }, - avgVolume: { BTC: 0, LTC: 0 }, - prices: {}, - depths: {}, - volumes: {}, - }; - profitStatus[algo][poolConfig.coin.symbol] = coinStatus; - profitSymbols[poolConfig.coin.symbol] = algo; - }); + if (!profitStatus.hasOwnProperty(algo)) { + profitStatus[algo] = {}; + } + var coinStatus = { + name: poolConfig.coin.name, + symbol: poolConfig.coin.symbol, + difficulty: 0, + reward: 0, + avgPrice: { BTC: 0, LTC: 0 }, + avgDepth: { BTC: 0, LTC: 0 }, + avgVolume: { BTC: 0, LTC: 0 }, + prices: {}, + depths: {}, + volumes: {}, + }; + profitStatus[algo][poolConfig.coin.symbol] = coinStatus; + profitSymbols[poolConfig.coin.symbol] = algo; + }); - // - // ensure we have something to switch - // - var isMoreThanOneCoin = false; - Object.keys(profitStatus).forEach(function(algo){ - if (Object.keys(profitStatus[algo]).length > 1) { - isMoreThanOneCoin = true; - } - }); - if (!isMoreThanOneCoin){ - logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); - return; - } - logger.debug(logSystem, 'profitStatus', JSON.stringify(profitStatus)); - logger.debug(logSystem, 'profitStatus', JSON.stringify(profitSymbols)); + // + // ensure we have something to switch + // + var isMoreThanOneCoin = false; + Object.keys(profitStatus).forEach(function(algo){ + if (Object.keys(profitStatus[algo]).length > 1) { + isMoreThanOneCoin = true; + } + }); + if (!isMoreThanOneCoin){ + logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); + return; + } + logger.debug(logSystem, 'profitStatus', JSON.stringify(profitStatus)); + logger.debug(logSystem, 'profitStatus', JSON.stringify(profitSymbols)); - // - // setup APIs - // - var poloApi = new Poloniex( + // + // setup APIs + // + var poloApi = new Poloniex( // 'API_KEY', - // 'API_SECRET' - ); + // 'API_SECRET' + ); - // - // market data collection from Poloniex - // + // + // market data collection from Poloniex + // this.getProfitDataPoloniex = function(callback){ - async.series([ - function(taskCallback){ - poloApi.getTicker(function(err, data){ - if (err){ - taskCallback(err); - return; - } - Object.keys(profitSymbols).forEach(function(symbol){ - var btcPrice = new Number(0); - var ltcPrice = new Number(0); + async.series([ + function(taskCallback){ + poloApi.getTicker(function(err, data){ + if (err){ + taskCallback(err); + return; + } + Object.keys(profitSymbols).forEach(function(symbol){ + var btcPrice = new Number(0); + var ltcPrice = new Number(0); - if (data.hasOwnProperty('BTC_' + symbol)) { - btcPrice = new Number(data['BTC_' + symbol]); - } - if (data.hasOwnProperty('LTC_' + symbol)) { - ltcPrice = new Number(data['LTC_' + symbol]); - } + if (data.hasOwnProperty('BTC_' + symbol)) { + btcPrice = new Number(data['BTC_' + symbol]); + } + if (data.hasOwnProperty('LTC_' + symbol)) { + ltcPrice = new Number(data['LTC_' + symbol]); + } - if (btcPrice > 0 || ltcPrice > 0) { - var prices = { - BTC: btcPrice, - LTC: ltcPrice - }; - profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices; - } - }); - taskCallback(); - }); - }, - function(taskCallback){ - poloApi.get24hVolume(function(err, data){ - if (err){ - taskCallback(err); - return; - } - Object.keys(profitSymbols).forEach(function(symbol){ - var btcVolume = new Number(0); - var ltcVolume = new Number(0); + if (btcPrice > 0 || ltcPrice > 0) { + var prices = { + BTC: btcPrice, + LTC: ltcPrice + }; + profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices; + } + }); + taskCallback(); + }); + }, + function(taskCallback){ + poloApi.get24hVolume(function(err, data){ + if (err){ + taskCallback(err); + return; + } + Object.keys(profitSymbols).forEach(function(symbol){ + var btcVolume = new Number(0); + var ltcVolume = new Number(0); - if (data.hasOwnProperty('BTC_' + symbol)) { - btcVolume = new Number(data['BTC_' + symbol].BTC); - } - if (data.hasOwnProperty('LTC_' + symbol)) { - ltcVolume = new Number(data['LTC_' + symbol].LTC); - } + if (data.hasOwnProperty('BTC_' + symbol)) { + btcVolume = new Number(data['BTC_' + symbol].BTC); + } + if (data.hasOwnProperty('LTC_' + symbol)) { + ltcVolume = new Number(data['LTC_' + symbol].LTC); + } - if (btcVolume > 0 || ltcVolume > 0) { - var volumes = { - BTC: btcVolume, - LTC: ltcVolume - }; - profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes; - } - }); - taskCallback(); - }); - }, - function(taskCallback){ - var depthTasks = []; - Object.keys(profitSymbols).forEach(function(symbol){ + if (btcVolume > 0 || ltcVolume > 0) { + var volumes = { + BTC: btcVolume, + LTC: ltcVolume + }; + profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes; + } + }); + taskCallback(); + }); + }, + function(taskCallback){ + var depthTasks = []; + Object.keys(profitSymbols).forEach(function(symbol){ var coinVolumes = profitStatus[profitSymbols[symbol]][symbol].volumes; var coinPrices = profitStatus[profitSymbols[symbol]][symbol].prices; - if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){ - var btcDepth = new Number(0); - var ltcDepth = new Number(0); + if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){ + var btcDepth = new Number(0); + var ltcDepth = new Number(0); - if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){ - var coinPrice = new Number(coinPrices['Poloniex']['BTC']); - depthTasks.push(function(callback){ + if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){ + var coinPrice = new Number(coinPrices['Poloniex']['BTC']); + depthTasks.push(function(callback){ _this.getMarketDepthFromPoloniex('BTC', symbol, coinPrice, callback) - }); - } - if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){ - var coinPrice = new Number(coinPrices['Poloniex']['LTC']); - depthTasks.push(function(callback){ + }); + } + if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){ + var coinPrice = new Number(coinPrices['Poloniex']['LTC']); + depthTasks.push(function(callback){ _this.getMarketDepthFromPoloniex('LTC', symbol, coinPrice, callback) - }); - } - } - }); + }); + } + } + }); - if (depthTasks.length == 0){ - taskCallback; - return; - } - async.parallel(depthTasks, function(err){ - if (err){ - logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); - return; - } - taskCallback(); - }); - } - ], function(err){ - if (err){ - callback(err); - return; - } - callback(null); - }); - + if (depthTasks.length == 0){ + taskCallback; + return; + } + async.parallel(depthTasks, function(err){ + if (err){ + logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); + return; + } + taskCallback(); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + }; this.getMarketDepthFromPoloniex = function(symbolA, symbolB, coinPrice, callback){ - poloApi.getOrderBook(symbolA, symbolB, function(err, data){ - if (err){ - callback(err); - return; - } + poloApi.getOrderBook(symbolA, symbolB, function(err, data){ + if (err){ + callback(err); + return; + } var depth = new Number(0); - if (data.hasOwnProperty('bids')){ - data['bids'].forEach(function(order){ - var price = new Number(order[0]); - var qty = new Number(order[1]); - // only measure the depth down to configured depth - if (price >= coinPrice * portalConfig.profitSwitch.depth){ - depth += (qty * price); - } - }); - } + if (data.hasOwnProperty('bids')){ + data['bids'].forEach(function(order){ + var price = new Number(order[0]); + var qty = new Number(order[1]); + // only measure the depth down to configured depth + if (price >= coinPrice * portalConfig.profitSwitch.depth){ + depth += (qty * price); + } + }); + } - if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){ - profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = { - BTC: 0, - LTC: 0 - }; - } - profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth; + if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){ + profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = { + BTC: 0, + LTC: 0 + }; + } + profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth; callback(); - }); - }; + }); + }; - // TODO + // TODO this.getProfitDataCryptsy = function(callback){ - callback(null); + callback(null); }; var checkProfitability = function(){ - logger.debug(logSystem, 'Check', 'Running profitability checks.'); + logger.debug(logSystem, 'Check', 'Running profitability checks.'); - async.parallel([ - _this.getProfitDataPoloniex, - _this.getProfitDataCryptsy - ], function(err){ - if (err){ - logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); - return; - } - logger.debug(logSystem, 'Check', JSON.stringify(profitStatus)); - }); - }; + async.parallel([ + _this.getProfitDataPoloniex, + _this.getProfitDataCryptsy + ], function(err){ + if (err){ + logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); + return; + } + logger.debug(logSystem, 'Check', JSON.stringify(profitStatus)); + }); + }; setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000); }; From 7b6044d674a220a17066c6ee5552ddeab092a2fc Mon Sep 17 00:00:00 2001 From: Matt Culpepper Date: Sun, 13 Apr 2014 13:59:19 -0500 Subject: [PATCH 012/150] create a config option for difficulty multiplier --- README.md | 1 + coins/darkcoin.json | 5 +++-- coins/hirocoin.json | 5 +++-- libs/mposCompatibility.js | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index baa047b..fd11497 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,7 @@ Here is an example of the required fields: "symbol": "ltc", "algorithm": "scrypt", //or "sha256", "scrypt-jane", "scrypt-n", "quark", "x11" "txMessages": false, //or true (not required, defaults to false) + "mposDiffMultiplier": 256, //only for x11 coins in mpos mode, set to 256 (optional) } ```` diff --git a/coins/darkcoin.json b/coins/darkcoin.json index ed863c3..fa806d4 100644 --- a/coins/darkcoin.json +++ b/coins/darkcoin.json @@ -1,5 +1,6 @@ { "name": "Darkcoin", "symbol": "DRK", - "algorithm": "x11" -} \ No newline at end of file + "algorithm": "x11", + "mposDiffMultiplier": 256 +} diff --git a/coins/hirocoin.json b/coins/hirocoin.json index 3b21d84..cd7da82 100644 --- a/coins/hirocoin.json +++ b/coins/hirocoin.json @@ -1,5 +1,6 @@ { "name": "Hirocoin", "symbol": "hic", - "algorithm": "x11" -} \ No newline at end of file + "algorithm": "x11", + "mposDiffMultiplier": 256 +} diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index bc2ae02..d0bf31c 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -68,7 +68,7 @@ module.exports = function(logger, poolConfig){ shareData.worker, isValidShare ? 'Y' : 'N', isValidBlock ? 'Y' : 'N', - poolConfig.coin.algorithm === 'x11' ? shareData.difficulty * 256 : shareData.difficulty, + shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1) typeof(shareData.error) === 'undefined' ? null : shareData.error, shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '') ]; @@ -102,4 +102,4 @@ module.exports = function(logger, poolConfig){ }; -}; \ No newline at end of file +}; From 2d6470cf3f74ae43423a6bce1d85133a69bcc934 Mon Sep 17 00:00:00 2001 From: Matt Culpepper Date: Sun, 13 Apr 2014 14:06:24 -0500 Subject: [PATCH 013/150] missing comma --- libs/mposCompatibility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index d0bf31c..2ee3f19 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -68,7 +68,7 @@ module.exports = function(logger, poolConfig){ shareData.worker, isValidShare ? 'Y' : 'N', isValidBlock ? 'Y' : 'N', - shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1) + shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1), typeof(shareData.error) === 'undefined' ? null : shareData.error, shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '') ]; From 93274f907b84010a0dff01aeb840a28c2bb3f039 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Sun, 13 Apr 2014 23:17:40 +0000 Subject: [PATCH 014/150] added support for difficulty and blockreward --- libs/profitSwitch.js | 344 +++++++++++++++++++++++++------------------ 1 file changed, 198 insertions(+), 146 deletions(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 3de8b9c..7d57fd7 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -1,6 +1,7 @@ var async = require('async'); var Poloniex = require('./apiPoloniex.js'); +var Stratum = require('stratum-pool'); module.exports = function(logger){ @@ -19,25 +20,22 @@ module.exports = function(logger){ Object.keys(poolConfigs).forEach(function(coin){ var poolConfig = poolConfigs[coin]; - var algo = poolConfig.coin.algorithm; + var algo = poolConfig.coin.algorithm; - if (!profitStatus.hasOwnProperty(algo)) { - profitStatus[algo] = {}; - } - var coinStatus = { - name: poolConfig.coin.name, - symbol: poolConfig.coin.symbol, - difficulty: 0, - reward: 0, - avgPrice: { BTC: 0, LTC: 0 }, - avgDepth: { BTC: 0, LTC: 0 }, - avgVolume: { BTC: 0, LTC: 0 }, - prices: {}, - depths: {}, - volumes: {}, - }; - profitStatus[algo][poolConfig.coin.symbol] = coinStatus; - profitSymbols[poolConfig.coin.symbol] = algo; + if (!profitStatus.hasOwnProperty(algo)) { + profitStatus[algo] = {}; + } + var coinStatus = { + name: poolConfig.coin.name, + symbol: poolConfig.coin.symbol, + difficulty: 0, + reward: 0, + prices: {}, + depths: {}, + volumes: {}, + }; + profitStatus[algo][poolConfig.coin.symbol] = coinStatus; + profitSymbols[poolConfig.coin.symbol] = algo; }); @@ -46,13 +44,13 @@ module.exports = function(logger){ // var isMoreThanOneCoin = false; Object.keys(profitStatus).forEach(function(algo){ - if (Object.keys(profitStatus[algo]).length > 1) { - isMoreThanOneCoin = true; - } + if (Object.keys(profitStatus[algo]).length > 1) { + isMoreThanOneCoin = true; + } }); if (!isMoreThanOneCoin){ - logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); - return; + logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); + return; } logger.debug(logSystem, 'profitStatus', JSON.stringify(profitStatus)); logger.debug(logSystem, 'profitStatus', JSON.stringify(profitSymbols)); @@ -63,143 +61,143 @@ module.exports = function(logger){ // var poloApi = new Poloniex( // 'API_KEY', - // 'API_SECRET' + // 'API_SECRET' ); // // market data collection from Poloniex // this.getProfitDataPoloniex = function(callback){ - async.series([ - function(taskCallback){ - poloApi.getTicker(function(err, data){ - if (err){ - taskCallback(err); - return; - } - Object.keys(profitSymbols).forEach(function(symbol){ - var btcPrice = new Number(0); - var ltcPrice = new Number(0); + async.series([ + function(taskCallback){ + poloApi.getTicker(function(err, data){ + if (err){ + taskCallback(err); + return; + } + Object.keys(profitSymbols).forEach(function(symbol){ + var btcPrice = new Number(0); + var ltcPrice = new Number(0); - if (data.hasOwnProperty('BTC_' + symbol)) { - btcPrice = new Number(data['BTC_' + symbol]); - } - if (data.hasOwnProperty('LTC_' + symbol)) { - ltcPrice = new Number(data['LTC_' + symbol]); - } + if (data.hasOwnProperty('BTC_' + symbol)) { + btcPrice = new Number(data['BTC_' + symbol]); + } + if (data.hasOwnProperty('LTC_' + symbol)) { + ltcPrice = new Number(data['LTC_' + symbol]); + } - if (btcPrice > 0 || ltcPrice > 0) { - var prices = { - BTC: btcPrice, - LTC: ltcPrice - }; - profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices; - } - }); - taskCallback(); - }); - }, - function(taskCallback){ - poloApi.get24hVolume(function(err, data){ - if (err){ - taskCallback(err); - return; - } - Object.keys(profitSymbols).forEach(function(symbol){ - var btcVolume = new Number(0); - var ltcVolume = new Number(0); + if (btcPrice > 0 || ltcPrice > 0) { + var prices = { + BTC: btcPrice, + LTC: ltcPrice + }; + profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices; + } + }); + taskCallback(); + }); + }, + function(taskCallback){ + poloApi.get24hVolume(function(err, data){ + if (err){ + taskCallback(err); + return; + } + Object.keys(profitSymbols).forEach(function(symbol){ + var btcVolume = new Number(0); + var ltcVolume = new Number(0); - if (data.hasOwnProperty('BTC_' + symbol)) { - btcVolume = new Number(data['BTC_' + symbol].BTC); - } - if (data.hasOwnProperty('LTC_' + symbol)) { - ltcVolume = new Number(data['LTC_' + symbol].LTC); - } + if (data.hasOwnProperty('BTC_' + symbol)) { + btcVolume = new Number(data['BTC_' + symbol].BTC); + } + if (data.hasOwnProperty('LTC_' + symbol)) { + ltcVolume = new Number(data['LTC_' + symbol].LTC); + } - if (btcVolume > 0 || ltcVolume > 0) { - var volumes = { - BTC: btcVolume, - LTC: ltcVolume - }; - profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes; - } - }); - taskCallback(); - }); - }, - function(taskCallback){ - var depthTasks = []; - Object.keys(profitSymbols).forEach(function(symbol){ + if (btcVolume > 0 || ltcVolume > 0) { + var volumes = { + BTC: btcVolume, + LTC: ltcVolume + }; + profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes; + } + }); + taskCallback(); + }); + }, + function(taskCallback){ + var depthTasks = []; + Object.keys(profitSymbols).forEach(function(symbol){ var coinVolumes = profitStatus[profitSymbols[symbol]][symbol].volumes; var coinPrices = profitStatus[profitSymbols[symbol]][symbol].prices; - if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){ - var btcDepth = new Number(0); - var ltcDepth = new Number(0); + if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){ + var btcDepth = new Number(0); + var ltcDepth = new Number(0); - if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){ - var coinPrice = new Number(coinPrices['Poloniex']['BTC']); - depthTasks.push(function(callback){ + if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){ + var coinPrice = new Number(coinPrices['Poloniex']['BTC']); + depthTasks.push(function(callback){ _this.getMarketDepthFromPoloniex('BTC', symbol, coinPrice, callback) - }); - } - if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){ - var coinPrice = new Number(coinPrices['Poloniex']['LTC']); - depthTasks.push(function(callback){ + }); + } + if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){ + var coinPrice = new Number(coinPrices['Poloniex']['LTC']); + depthTasks.push(function(callback){ _this.getMarketDepthFromPoloniex('LTC', symbol, coinPrice, callback) - }); - } - } - }); + }); + } + } + }); - if (depthTasks.length == 0){ - taskCallback; - return; - } - async.parallel(depthTasks, function(err){ - if (err){ - logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); - return; - } - taskCallback(); - }); - } - ], function(err){ - if (err){ + if (depthTasks.length == 0){ + taskCallback; + return; + } + async.parallel(depthTasks, function(err){ + if (err){ + taskCallback(err); + return; + } + taskCallback(); + }); + } + ], function(err){ + if (err){ callback(err); - return; - } - callback(null); - }); - + return; + } + callback(null); + }); + }; this.getMarketDepthFromPoloniex = function(symbolA, symbolB, coinPrice, callback){ - poloApi.getOrderBook(symbolA, symbolB, function(err, data){ - if (err){ - callback(err); - return; - } + poloApi.getOrderBook(symbolA, symbolB, function(err, data){ + if (err){ + callback(err); + return; + } var depth = new Number(0); - if (data.hasOwnProperty('bids')){ - data['bids'].forEach(function(order){ - var price = new Number(order[0]); - var qty = new Number(order[1]); - // only measure the depth down to configured depth - if (price >= coinPrice * portalConfig.profitSwitch.depth){ - depth += (qty * price); - } - }); - } + if (data.hasOwnProperty('bids')){ + data['bids'].forEach(function(order){ + var price = new Number(order[0]); + var qty = new Number(order[1]); + // only measure the depth down to configured depth + if (price >= coinPrice * portalConfig.profitSwitch.depth){ + depth += (qty * price); + } + }); + } - if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){ - profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = { - BTC: 0, - LTC: 0 - }; - } - profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth; + if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){ + profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = { + BTC: 0, + LTC: 0 + }; + } + profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth; callback(); - }); + }); }; // TODO @@ -207,20 +205,74 @@ module.exports = function(logger){ callback(null); }; + this.getCoindDaemonInfo = function(callback){ + var daemonTasks = []; + Object.keys(profitStatus).forEach(function(algo){ + Object.keys(profitStatus[algo]).forEach(function(symbol){ + var coinName = profitStatus[algo][symbol].name; + var poolConfig = poolConfigs[coinName]; + var daemonConfig = poolConfig.shareProcessing.internal.daemon; + daemonTasks.push(function(callback){ + _this.getDaemonInfoForCoin(symbol, daemonConfig, callback) + }); + }); + }); + + if (daemonTasks.length == 0){ + callback(); + return; + } + async.parallel(daemonTasks, function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + }; + this.getDaemonInfoForCoin = function(symbol, cfg, callback){ + var daemon = new Stratum.daemon.interface([cfg]); + daemon.once('online', function(){ + daemon.cmd('getdifficulty', null, function(result){ + if (result[0].error != null){ + callback(result[0].error); + return; + } + profitStatus[profitSymbols[symbol]][symbol].difficulty = result[0].response; + + daemon.cmd('getblocktemplate', + [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], + function(result){ + if (result[0].error != null){ + callback(result[0].error); + return; + } + profitStatus[profitSymbols[symbol]][symbol].reward = new Number(result[0].response.coinbasevalue / 100000000); + }); + callback(null) + }); + }).once('connectionFailed', function(error){ + callback(error); + }).on('error', function(error){ + callback(error); + }).init(); + }; + var checkProfitability = function(){ - logger.debug(logSystem, 'Check', 'Running profitability checks.'); + logger.debug(logSystem, 'Check', 'Running mining profitability check.'); - async.parallel([ - _this.getProfitDataPoloniex, - _this.getProfitDataCryptsy - ], function(err){ - if (err){ + async.parallel([ + _this.getProfitDataPoloniex, + _this.getProfitDataCryptsy, + _this.getCoindDaemonInfo + ], function(err){ + if (err){ logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); - return; - } + return; + } logger.debug(logSystem, 'Check', JSON.stringify(profitStatus)); - }); + }); }; setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000); From 4888aaffba0b29bd8ddbca3535dea54ffd1d6c43 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 15 Apr 2014 16:38:51 -0600 Subject: [PATCH 015/150] Changed all references to localhost to 127.0.0.1 for better system compatibility. Implemented thread-wide IP banning via IPC. Added redis config for portal-wide related db interactions (stats and proxy) --- README.md | 78 ++++++++++++++++++++++-------- config_example.json | 11 +++-- init.js | 23 +++++---- libs/mposCompatibility.js | 2 +- libs/poolWorker.js | 13 ++++- libs/stats.js | 2 +- libs/workerListener.js | 26 ---------- pool_configs/litecoin_example.json | 14 +++--- scripts/blockNotify.js | 2 +- scripts/coinSwitch.js | 2 +- 10 files changed, 101 insertions(+), 72 deletions(-) delete mode 100644 libs/workerListener.js diff --git a/README.md b/README.md index baa047b..d333499 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,34 @@ #### Node Open Mining Portal This portal is an extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool written -entirely in Node.js. It contains a stratum poolserver, reward/payment/share processor, and a (*not yet completed*) -front-end website. +entirely in Node.js. It contains a stratum poolserver; reward/payment/share processor; and a (*not yet completed*) +responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. -#### Features +#### Table of Contents +* [Features](#features) + * [Attack Mitigation](##attack-mitigation) + * [Security](#security) + * [Planned Features](#planned-features) +* [Community Support](#community--support) +* [Usage](#usage) + * [Requirements](#requirements) + * [Setting Up Coin Daemon](#0-setting-up-coin-daemon) + * [Downloading & Installing](#1-downloading--installing) + * [Configuration](#2-configuration) + * [Portal Config](#portal-config) + * [Coin Config](#coin-config) + * [Pool Config](#pool-config) + * [Setting Up Blocknotify](#optional-recommended-setting-up-blocknotify) + * [Starting the Portal](#3-start-the-portal) + * [Upgrading NOMP](#upgrading-nomp) +* [Donations](#donations) +* [Credits](#credits) +* [License](#license) + + + + +### Features * For the pool server it uses the highly efficient [node-stratum-pool](//github.com/zone117x/node-stratum-pool) module which supports vardiff, POW & POS, transaction messages, anti-DDoS, IP banning, [several hashing algorithms](//github.com/zone117x/node-stratum-pool#hashing-algorithms-supported). @@ -64,7 +88,7 @@ allow your own pool to connect upstream to a larger pool server. It will request redistribute the work to our own connected miners. -#### Community / Support +### Community / Support IRC * Support / general discussion join #nomp: https://webchat.freenode.net/?channels=#nomp * Development discussion join #nomp-dev: https://webchat.freenode.net/?channels=#nomp-dev @@ -80,8 +104,11 @@ If your pool uses NOMP let us know and we will list your website here. * http://chunkypools.com * http://clevermining.com * http://rapidhash.net +* http://suchpool.pw * http://hashfaster.com +* http://miningpoolhub.com * http://kryptochaos.com +* http://pool.uberpools.org Usage @@ -156,12 +183,7 @@ Explanation for each field: /* How many seconds to hold onto historical stats. Currently set to 24 hours. */ "historicalRetention": 43200, /* How many seconds worth of shares should be gathered to generate hashrate. */ - "hashrateWindow": 300, - /* Redis instance of where to store historical stats. */ - "redis": { - "host": "localhost", - "port": 6379 - } + "hashrateWindow": 300 }, /* Not done yet. */ "adminCenter": { @@ -170,6 +192,13 @@ Explanation for each field: } }, + /* Redis instance of where to store global portal data such as historical stats, proxy states, + ect.. */ + "redis": { + "host": "127.0.0.1", + "port": 6379 + }, + /* With this enabled, the master process listen on the configured port for messages from the 'scripts/blockNotify.js' script which your coin daemons can be configured to run when a new block is available. When a blocknotify message is received, the master process uses @@ -290,6 +319,12 @@ Description of options: due to mining apps using incorrect max diffs and this pool using correct max diffs. */ "shareVariancePercent": 10, + /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy + protocol enabled, such as HAProxy with 'send-proxy' param: + http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ + "tcpProxyProtocol": false, + + /* This determines what to do with submitted shares (and stratum worker authentication). You have two options: 1) Enable internal and disable mpos = this portal to handle all share payments. @@ -338,7 +373,7 @@ Description of options: configured 'address' that receives the block rewards, otherwise the daemon will not be able to confirm blocks or send out payments. */ "daemon": { - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" @@ -346,7 +381,7 @@ Description of options: /* Redis database used for storing share and block submission data. */ "redis": { - "host": "localhost", + "host": "127.0.0.1", "port": 6379 } }, @@ -355,7 +390,7 @@ Description of options: also want to use the "emitInvalidBlockHashes" option below if you require it. */ "mpos": { "enabled": false, - "host": "localhost", //MySQL db host + "host": "127.0.0.1", //MySQL db host "port": 3306, //MySQL db port "user": "me", //MySQL db user "password": "mypass", //MySQL db password @@ -368,8 +403,10 @@ Description of options: } }, - /* If a worker is submitting a high threshold of invalid shares we can temporarily ban them - to reduce system/network load. Also useful to fight against flooding attacks. */ + /* If a worker is submitting a high threshold of invalid shares we can temporarily ban their IP + to reduce system/network load. Also useful to fight against flooding attacks. The worker's + If running behind something like HAProxy be sure to enable the TCP Proxy Protocol config, + otherwise you'll end up banning your own IP address (and therefore all workers). */ "banning": { "enabled": true, "time": 600, //How many seconds to ban worker for @@ -404,13 +441,13 @@ Description of options: drops out-of-sync or offline. */ "daemons": [ { //Main daemon instance - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" }, { //Backup daemon instance - "host": "localhost", + "host": "127.0.0.1", "port": 19344, "user": "litecoinrpc", "password": "testnet" @@ -423,7 +460,7 @@ Description of options: intensive than blocknotify script). However its still under development (not yet working). */ "p2p": { "enabled": false, - "host": "localhost", + "host": "127.0.0.1", "port": 19333, /* Magic value is different for main/testnet and for each coin. It is found in the daemon @@ -455,7 +492,7 @@ node [path to scripts/blockNotify.js] [listener host]:[listener port] [listener ``` Example: inside `dogecoin.conf` add the line ``` -blocknotify="node scripts/blockNotify.js localhost:8117 mySuperSecurePassword dogecoin %s" +blocknotify="node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s" ``` Alternatively, you can use a more efficient block notify script written in pure C. Build and usage instructions @@ -506,7 +543,8 @@ Credits * [TheSeven](//github.com/TheSeven) - answering an absurd amount of my questions and being a very helpful gentleman * [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing * [Alex Petrov / sysmanalex](https://github.com/sysmanalex) - contributed the pure C block notify script -* Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool) +* [svirusxxx](//github.com/svirusxxx) - sponsored development of MPOS mode +* Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool#credits) License diff --git a/config_example.json b/config_example.json index 8cd417c..55e5e9b 100644 --- a/config_example.json +++ b/config_example.json @@ -13,11 +13,7 @@ "stats": { "updateInterval": 60, "historicalRetention": 43200, - "hashrateWindow": 300, - "redis": { - "host": "localhost", - "port": 6379 - } + "hashrateWindow": 300 }, "adminCenter": { "enabled": true, @@ -25,6 +21,11 @@ } }, + "redis": { + "host": "127.0.0.1", + "port": 6379 + }, + "blockNotifyListener": { "enabled": false, "port": 8117, diff --git a/init.js b/init.js index 0daf3fd..8044bb0 100644 --- a/init.js +++ b/init.js @@ -8,7 +8,6 @@ var PoolLogger = require('./libs/logUtil.js'); var BlocknotifyListener = require('./libs/blocknotifyListener.js'); var CoinswitchListener = require('./libs/coinswitchListener.js'); var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js'); -var WorkerListener = require('./libs/workerListener.js'); var PoolWorker = require('./libs/poolWorker.js'); var PaymentProcessor = require('./libs/paymentProcessor.js'); var Website = require('./libs/website.js'); @@ -139,6 +138,7 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ return portalConfig.clustering.forks; })(); + var poolWorkers = {}; var createPoolWorker = function(forkId){ var worker = cluster.fork({ @@ -147,11 +147,24 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ pools: serializedConfigs, portalConfig: JSON.stringify(portalConfig) }); + worker.forkId = forkId; + worker.type = 'pool'; + poolWorkers[forkId] = worker; worker.on('exit', function(code, signal){ logger.error('Master', 'PoolSpanwer', 'Fork ' + forkId + ' died, spawning replacement worker...'); setTimeout(function(){ createPoolWorker(forkId); }, 2000); + }).on('message', function(msg){ + switch(msg.type){ + case 'banIP': + Object.keys(cluster.workers).forEach(function(id) { + if (cluster.workers[id].type === 'pool'){ + cluster.workers[id].send({type: 'banIP', ip: msg.ip}); + } + }); + break; + } }); }; @@ -168,12 +181,6 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ }; -var startWorkerListener = function(poolConfigs){ - var workerListener = new WorkerListener(logger, poolConfigs); - workerListener.init(); -}; - - var startBlockListener = function(portalConfig){ //block notify options //setup block notify here and use IPC to tell appropriate pools @@ -301,8 +308,6 @@ var startWebsite = function(portalConfig, poolConfigs){ startRedisBlockListener(portalConfig); - startWorkerListener(poolConfigs); - startWebsite(portalConfig, poolConfigs); })(); diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 26b30a2..80bc0d8 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -41,7 +41,7 @@ module.exports = function(logger, poolConfig){ connection.query( 'SELECT password FROM pool_worker WHERE username = LOWER(?)', - [workerName], + [workerName.toLowerCase()], function(err, result){ if (err){ logger.error(logIdentify, logComponent, 'Database error when authenticating worker: ' + diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 71da314..c8b6680 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -22,6 +22,13 @@ module.exports = function(logger){ process.on('message', function(message) { switch(message.type){ + case 'banIP': + for (var p in pools){ + if (pools[p].stratumServer) + pools[p].stratumServer.addBannedIP(message.ip); + } + break; + case 'blocknotify': var messageCoin = message.coin.toLowerCase(); @@ -75,7 +82,7 @@ module.exports = function(logger){ ); proxySwitch[algo].currentPool = newCoin; - var redisClient = redis.createClient(6379, "localhost") + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) redisClient.on('ready', function(){ redisClient.hset('proxyState', algo, newCoin, function(error, obj) { if (error) { @@ -186,6 +193,8 @@ module.exports = function(logger){ handlers.diff(workerName, diff); }).on('log', function(severity, text) { logger[severity](logSystem, logComponent, logSubCat, text); + }).on('banIP', function(ip, worker){ + process.send({type: 'banIP', ip: ip}); }); pool.start(); @@ -206,7 +215,7 @@ module.exports = function(logger){ // on the last pool it was using when reloaded or restarted // logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis'); - var redisClient = redis.createClient(6379, "localhost") + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) redisClient.on('ready', function(){ redisClient.hgetall("proxyState", function(error, obj) { if (error || obj == null) { diff --git a/libs/stats.js b/libs/stats.js index 62b7f00..0893506 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -59,7 +59,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ function setupStatsRedis(){ - redisStats = redis.createClient(portalConfig.website.stats.redis.port, portalConfig.website.stats.redis.host); + redisStats = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); redisStats.on('error', function(err){ logger.error(logSystem, 'Historics', 'Redis for stats had an error ' + JSON.stringify(err)); }); diff --git a/libs/workerListener.js b/libs/workerListener.js deleted file mode 100644 index 6bb669b..0000000 --- a/libs/workerListener.js +++ /dev/null @@ -1,26 +0,0 @@ -var events = require('events'); -var cluster = require('cluster'); - -var MposCompatibility = require('./mposCompatibility.js'); -var ShareProcessor = require('./shareProcessor.js'); - - -var processor = module.exports = function processor(logger, poolConfigs){ - - var _this = this; - - - this.init = function(){ - - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].on('message', function(data){ - switch(data.type){ - - } - }); - }); - } -}; - - -processor.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index a753c31..82b243e 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -10,6 +10,8 @@ "emitInvalidBlockHashes": false, "shareVariancePercent": 15, + "tcpProxyProtocol": false, + "shareProcessing": { "internal": { "enabled": true, @@ -22,19 +24,19 @@ "feeReceiveAddress": "mppaGeNaSbG1Q7S6V3gL5uJztMhucgL9Vh", "feeWithdrawalThreshold": 5, "daemon": { - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" }, "redis": { - "host": "localhost", + "host": "127.0.0.1", "port": 6379 } }, "mpos": { "enabled": false, - "host": "localhost", + "host": "127.0.0.1", "port": 3306, "user": "me", "password": "mypass", @@ -72,13 +74,13 @@ "daemons": [ { - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" }, { - "host": "localhost", + "host": "127.0.0.1", "port": 19344, "user": "litecoinrpc", "password": "testnet" @@ -87,7 +89,7 @@ "p2p": { "enabled": false, - "host": "localhost", + "host": "127.0.0.1", "port": 19333, "protocolVersion": 70002, "magic": "fcc1b7dc" diff --git a/scripts/blockNotify.js b/scripts/blockNotify.js index 67ed235..42a0f8c 100644 --- a/scripts/blockNotify.js +++ b/scripts/blockNotify.js @@ -1,6 +1,6 @@ /* This script should be hooked to the coin daemon as follow: - litecoind -blocknotify="node /path/to/this/script/blockNotify.js localhost:8117 password litecoin %s" + litecoind -blocknotify="node /path/to/this/script/blockNotify.js 127.0.0.1:8117 password litecoin %s" The above will send tell litecoin to launch this script with those parameters every time a block is found. This script will then send the blockhash along with other information to a listening tcp socket */ diff --git a/scripts/coinSwitch.js b/scripts/coinSwitch.js index 18dc1c8..acf6ac1 100644 --- a/scripts/coinSwitch.js +++ b/scripts/coinSwitch.js @@ -2,7 +2,7 @@ This script demonstrates sending a coin switch request and can be invoked from the command line with: - "node coinSwitch.js localhost:8118 password %s" + "node coinSwitch.js 127.0.0.1:8118 password %s" where <%s> is the name of the coin proxy miners will be switched onto. From 1d848c7d5199686fb0fa5862a833b45235d0e7d0 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 15 Apr 2014 16:56:35 -0600 Subject: [PATCH 016/150] Made readme for banning more clear --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 351dd14..a4166b8 100644 --- a/README.md +++ b/README.md @@ -405,9 +405,9 @@ Description of options: }, /* If a worker is submitting a high threshold of invalid shares we can temporarily ban their IP - to reduce system/network load. Also useful to fight against flooding attacks. The worker's - If running behind something like HAProxy be sure to enable the TCP Proxy Protocol config, - otherwise you'll end up banning your own IP address (and therefore all workers). */ + to reduce system/network load. Also useful to fight against flooding attacks. If running + behind something like HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up + banning your own IP address (and therefore all workers). */ "banning": { "enabled": true, "time": 600, //How many seconds to ban worker for From 1b2c2f9e7d72712d5002048765e85a75dce2dd2c Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 15 Apr 2014 18:09:26 -0600 Subject: [PATCH 017/150] Made readme for banning more clear --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a4166b8..80fdbea 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ supports vardiff, POW & POS, transaction messages, anti-DDoS, IP banning, [sever * The portal has an [MPOS](//github.com/MPOS/php-mpos) compatibility mode so that the it can function as a drop-in-replacement for [python-stratum-mining](//github.com/Crypto-Expert/stratum-mining). This mode can be enabled in the configuration and will insert shares into a MySQL database in the format which MPOS expects. +For a direct tutorial see the wiki page [Setting up NOMP for MPOS usage](//github.com/zone117x/node-open-mining-portal/wiki/Setting-up-NOMP-for-MPOS-usage). * Multi-pool ability - this software was built from the ground up to run with multiple coins simultaneously (which can have different properties and hashing algorithms). It can be used to create a pool for a single coin or for multiple From 71e3883b4504283235f82a06ea71ce67e6daac0b Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 16 Apr 2014 06:43:35 -0600 Subject: [PATCH 018/150] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 80fdbea..fff3008 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ Usage * [Node.js](http://nodejs.org/) v0.10+ ([follow these installation instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager)) * [Redis](http://redis.io/) key-value store v2.6+ ([follow these instructions](http://redis.io/topics/quickstart)) +##### Seriously, those are real requirements. If you use old versions of Node.js or Redis that may come with your system package manager then you will have problems. Follow the linked instructions to get the last stable versions. #### 0) Setting up coin daemon Follow the build/install instructions for your coin daemon. Your coin.conf file should end up looking something like this: From b17f782ecc32ca2010a77574400fcd4eb47f5fd2 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 16 Apr 2014 06:44:57 -0600 Subject: [PATCH 019/150] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fff3008..8d32ae8 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,8 @@ Usage * [Node.js](http://nodejs.org/) v0.10+ ([follow these installation instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager)) * [Redis](http://redis.io/) key-value store v2.6+ ([follow these instructions](http://redis.io/topics/quickstart)) -##### Seriously, those are real requirements. If you use old versions of Node.js or Redis that may come with your system package manager then you will have problems. Follow the linked instructions to get the last stable versions. +##### Seriously +Those are legitimate requirements. If you use old versions of Node.js or Redis that may come with your system package manager then you will have problems. Follow the linked instructions to get the last stable versions. #### 0) Setting up coin daemon Follow the build/install instructions for your coin daemon. Your coin.conf file should end up looking something like this: From db9767f08e2c34e75770bf2a18f2f4bcb2cc2213 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 16 Apr 2014 11:50:33 -0600 Subject: [PATCH 020/150] Added documentation for p2p usage --- README.md | 27 ++++++++++++++++++--------- libs/poolWorker.js | 2 +- pool_configs/litecoin_example.json | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 80fdbea..91e627f 100644 --- a/README.md +++ b/README.md @@ -457,23 +457,30 @@ Description of options: ], - /* This allows the pool to connect to the daemon as a node peer to recieve 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 - intensive than blocknotify script). However its still under development (not yet working). */ + intensive than blocknotify script). It requires additional setup: the 'magic' field must + be exact (extracted from the coin source code). */ "p2p": { "enabled": false, + + /* Host for daemon */ "host": "127.0.0.1", + + /* Port configured for daemon (this is the actual peer port not RPC port) */ "port": 19333, + + /* 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 transaction data. + Assume its supported but if you have problems try disabling it. */ + "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", - - //Found in src as the PROTOCOL_VERSION variable, for example: http://git.io/KjuCrw - "protocolVersion": 70002, + And for litecoin testnet magic: http://git.io/NXBYJA */ + "magic": "fcc1b7dc" } } @@ -494,7 +501,7 @@ node [path to scripts/blockNotify.js] [listener host]:[listener port] [listener ``` Example: inside `dogecoin.conf` add the line ``` -blocknotify="node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s" +blocknotify=node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s ``` Alternatively, you can use a more efficient block notify script written in pure C. Build and usage instructions @@ -541,11 +548,13 @@ Credits ------- * [Jerry Brady / mintyfresh68](https://github.com/bluecircle) - got coin-switching fully working and developed proxy-per-algo feature * [Tony Dobbs](http://anthonydobbs.com) - designs for front-end and created the NOMP logo +* [LucasJones(//github.com/LucasJones) - getting p2p block notify script working * [vekexasia](//github.com/vekexasia) - co-developer & great tester * [TheSeven](//github.com/TheSeven) - answering an absurd amount of my questions and being a very helpful gentleman * [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing * [Alex Petrov / sysmanalex](https://github.com/sysmanalex) - contributed the pure C block notify script * [svirusxxx](//github.com/svirusxxx) - sponsored development of MPOS mode +* [icecube45](//github.com/icecube45) - helping out with the repo wiki * Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool#credits) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index c8b6680..69d130f 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -37,7 +37,7 @@ module.exports = function(logger){ })[0]; if (poolTarget) - pools[poolTarget].processBlockNotify(message.hash); + pools[poolTarget].processBlockNotify(message.hash, 'blocknotify script'); break; diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index 82b243e..7f75c58 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -91,7 +91,7 @@ "enabled": false, "host": "127.0.0.1", "port": 19333, - "protocolVersion": 70002, + "disableTransactions": true, "magic": "fcc1b7dc" } } From d4cac4a10563f640cadb602d6301fc8f56075334 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 16 Apr 2014 11:56:08 -0600 Subject: [PATCH 021/150] Updated readme overscrolling text --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c5edcfc..8be38b8 100644 --- a/README.md +++ b/README.md @@ -472,10 +472,9 @@ Description of options: /* Port configured for daemon (this is the actual peer port not RPC port) */ "port": 19333, - - /* 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 transaction data. - Assume its supported but if you have problems try disabling it. */ + /* 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 + transaction data. Assume its supported but if you have problems try disabling it. */ "disableTransactions": true, /* Magic value is different for main/testnet and for each coin. It is found in the daemon From e5d25cf11c20b545302efee667260f3cba48d7ff Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 16 Apr 2014 16:28:09 -0600 Subject: [PATCH 022/150] Added some keccak coins as its now officially supported. --- coins/copperlark.json | 7 +++++++ coins/cryptometh.json | 5 +++++ coins/ecoin.json | 7 +++++++ 3 files changed, 19 insertions(+) create mode 100644 coins/copperlark.json create mode 100644 coins/cryptometh.json create mode 100644 coins/ecoin.json diff --git a/coins/copperlark.json b/coins/copperlark.json new file mode 100644 index 0000000..7f07f12 --- /dev/null +++ b/coins/copperlark.json @@ -0,0 +1,7 @@ +{ + "name": "Copperlark", + "symbol": "CLR", + "algorithm": "keccak", + "normalHashing": true, + "diffShift": 32 +} \ No newline at end of file diff --git a/coins/cryptometh.json b/coins/cryptometh.json new file mode 100644 index 0000000..6015361 --- /dev/null +++ b/coins/cryptometh.json @@ -0,0 +1,5 @@ +{ + "name": "Cryptometh", + "symbol": "METH", + "algorithm": "keccak" +} \ No newline at end of file diff --git a/coins/ecoin.json b/coins/ecoin.json new file mode 100644 index 0000000..9703edf --- /dev/null +++ b/coins/ecoin.json @@ -0,0 +1,7 @@ +{ + "name": "Ecoin", + "symbol": "ECN", + "algorithm": "keccak", + "normalHashing": true, + "diffShift": 32 +} \ No newline at end of file From 384a10d82548da9f16df456c212436bdc3914e92 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 01:52:57 +0000 Subject: [PATCH 023/150] checkpoint --- libs/apiCryptsy.js | 204 +++++++++++++++++++++++++++++++++++++++++++++ libs/apiMintpal.js | 204 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 408 insertions(+) create mode 100644 libs/apiCryptsy.js create mode 100644 libs/apiMintpal.js diff --git a/libs/apiCryptsy.js b/libs/apiCryptsy.js new file mode 100644 index 0000000..6563ab5 --- /dev/null +++ b/libs/apiCryptsy.js @@ -0,0 +1,204 @@ +var request = require('request'); +var nonce = require('nonce'); + +module.exports = function() { + 'use strict'; + + // Module dependencies + + // Constants + var version = '0.1.0', + PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', + PRIVATE_API_URL = 'https://api.cryptsy.com/api', + USER_AGENT = 'nomp/node-open-mining-portal' + + // Constructor + function Cryptsy(key, secret){ + // Generate headers signed by this user's key and secret. + // The secret is encapsulated and never exposed + this._getPrivateHeaders = function(parameters){ + var paramString, signature; + + if (!key || !secret){ + throw 'Cryptsy: Error. API key and secret required'; + } + + // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` + paramString = Object.keys(parameters).sort().map(function(param){ + return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); + }).join('&'); + + signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); + + return { + Key: key, + Sign: signature + }; + }; + } + + // If a site uses non-trusted SSL certificates, set this value to false + Cryptsy.STRICT_SSL = true; + + // Helper methods + function joinCurrencies(currencyA, currencyB){ + return currencyA + '_' + currencyB; + } + + // Prototype + Cryptsy.prototype = { + constructor: Cryptsy, + + // Make an API request + _request: function(options, callback){ + if (!('headers' in options)){ + options.headers = {}; + } + + options.headers['User-Agent'] = USER_AGENT; + options.json = true; + options.strictSSL = Cryptsy.STRICT_SSL; + + request(options, function(err, response, body) { + callback(err, body); + }); + + return this; + }, + + // Make a public API request + _public: function(parameters, callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL, + qs: parameters + }; + + return this._request(options, callback); + }, + + // Make a private API request + _private: function(parameters, callback){ + var options; + + parameters.nonce = nonce(); + options = { + method: 'POST', + url: PRIVATE_API_URL, + form: parameters, + headers: this._getPrivateHeaders(parameters) + }; + + return this._request(options, callback); + }, + + + ///// + + + // PUBLIC METHODS + + getTicker: function(callback){ + var parameters = { + method: 'marketdatav2' + }; + + return this._public(parameters, callback); + }, + + getOrderBook: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOrderBook', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + getTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + + ///// + + + // PRIVATE METHODS + + myBalances: function(callback){ + var parameters = { + command: 'returnBalances' + }; + + return this._private(parameters, callback); + }, + + myOpenOrders: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOpenOrders', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + myTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + buy: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'buy', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + sell: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'sell', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + cancelOrder: function(currencyA, currencyB, orderNumber, callback){ + var parameters = { + command: 'cancelOrder', + currencyPair: joinCurrencies(currencyA, currencyB), + orderNumber: orderNumber + }; + + return this._private(parameters, callback); + }, + + withdraw: function(currency, amount, address, callback){ + var parameters = { + command: 'withdraw', + currency: currency, + amount: amount, + address: address + }; + + return this._private(parameters, callback); + } + }; + + return Cryptsy; +}(); diff --git a/libs/apiMintpal.js b/libs/apiMintpal.js new file mode 100644 index 0000000..6563ab5 --- /dev/null +++ b/libs/apiMintpal.js @@ -0,0 +1,204 @@ +var request = require('request'); +var nonce = require('nonce'); + +module.exports = function() { + 'use strict'; + + // Module dependencies + + // Constants + var version = '0.1.0', + PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', + PRIVATE_API_URL = 'https://api.cryptsy.com/api', + USER_AGENT = 'nomp/node-open-mining-portal' + + // Constructor + function Cryptsy(key, secret){ + // Generate headers signed by this user's key and secret. + // The secret is encapsulated and never exposed + this._getPrivateHeaders = function(parameters){ + var paramString, signature; + + if (!key || !secret){ + throw 'Cryptsy: Error. API key and secret required'; + } + + // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` + paramString = Object.keys(parameters).sort().map(function(param){ + return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); + }).join('&'); + + signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); + + return { + Key: key, + Sign: signature + }; + }; + } + + // If a site uses non-trusted SSL certificates, set this value to false + Cryptsy.STRICT_SSL = true; + + // Helper methods + function joinCurrencies(currencyA, currencyB){ + return currencyA + '_' + currencyB; + } + + // Prototype + Cryptsy.prototype = { + constructor: Cryptsy, + + // Make an API request + _request: function(options, callback){ + if (!('headers' in options)){ + options.headers = {}; + } + + options.headers['User-Agent'] = USER_AGENT; + options.json = true; + options.strictSSL = Cryptsy.STRICT_SSL; + + request(options, function(err, response, body) { + callback(err, body); + }); + + return this; + }, + + // Make a public API request + _public: function(parameters, callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL, + qs: parameters + }; + + return this._request(options, callback); + }, + + // Make a private API request + _private: function(parameters, callback){ + var options; + + parameters.nonce = nonce(); + options = { + method: 'POST', + url: PRIVATE_API_URL, + form: parameters, + headers: this._getPrivateHeaders(parameters) + }; + + return this._request(options, callback); + }, + + + ///// + + + // PUBLIC METHODS + + getTicker: function(callback){ + var parameters = { + method: 'marketdatav2' + }; + + return this._public(parameters, callback); + }, + + getOrderBook: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOrderBook', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + getTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + + ///// + + + // PRIVATE METHODS + + myBalances: function(callback){ + var parameters = { + command: 'returnBalances' + }; + + return this._private(parameters, callback); + }, + + myOpenOrders: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOpenOrders', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + myTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + buy: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'buy', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + sell: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'sell', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + cancelOrder: function(currencyA, currencyB, orderNumber, callback){ + var parameters = { + command: 'cancelOrder', + currencyPair: joinCurrencies(currencyA, currencyB), + orderNumber: orderNumber + }; + + return this._private(parameters, callback); + }, + + withdraw: function(currency, amount, address, callback){ + var parameters = { + command: 'withdraw', + currency: currency, + amount: amount, + address: address + }; + + return this._private(parameters, callback); + } + }; + + return Cryptsy; +}(); From 9ad11516d2aef63e69db39fb54f5b70a8c0118ea Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 01:53:40 +0000 Subject: [PATCH 024/150] checkpoint --- libs/apiPoloniex.js | 2 +- libs/poolWorker.js | 38 +++---- libs/profitSwitch.js | 262 ++++++++++++++++++++++++++----------------- 3 files changed, 180 insertions(+), 122 deletions(-) diff --git a/libs/apiPoloniex.js b/libs/apiPoloniex.js index 35f330c..fc483e1 100644 --- a/libs/apiPoloniex.js +++ b/libs/apiPoloniex.js @@ -8,7 +8,7 @@ module.exports = function() { // Constants var version = '0.1.0', - PUBLIC_API_URL = 'http://poloniex.com/public', + PUBLIC_API_URL = 'https://poloniex.com/public', PRIVATE_API_URL = 'https://poloniex.com/tradingApi', USER_AGENT = 'npm-crypto-apis/' + version diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 69d130f..0035eeb 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -63,10 +63,10 @@ module.exports = function(logger){ var oldPool = pools[oldCoin]; var proxyPort = proxySwitch[algo].port; - if (newCoin == oldCoin) { + if (newCoin == oldCoin) { logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin); - break; - } + break; + } logger.debug(logSystem, logComponent, logSubCat, 'Proxy message for ' + algo + ' from ' + oldCoin + ' to ' + newCoin); @@ -91,8 +91,8 @@ module.exports = function(logger){ else { logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo); } - }); - }); + }); + }); } break; } @@ -230,9 +230,9 @@ module.exports = function(logger){ // Setup proxySwitch object to control proxy operations from configuration and any restored // state. Each algorithm has a listening port, current coin name, and an active pool to // which traffic is directed when activated in the config. - // - // In addition, the proxy config also takes diff and varDiff parmeters the override the - // defaults for the standard config of the coin. + // + // In addition, the proxy config also takes diff and varDiff parmeters the override the + // defaults for the standard config of the coin. // Object.keys(portalConfig.proxy).forEach(function(algorithm) { @@ -245,21 +245,21 @@ module.exports = function(logger){ }; - // Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up - // - // Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was - // routed into instead. - // - if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { + // Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up + // + // Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was + // routed into instead. + // + if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff); proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff; - } + } Object.keys(pools).forEach(function (coinName) { var a = poolConfigs[coinName].coin.algorithm; var p = pools[coinName]; - if (a === algorithm) { + if (a === algorithm) { p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff); - } + } }); proxySwitch[algorithm].proxy = net.createServer(function(socket) { @@ -267,12 +267,12 @@ module.exports = function(logger){ var logSubCat = 'Thread ' + (parseInt(forkId) + 1); logger.debug(logSystem, 'Connect', logSubCat, 'Proxy connect from ' + socket.remoteAddress + ' on ' + proxySwitch[algorithm].port - + ' routing to ' + currentPool); + + ' routing to ' + currentPool); pools[currentPool].getStratumServer().handleNewClient(socket); }).listen(parseInt(proxySwitch[algorithm].port), function() { logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port - + ' into ' + proxySwitch[algorithm].currentPool); + + ' into ' + proxySwitch[algorithm].currentPool); }); } else { diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 7d57fd7..3d06d77 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -1,5 +1,6 @@ var async = require('async'); +var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); var Stratum = require('stratum-pool'); @@ -16,7 +17,7 @@ module.exports = function(logger){ // build status tracker for collecting coin market information // var profitStatus = {}; - var profitSymbols = {}; + var symbolToAlgorithmMap = {}; Object.keys(poolConfigs).forEach(function(coin){ var poolConfig = poolConfigs[coin]; @@ -30,12 +31,10 @@ module.exports = function(logger){ symbol: poolConfig.coin.symbol, difficulty: 0, reward: 0, - prices: {}, - depths: {}, - volumes: {}, + exchangeInfo: {} }; profitStatus[algo][poolConfig.coin.symbol] = coinStatus; - profitSymbols[poolConfig.coin.symbol] = algo; + symbolToAlgorithmMap[poolConfig.coin.symbol] = algo; }); @@ -52,8 +51,6 @@ module.exports = function(logger){ logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); return; } - logger.debug(logSystem, 'profitStatus', JSON.stringify(profitStatus)); - logger.debug(logSystem, 'profitStatus', JSON.stringify(profitSymbols)); // @@ -63,6 +60,10 @@ module.exports = function(logger){ // 'API_KEY', // 'API_SECRET' ); + var cryptsyApi = new Cryptsy( + // 'API_KEY', + // 'API_SECRET' + ); // // market data collection from Poloniex @@ -75,51 +76,34 @@ module.exports = function(logger){ taskCallback(err); return; } - Object.keys(profitSymbols).forEach(function(symbol){ - var btcPrice = new Number(0); - var ltcPrice = new Number(0); + + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; + if (!exchangeInfo.hasOwnProperty('Poloniex')) + exchangeInfo['Poloniex'] = {}; + var marketData = exchangeInfo['Poloniex']; if (data.hasOwnProperty('BTC_' + symbol)) { - btcPrice = new Number(data['BTC_' + symbol]); + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; + + var btcData = data['BTC_' + symbol]; + marketData['BTC'].ask = new Number(btcData.lowestAsk); + marketData['BTC'].bid = new Number(btcData.highestBid); + marketData['BTC'].last = new Number(btcData.last); + marketData['BTC'].baseVolume = new Number(btcData.baseVolume); + marketData['BTC'].quoteVolume = new Number(btcData.quoteVolume); } if (data.hasOwnProperty('LTC_' + symbol)) { - ltcPrice = new Number(data['LTC_' + symbol]); - } + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; - if (btcPrice > 0 || ltcPrice > 0) { - var prices = { - BTC: btcPrice, - LTC: ltcPrice - }; - profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices; - } - }); - taskCallback(); - }); - }, - function(taskCallback){ - poloApi.get24hVolume(function(err, data){ - if (err){ - taskCallback(err); - return; - } - Object.keys(profitSymbols).forEach(function(symbol){ - var btcVolume = new Number(0); - var ltcVolume = new Number(0); - - if (data.hasOwnProperty('BTC_' + symbol)) { - btcVolume = new Number(data['BTC_' + symbol].BTC); - } - if (data.hasOwnProperty('LTC_' + symbol)) { - ltcVolume = new Number(data['LTC_' + symbol].LTC); - } - - if (btcVolume > 0 || ltcVolume > 0) { - var volumes = { - BTC: btcVolume, - LTC: ltcVolume - }; - profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes; + var ltcData = data['LTC_' + symbol]; + marketData['LTC'].ask = new Number(ltcData.lowestAsk); + marketData['LTC'].bid = new Number(ltcData.highestBid); + marketData['LTC'].last = new Number(ltcData.last); + marketData['LTC'].baseVolume = new Number(ltcData.baseVolume); + marketData['LTC'].quoteVolume = new Number(ltcData.quoteVolume); } }); taskCallback(); @@ -127,34 +111,25 @@ module.exports = function(logger){ }, function(taskCallback){ var depthTasks = []; - Object.keys(profitSymbols).forEach(function(symbol){ - var coinVolumes = profitStatus[profitSymbols[symbol]][symbol].volumes; - var coinPrices = profitStatus[profitSymbols[symbol]][symbol].prices; - - if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){ - var btcDepth = new Number(0); - var ltcDepth = new Number(0); - - if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){ - var coinPrice = new Number(coinPrices['Poloniex']['BTC']); - depthTasks.push(function(callback){ - _this.getMarketDepthFromPoloniex('BTC', symbol, coinPrice, callback) - }); - } - if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){ - var coinPrice = new Number(coinPrices['Poloniex']['LTC']); - depthTasks.push(function(callback){ - _this.getMarketDepthFromPoloniex('LTC', symbol, coinPrice, callback) - }); - } + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Poloniex']; + if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromPoloniex('BTC', symbol, marketData['BTC'].bid, callback) + }); + } + if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromPoloniex('LTC', symbol, marketData['LTC'].bid, callback) + }); } }); - if (depthTasks.length == 0){ - taskCallback; + if (!depthTasks.length){ + taskCallback(); return; } - async.parallel(depthTasks, function(err){ + async.series(depthTasks, function(err){ if (err){ taskCallback(err); return; @@ -181,37 +156,110 @@ module.exports = function(logger){ if (data.hasOwnProperty('bids')){ data['bids'].forEach(function(order){ var price = new Number(order[0]); + var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); var qty = new Number(order[1]); // only measure the depth down to configured depth - if (price >= coinPrice * portalConfig.profitSwitch.depth){ + if (price >= limit){ depth += (qty * price); } }); } - if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){ - profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = { - BTC: 0, - LTC: 0 - }; - } - profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth; + var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Poloniex']; + marketData[symbolA].depth = depth; callback(); }); }; - // TODO + this.getProfitDataCryptsy = function(callback){ - callback(null); + async.series([ + function(taskCallback){ + cryptsyApi.getTicker(function(err, data){ + if (err || data.success != 1){ + taskCallback(err); + return; + } + + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; + if (!exchangeInfo.hasOwnProperty('Cryptsy')) + exchangeInfo['Cryptsy'] = {}; + + var marketData = exchangeInfo['Cryptsy']; + var results = data.return.markets; + + if (results.hasOwnProperty(symbol + '/BTC')) { + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; + + var btcData = results[symbol + '/BTC']; + marketData['BTC'].last = new Number(btcData.lasttradeprice); + marketData['BTC'].baseVolume = new Number(marketData['BTC'].last / btcData.volume); + marketData['BTC'].quoteVolume = new Number(btcData.volume); + if (btcData.sellorders != null) + marketData['BTC'].ask = new Number(btcData.sellorders[0].price); + if (btcData.buyorders != null) { + marketData['BTC'].bid = new Number(btcData.buyorders[0].price); + var limit = new Number(marketData['BTC'].bid * portalConfig.profitSwitch.depth); + var depth = new Number(0); + btcData['buyorders'].forEach(function(order){ + var price = new Number(order.price); + var qty = new Number(order.quantity); + if (price >= limit){ + depth += (qty * price); + } + }); + marketData['BTC'].depth = depth; + } + } + + if (data.hasOwnProperty(symbol + '/LTC')) { + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; + + var ltcData = results[symbol + '/LTC']; + marketData['LTC'].last = new Number(ltcData.lasttradeprice); + marketData['LTC'].baseVolume = new Number(marketData['LTC'].last / ltcData.volume); + marketData['LTC'].quoteVolume = new Number(ltcData.volume); + if (ltcData.sellorders != null) + marketData['LTC'].ask = new Number(ltcData.sellorders[0].price); + if (ltcData.buyorders != null) { + marketData['LTC'].bid = new Number(ltcData.buyorders[0].price); + var limit = new Number(marketData['LTC'].bid * portalConfig.profitSwitch.depth); + var depth = new Number(0); + ltcData['buyorders'].forEach(function(order){ + var price = new Number(order.price); + var qty = new Number(order.quantity); + if (price >= limit){ + depth += (qty * price); + } + }); + marketData['LTC'].depth = depth; + } + } + }); + taskCallback(); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + }; + this.getCoindDaemonInfo = function(callback){ var daemonTasks = []; Object.keys(profitStatus).forEach(function(algo){ Object.keys(profitStatus[algo]).forEach(function(symbol){ var coinName = profitStatus[algo][symbol].name; var poolConfig = poolConfigs[coinName]; - var daemonConfig = poolConfig.shareProcessing.internal.daemon; + var daemonConfig = poolConfig.shareProcessing.internal.daemon; daemonTasks.push(function(callback){ _this.getDaemonInfoForCoin(symbol, daemonConfig, callback) }); @@ -222,7 +270,7 @@ module.exports = function(logger){ callback(); return; } - async.parallel(daemonTasks, function(err){ + async.series(daemonTasks, function(err){ if (err){ callback(err); return; @@ -232,26 +280,36 @@ module.exports = function(logger){ }; this.getDaemonInfoForCoin = function(symbol, cfg, callback){ var daemon = new Stratum.daemon.interface([cfg]); - daemon.once('online', function(){ - daemon.cmd('getdifficulty', null, function(result){ - if (result[0].error != null){ - callback(result[0].error); - return; - } - profitStatus[profitSymbols[symbol]][symbol].difficulty = result[0].response; - - daemon.cmd('getblocktemplate', - [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], - function(result){ - if (result[0].error != null){ - callback(result[0].error); - return; - } - profitStatus[profitSymbols[symbol]][symbol].reward = new Number(result[0].response.coinbasevalue / 100000000); - }); - callback(null) - }); - }).once('connectionFailed', function(error){ + daemon.once('online', function(){ + async.parallel([ + function(taskCallback){ + daemon.cmd('getdifficulty', null, function(result){ + if (result[0].error != null){ + taskCallback(result[0].error); + return; + } + profitStatus[symbolToAlgorithmMap[symbol]][symbol].difficulty = result[0].response; + taskCallback(null); + }); + }, + function(taskCallback){ + daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ + if (result[0].error != null){ + taskCallback(result[0].error); + return; + } + profitStatus[symbolToAlgorithmMap[symbol]][symbol].reward = new Number(result[0].response.coinbasevalue / 100000000); + taskCallback(null); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + }).once('connectionFailed', function(error){ callback(error); }).on('error', function(error){ callback(error); From 603c6d150b745d1e3f8ff48a8eada54ac8a47c43 Mon Sep 17 00:00:00 2001 From: Matt Culpepper Date: Wed, 16 Apr 2014 21:17:55 -0500 Subject: [PATCH 025/150] lots of new coin configs --- coins/defcoin.json | 5 +++++ coins/jennycoin.json | 5 +++++ coins/klondikecoin.json | 5 +++++ coins/muniti.json | 6 ++++++ coins/potcoin.json | 5 +++++ coins/procoin.json | 5 +++++ coins/ronpaulcoin.json | 5 +++++ coins/rubycoin.json | 5 +++++ coins/spartancoin.json | 5 +++++ coins/stoopidcoin.json | 5 +++++ coins/suncoin.json | 5 +++++ coins/whitecoin.json | 5 +++++ 12 files changed, 61 insertions(+) create mode 100644 coins/defcoin.json create mode 100644 coins/jennycoin.json create mode 100644 coins/klondikecoin.json create mode 100644 coins/muniti.json create mode 100644 coins/potcoin.json create mode 100644 coins/procoin.json create mode 100644 coins/ronpaulcoin.json create mode 100644 coins/rubycoin.json create mode 100644 coins/spartancoin.json create mode 100644 coins/stoopidcoin.json create mode 100644 coins/suncoin.json create mode 100644 coins/whitecoin.json diff --git a/coins/defcoin.json b/coins/defcoin.json new file mode 100644 index 0000000..fecc90a --- /dev/null +++ b/coins/defcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Defcoin", + "symbol": "DEF", + "algorithm": "scrypt" +} diff --git a/coins/jennycoin.json b/coins/jennycoin.json new file mode 100644 index 0000000..41aae54 --- /dev/null +++ b/coins/jennycoin.json @@ -0,0 +1,5 @@ +{ + "name": "Jennycoin", + "symbol": "JNY", + "algorithm": "scrypt" +} diff --git a/coins/klondikecoin.json b/coins/klondikecoin.json new file mode 100644 index 0000000..0d0aa3a --- /dev/null +++ b/coins/klondikecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Klondikecoin", + "symbol": "KDC", + "algorithm": "scrypt" +} diff --git a/coins/muniti.json b/coins/muniti.json new file mode 100644 index 0000000..ba5e430 --- /dev/null +++ b/coins/muniti.json @@ -0,0 +1,6 @@ +{ + "name": "Muniti", + "symbol": "MUN", + "algorithm": "x11", + "mposDiffMultiplier": 256 +} diff --git a/coins/potcoin.json b/coins/potcoin.json new file mode 100644 index 0000000..785e511 --- /dev/null +++ b/coins/potcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Potcoin", + "symbol": "POT", + "algorithm": "scrypt" +} diff --git a/coins/procoin.json b/coins/procoin.json new file mode 100644 index 0000000..54cbfee --- /dev/null +++ b/coins/procoin.json @@ -0,0 +1,5 @@ +{ + "name": "Procoin", + "symbol": "PCN", + "algorithm": "scrypt" +} diff --git a/coins/ronpaulcoin.json b/coins/ronpaulcoin.json new file mode 100644 index 0000000..3122c5e --- /dev/null +++ b/coins/ronpaulcoin.json @@ -0,0 +1,5 @@ +{ + "name": "RonPaulCoin", + "symbol": "RPC", + "algorithm": "scrypt" +} diff --git a/coins/rubycoin.json b/coins/rubycoin.json new file mode 100644 index 0000000..d8de44b --- /dev/null +++ b/coins/rubycoin.json @@ -0,0 +1,5 @@ +{ + "name": "Rubycoin", + "symbol": "RUBY", + "algorithm": "scrypt" +} diff --git a/coins/spartancoin.json b/coins/spartancoin.json new file mode 100644 index 0000000..65c21ca --- /dev/null +++ b/coins/spartancoin.json @@ -0,0 +1,5 @@ +{ + "name": "Spartancoin", + "symbol": "SPN", + "algorithm": "scrypt" +} diff --git a/coins/stoopidcoin.json b/coins/stoopidcoin.json new file mode 100644 index 0000000..ce8d420 --- /dev/null +++ b/coins/stoopidcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Stoopidcoin", + "symbol": "STP", + "algorithm": "scrypt" +} diff --git a/coins/suncoin.json b/coins/suncoin.json new file mode 100644 index 0000000..fea2aed --- /dev/null +++ b/coins/suncoin.json @@ -0,0 +1,5 @@ +{ + "name": "Suncoin", + "symbol": "SUN", + "algorithm": "scrypt" +} diff --git a/coins/whitecoin.json b/coins/whitecoin.json new file mode 100644 index 0000000..fe4326d --- /dev/null +++ b/coins/whitecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Whitecoin", + "symbol": "WC", + "algorithm": "scrypt" +} From 4469cf546c9643f3e0c3eb314d31416cd225f075 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 02:42:38 +0000 Subject: [PATCH 026/150] profit data working --- config_example.json | 9 ++++-- libs/apiMintpal.js | 28 +++++++++--------- libs/profitSwitch.js | 67 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/config_example.json b/config_example.json index 5a47449..d28b0aa 100644 --- a/config_example.json +++ b/config_example.json @@ -70,9 +70,12 @@ }, "profitSwitch": { - "enabled": false, - "updateInterval": 60, - "depth": 0.80 + "enabled": false, + "updateInterval": 600, + "depth": 0.90, + "usePoloniex": true, + "useCryptsy": true, + "useMintpal": true }, "redisBlockNotifyListener": { diff --git a/libs/apiMintpal.js b/libs/apiMintpal.js index 6563ab5..bd0610c 100644 --- a/libs/apiMintpal.js +++ b/libs/apiMintpal.js @@ -8,19 +8,19 @@ module.exports = function() { // Constants var version = '0.1.0', - PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', - PRIVATE_API_URL = 'https://api.cryptsy.com/api', + PUBLIC_API_URL = 'https://api.mintpal.com/v2/market', + PRIVATE_API_URL = 'https://api.mintpal.com/v2/market', USER_AGENT = 'nomp/node-open-mining-portal' // Constructor - function Cryptsy(key, secret){ + function Mintpal(key, secret){ // Generate headers signed by this user's key and secret. // The secret is encapsulated and never exposed this._getPrivateHeaders = function(parameters){ var paramString, signature; if (!key || !secret){ - throw 'Cryptsy: Error. API key and secret required'; + throw 'Mintpal: Error. API key and secret required'; } // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` @@ -38,7 +38,7 @@ module.exports = function() { } // If a site uses non-trusted SSL certificates, set this value to false - Cryptsy.STRICT_SSL = true; + Mintpal.STRICT_SSL = true; // Helper methods function joinCurrencies(currencyA, currencyB){ @@ -46,8 +46,8 @@ module.exports = function() { } // Prototype - Cryptsy.prototype = { - constructor: Cryptsy, + Mintpal.prototype = { + constructor: Mintpal, // Make an API request _request: function(options, callback){ @@ -57,7 +57,7 @@ module.exports = function() { options.headers['User-Agent'] = USER_AGENT; options.json = true; - options.strictSSL = Cryptsy.STRICT_SSL; + options.strictSSL = Mintpal.STRICT_SSL; request(options, function(err, response, body) { callback(err, body); @@ -99,11 +99,13 @@ module.exports = function() { // PUBLIC METHODS getTicker: function(callback){ - var parameters = { - method: 'marketdatav2' - }; + var options = { + method: 'GET', + url: PUBLIC_API_URL + '/summary', + qs: null + }; - return this._public(parameters, callback); + return this._request(options, callback); }, getOrderBook: function(currencyA, currencyB, callback){ @@ -200,5 +202,5 @@ module.exports = function() { } }; - return Cryptsy; + return Mintpal; }(); diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 3d06d77..d8dac3d 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -2,6 +2,7 @@ var async = require('async'); var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); +var Mintpal = require('./apiMintpal.js'); var Stratum = require('stratum-pool'); module.exports = function(logger){ @@ -64,6 +65,10 @@ module.exports = function(logger){ // 'API_KEY', // 'API_SECRET' ); + var mintpalApi = new Mintpal( + // 'API_KEY', + // 'API_SECRET' + ); // // market data collection from Poloniex @@ -253,6 +258,50 @@ module.exports = function(logger){ }; + this.getProfitDataMintpal = function(callback){ + async.series([ + function(taskCallback){ + mintpalApi.getTicker(function(err, response){ + if (err){ + taskCallback(err); + return; + } + + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + response.data.forEach(function(market){ + var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; + if (!exchangeInfo.hasOwnProperty('Mintpal')) + exchangeInfo['Mintpal'] = {}; + + var marketData = exchangeInfo['Mintpal']; + + if (market.exchange == 'BTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; + + marketData['BTC'].last = new Number(market.last_price); + marketData['BTC'].baseVolume = new Number(market['24hvol']); + marketData['BTC'].quoteVolume = new Number(market['24hvol'] / market.last_price); + marketData['BTC'].ask = new Number(market.top_ask); + marketData['BTC'].bid = new Number(market.top_bid); + } + + }); + }); + taskCallback(); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + + }; + + this.getCoindDaemonInfo = function(callback){ var daemonTasks = []; Object.keys(profitStatus).forEach(function(algo){ @@ -320,11 +369,19 @@ module.exports = function(logger){ var checkProfitability = function(){ logger.debug(logSystem, 'Check', 'Running mining profitability check.'); - async.parallel([ - _this.getProfitDataPoloniex, - _this.getProfitDataCryptsy, - _this.getCoindDaemonInfo - ], function(err){ + profitabilityTasks = []; + if (portalConfig.profitSwitch.usePoloniex) + profitabilityTasks.push(_this.getProfitDataPoloniex); + + if (portalConfig.profitSwitch.useCryptsy) + profitabilityTasks.push(_this.getProfitDataCryptsy); + + if (portalConfig.profitSwitch.useMintpal) + profitabilityTasks.push(_this.getProfitDataMintpal); + + profitabilityTasks.push(_this.getCoindDaemonInfo); + + async.parallel(profitabilityTasks, function(err){ if (err){ logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); return; From 2e77108b3c47206f0d952e4dd7b75490836c4d2c Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 03:29:37 +0000 Subject: [PATCH 027/150] final mintpal changes --- libs/apiMintpal.js | 10 +++++++ libs/profitSwitch.js | 66 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/libs/apiMintpal.js b/libs/apiMintpal.js index bd0610c..fec22e6 100644 --- a/libs/apiMintpal.js +++ b/libs/apiMintpal.js @@ -108,6 +108,16 @@ module.exports = function() { return this._request(options, callback); }, + getBuyOrderBook: function(currencyA, currencyB, callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY', + qs: null + }; + + return this._request(options, callback); + }, + getOrderBook: function(currencyA, currencyB, callback){ var parameters = { command: 'returnOrderBook', diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index d8dac3d..a17f640 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -286,10 +286,49 @@ module.exports = function(logger){ marketData['BTC'].bid = new Number(market.top_bid); } + if (market.exchange == 'LTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; + + marketData['LTC'].last = new Number(market.last_price); + marketData['LTC'].baseVolume = new Number(market['24hvol']); + marketData['LTC'].quoteVolume = new Number(market['24hvol'] / market.last_price); + marketData['LTC'].ask = new Number(market.top_ask); + marketData['LTC'].bid = new Number(market.top_bid); + } + }); }); taskCallback(); }); + }, + function(taskCallback){ + var depthTasks = []; + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Mintpal']; + if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromMintpal('BTC', symbol, marketData['BTC'].bid, callback) + }); + } + if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromMintpal('LTC', symbol, marketData['LTC'].bid, callback) + }); + } + }); + + if (!depthTasks.length){ + taskCallback(); + return; + } + async.series(depthTasks, function(err){ + if (err){ + taskCallback(err); + return; + } + taskCallback(); + }); } ], function(err){ if (err){ @@ -298,7 +337,31 @@ module.exports = function(logger){ } callback(null); }); - + }; + this.getMarketDepthFromMintpal = function(symbolA, symbolB, coinPrice, callback){ + mintpalApi.getBuyOrderBook(symbolA, symbolB, function(err, response){ + if (err){ + callback(err); + return; + } + var depth = new Number(0); + if (response.hasOwnProperty('data')){ + response['data'].forEach(function(order){ + var price = new Number(order.price); + var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); + var qty = new Number(order.amount); + // only measure the depth down to configured depth + if (price >= limit){ + depth += (qty * price); + } + }); + } + + + var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Mintpal']; + marketData[symbolA].depth = depth; + callback(); + }); }; @@ -386,7 +449,6 @@ module.exports = function(logger){ logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); return; } - logger.debug(logSystem, 'Check', JSON.stringify(profitStatus)); }); }; setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000); From 57be035ff29519b4d21336bc111c8645a41f8618 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 17 Apr 2014 12:51:44 -0600 Subject: [PATCH 028/150] Include actual share diff in log ouput --- libs/poolWorker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 69d130f..cb77079 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -180,7 +180,7 @@ module.exports = function(logger){ logger.debug(logSystem, logComponent, logSubCat, 'Block found: ' + data.blockHash); if (isValidShare) - logger.debug(logSystem, logComponent, logSubCat, 'Share accepted at diff ' + data.difficulty + ' by ' + data.worker + ' [' + data.ip + ']' ); + logger.debug(logSystem, logComponent, logSubCat, 'Share accepted at diff ' + data.difficulty + '/' + data.shareDiff + ' by ' + data.worker + ' [' + data.ip + ']' ); else if (!isValidShare) logger.debug(logSystem, logComponent, logSubCat, 'Share rejected: ' + shareData); From ba30133bb2529d54405e09949193a28ebae7b8b1 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 22:55:54 +0000 Subject: [PATCH 029/150] final changes --- libs/profitSwitch.js | 113 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index a17f640..8074e33 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -1,4 +1,5 @@ var async = require('async'); +var net = require('net'); var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); @@ -42,13 +43,16 @@ module.exports = function(logger){ // // ensure we have something to switch // - var isMoreThanOneCoin = false; Object.keys(profitStatus).forEach(function(algo){ - if (Object.keys(profitStatus[algo]).length > 1) { - isMoreThanOneCoin = true; + if (Object.keys(profitStatus[algo]).length <= 1) { + delete profitStatus[algo]; + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + if (symbolToAlgorithmMap[symbol] === algo) + delete symbolToAlgorithmMap[symbol]; + }); } }); - if (!isMoreThanOneCoin){ + if (Object.keys(profitStatus).length == 0){ logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.'); return; } @@ -110,7 +114,13 @@ module.exports = function(logger){ marketData['LTC'].baseVolume = new Number(ltcData.baseVolume); marketData['LTC'].quoteVolume = new Number(ltcData.quoteVolume); } + // save LTC to BTC exchange rate + if (marketData.hasOwnProperty('LTC') && data.hasOwnProperty('BTC_LTC')) { + var btcLtc = data['BTC_LTC']; + marketData['LTC'].ltcToBtc = new Number(btcLtc.highestBid); + } }); + taskCallback(); }); }, @@ -158,6 +168,7 @@ module.exports = function(logger){ return; } var depth = new Number(0); + var totalQty = new Number(0); if (data.hasOwnProperty('bids')){ data['bids'].forEach(function(order){ var price = new Number(order[0]); @@ -166,12 +177,15 @@ module.exports = function(logger){ // only measure the depth down to configured depth if (price >= limit){ depth += (qty * price); + totalQty += qty; } }); } var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Poloniex']; marketData[symbolA].depth = depth; + if (totalQty > 0) + marketData[symbolA].weightedBid = new Number(depth / totalQty); callback(); }); }; @@ -194,7 +208,7 @@ module.exports = function(logger){ var marketData = exchangeInfo['Cryptsy']; var results = data.return.markets; - if (results.hasOwnProperty(symbol + '/BTC')) { + if (results && results.hasOwnProperty(symbol + '/BTC')) { if (!marketData.hasOwnProperty('BTC')) marketData['BTC'] = {}; @@ -208,18 +222,22 @@ module.exports = function(logger){ marketData['BTC'].bid = new Number(btcData.buyorders[0].price); var limit = new Number(marketData['BTC'].bid * portalConfig.profitSwitch.depth); var depth = new Number(0); + var totalQty = new Number(0); btcData['buyorders'].forEach(function(order){ var price = new Number(order.price); var qty = new Number(order.quantity); if (price >= limit){ depth += (qty * price); + totalQty += qty; } }); marketData['BTC'].depth = depth; + if (totalQty > 0) + marketData['BTC'].weightedBid = new Number(depth / totalQty); } } - if (data.hasOwnProperty(symbol + '/LTC')) { + if (results && results.hasOwnProperty(symbol + '/LTC')) { if (!marketData.hasOwnProperty('LTC')) marketData['LTC'] = {}; @@ -233,14 +251,18 @@ module.exports = function(logger){ marketData['LTC'].bid = new Number(ltcData.buyorders[0].price); var limit = new Number(marketData['LTC'].bid * portalConfig.profitSwitch.depth); var depth = new Number(0); + var totalQty = new Number(0); ltcData['buyorders'].forEach(function(order){ var price = new Number(order.price); var qty = new Number(order.quantity); if (price >= limit){ depth += (qty * price); + totalQty += qty; } }); marketData['LTC'].depth = depth; + if (totalQty > 0) + marketData['LTC'].weightedBid = new Number(depth / totalQty); } } }); @@ -262,7 +284,7 @@ module.exports = function(logger){ async.series([ function(taskCallback){ mintpalApi.getTicker(function(err, response){ - if (err){ + if (err || !response.data){ taskCallback(err); return; } @@ -346,6 +368,7 @@ module.exports = function(logger){ } var depth = new Number(0); if (response.hasOwnProperty('data')){ + var totalQty = new Number(0); response['data'].forEach(function(order){ var price = new Number(order.price); var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); @@ -353,13 +376,15 @@ module.exports = function(logger){ // only measure the depth down to configured depth if (price >= limit){ depth += (qty * price); + totalQty += qty; } }); } - var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Mintpal']; marketData[symbolA].depth = depth; + if (totalQty > 0) + marketData[symbolA].weightedBid = new Number(depth / totalQty); callback(); }); }; @@ -429,8 +454,70 @@ module.exports = function(logger){ }; + this.getMiningRate = function(callback){ + var daemonTasks = []; + Object.keys(profitStatus).forEach(function(algo){ + Object.keys(profitStatus[algo]).forEach(function(symbol){ + var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; + coinStatus.blocksPerMhPerHour = new Number(3600 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); + coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); + }); + }); + callback(null); + }; + + + this.switchToMostProfitableCoins = function() { + Object.keys(profitStatus).forEach(function(algo) { + var algoStatus = profitStatus[algo]; + + var bestExchange; + var bestCoin; + var bestBtcPerMhPerHour = new Number(0); + + Object.keys(profitStatus[algo]).forEach(function(symbol) { + var coinStatus = profitStatus[algo][symbol]; + + Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){ + var exchangeData = coinStatus.exchangeInfo[exchange]; + if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){ + var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour); + if (btcPerMhPerHour > bestBtcPerMhPerHour){ + bestBtcPerMhPerHour = btcPerMhPerHour; + bestExchange = exchange; + bestCoin = profitStatus[algo][symbol].name; + } + coinStatus.btcPerMhPerHour = btcPerMhPerHour; + logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); + } + if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ + var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); + logger.debug(logSystem, 'LTC Check', 'btcPerMhPerHour = ' + btcPerMhPerHour); + if (btcPerMhPerHour > bestBtcPerMhPerHour){ + bestBtcPerMhPerHour = btcPerMhPerHour; + bestExchange = exchange; + bestCoin = profitStatus[algo][symbol].name; + } + coinStatus.btcPerMhPerHour = btcPerMhPerHour; + logger.debug(logSystem, 'CALC', 'LTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); + } + }); + }); + logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); + if (portalConfig.coinSwitchListener.enabled){ + var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () { + client.write(JSON.stringify({ + password: portalConfig.coinSwitchListener.password, + coin: bestCoin + }) + '\n'); + }); + } + }); + }; + + var checkProfitability = function(){ - logger.debug(logSystem, 'Check', 'Running mining profitability check.'); + logger.debug(logSystem, 'Check', 'Collecting profitability data.'); profitabilityTasks = []; if (portalConfig.profitSwitch.usePoloniex) @@ -443,12 +530,18 @@ module.exports = function(logger){ profitabilityTasks.push(_this.getProfitDataMintpal); profitabilityTasks.push(_this.getCoindDaemonInfo); + profitabilityTasks.push(_this.getMiningRate); - async.parallel(profitabilityTasks, function(err){ + // has to be series + async.series(profitabilityTasks, function(err){ if (err){ logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); return; } + // + // TODO offer support for a userConfigurable function for deciding on coin to override the default + // + _this.switchToMostProfitableCoins(); }); }; setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000); From 60b73a43a8350978f096579ec4decd3bda7999f3 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 22:57:10 +0000 Subject: [PATCH 030/150] final changes --- config_example.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config_example.json b/config_example.json index d28b0aa..869b3db 100644 --- a/config_example.json +++ b/config_example.json @@ -34,6 +34,7 @@ "coinSwitchListener": { "enabled": false, + "host": "127.0.0.1", "port": 8118, "password": "test" }, From 1134f13a4051ab03239af39851d6ae951862eaa6 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Thu, 17 Apr 2014 23:09:53 +0000 Subject: [PATCH 031/150] remove extra logging --- libs/profitSwitch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 8074e33..c1a96bc 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -492,7 +492,6 @@ module.exports = function(logger){ } if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); - logger.debug(logSystem, 'LTC Check', 'btcPerMhPerHour = ' + btcPerMhPerHour); if (btcPerMhPerHour > bestBtcPerMhPerHour){ bestBtcPerMhPerHour = btcPerMhPerHour; bestExchange = exchange; From b7e8f67f701a644e88516eabf617696618c703eb Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Fri, 18 Apr 2014 12:48:59 +0000 Subject: [PATCH 032/150] removed tabs --- libs/profitSwitch.js | 328 +++++++++++++++++++++---------------------- 1 file changed, 164 insertions(+), 164 deletions(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index c1a96bc..1705daa 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -46,10 +46,10 @@ module.exports = function(logger){ Object.keys(profitStatus).forEach(function(algo){ if (Object.keys(profitStatus[algo]).length <= 1) { delete profitStatus[algo]; - Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ - if (symbolToAlgorithmMap[symbol] === algo) - delete symbolToAlgorithmMap[symbol]; - }); + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + if (symbolToAlgorithmMap[symbol] === algo) + delete symbolToAlgorithmMap[symbol]; + }); } }); if (Object.keys(profitStatus).length == 0){ @@ -88,15 +88,15 @@ module.exports = function(logger){ Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; - if (!exchangeInfo.hasOwnProperty('Poloniex')) - exchangeInfo['Poloniex'] = {}; - var marketData = exchangeInfo['Poloniex']; + if (!exchangeInfo.hasOwnProperty('Poloniex')) + exchangeInfo['Poloniex'] = {}; + var marketData = exchangeInfo['Poloniex']; if (data.hasOwnProperty('BTC_' + symbol)) { - if (!marketData.hasOwnProperty('BTC')) - marketData['BTC'] = {}; + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; - var btcData = data['BTC_' + symbol]; + var btcData = data['BTC_' + symbol]; marketData['BTC'].ask = new Number(btcData.lowestAsk); marketData['BTC'].bid = new Number(btcData.highestBid); marketData['BTC'].last = new Number(btcData.last); @@ -104,21 +104,21 @@ module.exports = function(logger){ marketData['BTC'].quoteVolume = new Number(btcData.quoteVolume); } if (data.hasOwnProperty('LTC_' + symbol)) { - if (!marketData.hasOwnProperty('LTC')) - marketData['LTC'] = {}; + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; - var ltcData = data['LTC_' + symbol]; + var ltcData = data['LTC_' + symbol]; marketData['LTC'].ask = new Number(ltcData.lowestAsk); marketData['LTC'].bid = new Number(ltcData.highestBid); marketData['LTC'].last = new Number(ltcData.last); marketData['LTC'].baseVolume = new Number(ltcData.baseVolume); marketData['LTC'].quoteVolume = new Number(ltcData.quoteVolume); } - // save LTC to BTC exchange rate + // save LTC to BTC exchange rate if (marketData.hasOwnProperty('LTC') && data.hasOwnProperty('BTC_LTC')) { - var btcLtc = data['BTC_LTC']; + var btcLtc = data['BTC_LTC']; marketData['LTC'].ltcToBtc = new Number(btcLtc.highestBid); - } + } }); taskCallback(); @@ -130,13 +130,13 @@ module.exports = function(logger){ var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Poloniex']; if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){ depthTasks.push(function(callback){ - _this.getMarketDepthFromPoloniex('BTC', symbol, marketData['BTC'].bid, callback) - }); + _this.getMarketDepthFromPoloniex('BTC', symbol, marketData['BTC'].bid, callback) + }); } if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){ depthTasks.push(function(callback){ - _this.getMarketDepthFromPoloniex('LTC', symbol, marketData['LTC'].bid, callback) - }); + _this.getMarketDepthFromPoloniex('LTC', symbol, marketData['LTC'].bid, callback) + }); } }); @@ -172,19 +172,19 @@ module.exports = function(logger){ if (data.hasOwnProperty('bids')){ data['bids'].forEach(function(order){ var price = new Number(order[0]); - var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); + var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); var qty = new Number(order[1]); // only measure the depth down to configured depth if (price >= limit){ depth += (qty * price); - totalQty += qty; + totalQty += qty; } }); } var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Poloniex']; marketData[symbolA].depth = depth; - if (totalQty > 0) + if (totalQty > 0) marketData[symbolA].weightedBid = new Number(depth / totalQty); callback(); }); @@ -202,25 +202,25 @@ module.exports = function(logger){ Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; - if (!exchangeInfo.hasOwnProperty('Cryptsy')) - exchangeInfo['Cryptsy'] = {}; + if (!exchangeInfo.hasOwnProperty('Cryptsy')) + exchangeInfo['Cryptsy'] = {}; - var marketData = exchangeInfo['Cryptsy']; - var results = data.return.markets; + var marketData = exchangeInfo['Cryptsy']; + var results = data.return.markets; if (results && results.hasOwnProperty(symbol + '/BTC')) { - if (!marketData.hasOwnProperty('BTC')) - marketData['BTC'] = {}; + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; - var btcData = results[symbol + '/BTC']; + var btcData = results[symbol + '/BTC']; marketData['BTC'].last = new Number(btcData.lasttradeprice); marketData['BTC'].baseVolume = new Number(marketData['BTC'].last / btcData.volume); marketData['BTC'].quoteVolume = new Number(btcData.volume); - if (btcData.sellorders != null) + if (btcData.sellorders != null) marketData['BTC'].ask = new Number(btcData.sellorders[0].price); - if (btcData.buyorders != null) { + if (btcData.buyorders != null) { marketData['BTC'].bid = new Number(btcData.buyorders[0].price); - var limit = new Number(marketData['BTC'].bid * portalConfig.profitSwitch.depth); + var limit = new Number(marketData['BTC'].bid * portalConfig.profitSwitch.depth); var depth = new Number(0); var totalQty = new Number(0); btcData['buyorders'].forEach(function(order){ @@ -228,28 +228,28 @@ module.exports = function(logger){ var qty = new Number(order.quantity); if (price >= limit){ depth += (qty * price); - totalQty += qty; + totalQty += qty; } - }); + }); marketData['BTC'].depth = depth; - if (totalQty > 0) + if (totalQty > 0) marketData['BTC'].weightedBid = new Number(depth / totalQty); - } - } + } + } if (results && results.hasOwnProperty(symbol + '/LTC')) { - if (!marketData.hasOwnProperty('LTC')) - marketData['LTC'] = {}; + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; - var ltcData = results[symbol + '/LTC']; + var ltcData = results[symbol + '/LTC']; marketData['LTC'].last = new Number(ltcData.lasttradeprice); marketData['LTC'].baseVolume = new Number(marketData['LTC'].last / ltcData.volume); marketData['LTC'].quoteVolume = new Number(ltcData.volume); - if (ltcData.sellorders != null) + if (ltcData.sellorders != null) marketData['LTC'].ask = new Number(ltcData.sellorders[0].price); - if (ltcData.buyorders != null) { + if (ltcData.buyorders != null) { marketData['LTC'].bid = new Number(ltcData.buyorders[0].price); - var limit = new Number(marketData['LTC'].bid * portalConfig.profitSwitch.depth); + var limit = new Number(marketData['LTC'].bid * portalConfig.profitSwitch.depth); var depth = new Number(0); var totalQty = new Number(0); ltcData['buyorders'].forEach(function(order){ @@ -257,13 +257,13 @@ module.exports = function(logger){ var qty = new Number(order.quantity); if (price >= limit){ depth += (qty * price); - totalQty += qty; + totalQty += qty; } - }); + }); marketData['LTC'].depth = depth; - if (totalQty > 0) + if (totalQty > 0) marketData['LTC'].weightedBid = new Number(depth / totalQty); - } + } } }); taskCallback(); @@ -289,38 +289,38 @@ module.exports = function(logger){ return; } - Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ - response.data.forEach(function(market){ - var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; - if (!exchangeInfo.hasOwnProperty('Mintpal')) - exchangeInfo['Mintpal'] = {}; + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + response.data.forEach(function(market){ + var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; + if (!exchangeInfo.hasOwnProperty('Mintpal')) + exchangeInfo['Mintpal'] = {}; - var marketData = exchangeInfo['Mintpal']; + var marketData = exchangeInfo['Mintpal']; - if (market.exchange == 'BTC' && market.code == symbol) { - if (!marketData.hasOwnProperty('BTC')) - marketData['BTC'] = {}; + if (market.exchange == 'BTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; - marketData['BTC'].last = new Number(market.last_price); - marketData['BTC'].baseVolume = new Number(market['24hvol']); - marketData['BTC'].quoteVolume = new Number(market['24hvol'] / market.last_price); - marketData['BTC'].ask = new Number(market.top_ask); - marketData['BTC'].bid = new Number(market.top_bid); - } + marketData['BTC'].last = new Number(market.last_price); + marketData['BTC'].baseVolume = new Number(market['24hvol']); + marketData['BTC'].quoteVolume = new Number(market['24hvol'] / market.last_price); + marketData['BTC'].ask = new Number(market.top_ask); + marketData['BTC'].bid = new Number(market.top_bid); + } - if (market.exchange == 'LTC' && market.code == symbol) { - if (!marketData.hasOwnProperty('LTC')) - marketData['LTC'] = {}; + if (market.exchange == 'LTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; - marketData['LTC'].last = new Number(market.last_price); - marketData['LTC'].baseVolume = new Number(market['24hvol']); - marketData['LTC'].quoteVolume = new Number(market['24hvol'] / market.last_price); - marketData['LTC'].ask = new Number(market.top_ask); - marketData['LTC'].bid = new Number(market.top_bid); - } + marketData['LTC'].last = new Number(market.last_price); + marketData['LTC'].baseVolume = new Number(market['24hvol']); + marketData['LTC'].quoteVolume = new Number(market['24hvol'] / market.last_price); + marketData['LTC'].ask = new Number(market.top_ask); + marketData['LTC'].bid = new Number(market.top_bid); + } - }); - }); + }); + }); taskCallback(); }); }, @@ -330,13 +330,13 @@ module.exports = function(logger){ var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Mintpal']; if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){ depthTasks.push(function(callback){ - _this.getMarketDepthFromMintpal('BTC', symbol, marketData['BTC'].bid, callback) - }); + _this.getMarketDepthFromMintpal('BTC', symbol, marketData['BTC'].bid, callback) + }); } if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){ depthTasks.push(function(callback){ - _this.getMarketDepthFromMintpal('LTC', symbol, marketData['LTC'].bid, callback) - }); + _this.getMarketDepthFromMintpal('LTC', symbol, marketData['LTC'].bid, callback) + }); } }); @@ -371,19 +371,19 @@ module.exports = function(logger){ var totalQty = new Number(0); response['data'].forEach(function(order){ var price = new Number(order.price); - var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); + var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); var qty = new Number(order.amount); // only measure the depth down to configured depth if (price >= limit){ depth += (qty * price); - totalQty += qty; + totalQty += qty; } }); } var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Mintpal']; marketData[symbolA].depth = depth; - if (totalQty > 0) + if (totalQty > 0) marketData[symbolA].weightedBid = new Number(depth / totalQty); callback(); }); @@ -417,36 +417,36 @@ module.exports = function(logger){ }; this.getDaemonInfoForCoin = function(symbol, cfg, callback){ var daemon = new Stratum.daemon.interface([cfg]); - daemon.once('online', function(){ - async.parallel([ - function(taskCallback){ - daemon.cmd('getdifficulty', null, function(result){ - if (result[0].error != null){ - taskCallback(result[0].error); - return; - } - profitStatus[symbolToAlgorithmMap[symbol]][symbol].difficulty = result[0].response; - taskCallback(null); - }); - }, - function(taskCallback){ - daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ - if (result[0].error != null){ - taskCallback(result[0].error); - return; - } - profitStatus[symbolToAlgorithmMap[symbol]][symbol].reward = new Number(result[0].response.coinbasevalue / 100000000); - taskCallback(null); - }); - } - ], function(err){ - if (err){ - callback(err); - return; - } - callback(null); - }); - }).once('connectionFailed', function(error){ + daemon.once('online', function(){ + async.parallel([ + function(taskCallback){ + daemon.cmd('getdifficulty', null, function(result){ + if (result[0].error != null){ + taskCallback(result[0].error); + return; + } + profitStatus[symbolToAlgorithmMap[symbol]][symbol].difficulty = result[0].response; + taskCallback(null); + }); + }, + function(taskCallback){ + daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ + if (result[0].error != null){ + taskCallback(result[0].error); + return; + } + profitStatus[symbolToAlgorithmMap[symbol]][symbol].reward = new Number(result[0].response.coinbasevalue / 100000000); + taskCallback(null); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + }).once('connectionFailed', function(error){ callback(error); }).on('error', function(error){ callback(error); @@ -458,78 +458,78 @@ module.exports = function(logger){ var daemonTasks = []; Object.keys(profitStatus).forEach(function(algo){ Object.keys(profitStatus[algo]).forEach(function(symbol){ - var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; - coinStatus.blocksPerMhPerHour = new Number(3600 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); - coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); + var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; + coinStatus.blocksPerMhPerHour = new Number(3600 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); + coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); }); }); callback(null); }; - this.switchToMostProfitableCoins = function() { - Object.keys(profitStatus).forEach(function(algo) { - var algoStatus = profitStatus[algo]; + this.switchToMostProfitableCoins = function() { + Object.keys(profitStatus).forEach(function(algo) { + var algoStatus = profitStatus[algo]; var bestExchange; var bestCoin; var bestBtcPerMhPerHour = new Number(0); - Object.keys(profitStatus[algo]).forEach(function(symbol) { - var coinStatus = profitStatus[algo][symbol]; + Object.keys(profitStatus[algo]).forEach(function(symbol) { + var coinStatus = profitStatus[algo][symbol]; - Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){ - var exchangeData = coinStatus.exchangeInfo[exchange]; - if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){ - var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour); - if (btcPerMhPerHour > bestBtcPerMhPerHour){ - bestBtcPerMhPerHour = btcPerMhPerHour; - bestExchange = exchange; - bestCoin = profitStatus[algo][symbol].name; - } - coinStatus.btcPerMhPerHour = btcPerMhPerHour; + Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){ + var exchangeData = coinStatus.exchangeInfo[exchange]; + if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){ + var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour); + if (btcPerMhPerHour > bestBtcPerMhPerHour){ + bestBtcPerMhPerHour = btcPerMhPerHour; + bestExchange = exchange; + bestCoin = profitStatus[algo][symbol].name; + } + coinStatus.btcPerMhPerHour = btcPerMhPerHour; logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); - } - if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ - var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); - if (btcPerMhPerHour > bestBtcPerMhPerHour){ - bestBtcPerMhPerHour = btcPerMhPerHour; - bestExchange = exchange; - bestCoin = profitStatus[algo][symbol].name; - } - coinStatus.btcPerMhPerHour = btcPerMhPerHour; + } + if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ + var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); + if (btcPerMhPerHour > bestBtcPerMhPerHour){ + bestBtcPerMhPerHour = btcPerMhPerHour; + bestExchange = exchange; + bestCoin = profitStatus[algo][symbol].name; + } + coinStatus.btcPerMhPerHour = btcPerMhPerHour; logger.debug(logSystem, 'CALC', 'LTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); - } - }); - }); + } + }); + }); logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); - if (portalConfig.coinSwitchListener.enabled){ - var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () { - client.write(JSON.stringify({ - password: portalConfig.coinSwitchListener.password, - coin: bestCoin - }) + '\n'); - }); - } - }); - }; + if (portalConfig.coinSwitchListener.enabled){ + var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () { + client.write(JSON.stringify({ + password: portalConfig.coinSwitchListener.password, + coin: bestCoin + }) + '\n'); + }); + } + }); + }; var checkProfitability = function(){ logger.debug(logSystem, 'Check', 'Collecting profitability data.'); - profitabilityTasks = []; - if (portalConfig.profitSwitch.usePoloniex) - profitabilityTasks.push(_this.getProfitDataPoloniex); + profitabilityTasks = []; + if (portalConfig.profitSwitch.usePoloniex) + profitabilityTasks.push(_this.getProfitDataPoloniex); - if (portalConfig.profitSwitch.useCryptsy) - profitabilityTasks.push(_this.getProfitDataCryptsy); + if (portalConfig.profitSwitch.useCryptsy) + profitabilityTasks.push(_this.getProfitDataCryptsy); - if (portalConfig.profitSwitch.useMintpal) - profitabilityTasks.push(_this.getProfitDataMintpal); + if (portalConfig.profitSwitch.useMintpal) + profitabilityTasks.push(_this.getProfitDataMintpal); - profitabilityTasks.push(_this.getCoindDaemonInfo); - profitabilityTasks.push(_this.getMiningRate); + profitabilityTasks.push(_this.getCoindDaemonInfo); + profitabilityTasks.push(_this.getMiningRate); // has to be series async.series(profitabilityTasks, function(err){ @@ -537,10 +537,10 @@ module.exports = function(logger){ logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err); return; } - // - // TODO offer support for a userConfigurable function for deciding on coin to override the default - // - _this.switchToMostProfitableCoins(); + // + // TODO offer support for a userConfigurable function for deciding on coin to override the default + // + _this.switchToMostProfitableCoins(); }); }; setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000); From 167be4baf0753f80a0ec388b5a86710cf696212b Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 18 Apr 2014 13:44:19 -0600 Subject: [PATCH 033/150] Minor change in logging: does not log 'Share data and stats recorded' anymore --- libs/shareProcessor.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 815eb1a..a4e88c9 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -42,7 +42,6 @@ module.exports = function(logger, poolConfig){ this.handleShare = function(isValidShare, isValidBlock, shareData){ - var redisCommands = []; if (isValidShare){ @@ -71,8 +70,8 @@ module.exports = function(logger, poolConfig){ connection.multi(redisCommands).exec(function(err, replies){ if (err) logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err)); - else - logger.debug(logSystem, logComponent, logSubCat, 'Share data and stats recorded'); + //else + //logger.debug(logSystem, logComponent, logSubCat, 'Share data and stats recorded'); }); From f6610a914d62c8d318b3773e36bc36d27f4f78f4 Mon Sep 17 00:00:00 2001 From: icecube45 Date: Sat, 19 Apr 2014 12:32:47 -0700 Subject: [PATCH 034/150] Update quarkcoin.json --- coins/quarkcoin.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coins/quarkcoin.json b/coins/quarkcoin.json index 797e7cd..983f340 100644 --- a/coins/quarkcoin.json +++ b/coins/quarkcoin.json @@ -1,5 +1,6 @@ { "name": "Quarkcoin", "symbol": "QRK", - "algorithm": "quark" -} \ No newline at end of file + "algorithm": "quark", + "mposDiffMultiplier": 256 +} From 31f6091a149662580c80e0b511602277b0549551 Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Sat, 19 Apr 2014 23:08:52 +0000 Subject: [PATCH 035/150] diff computed from block info --- libs/profitSwitch.js | 64 ++++++++++++++++++++------------------------ package.json | 3 ++- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 1705daa..7c1f449 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -1,10 +1,13 @@ -var async = require('async'); -var net = require('net'); +var async = require('async'); +var net = require('net'); +var bignum = require('bignum'); +var algos = require('stratum-pool/lib/algoProperties.js'); +var util = require('stratum-pool/lib/util.js'); -var Cryptsy = require('./apiCryptsy.js'); +var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); -var Mintpal = require('./apiMintpal.js'); -var Stratum = require('stratum-pool'); +var Mintpal = require('./apiMintpal.js'); +var Stratum = require('stratum-pool'); module.exports = function(logger){ @@ -418,38 +421,29 @@ module.exports = function(logger){ this.getDaemonInfoForCoin = function(symbol, cfg, callback){ var daemon = new Stratum.daemon.interface([cfg]); daemon.once('online', function(){ - async.parallel([ - function(taskCallback){ - daemon.cmd('getdifficulty', null, function(result){ - if (result[0].error != null){ - taskCallback(result[0].error); - return; - } - profitStatus[symbolToAlgorithmMap[symbol]][symbol].difficulty = result[0].response; - taskCallback(null); - }); - }, - function(taskCallback){ - daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ - if (result[0].error != null){ - taskCallback(result[0].error); - return; - } - profitStatus[symbolToAlgorithmMap[symbol]][symbol].reward = new Number(result[0].response.coinbasevalue / 100000000); - taskCallback(null); - }); - } - ], function(err){ - if (err){ - callback(err); + daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ + if (result[0].error != null){ + logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0])); + callback(null); // fail gracefully for each coin return; } + var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; + var response = result[0].response; + + // some shitcoins dont provide target, only bits, so we need to deal with both + var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits); + coinStatus.difficulty = parseFloat((diff1.toNumber() / target.toNumber()).toFixed(9)); + logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty); + + coinStatus.reward = new Number(response.coinbasevalue / 100000000); callback(null); }); }).once('connectionFailed', function(error){ - callback(error); + logger.error(logSystem, symbol, JSON.stringify(error)); + callback(null); // fail gracefully for each coin }).on('error', function(error){ - callback(error); + logger.error(logSystem, symbol, JSON.stringify(error)); + callback(null); // fail gracefully for each coin }).init(); }; @@ -459,7 +453,7 @@ module.exports = function(logger){ Object.keys(profitStatus).forEach(function(algo){ Object.keys(profitStatus[algo]).forEach(function(symbol){ var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; - coinStatus.blocksPerMhPerHour = new Number(3600 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); + coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); }); }); @@ -488,7 +482,7 @@ module.exports = function(logger){ bestCoin = profitStatus[algo][symbol].name; } coinStatus.btcPerMhPerHour = btcPerMhPerHour; - logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); + logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); } if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); @@ -498,11 +492,11 @@ module.exports = function(logger){ bestCoin = profitStatus[algo][symbol].name; } coinStatus.btcPerMhPerHour = btcPerMhPerHour; - logger.debug(logSystem, 'CALC', 'LTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); + logger.debug(logSystem, 'CALC', 'LTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); } }); }); - logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/Mh/hr'); + logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); if (portalConfig.coinSwitchListener.enabled){ var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () { client.write(JSON.stringify({ diff --git a/package.json b/package.json index 2140f2c..dc3e7c1 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "colors": "*", "node-watch": "*", "request": "*", - "nonce": "*" + "nonce": "*", + "bignum": "*" }, "engines": { "node": ">=0.10" From e58689956dd17dd616da9f6926464c00005e1574 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 20 Apr 2014 12:04:41 -0600 Subject: [PATCH 036/150] Changed stats to use algo multiplier --- README.md | 4 ++-- libs/poolWorker.js | 6 +++--- libs/stats.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8be38b8..c2e9cb2 100644 --- a/README.md +++ b/README.md @@ -302,8 +302,8 @@ Description of options: job broadcast. */ "txRefreshInterval": 20000, - /* Some miner software is bugged and will consider the pool offline if it doesn't receive - anything for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast + /* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs + for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast in this many seconds unless we find a new job. Set to zero or remove to disable this. */ "jobRebroadcastTimeout": 55, diff --git a/libs/poolWorker.js b/libs/poolWorker.js index cb77079..adec236 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -117,7 +117,7 @@ module.exports = function(logger){ //Functions required for MPOS compatibility if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){ - var mposCompat = new MposCompatibility(logger, poolOptions) + var mposCompat = new MposCompatibility(logger, poolOptions); handlers.auth = function(workerName, password, authCallback){ mposCompat.handleAuth(workerName, password, authCallback); @@ -135,7 +135,7 @@ module.exports = function(logger){ //Functions required for internal payment processing else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){ - var shareProcessor = new ShareProcessor(logger, poolOptions) + var shareProcessor = new ShareProcessor(logger, poolOptions); handlers.auth = function(workerName, password, authCallback){ if (shareProcessing.internal.validateWorkerAddress !== true) @@ -215,7 +215,7 @@ module.exports = function(logger){ // on the last pool it was using when reloaded or restarted // logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis'); - var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); redisClient.on('ready', function(){ redisClient.hgetall("proxyState", function(error, obj) { if (error || obj == null) { diff --git a/libs/stats.js b/libs/stats.js index 0893506..ade763b 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -191,7 +191,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ else coinStats.workers[worker] = workerShares; }); - var shareMultiplier = algos[coinStats.algorithm].multiplier || 0; + var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier; var hashratePre = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; coinStats.hashrate = hashratePre | 0; coinStats.workerCount = Object.keys(coinStats.workers).length; From 59cfa8b1fe7fdc5bba53439f4d1396a9ae176a16 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 20 Apr 2014 12:10:19 -0600 Subject: [PATCH 037/150] Minor readme fix with anchor link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2e9cb2..d99804c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ responsive user-friendly front-end website featuring mining instructions, in-dep #### Table of Contents * [Features](#features) - * [Attack Mitigation](##attack-mitigation) + * [Attack Mitigation](#attack-mitigation) * [Security](#security) * [Planned Features](#planned-features) * [Community Support](#community--support) From a4a22784437b1dde397b9ddf95572e4a7ac954ec Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 20 Apr 2014 12:17:44 -0600 Subject: [PATCH 038/150] diff1 is a normal JS number now rather than a bignum --- libs/profitSwitch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 7c1f449..83491a0 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -432,7 +432,7 @@ module.exports = function(logger){ // some shitcoins dont provide target, only bits, so we need to deal with both var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits); - coinStatus.difficulty = parseFloat((diff1.toNumber() / target.toNumber()).toFixed(9)); + coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9)); logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty); coinStatus.reward = new Number(response.coinbasevalue / 100000000); From 9722c054f90b430ae0a090afbdde632a667a1821 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 20 Apr 2014 21:16:58 -0600 Subject: [PATCH 039/150] Added mintcoin --- coins/mintcoin.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/mintcoin.json diff --git a/coins/mintcoin.json b/coins/mintcoin.json new file mode 100644 index 0000000..fb93cfb --- /dev/null +++ b/coins/mintcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Mintcoin", + "symbol": "MINT", + "algorithm": "scrypt" +} \ No newline at end of file From 882f779e204dfdad836ab17c08ae549474d18ddf Mon Sep 17 00:00:00 2001 From: k7 Date: Mon, 21 Apr 2014 16:09:37 +0800 Subject: [PATCH 040/150] auto create mpos worker --- libs/mposCompatibility.js | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 659d290..94239b5 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -38,7 +38,7 @@ module.exports = function(logger, poolConfig){ connect(); this.handleAuth = function(workerName, password, authCallback){ - + connection.query( 'SELECT password FROM pool_worker WHERE username = LOWER(?)', [workerName.toLowerCase()], @@ -48,8 +48,39 @@ module.exports = function(logger, poolConfig){ JSON.stringify(err)); authCallback(false); } - else if (!result[0]) - authCallback(false); + else if (!result[0]){ + if(mposConfig.autoCreateWorker){ + var account = workerName.split('.')[0]; + connection.query( + 'SELECT username FROM accounts WHERE username = LOWER(?)', + [account.toLowerCase()], + function(err, result){ + if (err){ + logger.error(logIdentify, logComponent, 'Database error when authenticating account: ' + + JSON.stringify(err)); + authCallback(false); + }else if(!result[0]){ + authCallback(false); + }else{ + connection.query( + "INSERT INTO `pool_worker` (`id`, `account_id`, `username`, `password`, `difficulty`, `monitor`) VALUES (NULL, ?, ?, '123', '0', '0');", + [result[0].id,workerName.toLowerCase()], + function(err, result){ + if (err){ + logger.error(logIdentify, logComponent, 'Database error when insert worker: ' + + JSON.stringify(err)); + authCallback(false); + }else { + authCallback(true); + } + }) + } + } + ); + }else{ + authCallback(false); + } + } else if (mposConfig.stratumAuth === 'worker') authCallback(true); else if (result[0].password === password) From 75c091457b31b160fe541f5262271997b57ec51e Mon Sep 17 00:00:00 2001 From: k7 Date: Mon, 21 Apr 2014 23:58:50 +0800 Subject: [PATCH 041/150] fix auto create worker with right password --- libs/mposCompatibility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 94239b5..a82875d 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -63,8 +63,8 @@ module.exports = function(logger, poolConfig){ authCallback(false); }else{ connection.query( - "INSERT INTO `pool_worker` (`id`, `account_id`, `username`, `password`, `difficulty`, `monitor`) VALUES (NULL, ?, ?, '123', '0', '0');", - [result[0].id,workerName.toLowerCase()], + "INSERT INTO `pool_worker` (`id`, `account_id`, `username`, `password`, `difficulty`, `monitor`) VALUES (NULL, ?, ?, ?, '0', '0');", + [result[0].id,workerName.toLowerCase(),password], function(err, result){ if (err){ logger.error(logIdentify, logComponent, 'Database error when insert worker: ' + From 019865bc95061e2b51be527b2f0f974ed97aa7b2 Mon Sep 17 00:00:00 2001 From: k7 Date: Tue, 22 Apr 2014 00:19:15 +0800 Subject: [PATCH 042/150] fix auto create worker bug --- libs/mposCompatibility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index a82875d..d94c8cd 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -52,7 +52,7 @@ module.exports = function(logger, poolConfig){ if(mposConfig.autoCreateWorker){ var account = workerName.split('.')[0]; connection.query( - 'SELECT username FROM accounts WHERE username = LOWER(?)', + 'SELECT id,username FROM accounts WHERE username = LOWER(?)', [account.toLowerCase()], function(err, result){ if (err){ @@ -63,7 +63,7 @@ module.exports = function(logger, poolConfig){ authCallback(false); }else{ connection.query( - "INSERT INTO `pool_worker` (`id`, `account_id`, `username`, `password`, `difficulty`, `monitor`) VALUES (NULL, ?, ?, ?, '0', '0');", + "INSERT INTO `pool_worker` (`account_id`, `username`, `password`) VALUES (?, ?, ?);", [result[0].id,workerName.toLowerCase(),password], function(err, result){ if (err){ From 39d02b9af9a1b9f9c436e284c3cadb59bf041dcf Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 21 Apr 2014 12:27:34 -0600 Subject: [PATCH 043/150] Added autoCreateWorker config to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d99804c..9fcb589 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,10 @@ Description of options: "password": "mypass", //MySQL db password "database": "ltc", //MySQL db database name + /* Unregistered workers can automatically be registered (added to database) on stratum + worker authentication if this is true. */ + "autoCreateWorker": false, + /* For when miner's authenticate: set to "password" for both worker name and password to be checked for in the database, set to "worker" for only work name to be checked, or don't use this option (set to "none") for no auth checks */ From d7a26f67cdc6b9fa569dd0739f0ba00687aab78a Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 21 Apr 2014 12:40:19 -0600 Subject: [PATCH 044/150] minor readme update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9fcb589..3ba93c3 100644 --- a/README.md +++ b/README.md @@ -530,7 +530,8 @@ output from NOMP. #### Upgrading NOMP -When updating NOMP to the latest code its important to not only `git pull` the latest from this repo, but to also update the `node-statum-pool` module and any config files that may have been changed. +When updating NOMP to the latest code its important to not only `git pull` the latest from this repo, but to also update +the `node-statum-pool` and `node-multi-hashing` modules, and any config files that may have been changed. * Inside your NOMP directory (where the init.js script is) do `git pull` to get the latest NOMP code. * Remove the dependenices by deleting the `node_modules` directory with `rm -r node_modules`. * Run `npm update` to force updating/reinstalling of the dependencies. From 50a560d92f7523623c46c105620e162192f3365c Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 21 Apr 2014 12:44:35 -0600 Subject: [PATCH 045/150] readme markup fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ba93c3..acc53e2 100644 --- a/README.md +++ b/README.md @@ -554,7 +554,7 @@ Credits ------- * [Jerry Brady / mintyfresh68](https://github.com/bluecircle) - got coin-switching fully working and developed proxy-per-algo feature * [Tony Dobbs](http://anthonydobbs.com) - designs for front-end and created the NOMP logo -* [LucasJones(//github.com/LucasJones) - getting p2p block notify script working +* [LucasJones](//github.com/LucasJones) - got p2p block notify working and implemented additional hashing algos * [vekexasia](//github.com/vekexasia) - co-developer & great tester * [TheSeven](//github.com/TheSeven) - answering an absurd amount of my questions and being a very helpful gentleman * [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing From a279fb486f19bfe97553ec9ab195ae47fae527db Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 23 Apr 2014 12:53:19 -0600 Subject: [PATCH 046/150] Probably fixed negative hashrate issue --- README.md | 1 + init.js | 4 ++-- libs/poolWorker.js | 4 ++-- libs/stats.js | 5 +++-- package.json | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index acc53e2..0a94830 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ If your pool uses NOMP let us know and we will list your website here. * http://suchpool.pw * http://hashfaster.com * http://miningpoolhub.com +* http://teamdoge.com * http://kryptochaos.com * http://pool.uberpools.org diff --git a/init.js b/init.js index a7b15b6..6ff3e74 100644 --- a/init.js +++ b/init.js @@ -296,8 +296,8 @@ var startWebsite = function(portalConfig, poolConfigs){ var startProfitSwitch = function(portalConfig, poolConfigs){ - if (!portalConfig.profitSwitch.enabled){ - logger.error('Master', 'Profit', 'Profit auto switching disabled'); + if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){ + //logger.error('Master', 'Profit', 'Profit auto switching disabled'); return; } diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 982c710..b1a5d47 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -219,7 +219,7 @@ module.exports = function(logger){ redisClient.on('ready', function(){ redisClient.hgetall("proxyState", function(error, obj) { if (error || obj == null) { - logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); + //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); } else { proxyState = obj; @@ -276,7 +276,7 @@ module.exports = function(logger){ }); } else { - logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); + //logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); } }); }); diff --git a/libs/stats.js b/libs/stats.js index ade763b..1f3c3d6 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -191,9 +191,10 @@ module.exports = function(logger, portalConfig, poolConfigs){ else coinStats.workers[worker] = workerShares; }); + var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier; - var hashratePre = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; - coinStats.hashrate = hashratePre | 0; + coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; + coinStats.workerCount = Object.keys(coinStats.workers).length; portalStats.global.workers += coinStats.workerCount; diff --git a/package.json b/package.json index dc3e7c1..133d279 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-open-mining-portal", - "version": "0.0.3", + "version": "0.0.4", "description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool", "keywords": [ "stratum", From 432af28a78310ee13d6af2e20d2230492142b742 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 23 Apr 2014 23:59:53 -0600 Subject: [PATCH 047/150] Added reward address account detection in payment processing so addresses not under the default account of "" (empty string) will work. --- README.md | 1 + libs/paymentProcessor.js | 45 ++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0a94830..9595242 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ If your pool uses NOMP let us know and we will list your website here. * http://hashfaster.com * http://miningpoolhub.com * http://teamdoge.com +* http://miningwith.us * http://kryptochaos.com * http://pool.uberpools.org diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 3d3482a..77cf865 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -216,6 +216,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ return ['gettransaction', [r.txHash]]; }); + batchRPCcommand.push(['getaccount', [poolOptions.address]]); + daemon.batchCmd(batchRPCcommand, function(error, txDetails){ if (error || !txDetails){ @@ -224,7 +226,15 @@ function SetupForPool(logger, poolOptions, setupFinished){ return; } + var addressAccount; + txDetails.forEach(function(tx, i){ + + if (i === txDetails.length - 1){ + addressAccount = tx.result; + return; + } + var round = rounds[i]; if (tx.error && tx.error.code === -5 || round.blockHash !== tx.result.blockhash){ @@ -273,7 +283,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ rpc.gettransaction.amount tells us how much we get in whole coin units. Therefore, we simply divide the two to get the magnitude. I don't know math, there is probably a better term than 'magnitude'. Sue me or do a pull request to fix it. */ - var roundMagnitude = r.reward / r.amount; + var roundMagnitude = Math.round(r.reward / r.amount); if (!magnitude) { magnitude = roundMagnitude; @@ -303,7 +313,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ callback('Check finished - no confirmed or orphaned blocks found'); } else{ - callback(null, rounds, magnitude); + callback(null, rounds, magnitude, addressAccount); } }); }, @@ -311,7 +321,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ /* Does a batch redis call to get shares contributed to each round. Then calculates the reward amount owned to each miner for each round. */ - function(rounds, magnitude, callback){ + function(rounds, magnitude, addressAccount, callback){ var shareLookups = rounds.map(function(r){ @@ -370,13 +380,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands); + callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount); }); }, /* Does a batch call to redis to get worker existing balances from coin_balances*/ - function(rounds, magnitude, workerRewards, orphanMergeCommands, callback){ + function(rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount, callback){ var workers = Object.keys(workerRewards); @@ -394,7 +404,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ } - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances); + callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount); }); }, @@ -406,12 +416,11 @@ function SetupForPool(logger, poolOptions, setupFinished){ when deciding the sent balance, it the difference should be -1*amount they had in db, if not sending the balance, the differnce should be +(the amount they earned this round) */ - function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, callback){ + function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount, callback){ //number of satoshis in a single coin unit - this can be different for coins so we calculate it :) - - daemon.cmd('getbalance', [''], function(results){ + daemon.cmd('getbalance', [addressAccount || ''], function(results){ var totalBalance = results[0].response * magnitude; var toBePaid = 0; @@ -474,9 +483,9 @@ function SetupForPool(logger, poolOptions, setupFinished){ /* TODO: Need to convert all these variables into whole coin units before displaying because humans aren't good at reading satoshi units. */ callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' + - toBePaid + ' and collect ' + feeAmountToBeCollected + ' as fees' + - ' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver + - ', needs to be at least ' + minReserveSatoshis); + (toBePaid/magnitude) + ' and collect ' + (feeAmountToBeCollected/magnitude) + ' as fees' + + ' but only have ' + (totalBalance/magnitude) + '. Left over balance would be ' + (balanceLeftOver/magnitude) + + ', needs to be at least ' + (minReserveSatoshis/magnitude)); return; } @@ -522,23 +531,23 @@ function SetupForPool(logger, poolOptions, setupFinished){ finalRedisCommands.push(['bgsave']); - callback(null, magnitude, workerPayments, finalRedisCommands); + callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); }); }, - function(magnitude, workerPayments, finalRedisCommands, callback) { + function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback) { /* Save final redis cleanout commands in case something goes wrong during payments */ redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { if (error){ callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); return; } - callback(null, magnitude, workerPayments, finalRedisCommands); + callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); }); }, - function(magnitude, workerPayments, finalRedisCommands, callback){ + function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback){ //This does the final all-or-nothing atom transaction if block deamon sent payments var finalizeRedisTx = function(){ @@ -570,7 +579,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts)); processingPayments = true; - daemon.cmd('sendmany', ['', addressAmounts], function(results){ + daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function(results){ if (results[0].error){ callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error)); @@ -591,7 +600,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ } var feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision)); var poolFees = feeAmountUnits - results[0].response.fee; - daemon.cmd('move', ['', processingConfig.feeCollectAccount, poolFees], function(results){ + daemon.cmd('move', [addressAccount || '', processingConfig.feeCollectAccount, poolFees], function(results){ if (results[0].error){ callback('Check finished - error with move ' + JSON.stringify(results[0].error)); return; From 86fc01388f8ae332f18b6ad5464a1b173161c774 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 24 Apr 2014 00:02:38 -0600 Subject: [PATCH 048/150] Added crombie's site to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9595242..122c57f 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ If your pool uses NOMP let us know and we will list your website here. * http://miningwith.us * http://kryptochaos.com * http://pool.uberpools.org +* http://onebtcplace.com Usage From 38ececb9ab435a603920672626c9106730bba93d Mon Sep 17 00:00:00 2001 From: Ian Carroll Date: Thu, 24 Apr 2014 16:07:46 -0400 Subject: [PATCH 049/150] gotta represent --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 122c57f..ac8eec5 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ If your pool uses NOMP let us know and we will list your website here. * http://kryptochaos.com * http://pool.uberpools.org * http://onebtcplace.com - +* https://minr.es Usage ===== From f1627f222de7908e7c63bcca3e7b71e2b02c1a59 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 24 Apr 2014 14:53:06 -0600 Subject: [PATCH 050/150] Possible fix for payment processing with x11 coins --- libs/paymentProcessor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 77cf865..851f121 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -366,11 +366,11 @@ function SetupForPool(logger, poolOptions, setupFinished){ var reward = round.reward * (1 - processingConfig.feePercent); var totalShares = Object.keys(workerShares).reduce(function(p, c){ - return p + parseInt(workerShares[c]) + return p + parseFloat(workerShares[c]) }, 0); for (var worker in workerShares){ - var percent = parseInt(workerShares[worker]) / totalShares; + var percent = parseFloat(workerShares[worker]) / totalShares; var workerRewardTotal = Math.floor(reward * percent); if (!(worker in workerRewards)) workerRewards[worker] = 0; workerRewards[worker] += workerRewardTotal; From 444fc6c0700808c10f618e4a0e2329df2014869f Mon Sep 17 00:00:00 2001 From: Alex Petrov Date: Thu, 24 Apr 2014 19:19:14 -0400 Subject: [PATCH 051/150] Added new tab, Table statistics per coin/algo --- libs/website.js | 3 ++- website/index.html | 8 +++++- website/pages/api.html | 7 ++++++ website/pages/tbs.html | 56 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 website/pages/tbs.html diff --git a/libs/website.js b/libs/website.js index 44d9b55..6ac1962 100644 --- a/libs/website.js +++ b/libs/website.js @@ -31,6 +31,7 @@ module.exports = function(logger){ 'home.html': '', 'getting_started.html': 'getting_started', 'stats.html': 'stats', + 'tbs.html': 'tbs', 'api.html': 'api', 'admin.html': 'admin' }; @@ -181,4 +182,4 @@ module.exports = function(logger){ }); -}; \ No newline at end of file +}; diff --git a/website/index.html b/website/index.html index 0542569..03544ac 100644 --- a/website/index.html +++ b/website/index.html @@ -42,7 +42,13 @@
  •   - Stats + Graph Stats + +
  • +
  • + +   + Tab Stats
  • diff --git a/website/pages/api.html b/website/pages/api.html index 6eaea98..9cb343e 100644 --- a/website/pages/api.html +++ b/website/pages/api.html @@ -1,3 +1,10 @@
    API Docs here + +
      +
    • /stats - raw json statistic
    • +
    • /pool_stats - historical time per pool json
    • +
    • /live_stats - live stats
    • + +
    \ No newline at end of file diff --git a/website/pages/tbs.html b/website/pages/tbs.html new file mode 100644 index 0000000..ccc1b3b --- /dev/null +++ b/website/pages/tbs.html @@ -0,0 +1,56 @@ + + From 455da9b5d73e530da7af0a4421f631199787e53d Mon Sep 17 00:00:00 2001 From: Alex Petrov Date: Thu, 24 Apr 2014 19:44:36 -0400 Subject: [PATCH 052/150] New coins added asiccoin,battlecoin,benjamins,betacoin,devcoin,emark,fireflycoin,freicoin,ixcoin,joulecoin,mazacoin,opensourcecoin,takcoin,teacoin,tekcoin,tigercoin --- coins/asiccoin.json | 5 +++++ coins/battlecoin.json | 5 +++++ coins/benjamins.json | 5 +++++ coins/betacoin.json | 5 +++++ coins/devcoin.json | 5 +++++ coins/emark.json | 5 +++++ coins/fireflycoin.json | 5 +++++ coins/freicoin.json | 5 +++++ coins/ixcoin.json | 5 +++++ coins/joulecoin.json | 5 +++++ coins/mazacoin.json | 5 +++++ coins/opensourcecoin.json | 5 +++++ coins/takcoin.json | 5 +++++ coins/teacoin.json | 5 +++++ coins/tekcoin.json | 5 +++++ coins/tigercoin.json | 5 +++++ 16 files changed, 80 insertions(+) create mode 100644 coins/asiccoin.json create mode 100644 coins/battlecoin.json create mode 100644 coins/benjamins.json create mode 100644 coins/betacoin.json create mode 100644 coins/devcoin.json create mode 100644 coins/emark.json create mode 100644 coins/fireflycoin.json create mode 100644 coins/freicoin.json create mode 100644 coins/ixcoin.json create mode 100644 coins/joulecoin.json create mode 100644 coins/mazacoin.json create mode 100644 coins/opensourcecoin.json create mode 100644 coins/takcoin.json create mode 100644 coins/teacoin.json create mode 100644 coins/tekcoin.json create mode 100644 coins/tigercoin.json diff --git a/coins/asiccoin.json b/coins/asiccoin.json new file mode 100644 index 0000000..3c039b9 --- /dev/null +++ b/coins/asiccoin.json @@ -0,0 +1,5 @@ +{ + "name": "ASICcoin", + "symbol": "ASC", + "algorithm": "sha256" +} diff --git a/coins/battlecoin.json b/coins/battlecoin.json new file mode 100644 index 0000000..1e7fd9e --- /dev/null +++ b/coins/battlecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Battlecoin", + "symbol": "BCX", + "algorithm": "sha256" +} diff --git a/coins/benjamins.json b/coins/benjamins.json new file mode 100644 index 0000000..e73a1b6 --- /dev/null +++ b/coins/benjamins.json @@ -0,0 +1,5 @@ +{ + "name": "Benjamins", + "symbol": "BEN", + "algorithm": "sha256" +} diff --git a/coins/betacoin.json b/coins/betacoin.json new file mode 100644 index 0000000..b98efcd --- /dev/null +++ b/coins/betacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Betacoin", + "symbol": "BET", + "algorithm": "sha256" +} diff --git a/coins/devcoin.json b/coins/devcoin.json new file mode 100644 index 0000000..fcbf0f0 --- /dev/null +++ b/coins/devcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Devcoin", + "symbol": "DVC", + "algorithm": "sha256" +} diff --git a/coins/emark.json b/coins/emark.json new file mode 100644 index 0000000..d14cd3e --- /dev/null +++ b/coins/emark.json @@ -0,0 +1,5 @@ +{ + "name": "eMark", + "symbol": "DEM", + "algorithm": "sha256" +} diff --git a/coins/fireflycoin.json b/coins/fireflycoin.json new file mode 100644 index 0000000..98c3eb6 --- /dev/null +++ b/coins/fireflycoin.json @@ -0,0 +1,5 @@ +{ + "name": "Fireflycoin", + "symbol": "FFC", + "algorithm": "sha256" +} diff --git a/coins/freicoin.json b/coins/freicoin.json new file mode 100644 index 0000000..8bf60d9 --- /dev/null +++ b/coins/freicoin.json @@ -0,0 +1,5 @@ +{ + "name": "Freicoin", + "symbol": "FRC", + "algorithm": "sha256" +} diff --git a/coins/ixcoin.json b/coins/ixcoin.json new file mode 100644 index 0000000..80ad2eb --- /dev/null +++ b/coins/ixcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Ixcoin", + "symbol": "IXC", + "algorithm": "sha256" +} diff --git a/coins/joulecoin.json b/coins/joulecoin.json new file mode 100644 index 0000000..1500432 --- /dev/null +++ b/coins/joulecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Joulecoin", + "symbol": "XJO", + "algorithm": "sha256" +} diff --git a/coins/mazacoin.json b/coins/mazacoin.json new file mode 100644 index 0000000..451738c --- /dev/null +++ b/coins/mazacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Mazacoin", + "symbol": "MZC", + "algorithm": "sha256" +} diff --git a/coins/opensourcecoin.json b/coins/opensourcecoin.json new file mode 100644 index 0000000..2867ae8 --- /dev/null +++ b/coins/opensourcecoin.json @@ -0,0 +1,5 @@ +{ + "name": "OpenSourcecoin", + "symbol": "OSC", + "algorithm": "sha256"" +} diff --git a/coins/takcoin.json b/coins/takcoin.json new file mode 100644 index 0000000..d84507c --- /dev/null +++ b/coins/takcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Takcoin", + "symbol": "TAK", + "algorithm": "sha256" +} diff --git a/coins/teacoin.json b/coins/teacoin.json new file mode 100644 index 0000000..c6a0826 --- /dev/null +++ b/coins/teacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Teacoin", + "symbol": "TEA", + "algorithm": "sha256" +} diff --git a/coins/tekcoin.json b/coins/tekcoin.json new file mode 100644 index 0000000..44ba31b --- /dev/null +++ b/coins/tekcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Tekcoin", + "symbol": "TEK", + "algorithm": "sha256" +} diff --git a/coins/tigercoin.json b/coins/tigercoin.json new file mode 100644 index 0000000..6edd7e3 --- /dev/null +++ b/coins/tigercoin.json @@ -0,0 +1,5 @@ +{ + "name": "Tigercoin", + "symbol": "TGC", + "algorithm": "sha256" +} From c6b978d88dfc3d7c7e5aca68a3827d932ef07fcc Mon Sep 17 00:00:00 2001 From: Ian Carroll Date: Thu, 24 Apr 2014 20:02:44 -0400 Subject: [PATCH 053/150] Add FedoraCoin/TiPS --- coins/fedoracoin.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/fedoracoin.json diff --git a/coins/fedoracoin.json b/coins/fedoracoin.json new file mode 100644 index 0000000..a383262 --- /dev/null +++ b/coins/fedoracoin.json @@ -0,0 +1,5 @@ +{ + "name": "FedoraCoin", + "symbol": "TiPS", + "algorithm": "scrypt" +} From 952c7105cc7f91decc4b54be349d61a17bbcd23d Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 26 Apr 2014 16:23:23 -0600 Subject: [PATCH 054/150] Replaced various listeners (coinswitch and blocknotify) with united NOMP CLI (command-line interface) --- README.md | 37 ++----- coins/ecoin.json | 7 -- config_example.json | 70 ++++++------ init.js | 107 ++++++++++++++---- libs/blocknotifyListener.js | 69 ------------ libs/cliListener.js | 40 +++++++ libs/coinswitchListener.js | 56 ---------- libs/poolWorker.js | 211 ++++++++++++++++++++++-------------- libs/stats.js | 6 +- scripts/blockNotify.js | 34 ------ scripts/blocknotify.c | 89 +++++++-------- scripts/cli.js | 38 +++++++ scripts/coinSwitch.js | 37 ------- website/pages/tbs.html | 67 ++++++------ 14 files changed, 422 insertions(+), 446 deletions(-) delete mode 100644 coins/ecoin.json delete mode 100644 libs/blocknotifyListener.js create mode 100644 libs/cliListener.js delete mode 100644 libs/coinswitchListener.js delete mode 100644 scripts/blockNotify.js create mode 100644 scripts/cli.js delete mode 100644 scripts/coinSwitch.js diff --git a/README.md b/README.md index ac8eec5..7b8f582 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,8 @@ If your pool uses NOMP let us know and we will list your website here. * http://kryptochaos.com * http://pool.uberpools.org * http://onebtcplace.com -* https://minr.es +* http://minr.es +* http://mining.theminingpools.com Usage ===== @@ -166,7 +167,12 @@ Explanation for each field: /* Specifies the level of log output verbosity. Anything more severy than the level specified will also be logged. */ "logLevel": "debug", //or "warning", "error" - + + + /* The NOMP CLI (command-line interface) will listen for commands on this port. For example, + blocknotify messages are sent to NOMP through this. */ + "cliPort": 17117, + /* By default 'forks' is set to "auto" which will spawn one process/fork/worker for each CPU core in your system. Each of these workers will run a separate instance of your pool(s), and the kernel will load balance miners using these forks. Optionally, the 'forks' field @@ -205,33 +211,12 @@ Explanation for each field: "port": 6379 }, - /* With this enabled, the master process listen on the configured port for messages from the - 'scripts/blockNotify.js' script which your coin daemons can be configured to run when a - new block is available. When a blocknotify message is received, the master process uses - IPC (inter-process communication) to notify each thread about the message. Each thread - then sends the message to the appropriate coin pool. See "Setting up blocknotify" below to - set up your daemon to use this feature. */ - "blockNotifyListener": { - "enabled": true, - "port": 8117, - "password": "test" - }, - - /* With this enabled, the master process will listen on the configured port for messages from - the 'scripts/coinSwitch.js' script which will trigger your proxy pools to switch to the - specified coin (non-case-sensitive). This setting is used in conjuction with the proxy - feature below. */ - "coinSwitchListener": { - "enabled": false, - "port": 8118, - "password": "test" - }, /* In a proxy configuration, you can setup ports that accept miners for work based on a specific algorithm instead of a specific coin. Miners that connect to these ports are automatically switched a coin determined by the server. The default coin is the first configured pool for each algorithm and coin switching can be triggered using the - coinSwitch.js script in the scripts folder. + cli.js script in the scripts folder. Please note miner address authentication must be disabled when using NOMP in a proxy configuration and that payout processing is left up to the server administrator. */ @@ -505,11 +490,11 @@ For more information on these configuration options see the [pool module documen 1. In `config.json` set the port and password for `blockNotifyListener` 2. In your daemon conf file set the `blocknotify` command to use: ``` -node [path to scripts/blockNotify.js] [listener host]:[listener port] [listener password] [coin name in config] %s +node [path to cli.js] [coin name in config] [block hash symbol] ``` Example: inside `dogecoin.conf` add the line ``` -blocknotify=node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s +blocknotify=node /home/nomp/scripts/cli.js blocknotify dogecoin %s ``` Alternatively, you can use a more efficient block notify script written in pure C. Build and usage instructions diff --git a/coins/ecoin.json b/coins/ecoin.json deleted file mode 100644 index 9703edf..0000000 --- a/coins/ecoin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Ecoin", - "symbol": "ECN", - "algorithm": "keccak", - "normalHashing": true, - "diffShift": 32 -} \ No newline at end of file diff --git a/config_example.json b/config_example.json index 869b3db..bc09c9b 100644 --- a/config_example.json +++ b/config_example.json @@ -1,6 +1,8 @@ { "logLevel": "debug", + "cliPort": 17117, + "clustering": { "enabled": true, "forks": "auto" @@ -26,47 +28,47 @@ "port": 6379 }, - "blockNotifyListener": { - "enabled": false, - "port": 8117, - "password": "test" - }, - - "coinSwitchListener": { - "enabled": false, - "host": "127.0.0.1", - "port": 8118, - "password": "test" - }, - - "proxy": { - "sha256": { + "switching": { + "switch1": { "enabled": false, - "port": "3333", - "diff": 10, - "varDiff": { - "minDiff": 16, - "maxDiff": 512, - "targetTime": 15, - "retargetTime": 90, - "variancePercent": 30 + "algorithm": "sha256", + "ports": { + "3333": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt": { + "switch2": { "enabled": false, - "port": "4444", - "diff": 10, - "varDiff": { - "minDiff": 16, - "maxDiff": 512, - "targetTime": 15, - "retargetTime": 90, - "variancePercent": 30 + "algorithm": "scrypt", + "ports": { + "4444": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt-n": { + "switch3": { "enabled": false, - "port": "5555" + "algorithm": "x11", + "ports": { + "5555": { + "diff": 0.001 + } + } } }, diff --git a/init.js b/init.js index 6ff3e74..4d086ce 100644 --- a/init.js +++ b/init.js @@ -5,8 +5,7 @@ var cluster = require('cluster'); var async = require('async'); var PoolLogger = require('./libs/logUtil.js'); -var BlocknotifyListener = require('./libs/blocknotifyListener.js'); -var CoinswitchListener = require('./libs/coinswitchListener.js'); +var CliListener = require('./libs/cliListener.js'); var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js'); var PoolWorker = require('./libs/poolWorker.js'); var PaymentProcessor = require('./libs/paymentProcessor.js'); @@ -82,18 +81,68 @@ if (cluster.isWorker){ var buildPoolConfigs = function(){ var configs = {}; var configDir = 'pool_configs/'; + + var poolConfigFiles = []; + + + /* Get filenames of pool config json files that are enabled */ fs.readdirSync(configDir).forEach(function(file){ if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return; var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'}))); if (!poolOptions.enabled) return; - var coinFilePath = 'coins/' + poolOptions.coin; + poolOptions.fileName = file; + poolConfigFiles.push(poolOptions); + }); + + + /* Ensure no pool uses any of the same ports as another pool */ + for (var i = 0; i < poolConfigFiles.length; i++){ + var ports = Object.keys(poolConfigFiles[i].ports); + for (var f = 0; f < poolConfigFiles.length; f++){ + if (f === i) continue; + var portsF = Object.keys(poolConfigFiles[f].ports); + for (var g = 0; g < portsF.length; g++){ + if (ports.indexOf(portsF[g]) !== -1){ + logger.error('Master', poolConfigFiles[f].fileName, 'Has same configured port of ' + portsF[g] + ' as ' + poolConfigFiles[i].fileName); + process.exit(1); + return; + } + } + + if (poolConfigFiles[f].coin === poolConfigFiles[i].coin){ + logger.error('Master', poolConfigFiles[f].fileName, 'Pool has same configured coin file coins/' + poolConfigFiles[f].coin + ' as ' + poolConfigFiles[i].fileName + ' pool'); + process.exit(1); + return; + } + + } + } + + + poolConfigFiles.forEach(function(poolOptions){ + + poolOptions.coinFileName = poolOptions.coin; + + var coinFilePath = 'coins/' + poolOptions.coinFileName; if (!fs.existsSync(coinFilePath)){ - logger.error('Master', poolOptions.coin, 'could not find file: ' + coinFilePath); + logger.error('Master', poolOptions.coinFileName, 'could not find file: ' + coinFilePath); return; } var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'}))); poolOptions.coin = coinProfile; + + if (poolOptions.coin.name in configs){ + + logger.error('Master', poolOptions.fileName, 'coins/' + poolOptions.coinFileName + + ' has same configured coin name ' + poolOptions.coin.name + ' as coins/' + + configs[poolOptions.coin.name].coinFileName + ' used by pool config ' + + configs[poolOptions.coin.name].fileName); + + process.exit(1); + return; + } + configs[poolOptions.coin.name] = poolOptions; if (!(coinProfile.algorithm in algos)){ @@ -130,6 +179,10 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ return; } + for (var p in poolConfigs){ + + } + var serializedConfigs = JSON.stringify(poolConfigs); var numForks = (function(){ @@ -185,25 +238,34 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ }; -var startBlockListener = function(portalConfig){ - //block notify options - //setup block notify here and use IPC to tell appropriate pools - var listener = new BlocknotifyListener(portalConfig.blockNotifyListener); +var startCliListener = function(cliPort){ + var listener = new CliListener(cliPort); listener.on('log', function(text){ - logger.debug('Master', 'Blocknotify', text); - }); - listener.on('hash', function(message){ + logger.debug('Master', 'CLI', text); + }).on('command', function(command, params, options){ - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); + switch(command){ + case 'blocknotify': + Object.keys(cluster.workers).forEach(function(id) { + cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]}); + }); + break; + case 'coinswitch': + Object.keys(cluster.workers).forEach(function(id) { + cluster.workers[id].send({type: 'coinswitch', switchName: params[0], coin: params[1] }); + }); + break; + case 'restartpool': + Object.keys(cluster.workers).forEach(function(id) { + cluster.workers[id].send({type: 'restartpool', coin: params[0] }); + }); + } - }); - listener.start(); + console.log('command: ' + JSON.stringify([command, params, options])); + }).start(); }; - +/* // // Receives authenticated events from coin switch listener and triggers proxy // to swtich to a new coin. @@ -219,7 +281,7 @@ var startCoinswitchListener = function(portalConfig){ cluster.workers[id].send(ipcMessage); }); var ipcMessage = { - type:'switch', + type:'coinswitch', coin: message.coin }; Object.keys(cluster.workers).forEach(function(id) { @@ -228,6 +290,7 @@ var startCoinswitchListener = function(portalConfig){ }); listener.start(); }; +*/ var startRedisBlockListener = function(portalConfig){ //block notify options @@ -324,14 +387,12 @@ var startProfitSwitch = function(portalConfig, poolConfigs){ startPaymentProcessor(poolConfigs); - startBlockListener(portalConfig); - - startCoinswitchListener(portalConfig); - startRedisBlockListener(portalConfig); startWebsite(portalConfig, poolConfigs); startProfitSwitch(portalConfig, poolConfigs); + startCliListener(portalConfig.cliPort); + })(); diff --git a/libs/blocknotifyListener.js b/libs/blocknotifyListener.js deleted file mode 100644 index 4691c13..0000000 --- a/libs/blocknotifyListener.js +++ /dev/null @@ -1,69 +0,0 @@ -var events = require('events'); -var net = require('net'); - -var listener = module.exports = function listener(options){ - - var _this = this; - - var emitLog = function(text){ - _this.emit('log', text); - }; - - - this.start = function(){ - if (!options || !options.enabled){ - emitLog('Blocknotify listener disabled'); - return; - } - - var blockNotifyServer = net.createServer(function(c) { - - emitLog('Block listener has incoming connection'); - var data = ''; - try { - c.on('data', function (d) { - emitLog('Block listener received blocknotify data'); - data += d; - if (data.slice(-1) === '\n') { - c.end(); - } - }); - c.on('end', function () { - - emitLog('Block listener connection ended'); - - var message; - - try{ - message = JSON.parse(data); - } - catch(e){ - emitLog('Block listener failed to parse message ' + data); - return; - } - - if (message.password === options.password) { - _this.emit('hash', message); - } - else - emitLog('Block listener received notification with incorrect password'); - - }); - } - catch(e){ - emitLog('Block listener had an error: ' + e); - } - - }); - blockNotifyServer.listen(options.port, function() { - emitLog('Block notify listener server started on port ' + options.port) - }); - - emitLog("Block listener is enabled, starting server on port " + options.port); - } - - - -}; - -listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/libs/cliListener.js b/libs/cliListener.js new file mode 100644 index 0000000..96744b5 --- /dev/null +++ b/libs/cliListener.js @@ -0,0 +1,40 @@ +var events = require('events'); +var net = require('net'); + +var listener = module.exports = function listener(port){ + + var _this = this; + + var emitLog = function(text){ + _this.emit('log', text); + }; + + + this.start = function(){ + net.createServer(function(c) { + + var data = ''; + try { + c.on('data', function (d) { + data += d; + if (data.slice(-1) === '\n') { + c.end(); + } + }); + c.on('end', function () { + var message = JSON.parse(data); + _this.emit('command', message.command, message.params, message.options); + }); + } + catch(e){ + emitLog('CLI listener failed to parse message ' + data); + } + + }).listen(port, '127.0.0.1', function() { + emitLog('CLI listening on port ' + port) + }); + } + +}; + +listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/libs/coinswitchListener.js b/libs/coinswitchListener.js deleted file mode 100644 index fad0d2f..0000000 --- a/libs/coinswitchListener.js +++ /dev/null @@ -1,56 +0,0 @@ -var events = require('events'); -var net = require('net'); - -var listener = module.exports = function listener(options){ - - var _this = this; - - var emitLog = function(text){ - _this.emit('log', text); - }; - - - this.start = function(){ - if (!options || !options.enabled){ - emitLog('Coinswitch listener disabled'); - return; - } - - var coinswitchServer = net.createServer(function(c) { - - emitLog('Coinswitch listener has incoming connection'); - var data = ''; - try { - c.on('data', function (d) { - emitLog('Coinswitch listener received switch request'); - data += d; - if (data.slice(-1) === '\n') { - c.end(); - } - }); - c.on('end', function () { - - var message = JSON.parse(data); - if (message.password === options.password) { - _this.emit('switchcoin', message); - } - else - emitLog('Coinswitch listener received notification with incorrect password'); - - }); - } - catch(e){ - emitLog('Coinswitch listener failed to parse message ' + data); - } - - }); - coinswitchServer.listen(options.port, function() { - emitLog('Coinswitch notify listener server started on port ' + options.port) - }); - - emitLog("Coinswitch listener is enabled, starting server on port " + options.port); - } - -}; - -listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/libs/poolWorker.js b/libs/poolWorker.js index b1a5d47..5c66279 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -18,6 +18,8 @@ module.exports = function(logger){ var proxySwitch = {}; + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); + //Handle messages from master process sent via IPC process.on('message', function(message) { switch(message.type){ @@ -42,26 +44,39 @@ module.exports = function(logger){ break; // IPC message for pool switching - case 'switch': + case 'coinswitch': var logSystem = 'Proxy'; var logComponent = 'Switch'; var logSubCat = 'Thread ' + (parseInt(forkId) + 1); + var switchName = message.switchName; + if (!portalConfig.switching[switchName]) { + logger.error(logSystem, logComponent, logSubCat, 'Switching key not recognized: ' + switchName); + } + var messageCoin = message.coin.toLowerCase(); var newCoin = Object.keys(pools).filter(function(p){ return p.toLowerCase() === messageCoin; })[0]; if (!newCoin){ - logger.debug(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin); + logger.error(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin); break; } var algo = poolConfigs[newCoin].coin.algorithm; + + if (algo !== proxySwitch[switchName].algorithm){ + logger.error(logSystem, logComponent, logSubCat, 'Cannot switch a ' + + proxySwitch[switchName].algorithm + + ' algo pool to coin ' + newCoin + ' with ' + algo + ' algo'); + break; + } + var newPool = pools[newCoin]; - var oldCoin = proxySwitch[algo].currentPool; + var oldCoin = proxySwitch[switchName].currentPool; var oldPool = pools[oldCoin]; - var proxyPort = proxySwitch[algo].port; + var proxyPorts = Object.keys(proxySwitch[switchName].ports); if (newCoin == oldCoin) { logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin); @@ -74,25 +89,23 @@ module.exports = function(logger){ oldPool.relinquishMiners( function (miner, cback) { // relinquish miners that are attached to one of the "Auto-switch" ports and leave the others there. - cback(miner.client.socket.localPort == proxyPort) + cback(proxyPorts.indexOf(miner.client.socket.localPort.toString()) !== -1) }, function (clients) { newPool.attachMiners(clients); } ); - proxySwitch[algo].currentPool = newCoin; + proxySwitch[switchName].currentPool = newCoin; - var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) - redisClient.on('ready', function(){ - redisClient.hset('proxyState', algo, newCoin, function(error, obj) { - if (error) { - logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err)) - } - else { - logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo); - } - }); + redisClient.hset('proxyState', algo, newCoin, function(error, obj) { + if (error) { + logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err)) + } + else { + logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo); + } }); + } break; } @@ -119,7 +132,7 @@ module.exports = function(logger){ if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){ var mposCompat = new MposCompatibility(logger, poolOptions); - handlers.auth = function(workerName, password, authCallback){ + handlers.auth = function(port, workerName, password, authCallback){ mposCompat.handleAuth(workerName, password, authCallback); }; @@ -137,10 +150,30 @@ module.exports = function(logger){ var shareProcessor = new ShareProcessor(logger, poolOptions); - handlers.auth = function(workerName, password, authCallback){ + handlers.auth = function(port, workerName, password, authCallback){ if (shareProcessing.internal.validateWorkerAddress !== true) authCallback(true); else { + port = port.toString(); + if (portalConfig.switching) { + for (var switchName in portalConfig.switching) { + if (portalConfig.switching[switchName].enabled && Object.keys(portalConfig.switching[switchName].ports).indexOf(port) !== -1) { + if (workerName.length === 40) { + try { + new Buffer(workerName, 'hex'); + authCallback(true); + } + catch (e) { + authCallback(false); + } + } + else + authCallback(false); + return; + } + } + } + pool.daemon.cmd('validateaddress', [workerName], function(results){ var isValid = results.filter(function(r){return r.response.isvalid}).length > 0; authCallback(isValid); @@ -153,8 +186,8 @@ module.exports = function(logger){ }; } - var authorizeFN = function (ip, workerName, password, callback) { - handlers.auth(workerName, password, function(authorized){ + var authorizeFN = function (ip, port, workerName, password, callback) { + handlers.auth(port, workerName, password, function(authorized){ var authString = authorized ? 'Authorized' : 'Unauthorized '; @@ -202,9 +235,9 @@ module.exports = function(logger){ }); - if (typeof(portalConfig.proxy) !== 'undefined') { + if (portalConfig.switching) { - var logSystem = 'Proxy'; + var logSystem = 'Switching'; var logComponent = 'Setup'; var logSubCat = 'Thread ' + (parseInt(forkId) + 1); @@ -215,73 +248,93 @@ module.exports = function(logger){ // on the last pool it was using when reloaded or restarted // logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis'); - var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); - redisClient.on('ready', function(){ - redisClient.hgetall("proxyState", function(error, obj) { - if (error || obj == null) { - //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); - } - else { - proxyState = obj; - logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); - } - // - // Setup proxySwitch object to control proxy operations from configuration and any restored - // state. Each algorithm has a listening port, current coin name, and an active pool to - // which traffic is directed when activated in the config. - // - // In addition, the proxy config also takes diff and varDiff parmeters the override the - // defaults for the standard config of the coin. - // - Object.keys(portalConfig.proxy).forEach(function(algorithm) { - if (portalConfig.proxy[algorithm].enabled === true) { - var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); - proxySwitch[algorithm] = { - port: portalConfig.proxy[algorithm].port, - currentPool: initalPool, - proxy: {} - }; - - // Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up - // - // Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was - // routed into instead. - // - if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { - proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff); - proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff; - } - Object.keys(pools).forEach(function (coinName) { - var a = poolConfigs[coinName].coin.algorithm; - var p = pools[coinName]; - if (a === algorithm) { - p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff); + /*redisClient.on('error', function(err){ + logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err); + });*/ + + redisClient.hgetall("proxyState", function(error, obj) { + if (error || obj == null) { + //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); + } + else { + proxyState = obj; + logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); + } + + // + // Setup proxySwitch object to control proxy operations from configuration and any restored + // state. Each algorithm has a listening port, current coin name, and an active pool to + // which traffic is directed when activated in the config. + // + // In addition, the proxy config also takes diff and varDiff parmeters the override the + // defaults for the standard config of the coin. + // + Object.keys(portalConfig.switching).forEach(function(switchName) { + + var algorithm = portalConfig.switching[switchName].algorithm; + + if (portalConfig.switching[switchName].enabled === true) { + var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); + proxySwitch[switchName] = { + algorithm: algorithm, + ports: portalConfig.switching[switchName].ports, + currentPool: initalPool, + servers: [] + }; + + + // Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up + // + // Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was + // routed into instead. + // + /*if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { + proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff); + proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff; + }*/ + + + + Object.keys(pools).forEach(function (coinName) { + var p = pools[coinName]; + if (poolConfigs[coinName].coin.algorithm === algorithm) { + for (var port in portalConfig.switching[switchName].ports) { + if (portalConfig.switching[switchName].ports[port].vardiff) + p.setVarDiff(port, portalConfig.switching[switchName].ports[port].vardiff); } - }); + } + }); - proxySwitch[algorithm].proxy = net.createServer(function(socket) { - var currentPool = proxySwitch[algorithm].currentPool; - var logSubCat = 'Thread ' + (parseInt(forkId) + 1); - logger.debug(logSystem, 'Connect', logSubCat, 'Proxy connect from ' + socket.remoteAddress + ' on ' + proxySwitch[algorithm].port - + ' routing to ' + currentPool); + Object.keys(proxySwitch[switchName].ports).forEach(function(port){ + var f = net.createServer(function(socket) { + var currentPool = proxySwitch[switchName].currentPool; + + logger.debug(logSystem, 'Connect', logSubCat, 'Connection to ' + + switchName + ' from ' + + socket.remoteAddress + ' on ' + + port + ' routing to ' + currentPool); + pools[currentPool].getStratumServer().handleNewClient(socket); - }).listen(parseInt(proxySwitch[algorithm].port), function() { - logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port - + ' into ' + proxySwitch[algorithm].currentPool); + }).listen(parseInt(port), function() { + logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName + + '" listening for ' + algorithm + + ' on port ' + port + + ' into ' + proxySwitch[switchName].currentPool); }); - } - else { - //logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); - } - }); + proxySwitch[switchName].servers.push(f); + }); + + + } + else { + //logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); + } }); - }).on('error', function(err){ - logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err); }); } diff --git a/libs/stats.js b/libs/stats.js index 1f3c3d6..527e19d 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -148,7 +148,11 @@ module.exports = function(logger, portalConfig, poolConfigs){ symbol: poolConfigs[coinName].coin.symbol.toUpperCase(), algorithm: poolConfigs[coinName].coin.algorithm, hashrates: replies[i + 1], - poolStats: replies[i + 2] != null ? replies[i + 2] : { validShares: 0, validBlocks: 0, invalidShares: 0 }, + poolStats: { + validShares: replies[i + 2] ? (replies[i + 2].validShares || 0) : 0, + validBlocks: replies[i + 2] ? (replies[i + 2].validBlocks || 0) : 0, + invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0 + }, blocks: { pending: replies[i + 3], confirmed: replies[i + 4], diff --git a/scripts/blockNotify.js b/scripts/blockNotify.js deleted file mode 100644 index 42a0f8c..0000000 --- a/scripts/blockNotify.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - This script should be hooked to the coin daemon as follow: - litecoind -blocknotify="node /path/to/this/script/blockNotify.js 127.0.0.1:8117 password litecoin %s" - The above will send tell litecoin to launch this script with those parameters every time a block is found. - This script will then send the blockhash along with other information to a listening tcp socket - */ - -var net = require('net'); -var config = process.argv[2]; -var parts = config.split(':'); -var host = parts[0]; -var port = parts[1]; -var password = process.argv[3]; -var coin = process.argv[4]; -var blockHash = process.argv[5]; - -var client = net.connect(port, host, function () { - console.log('client connected'); - client.write(JSON.stringify({ - password: password, - coin: coin, - hash: blockHash - }) + '\n'); -}); - -client.on('data', function (data) { - console.log(data.toString()); - //client.end(); -}); - -client.on('end', function () { - console.log('client disconnected'); - //process.exit(); -}); \ No newline at end of file diff --git a/scripts/blocknotify.c b/scripts/blocknotify.c index 78c9db7..a119096 100644 --- a/scripts/blocknotify.c +++ b/scripts/blocknotify.c @@ -16,76 +16,69 @@ Simple lightweight & fast - a more efficient block notify script in pure C. (may also work as coin switch) -Platforms : Linux,BSD,Solaris (mostly OS independent) +Platforms : Linux, BSD, Solaris (mostly OS independent) Build with: gcc blocknotify.c -o blocknotify -Usage in daemon coin.conf - blocknotify="/bin/blocknotify 127.0.0.1:8117 mySuperSecurePassword dogecoin %s" +Example usage in daemon coin.conf using default NOMP CLI port of 17117 + blocknotify="/bin/blocknotify 127.0.0.1:17117 dogecoin %s" -*NOTE* If you use "localhost" as hostname you may get a "13" error (socket / connect / send may consider "localhost" as a broadcast address) -// {"password":"notepas","coin":"Xcoin","hash":"d2191a8b644c9cd903439edf1d89ee060e196b3e116e0d48a3f11e5e3987a03b"} -// simplest connect + send json string to server -# $Id: blocknotify.c,v 0.1 2014/04/07 22:38:09 sysman Exp $ */ int main(int argc, char **argv) { - int sockfd,n; - struct sockaddr_in servaddr,cliaddr; - char sendline[1000]; - char recvline[1000]; - char host[200]; - char *p,*arg,*errptr; - int port; + int sockfd,n; + struct sockaddr_in servaddr, cliaddr; + char sendline[1000]; + char recvline[1000]; + char host[200]; + char *p, *arg, *errptr; + int port; - if (argc < 4) - { - // print help - printf("NOMP pool block notify\n usage: \n"); - exit(1); - } + if (argc < 3) + { + // print help + printf("NOMP pool block notify\n usage: \n"); + exit(1); + } - strncpy(host,argv[1],(sizeof(host)-1)); - p=host; + strncpy(host, argv[1], (sizeof(host)-1)); + p = host; - if ( (arg=strchr(p,':')) ) - { - *arg='\0'; + if ( (arg = strchr(p,':')) ) + { + *arg = '\0'; - errno=0; // reset errno - port=strtol(++arg,&errptr,10); + errno = 0; // reset errno + port = strtol(++arg, &errptr, 10); - if ( (errno != 0) || (errptr == arg) ) { fprintf(stderr, "port number fail [%s]\n",errptr); } - // if(strlen(arg) > (errptr-arg) ) also fail, but we ignore it for now - // printf("host %s:%d\n",host,port); - } + if ( (errno != 0) || (errptr == arg) ) + { + fprintf(stderr, "port number fail [%s]\n", errptr); + } - // printf("pass: %s coin: %s block:[%s]\n",argv[2],argv[3],argv[4]); - snprintf(sendline,sizeof(sendline)-1, - "{\"password\":\"%s\",\"coin\":\"%s\",\"hash\":\"%s\"}\n", - argv[2], argv[3], argv[4]); + } - // printf("sendline:[%s]",sendline); + snprintf(sendline, sizeof(sendline) - 1, "{\"command\":\"blocknotify\",\"params\":[\"%s\",\"%s\"]}\n", argv[2], argv[3]); - sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); - bzero(&servaddr,sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr=inet_addr(host); - servaddr.sin_port=htons(port); - connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); + sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = inet_addr(host); + servaddr.sin_port = htons(port); + connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); - int result = send(sockfd,sendline,strlen(sendline),0); - close(sockfd); + int result = send(sockfd, sendline, strlen(sendline), 0); + close(sockfd); - if(result == -1) { - printf("Error sending: %i\n",errno); + if(result == -1) { + printf("Error sending: %i\n", errno); exit(-1); - } - exit(0); + } + exit(0); } diff --git a/scripts/cli.js b/scripts/cli.js new file mode 100644 index 0000000..013678c --- /dev/null +++ b/scripts/cli.js @@ -0,0 +1,38 @@ +var net = require('net'); + +var defaultPort = 17117; +var defaultHost = '127.0.0.1'; + +var args = process.argv.slice(2); +var params = []; +var options = {}; + +for(var i = 0; i < args.length; i++){ + if (args[i].indexOf('-') === 0 && args[i].indexOf('=') !== -1){ + var s = args[i].substr(1).split('='); + options[s[0]] = s[1]; + } + else + params.push(args[i]); +} + +var command = params.shift(); + + + +var client = net.connect(options.port || defaultPort, options.host || defaultHost, function () { + client.write(JSON.stringify({ + command: command, + params: params, + options: options + }) + '\n'); +}).on('error', function(error){ + if (error.code === 'ECONNREFUSED') + console.log('Could not connect to NOMP instance at ' + defaultHost + ':' + defaultPort); + else + console.log('Socket error ' + JSON.stringify(error)); +}).on('data', function(data) { + console.log(data.toString()); +}).on('close', function () { + +}); \ No newline at end of file diff --git a/scripts/coinSwitch.js b/scripts/coinSwitch.js deleted file mode 100644 index acf6ac1..0000000 --- a/scripts/coinSwitch.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - This script demonstrates sending a coin switch request and can be invoked from the command line - with: - - "node coinSwitch.js 127.0.0.1:8118 password %s" - - where <%s> is the name of the coin proxy miners will be switched onto. - - If the coin name is not configured, disabled or matches the existing proxy setting, no action - will be taken by NOMP on receipt of the message. - */ - -var net = require('net'); -var config = process.argv[2]; -var parts = config.split(':'); -var host = parts[0]; -var port = parts[1]; -var password = process.argv[3]; -var coin = process.argv[4]; - -var client = net.connect(port, host, function () { - console.log('client connected'); - client.write(JSON.stringify({ - password: password, - coin: coin - }) + '\n'); -}); - -client.on('data', function (data) { - console.log(data.toString()); - //client.end(); -}); - -client.on('end', function () { - console.log('client disconnected'); - //process.exit(); -}); diff --git a/website/pages/tbs.html b/website/pages/tbs.html index ccc1b3b..12ce72b 100644 --- a/website/pages/tbs.html +++ b/website/pages/tbs.html @@ -1,56 +1,59 @@ - + + + + + + + + + + + + + + + {{ for(var pool in it.stats.pools) { }} + + + + + + + + + + {{ } }} +
    PoolAlgoWorkersValid SharesInvalid SharesBlocksHashrate
    {{=it.stats.pools[pool].name}}{{=it.stats.pools[pool].algorithm}}{{=Object.keys(it.stats.pools[pool].workers).length}}{{=it.stats.pools[pool].poolStats.validShares}}{{=it.stats.pools[pool].poolStats.invalidShares}}{{=it.stats.pools[pool].poolStats.validBlocks}}{{=it.stats.pools[pool].hashrateString}}
    From 4413bfd7e6bb410026cd050dd473178a22b5b212 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 26 Apr 2014 16:24:06 -0600 Subject: [PATCH 055/150] Mining with public key on a switching port will now do payouts. --- libs/paymentProcessor.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 851f121..6e05767 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -2,7 +2,7 @@ var redis = require('redis'); var async = require('async'); var Stratum = require('stratum-pool'); - +var util = require('stratum-pool/lib/util.js'); module.exports = function(logger){ @@ -38,8 +38,6 @@ module.exports = function(logger){ }); }); - - }; @@ -572,7 +570,12 @@ function SetupForPool(logger, poolOptions, setupFinished){ var totalAmountUnits = 0; for (var address in workerPayments){ var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision); - addressAmounts[address] = coinUnits; + var properAddress = getProperAddress(address); + if (!properAddress){ + logger.error(logSystem, logComponent, 'Could not convert pubkey ' + address + ' into address'); + continue; + } + addressAmounts[properAddress] = coinUnits; totalAmountUnits += coinUnits; } @@ -628,6 +631,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ }; + var getProperAddress = function(address){ + if (address.length === 40){ + return util.addressFromEx(poolOptions.address, address); + } + else return address; + }; + var withdrawalProfit = function(){ if (!processingConfig.feeWithdrawalThreshold) return; @@ -661,5 +671,4 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); }; - -}; \ No newline at end of file +} \ No newline at end of file From f47b023bd7ea5b791926b5a96cfea3566827b491 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 26 Apr 2014 16:31:27 -0600 Subject: [PATCH 056/150] Updated readme for switching config --- README.md | 73 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7b8f582..dd27db8 100644 --- a/README.md +++ b/README.md @@ -212,43 +212,66 @@ Explanation for each field: }, - /* In a proxy configuration, you can setup ports that accept miners for work based on a - specific algorithm instead of a specific coin. Miners that connect to these ports are + /* With this switching configuration, you can setup ports that accept miners for work based on + a specific algorithm instead of a specific coin. Miners that connect to these ports are automatically switched a coin determined by the server. The default coin is the first configured pool for each algorithm and coin switching can be triggered using the cli.js script in the scripts folder. - Please note miner address authentication must be disabled when using NOMP in a proxy - configuration and that payout processing is left up to the server administrator. */ - "proxy": { - "sha256": { + Miners connecting to these switching ports must use their public key in the format of + RIPEMD160(SHA256(public-key)). An address for each type of coin is derived from the miner's + public key, and payments are sent to that address. */ + "switching": { + "switch1": { "enabled": false, - "port": "3333", - "diff": 10, - "varDiff": { - "minDiff": 16, //Minimum difficulty - "maxDiff": 512, //Network difficulty will be used if it is lower than this - "targetTime": 15, //Try to get 1 share per this many seconds - "retargetTime": 90, //Check to see if we should retarget every this many seconds - "variancePercent": 30 //Allow time to very this % from target without retargeting + "algorithm": "sha256", + "ports": { + "3333": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt": { + "switch2": { "enabled": false, - "port": "4444", - "diff": 10, - "varDiff": { - "minDiff": 16, //Minimum difficulty - "maxDiff": 512, //Network difficulty will be used if it is lower than this - "targetTime": 15, //Try to get 1 share per this many seconds - "retargetTime": 90, //Check to see if we should retarget every this many seconds - "variancePercent": 30 //Allow time to very this % from target without retargeting + "algorithm": "scrypt", + "ports": { + "4444": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt-n": { + "switch3": { "enabled": false, - "port": "5555" + "algorithm": "x11", + "ports": { + "5555": { + "diff": 0.001 + } + } } + }, + + "profitSwitch": { + "enabled": false, + "updateInterval": 600, + "depth": 0.90, + "usePoloniex": true, + "useCryptsy": true, + "useMintpal": true } } ```` From 9ba337f33eac8b02ae4eb62eccc19b6ebf130a62 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 26 Apr 2014 16:52:47 -0600 Subject: [PATCH 057/150] Updated profit switching in readme --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dd27db8..6720632 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,8 @@ Each and every share will be rewarded - even for rounds resulting in orphaned bl authentication. A minimalistic HTML5 front-end connects to the portals statistics API to display stats from from each pool such as connected miners, network/pool difficulty/hash rate, etc. -* Automated switching of connected miners to different pools/coins is also easily done due to the multi-pool architecture -of this software. To use this feature the switching must be controlled by your own script, such as one that calculates -coin profitability via an API such as CoinChoose.com or CoinWarz.com (or calculated locally using daemon-reported network -difficulties and exchange APIs). NOMP's regular payment processing and miner authentication which using coin address as stratum -username will obviously not work with this coin switching feature - so you must control those with your own script as well. +* Coin-switching ports using coin-networks and crypto-exchange APIs to detect profitability. Miner's connect to these ports +with their public key which NOMP uses to derive an address for any coin needed to be paid out. #### Attack Mitigation From 52108e3b7ad32a863876fe911a02ec77b082ca36 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 27 Apr 2014 14:24:40 -0600 Subject: [PATCH 058/150] Added mysql connection pooling for reconnecting to db and better performance --- libs/mposCompatibility.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index d94c8cd..21c378d 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -5,13 +5,23 @@ module.exports = function(logger, poolConfig){ var mposConfig = poolConfig.shareProcessing.mpos; var coin = poolConfig.coin.name; - var connection; + //var connection; + var logIdentify = 'MySQL'; var logComponent = coin; function connect(){ - connection = mysql.createConnection({ + + connection = mysql.createPool({ + host: mposConfig.host, + port: mposConfig.port, + user: mposConfig.user, + password: mposConfig.password, + database: mposConfig.database + }); + + /*connection = mysql.createConnection({ host: mposConfig.host, port: mposConfig.port, user: mposConfig.user, @@ -33,7 +43,9 @@ module.exports = function(logger, poolConfig){ else{ logger.error(logIdentify, logComponent, 'Database error: ' + JSON.stringify(err)) } - }); + });*/ + + } connect(); From 25406582b9b17528174f12b322081d4352f4339e Mon Sep 17 00:00:00 2001 From: Alejandro Reyero Date: Mon, 28 Apr 2014 09:54:24 +0200 Subject: [PATCH 059/150] Update api.html Missing closing tag in
  • pool_stats --- website/pages/api.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/pages/api.html b/website/pages/api.html index 9cb343e..b0f73c6 100644 --- a/website/pages/api.html +++ b/website/pages/api.html @@ -3,8 +3,8 @@
    • /stats - raw json statistic
    • -
    • /pool_stats - historical time per pool json
    • +
    • /pool_stats - historical time per pool json
    • /live_stats - live stats
    - \ No newline at end of file + From 66d7640c9b035600c8a39f395bb939369c25820c Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 28 Apr 2014 15:04:12 -0600 Subject: [PATCH 060/150] NOMP CLI gives replies now. Profit switching transitioned to using CLI port rather than old profitListener port. --- README.md | 7 +- init.js | 148 +++++++++++++++++++++++++------------- libs/cliListener.js | 8 ++- libs/mposCompatibility.js | 26 +------ libs/poolWorker.js | 20 +----- libs/profitSwitch.js | 74 ++++++++++--------- scripts/cli.js | 1 - 7 files changed, 149 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 6720632..eeb7214 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ coins at once. The pools use clustering to load balance across multiple CPU core * For reward/payment processing, shares are inserted into Redis (a fast NoSQL key/value store). The PROP (proportional) reward system is used with [Redis Transactions](http://redis.io/topics/transactions) for secure and super speedy payouts. -Each and every share will be rewarded - even for rounds resulting in orphaned blocks. +There is zero risk to the pool operator. Shares from rounds resulting in orphaned blocks will be merged into share in the +current round so that each and every share will be rewarded * This portal does not have user accounts/logins/registrations. Instead, miners simply use their coin address for stratum authentication. A minimalistic HTML5 front-end connects to the portals statistics API to display stats from from each @@ -108,10 +109,12 @@ If your pool uses NOMP let us know and we will list your website here. * http://teamdoge.com * http://miningwith.us * http://kryptochaos.com -* http://pool.uberpools.org +* http://uberpools.org * http://onebtcplace.com * http://minr.es * http://mining.theminingpools.com +* http://www.omargpools.ca/pools.html +* http://pool.trademybit.com/ Usage ===== diff --git a/init.js b/init.js index 4d086ce..bf5c0c9 100644 --- a/init.js +++ b/init.js @@ -22,6 +22,7 @@ if (!fs.existsSync('config.json')){ } var portalConfig = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'}))); +var poolConfigs; var logger = new PoolLogger({ @@ -156,7 +157,7 @@ var buildPoolConfigs = function(){ -var spawnPoolWorkers = function(portalConfig, poolConfigs){ +var spawnPoolWorkers = function(){ Object.keys(poolConfigs).forEach(function(coin){ var p = poolConfigs[coin]; @@ -179,9 +180,6 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ return; } - for (var p in poolConfigs){ - - } var serializedConfigs = JSON.stringify(poolConfigs); @@ -238,61 +236,111 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ }; -var startCliListener = function(cliPort){ +var startCliListener = function(){ + + var cliPort = portalConfig.cliPort; + var listener = new CliListener(cliPort); listener.on('log', function(text){ logger.debug('Master', 'CLI', text); - }).on('command', function(command, params, options){ + }).on('command', function(command, params, options, reply){ switch(command){ case 'blocknotify': Object.keys(cluster.workers).forEach(function(id) { cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]}); }); + reply('Pool workers notified'); break; case 'coinswitch': - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send({type: 'coinswitch', switchName: params[0], coin: params[1] }); - }); + processCoinSwitchCommand(params, options, reply); break; - case 'restartpool': + case 'reloadpool': Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send({type: 'restartpool', coin: params[0] }); + cluster.workers[id].send({type: 'reloadpool', coin: params[0] }); }); + reply('reloaded pool ' + params[0]); + break; + default: + reply('unrecognized command "' + command + '"'); + break; } - - console.log('command: ' + JSON.stringify([command, params, options])); }).start(); }; -/* -// -// Receives authenticated events from coin switch listener and triggers proxy -// to swtich to a new coin. -// -var startCoinswitchListener = function(portalConfig){ - var listener = new CoinswitchListener(portalConfig.coinSwitchListener); - listener.on('log', function(text){ - logger.debug('Master', 'Coinswitch', text); - }); - listener.on('switchcoin', function(message){ - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); - var ipcMessage = { - type:'coinswitch', - coin: message.coin - }; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); - }); - listener.start(); -}; -*/ -var startRedisBlockListener = function(portalConfig){ +var processCoinSwitchCommand = function(params, options, reply){ + + var logSystem = 'CLI'; + var logComponent = 'coinswitch'; + + var replyError = function(msg){ + reply(msg); + logger.error(logSystem, logComponent, msg); + }; + + if (!params[0]) { + replyError('Coin name required'); + return; + } + + if (!params[1] && !options.algorithm){ + replyError('If switch key is not provided then algorithm options must be specified'); + return; + } + else if (params[1] && !portalConfig.switching[params[1]]){ + replyError('Switch key not recognized: ' + params[1]); + return; + } + else if (options.algorithm && !Object.keys(portalConfig.switching).filter(function(s){ + return portalConfig.switching[s].algorithm === options.algorithm; + })[0]){ + replyError('No switching options contain the algorithm ' + options.algorithm); + return; + } + + var messageCoin = params[0].toLowerCase(); + var newCoin = Object.keys(poolConfigs).filter(function(p){ + return p.toLowerCase() === messageCoin; + })[0]; + + if (!newCoin){ + replyError('Switch message to coin that is not recognized: ' + messageCoin); + return; + } + + + var switchNames = []; + + if (params[1]) { + switchNames.push(params[1]); + } + else{ + for (var name in portalConfig.switching){ + if (portalConfig.switching[name].enabled && portalConfig.switching[name].algorithm === options.algorithm) + switchNames.push(name); + } + } + + switchNames.forEach(function(name){ + if (poolConfigs[newCoin].coin.algorithm !== portalConfig.switching[name].algorithm){ + replyError('Cannot switch a ' + + portalConfig.switching[name].algorithm + + ' algo pool to coin ' + newCoin + ' with ' + poolConfigs[newCoin].coin.algorithm + ' algo'); + return; + } + + Object.keys(cluster.workers).forEach(function (id) { + cluster.workers[id].send({type: 'coinswitch', coin: newCoin, switchName: name }); + }); + }); + + reply('Switch message sent to pool workers'); + +}; + + +var startRedisBlockListener = function(){ //block notify options //setup block notify here and use IPC to tell appropriate pools @@ -311,7 +359,7 @@ var startRedisBlockListener = function(portalConfig){ }; -var startPaymentProcessor = function(poolConfigs){ +var startPaymentProcessor = function(){ var enabledForAny = false; for (var pool in poolConfigs){ @@ -339,7 +387,7 @@ var startPaymentProcessor = function(poolConfigs){ }; -var startWebsite = function(portalConfig, poolConfigs){ +var startWebsite = function(){ if (!portalConfig.website.enabled) return; @@ -357,7 +405,7 @@ var startWebsite = function(portalConfig, poolConfigs){ }; -var startProfitSwitch = function(portalConfig, poolConfigs){ +var startProfitSwitch = function(){ if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){ //logger.error('Master', 'Profit', 'Profit auto switching disabled'); @@ -381,18 +429,18 @@ var startProfitSwitch = function(portalConfig, poolConfigs){ (function init(){ - var poolConfigs = buildPoolConfigs(); + poolConfigs = buildPoolConfigs(); - spawnPoolWorkers(portalConfig, poolConfigs); + spawnPoolWorkers(); - startPaymentProcessor(poolConfigs); + startPaymentProcessor(); - startRedisBlockListener(portalConfig); + startRedisBlockListener(); - startWebsite(portalConfig, poolConfigs); + startWebsite(); - startProfitSwitch(portalConfig, poolConfigs); + startProfitSwitch(); - startCliListener(portalConfig.cliPort); + startCliListener(); })(); diff --git a/libs/cliListener.js b/libs/cliListener.js index 96744b5..efb18cf 100644 --- a/libs/cliListener.js +++ b/libs/cliListener.js @@ -18,12 +18,14 @@ var listener = module.exports = function listener(port){ c.on('data', function (d) { data += d; if (data.slice(-1) === '\n') { - c.end(); + var message = JSON.parse(data); + _this.emit('command', message.command, message.params, message.options, function(message){ + c.end(message); + }); } }); c.on('end', function () { - var message = JSON.parse(data); - _this.emit('command', message.command, message.params, message.options); + }); } catch(e){ diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 21c378d..0a3b1c7 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -5,7 +5,7 @@ module.exports = function(logger, poolConfig){ var mposConfig = poolConfig.shareProcessing.mpos; var coin = poolConfig.coin.name; - //var connection; + var connection; var logIdentify = 'MySQL'; @@ -21,30 +21,6 @@ module.exports = function(logger, poolConfig){ database: mposConfig.database }); - /*connection = mysql.createConnection({ - host: mposConfig.host, - port: mposConfig.port, - user: mposConfig.user, - password: mposConfig.password, - database: mposConfig.database - }); - connection.connect(function(err){ - if (err) - logger.error(logIdentify, logComponent, 'Could not connect to mysql database: ' + JSON.stringify(err)) - else{ - logger.debug(logIdentify, logComponent, 'Successful connection to MySQL database'); - } - }); - connection.on('error', function(err){ - if(err.code === 'PROTOCOL_CONNECTION_LOST') { - logger.warning(logIdentify, logComponent, 'Lost connection to MySQL database, attempting reconnection...'); - connect(); - } - else{ - logger.error(logIdentify, logComponent, 'Database error: ' + JSON.stringify(err)) - } - });*/ - } connect(); diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 5c66279..2fc114d 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -50,29 +50,11 @@ module.exports = function(logger){ var logSubCat = 'Thread ' + (parseInt(forkId) + 1); var switchName = message.switchName; - if (!portalConfig.switching[switchName]) { - logger.error(logSystem, logComponent, logSubCat, 'Switching key not recognized: ' + switchName); - } - var messageCoin = message.coin.toLowerCase(); - var newCoin = Object.keys(pools).filter(function(p){ - return p.toLowerCase() === messageCoin; - })[0]; - - if (!newCoin){ - logger.error(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin); - break; - } + var newCoin = message.coin; var algo = poolConfigs[newCoin].coin.algorithm; - if (algo !== proxySwitch[switchName].algorithm){ - logger.error(logSystem, logComponent, logSubCat, 'Cannot switch a ' - + proxySwitch[switchName].algorithm - + ' algo pool to coin ' + newCoin + ' with ' + algo + ' algo'); - break; - } - var newPool = pools[newCoin]; var oldCoin = proxySwitch[switchName].currentPool; var oldPool = pools[oldCoin]; diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 83491a0..6e0e6c6 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -420,31 +420,28 @@ module.exports = function(logger){ }; this.getDaemonInfoForCoin = function(symbol, cfg, callback){ var daemon = new Stratum.daemon.interface([cfg]); - daemon.once('online', function(){ - daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ - if (result[0].error != null){ - logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0])); - callback(null); // fail gracefully for each coin - return; - } - var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; - var response = result[0].response; - - // some shitcoins dont provide target, only bits, so we need to deal with both - var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits); - coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9)); - logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty); - - coinStatus.reward = new Number(response.coinbasevalue / 100000000); - callback(null); - }); - }).once('connectionFailed', function(error){ - logger.error(logSystem, symbol, JSON.stringify(error)); - callback(null); // fail gracefully for each coin - }).on('error', function(error){ + daemon.on('error', function(error){ logger.error(logSystem, symbol, JSON.stringify(error)); callback(null); // fail gracefully for each coin }).init(); + + daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result) { + if (result[0].error != null) { + logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0])); + callback(null); // fail gracefully for each coin + return; + } + var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; + var response = result[0].response; + + // some shitcoins dont provide target, only bits, so we need to deal with both + var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits); + coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9)); + logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty); + + coinStatus.reward = response.coinbasevalue / 100000000; + callback(null); + }); }; @@ -453,8 +450,8 @@ module.exports = function(logger){ Object.keys(profitStatus).forEach(function(algo){ Object.keys(profitStatus[algo]).forEach(function(symbol){ var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; - coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); - coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); + coinStatus.blocksPerMhPerHour = 86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000)); + coinStatus.coinsPerMhPerHour = coinStatus.reward * coinStatus.blocksPerMhPerHour; }); }); callback(null); @@ -467,7 +464,7 @@ module.exports = function(logger){ var bestExchange; var bestCoin; - var bestBtcPerMhPerHour = new Number(0); + var bestBtcPerMhPerHour = 0; Object.keys(profitStatus[algo]).forEach(function(symbol) { var coinStatus = profitStatus[algo][symbol]; @@ -475,7 +472,7 @@ module.exports = function(logger){ Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){ var exchangeData = coinStatus.exchangeInfo[exchange]; if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){ - var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour); + var btcPerMhPerHour = exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour; if (btcPerMhPerHour > bestBtcPerMhPerHour){ bestBtcPerMhPerHour = btcPerMhPerHour; bestExchange = exchange; @@ -485,7 +482,7 @@ module.exports = function(logger){ logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); } if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ - var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); + var btcPerMhPerHour = (exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc; if (btcPerMhPerHour > bestBtcPerMhPerHour){ bestBtcPerMhPerHour = btcPerMhPerHour; bestExchange = exchange; @@ -497,14 +494,21 @@ module.exports = function(logger){ }); }); logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); - if (portalConfig.coinSwitchListener.enabled){ - var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () { - client.write(JSON.stringify({ - password: portalConfig.coinSwitchListener.password, - coin: bestCoin - }) + '\n'); - }); - } + + + var client = net.connect(portalConfig.cliPort, function () { + client.write(JSON.stringify({ + command: 'coinswitch', + params: [bestCoin], + options: {algorithm: algo} + }) + '\n'); + }).on('error', function(error){ + if (error.code === 'ECONNREFUSED') + logger.error(logSystem, 'CLI', 'Could not connect to NOMP instance on port ' + portalConfig.cliPort); + else + logger.error(logSystem, 'CLI', 'Socket error ' + JSON.stringify(error)); + }); + }); }; diff --git a/scripts/cli.js b/scripts/cli.js index 013678c..5e1cfd6 100644 --- a/scripts/cli.js +++ b/scripts/cli.js @@ -34,5 +34,4 @@ var client = net.connect(options.port || defaultPort, options.host || defaultHos }).on('data', function(data) { console.log(data.toString()); }).on('close', function () { - }); \ No newline at end of file From 62c9a59b72b2ef7188d919ae7ce16b656282b5e4 Mon Sep 17 00:00:00 2001 From: Alejandro Reyero Date: Tue, 29 Apr 2014 10:56:35 +0200 Subject: [PATCH 061/150] Update tbs.html Added info about Block status --- website/pages/tbs.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/website/pages/tbs.html b/website/pages/tbs.html index 12ce72b..9ca8352 100644 --- a/website/pages/tbs.html +++ b/website/pages/tbs.html @@ -40,7 +40,10 @@ Workers Valid Shares Invalid Shares - Blocks + Total Blocks + Pending + Confirmed + Orphaned Hashrate @@ -53,6 +56,9 @@ {{=it.stats.pools[pool].poolStats.validShares}} {{=it.stats.pools[pool].poolStats.invalidShares}} {{=it.stats.pools[pool].poolStats.validBlocks}} + {{=it.stats.pools[pool].blocks.pending}} + {{=it.stats.pools[pool].blocks.confirmed}} + {{=it.stats.pools[pool].blocks.orphaned}} {{=it.stats.pools[pool].hashrateString}} {{ } }} From 6291ad0b69ff9d330b10416e8d00c99f9afbe3a1 Mon Sep 17 00:00:00 2001 From: Elliot Boney Date: Tue, 29 Apr 2014 16:01:55 +0000 Subject: [PATCH 062/150] added starcoin and safer gitignore --- .gitignore | 4 +++- coins/starcoin.json | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 coins/starcoin.json diff --git a/.gitignore b/.gitignore index 88d9a09..b2f46e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ .idea/ -config.json \ No newline at end of file +config.json +pool_configs/*.json +!pool_configs/litecoin_example.json diff --git a/coins/starcoin.json b/coins/starcoin.json new file mode 100644 index 0000000..ca65a81 --- /dev/null +++ b/coins/starcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Starcoin", + "symbol": "STR", + "algorithm": "scrypt" +} From f922a92898a688850ac77ac773c7f45d0b0bb4f4 Mon Sep 17 00:00:00 2001 From: Alejandro Reyero Date: Tue, 29 Apr 2014 23:30:12 +0200 Subject: [PATCH 063/150] Create globalboost.json GlobalBoost Coin --- coins/globalboost.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/globalboost.json diff --git a/coins/globalboost.json b/coins/globalboost.json new file mode 100644 index 0000000..8114798 --- /dev/null +++ b/coins/globalboost.json @@ -0,0 +1,5 @@ +{ + "name": "GlobalBoost", + "symbol": "BST", + "algorithm": "scrypt" +} From 5ea852d3696847844b6c043f5a393adec4d8df0d Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 30 Apr 2014 20:43:43 -0600 Subject: [PATCH 064/150] Added client-side mining key generation script for coin-switching port mining --- libs/paymentProcessor.js | 5 +- libs/website.js | 99 +- website/key.html | 2798 +++++++++++++++++++++++++++++++++ website/pages/mining_key.html | 25 + 4 files changed, 2923 insertions(+), 4 deletions(-) create mode 100644 website/key.html create mode 100644 website/pages/mining_key.html diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 6e05767..638c64d 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -502,7 +502,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ } })(); movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]); - roundsToDelete.push(coin + '_shares:round' + r.height) + if (r.category === 'generate') + roundsToDelete.push(coin + '_shares:round' + r.height) }); var finalRedisCommands = []; @@ -660,7 +661,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){ if (results[0].error){ - logger.debug(logSystem, logComponent, 'Profit withdrawal finished - error with sendmany ' + logger.debug(logSystem, logComponent, 'Profit withdrawal of ' + withdrawalAmount + ' failed - error with sendmany ' + JSON.stringify(results[0].error)); return; } diff --git a/libs/website.js b/libs/website.js index 6ac1962..3af59c5 100644 --- a/libs/website.js +++ b/libs/website.js @@ -3,18 +3,24 @@ var fs = require('fs'); var path = require('path'); var async = require('async'); +var watch = require('node-watch'); +var redis = require('redis'); + var dot = require('dot'); var express = require('express'); var bodyParser = require('body-parser'); var compress = require('compression'); -var watch = require('node-watch'); +var Stratum = require('stratum-pool'); +var util = require('stratum-pool/lib/util.js'); var api = require('./api.js'); module.exports = function(logger){ + dot.templateSettings.strip = false; + var portalConfig = JSON.parse(process.env.portalConfig); var poolConfigs = JSON.parse(process.env.pools); @@ -33,7 +39,8 @@ module.exports = function(logger){ 'stats.html': 'stats', 'tbs.html': 'tbs', 'api.html': 'api', - 'admin.html': 'admin' + 'admin.html': 'admin', + 'mining_key.html': 'mining_key' }; var pageTemplates = {}; @@ -41,6 +48,9 @@ module.exports = function(logger){ var pageProcessed = {}; var indexesProcessed = {}; + var keyScriptTemplate = ''; + var keyScriptProcessed = ''; + var processTemplates = function(){ @@ -113,6 +123,87 @@ module.exports = function(logger){ setInterval(buildUpdatedWebsite, websiteConfig.stats.updateInterval * 1000); + var buildKeyScriptPage = function(){ + async.waterfall([ + function(callback){ + var client = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); + client.hgetall('coinVersionBytes', function(err, coinBytes){ + if (err){ + client.quit(); + return callback('Failed grabbing coin version bytes from redis ' + JSON.stringify(err)); + } + callback(null, client, coinBytes || {}); + }); + }, + function (client, coinBytes, callback){ + var enabledCoins = Object.keys(poolConfigs).map(function(c){return c.toLowerCase()}); + var missingCoins = []; + enabledCoins.forEach(function(c){ + if (!(c in coinBytes)) + missingCoins.push(c); + }); + callback(null, client, coinBytes, missingCoins); + }, + function(client, coinBytes, missingCoins, callback){ + var coinsForRedis = {}; + async.each(missingCoins, function(c, cback){ + var coinInfo = (function(){ + for (var pName in poolConfigs){ + if (pName.toLowerCase() === c) + return { + daemon: poolConfigs[pName].shareProcessing.internal.daemon, + address: poolConfigs[pName].address + } + } + })(); + var daemon = new Stratum.daemon.interface([coinInfo.daemon]); + daemon.cmd('dumpprivkey', [coinInfo.address], function(result){ + if (result[0].error){ + logger.error(logSystem, 'daemon', 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error)); + cback(); + return; + } + + var vBytePub = util.getVersionByte(coinInfo.address)[0]; + var vBytePriv = util.getVersionByte(result[0].response)[0]; + + coinBytes[c] = vBytePub.toString() + ',' + vBytePriv.toString(); + coinsForRedis[c] = coinBytes[c]; + cback(); + }); + }, function(err){ + callback(null, client, coinBytes, coinsForRedis); + }); + }, + function(client, coinBytes, coinsForRedis, callback){ + if (Object.keys(coinsForRedis).length > 0){ + client.hmset('coinVersionBytes', coinsForRedis, function(err){ + if (err) + logger.error(logSystem, 'Init', 'Failed inserting coin byte version into redis ' + JSON.stringify(err)); + client.quit(); + }); + } + else{ + client.quit(); + } + callback(null, coinBytes); + } + ], function(err, coinBytes){ + if (err){ + logger.error(logSystem, 'Init', err); + return; + } + try{ + keyScriptTemplate = dot.template(fs.readFileSync('website/key.html', {encoding: 'utf8'})); + keyScriptProcessed = keyScriptTemplate({coins: coinBytes}); + } + catch(e){ + logger.error(logSystem, 'Init', 'Failed to read key.html file'); + } + }); + + }; + buildKeyScriptPage(); var getPage = function(pageId){ if (pageId in pageProcessed){ @@ -147,6 +238,10 @@ module.exports = function(logger){ next(); }); + app.get('/key.html', function(reg, res, next){ + res.end(keyScriptProcessed); + }); + app.get('/:page', route); app.get('/', route); diff --git a/website/key.html b/website/key.html new file mode 100644 index 0000000..9978e9e --- /dev/null +++ b/website/key.html @@ -0,0 +1,2798 @@ + + + + + + Mining Key Script + + + + + + + + +
    +

    Mining key generation or input options:

    + +
    +
    1)
    +
    Create new private key
    + +
    +
    - or-
    +
    +
    2)
    +
    Import existing private key
    + +
    +
    - or-
    +
    +
    3)
    +
    Input private key hex
    + + +
    Private key must be 64 hexadecimal characters
    +
    +
    + +
    + + + +
    +
    NO NOT LOSE THIS PRIVATE KEY. Any coins mined using this public key can + only be controlled with this private key.
    + +
    Private key:
    +
    Key for mining (hashed public key):
    + +

    + An address for any type of coin can be derived from this mining key - and each of those coin address + can only be controlled by this private key. +

    + +
    + +
    +
    Backup your private key
    + + + + + + + + + +
    Step 1) + +
    Step 2) + +
    +
    + +
    +
    Coin formatted keys
    + + +
    Public address
    + + +
    Private key in wallet import format
    + + +
    +
    How to import your private key for :
    +
      +
    1. Open your wallet app
    2. +
    3. Go to Help -> click Debug window -> click Console tab
    4. +
    5. Enter the following command: importprivkey
    6. +
    +
    + +
    + + +
    + + + + + + + + + \ No newline at end of file diff --git a/website/pages/mining_key.html b/website/pages/mining_key.html new file mode 100644 index 0000000..d380676 --- /dev/null +++ b/website/pages/mining_key.html @@ -0,0 +1,25 @@ + + +
    + +

    + This script run client-side (in your browser). For maximum security download the script and run it locally and + offline in a modern web browser. +

    + + + +
    From 850c2703816290208fe01d1b23a69b41d0820303 Mon Sep 17 00:00:00 2001 From: bitcoinland Date: Thu, 1 May 2014 00:17:19 -0300 Subject: [PATCH 065/150] removed extra " from algorithm --- coins/opensourcecoin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coins/opensourcecoin.json b/coins/opensourcecoin.json index 2867ae8..9b86d03 100644 --- a/coins/opensourcecoin.json +++ b/coins/opensourcecoin.json @@ -1,5 +1,5 @@ { "name": "OpenSourcecoin", "symbol": "OSC", - "algorithm": "sha256"" + "algorithm": "sha256" } From 13a22d0ed63dd20238856185ee188502a36b164d Mon Sep 17 00:00:00 2001 From: thrassos Date: Thu, 1 May 2014 20:01:54 +0300 Subject: [PATCH 066/150] Update poolWorker.js --- libs/poolWorker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 2fc114d..fc310b7 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -284,8 +284,8 @@ module.exports = function(logger){ var p = pools[coinName]; if (poolConfigs[coinName].coin.algorithm === algorithm) { for (var port in portalConfig.switching[switchName].ports) { - if (portalConfig.switching[switchName].ports[port].vardiff) - p.setVarDiff(port, portalConfig.switching[switchName].ports[port].vardiff); + if (portalConfig.switching[switchName].ports[port].varDiff) + p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); } } }); From 73668709ce2a73c36cb87b318bc2cc30bfef4d41 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 2 May 2014 15:59:46 -0600 Subject: [PATCH 067/150] Added rewardRecipients configuration, refactored payment processing, moved p2p magic to coin config --- README.md | 166 ++++--- coins/litecoin.json | 4 +- init.js | 9 +- libs/mposCompatibility.js | 40 +- libs/paymentProcessor.js | 698 +++++++++++------------------ libs/poolWorker.js | 100 ++--- libs/profitSwitch.js | 2 +- libs/shareProcessor.js | 7 +- libs/stats.js | 15 +- libs/website.js | 2 +- pool_configs/litecoin_example.json | 82 ++-- 11 files changed, 450 insertions(+), 675 deletions(-) diff --git a/README.md b/README.md index eeb7214..85333f5 100644 --- a/README.md +++ b/README.md @@ -284,13 +284,22 @@ Here is an example of the required fields: { "name": "Litecoin", "symbol": "ltc", - "algorithm": "scrypt", //or "sha256", "scrypt-jane", "scrypt-n", "quark", "x11" - "txMessages": false, //or true (not required, defaults to false) - "mposDiffMultiplier": 256, //only for x11 coins in mpos mode, set to 256 (optional) + "algorithm": "scrypt", + + /* 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 + + //"txMessages": false, //options - defaults to false + + //"mposDiffMultiplier": 256, //options - only for x11 coins in mpos mode } ```` -For additional documentation how to configure coins *(especially important for scrypt-n and scrypt-jane coins)* +For additional documentation how to configure coins and their different algorithms see [these instructions](//github.com/zone117x/node-stratum-pool#module-usage). @@ -307,6 +316,17 @@ Description of options: "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 /* How many milliseconds should have passed before new block transactions will trigger a new @@ -332,96 +352,68 @@ Description of options: miners/pools that deal with scrypt use a guesstimated one that is about 5.86% off from the actual one. So here we can set a tolerable threshold for if a share is slightly too low due to mining apps using incorrect max diffs and this pool using correct max diffs. */ - "shareVariancePercent": 10, + "shareVariancePercent": 2, /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy protocol enabled, such as HAProxy with 'send-proxy' param: http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ "tcpProxyProtocol": false, + /* To receive payments, miners must connect with their address or mining key as their username. + This option will only authenticate miners using an address or mining key. */ + "validateWorkerAddress": true, - /* This determines what to do with submitted shares (and stratum worker authentication). - You have two options: - 1) Enable internal and disable mpos = this portal to handle all share payments. - 2) Enable mpos and disable internal = shares will be inserted into MySQL database - for MPOS to process. */ - "shareProcessing": { + "paymentProcessing": { + "enabled": true, - "internal": { - "enabled": true, + /* Every this many seconds get submitted blocks from redis, use daemon RPC to check + their confirmation status, if confirmed then get shares from redis that contributed + to block and send out payments. */ + "paymentInterval": 30, - /* When workers connect, to receive payments, their address must be used as the worker - name. If this option is true, on worker authentication, their address will be - verified via a validateaddress API call to the daemon. Miners with invalid addresses - will be rejected. */ - "validateWorkerAddress": true, + /* Minimum number of coins that a miner must earn before sending payment. Typically, + a higher minimum means less transactions fees (you profit more) but miners see + payments less frequently (they dislike). Opposite for a lower minimum payment. */ + "minimumPayment": 0.01, - /* Every this many seconds get submitted blocks from redis, use daemon RPC to check - their confirmation status, if confirmed then get shares from redis that contributed - to block and send out payments. */ - "paymentInterval": 30, - - /* Minimum number of coins that a miner must earn before sending payment. Typically, - a higher minimum means less transactions fees (you profit more) but miners see - payments less frequently (they dislike). Opposite for a lower minimum payment. */ - "minimumPayment": 0.001, - - /* Minimum number of coins to keep in pool wallet. It is recommended to deposit at - at least this many coins into the pool wallet when first starting the pool. */ - "minimumReserve": 10, - - /* (2% default) What percent fee your pool takes from the block reward. */ - "feePercent": 0.02, - - /* Name of the daemon account to use when moving coin profit within daemon wallet. */ - "feeCollectAccount": "feesCollected", - - /* Your address that receives pool revenue from fees. */ - "feeReceiveAddress": "LZz44iyF4zLCXJTU8RxztyyJZBntdS6fvv", - - /* How many coins from fee revenue must accumulate on top of the - minimum reserve amount in order to trigger withdrawal to fee address. The higher - this threshold, the less of your profit goes to transactions fees. */ - "feeWithdrawalThreshold": 5, - - /* This daemon is used to send out payments. It MUST be for the daemon that owns the - configured 'address' that receives the block rewards, otherwise the daemon will not - be able to confirm blocks or send out payments. */ - "daemon": { - "host": "127.0.0.1", - "port": 19332, - "user": "litecoinrpc", - "password": "testnet" - }, - - /* Redis database used for storing share and block submission data. */ - "redis": { - "host": "127.0.0.1", - "port": 6379 - } - }, - - /* Enabled mpos and shares will be inserted into share table in a MySQL database. You may - also want to use the "emitInvalidBlockHashes" option below if you require it. */ - "mpos": { - "enabled": false, - "host": "127.0.0.1", //MySQL db host - "port": 3306, //MySQL db port - "user": "me", //MySQL db user - "password": "mypass", //MySQL db password - "database": "ltc", //MySQL db database name - - /* Unregistered workers can automatically be registered (added to database) on stratum - worker authentication if this is true. */ - "autoCreateWorker": false, - - /* For when miner's authenticate: set to "password" for both worker name and password to - be checked for in the database, set to "worker" for only work name to be checked, or - don't use this option (set to "none") for no auth checks */ - "stratumAuth": "password" + /* This daemon is used to send out payments. It MUST be for the daemon that owns the + configured 'address' that receives the block rewards, otherwise the daemon will not + be able to confirm blocks or send out payments. */ + "daemon": { + "host": "127.0.0.1", + "port": 19332, + "user": "litecoinrpc", + "password": "testnet" } }, + /* Redis database used for storing share and block submission data and payment processing. */ + "redis": { + "host": "127.0.0.1", + "port": 6379 + } + + /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want + to use the "emitInvalidBlockHashes" option below if you require it. The config options + "redis" and "paymentProcessing" will be ignored/unused if this is enabled. */ + "mposMode": { + "enabled": false, + "host": "127.0.0.1", //MySQL db host + "port": 3306, //MySQL db port + "user": "me", //MySQL db user + "password": "mypass", //MySQL db password + "database": "ltc", //MySQL db database name + + /* Checks for valid password in database when miners connect. */ + "checkPassword": true, + + /* Unregistered workers can automatically be registered (added to database) on stratum + worker authentication if this is true. */ + "autoCreateWorker": false + + + }, + /* If a worker is submitting a high threshold of invalid shares we can temporarily ban their IP to reduce system/network load. Also useful to fight against flooding attacks. If running behind something like HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up @@ -476,8 +468,8 @@ Description of options: /* 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 - intensive than blocknotify script). It requires additional setup: the 'magic' field must - be exact (extracted from the coin source code). */ + intensive than blocknotify script). It requires the additional field "peerMagic" in + the coin config. */ "p2p": { "enabled": false, @@ -490,13 +482,7 @@ Description of options: /* 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 transaction data. Assume its supported but if you have problems try disabling it. */ - "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" + "disableTransactions": true } } diff --git a/coins/litecoin.json b/coins/litecoin.json index 73307fb..588af55 100644 --- a/coins/litecoin.json +++ b/coins/litecoin.json @@ -1,5 +1,7 @@ { "name": "Litecoin", "symbol": "LTC", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "fbc0b6db", + "peerMagicTestnet": "fcc1b7dc" } \ No newline at end of file diff --git a/init.js b/init.js index bf5c0c9..aacb92e 100644 --- a/init.js +++ b/init.js @@ -161,13 +161,6 @@ var spawnPoolWorkers = function(){ Object.keys(poolConfigs).forEach(function(coin){ var p = poolConfigs[coin]; - var internalEnabled = p.shareProcessing && p.shareProcessing.internal && p.shareProcessing.internal.enabled; - var mposEnabled = p.shareProcessing && p.shareProcessing.mpos && p.shareProcessing.mpos.enabled; - - if (!internalEnabled && !mposEnabled){ - logger.error('Master', coin, 'Share processing is not configured so a pool cannot be started for this coin.'); - delete poolConfigs[coin]; - } if (!Array.isArray(p.daemons) || p.daemons.length < 1){ logger.error('Master', coin, 'No daemons configured so a pool cannot be started for this coin.'); @@ -364,7 +357,7 @@ var startPaymentProcessor = function(){ var enabledForAny = false; for (var pool in poolConfigs){ var p = poolConfigs[pool]; - var enabled = p.enabled && p.shareProcessing && p.shareProcessing.internal && p.shareProcessing.internal.enabled; + var enabled = p.enabled && p.paymentProcessing && p.paymentProcessing.enabled; if (enabled){ enabledForAny = true; break; diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 0a3b1c7..6f044c4 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -2,31 +2,30 @@ var mysql = require('mysql'); var cluster = require('cluster'); module.exports = function(logger, poolConfig){ - var mposConfig = poolConfig.shareProcessing.mpos; + var mposConfig = poolConfig.mposMode; var coin = poolConfig.coin.name; - var connection; + var connection = mysql.createPool({ + host: mposConfig.host, + port: mposConfig.port, + user: mposConfig.user, + password: mposConfig.password, + database: mposConfig.database + }); var logIdentify = 'MySQL'; var logComponent = coin; - function connect(){ - connection = mysql.createPool({ - host: mposConfig.host, - port: mposConfig.port, - user: mposConfig.user, - password: mposConfig.password, - database: mposConfig.database - }); - - - } - connect(); this.handleAuth = function(workerName, password, authCallback){ - + + if (poolConfig.validateWorkerUsername !== true && mposConfig.autoCreateWorker !== true){ + authCallback(true); + return; + } + connection.query( 'SELECT password FROM pool_worker WHERE username = LOWER(?)', [workerName.toLowerCase()], @@ -65,16 +64,15 @@ module.exports = function(logger, poolConfig){ } } ); - }else{ + } + else{ authCallback(false); } } - else if (mposConfig.stratumAuth === 'worker') - authCallback(true); - else if (result[0].password === password) - authCallback(true) - else + else if (mposConfig.checkPassword && result[0].password !== password) authCallback(false); + else + authCallback(true); } ); diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 638c64d..d3f2b51 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -13,9 +13,8 @@ module.exports = function(logger){ Object.keys(poolConfigs).forEach(function(coin) { var poolOptions = poolConfigs[coin]; - if (poolOptions.shareProcessing && - poolOptions.shareProcessing.internal && - poolOptions.shareProcessing.internal.enabled) + if (poolOptions.paymentProcessing && + poolOptions.paymentProcessing.enabled) enabledPools.push(coin); }); @@ -27,14 +26,14 @@ module.exports = function(logger){ coins.forEach(function(coin){ var poolOptions = poolConfigs[coin]; - var processingConfig = poolOptions.shareProcessing.internal; + var processingConfig = poolOptions.paymentProcessing; var logSystem = 'Payments'; var logComponent = coin; logger.debug(logSystem, logComponent, 'Payment processing setup to run every ' + processingConfig.paymentInterval + ' second(s) with daemon (' + processingConfig.daemon.user + '@' + processingConfig.daemon.host + ':' + processingConfig.daemon.port - + ') and redis (' + processingConfig.redis.host + ':' + processingConfig.redis.port + ')'); + + ') and redis (' + poolOptions.redis.host + ':' + poolOptions.redis.port + ')'); }); }); @@ -45,68 +44,64 @@ function SetupForPool(logger, poolOptions, setupFinished){ var coin = poolOptions.coin.name; - var processingConfig = poolOptions.shareProcessing.internal; + var processingConfig = poolOptions.paymentProcessing; var logSystem = 'Payments'; var logComponent = coin; - var processingPayments = true; + var daemon = new Stratum.daemon.interface([processingConfig.daemon]); + var redisClient = redis.createClient(poolOptions.redis.port, poolOptions.redis.host); - var daemon; - var redisClient; + var magnitude; + var minPaymentSatoshis; + var coinPrecision; + + var paymentInterval; async.parallel([ - function(callback){ - daemon = new Stratum.daemon.interface([processingConfig.daemon]); - daemon.once('online', function(){ - daemon.cmd('validateaddress', [poolOptions.address], function(result){ - if (!result[0].response || !result[0].response.ismine){ - logger.error(logSystem, logComponent, + daemon.cmd('validateaddress', [poolOptions.address], function(result) { + if (result.error){ + logger.error(logSystem, logComponent, 'Error with payment processing daemon ' + JSON.stringify(result.error)); + callback(true); + } + else if (!result.response || !result.response.ismine) { + logger.error(logSystem, logComponent, 'Daemon does not own pool address - payment processing can not be done with this daemon, ' - + JSON.stringify(result[0].response)); - return; - } + + JSON.stringify(result.response)); + callback(true); + } + else{ callback() - }); - }).once('connectionFailed', function(error){ - logger.error(logSystem, logComponent, 'Failed to connect to daemon for payment processing: config ' + - JSON.stringify(processingConfig.daemon) + ', error: ' + - JSON.stringify(error)); - callback('Error connecting to deamon'); - }).on('error', function(error){ - logger.error(logSystem, logComponent, 'Daemon error ' + JSON.stringify(error)); - }).init(); + } + }, true); }, function(callback){ - - redisClient = redis.createClient(processingConfig.redis.port, processingConfig.redis.host); - redisClient.on('ready', function(){ - if (callback) { - callback(); - callback = null; + daemon.cmd('getbalance', [], function(result){ + if (result.error){ + callback(true); return; } - logger.debug(logSystem, logComponent, 'Connected to redis at ' - + processingConfig.redis.host + ':' + processingConfig.redis.port + ' for payment processing'); - }).on('end', function(){ - logger.error(logSystem, logComponent, 'Connection to redis database as been ended'); - }).once('error', function(){ - if (callback) { - logger.error(logSystem, logComponent, 'Failed to connect to redis at ' - + processingConfig.redis.host + ':' + processingConfig.redis.port + ' for payment processing'); - callback('Error connecting to redis'); - callback = null; + try { + var d = result.data.split('result":')[1].split(',')[0].split('.')[1]; + magnitude = parseInt('10' + new Array(d.length).join('0')); + minPaymentSatoshis = parseInt(processingConfig.minimumPayment * magnitude); + coinPrecision = magnitude.toString().length - 1; + callback(); + } + catch(e){ + logger.error(logSystem, logComponent, 'Error detecting number of satoshis in a coin, cannot do payment processing'); + callback(true); } - }); + }, true, true); } ], function(err){ if (err){ setupFinished(false); return; } - setInterval(function(){ + paymentInterval = setInterval(function(){ try { processPayments(); } catch(e){ @@ -118,97 +113,74 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - /* Call redis to check if previous sendmany and/or redis cleanout commands completed successfully. - If sendmany worked fine but redis commands failed you HAVE TO run redis commands again - (manually) to prevent double payments. If sendmany failed too you can safely delete - coin + '_finalRedisCommands' string from redis to let pool calculate payments again. */ - function checkPreviousPaymentsStatus(callback) { - redisClient.get(coin + '_finalRedisCommands', function(error, reply) { - if (error){ - callback('Could not get finalRedisCommands - ' + JSON.stringify(error)); - return; - } - if (reply) { - callback('Payments stopped because of the critical error - failed commands saved in ' - + coin + '_finalRedisCommands redis set:\n' + reply); - return; - } else { - /* There was no error in previous sendmany and/or redis cleanout commands - so we can safely continue */ - processingPayments = false; - callback(); - } - }); - } - /* Number.toFixed gives us the decimal places we want, but as a string. parseFloat turns it back into number - we don't care about trailing zeros in this case. */ - var toPrecision = function(value, precision){ - return parseFloat(value.toFixed(precision)); + var satoshisToCoins = function(satoshis){ + return parseFloat((satoshis / magnitude).toFixed(coinPrecision)); }; - /* Deal with numbers in smallest possible units (satoshis) as much as possible. This greatly helps with accuracy when rounding and whatnot. When we are storing numbers for only humans to see, store in whole coin units. */ var processPayments = function(){ - var startPaymentProcess = Date.now(); - async.waterfall([ + var timeSpentRPC = 0; + var timeSpentRedis = 0; - function(callback) { - if (processingPayments) { - checkPreviousPaymentsStatus(function(error){ - if (error) { - logger.error(logSystem, logComponent, error); - callback('Check finished - previous payments processing error'); - return; - } - callback(); - }); - return; - } - callback(); - }, + var startTimeRedis; + var startTimeRPC; + + var startRedisTimer = function(){ startTimeRedis = Date.now() }; + var endRedisTimer = function(){ timeSpentRedis += Date.now() - startTimeRedis }; + + var startRPCTimer = function(){ startTimeRPC = Date.now(); }; + var endRPCTimer = function(){ timeSpentRPC += Date.now() - startTimeRedis }; + + async.waterfall([ /* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted blocks. */ function(callback){ - redisClient.smembers(coin + '_blocksPending', function(error, results){ + startRedisTimer(); + redisClient.multi([ + ['hgetall', coin + '_balances'], + ['smembers', coin + '_blocksPending'] + ]).exec(function(error, results){ + endRedisTimer(); if (error){ logger.error(logSystem, logComponent, 'Could not get blocks from redis ' + JSON.stringify(error)); - callback('Check finished - redis error for getting blocks'); - return; - } - if (results.length === 0){ - callback('Check finished - no pending blocks in redis'); + callback(true); return; } - var rounds = results.map(function(r){ + + + var workers = {}; + for (var w in results[0]){ + workers[w] = {balance: parseInt(results[0][w])}; + } + + var rounds = results[1].map(function(r){ var details = r.split(':'); return { - category: details[0].category, blockHash: details[0], txHash: details[1], height: details[2], - reward: details[3], serialized: r }; }); - callback(null, rounds); + callback(null, workers, rounds); }); }, /* Does a batch rpc call to daemon with all the transaction hashes to see if they are confirmed yet. It also adds the block reward amount to the round object - which the daemon gives also gives us. */ - function(rounds, callback){ + function(workers, rounds, callback){ var batchRPCcommand = rounds.map(function(r){ return ['gettransaction', [r.txHash]]; @@ -216,11 +188,14 @@ function SetupForPool(logger, poolOptions, setupFinished){ batchRPCcommand.push(['getaccount', [poolOptions.address]]); + startRPCTimer(); daemon.batchCmd(batchRPCcommand, function(error, txDetails){ + endRPCTimer(); if (error || !txDetails){ - callback('Check finished - daemon rpc error with batch gettransactions ' + - JSON.stringify(error)); + logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch gettransactions ' + + JSON.stringify(error)); + callback(true); return; } @@ -235,70 +210,51 @@ function SetupForPool(logger, poolOptions, setupFinished){ var round = rounds[i]; - if (tx.error && tx.error.code === -5 || round.blockHash !== tx.result.blockhash){ - - /* Block was dropped from coin daemon even after it happily accepted it earlier. */ - - //If we find another block at the same height then this block was drop-kicked orphaned - var dropKicked = rounds.filter(function(r){ - return r.height === round.height && r.blockHash !== round.blockHash && r.category !== 'dropkicked'; - }).length > 0; - - if (dropKicked){ - logger.warning(logSystem, logComponent, - 'A block was drop-kicked orphaned' - + ' - we found a better block at the same height, blockHash ' - + round.blockHash + " round " + round.height); - round.category = 'dropkicked'; - } - else{ - /* We have no other blocks that match this height so convert to orphan in order for - shares from the round to be rewarded. */ - round.category = 'orphan'; - } + if (tx.error && tx.error.code === -5){ + logger.error(logSystem, logComponent, 'Daemon reports invalid transaction ' + round.txHash + ' ' + + JSON.stringify(tx.error)); + return; } else if (tx.error || !tx.result){ - logger.error(logSystem, logComponent, - 'Error with requesting transaction from block daemon: ' + JSON.stringify(tx)); + logger.error(logSystem, logComponent, 'Odd error with gettransaction ' + round.txHash + ' ' + + JSON.stringify(tx)); + return; } - else{ - round.category = tx.result.details[0].category; - if (round.category === 'generate') - round.amount = tx.result.amount; + else if (round.blockHash !== tx.result.blockhash){ + logger.error(logSystem, logComponent, 'Daemon reports blockhash ' + tx.result.blockhash + + ' for tx ' + round.txHash + ' is not the one we have stored: ' + round.blockHash); + return; } + else if (!(tx.result.details instanceof Array)){ + logger.error(logSystem, logComponent, 'Details array missing from transaction ' + + round.txHash); + return; + } + + var generationTx = tx.result.details.filter(function(tx){ + return tx.address === poolOptions.address; + })[0]; + + if (!generationTx){ + logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + + round.txHash); + return; + } + + round.category = generationTx.category; + if (round.category === 'generate') { + round.reward = generationTx.amount; + } + + }); - var magnitude; - //Filter out all rounds that are immature (not confirmed or orphaned yet) rounds = rounds.filter(function(r){ switch (r.category) { - case 'generate': - /* Here we calculate the smallest unit in this coin's currency; the 'satoshi'. - The rpc.getblocktemplate.amount tells us how much we get in satoshis, while the - rpc.gettransaction.amount tells us how much we get in whole coin units. Therefore, - we simply divide the two to get the magnitude. I don't know math, there is probably - a better term than 'magnitude'. Sue me or do a pull request to fix it. */ - var roundMagnitude = Math.round(r.reward / r.amount); - - if (!magnitude) { - magnitude = roundMagnitude; - - if (roundMagnitude % 10 !== 0) - logger.error(logSystem, logComponent, - 'Satosihis in coin is not divisible by 10 which is very odd'); - } - else if (magnitude != roundMagnitude) { - /* Magnitude for a coin should ALWAYS be the same. For BTC and most coins there are - 100,000,000 satoshis in one coin unit. */ - logger.error(logSystem, logComponent, - 'Magnitude in a round was different than in another round. HUGE PROBLEM.'); - } return true; - - case 'dropkicked': case 'orphan': return true; default: @@ -307,35 +263,30 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - if (rounds.length === 0){ - callback('Check finished - no confirmed or orphaned blocks found'); - } - else{ - callback(null, rounds, magnitude, addressAccount); - } + callback(null, workers, rounds, addressAccount); + }); }, /* Does a batch redis call to get shares contributed to each round. Then calculates the reward amount owned to each miner for each round. */ - function(rounds, magnitude, addressAccount, callback){ + function(workers, rounds, addressAccount, callback){ var shareLookups = rounds.map(function(r){ return ['hgetall', coin + '_shares:round' + r.height] }); - + startRedisTimer(); redisClient.multi(shareLookups).exec(function(error, allWorkerShares){ + endRedisTimer(); + if (error){ - callback('Check finished - redis error with multi get rounds share') + callback('Check finished - redis error with multi get rounds share'); return; } - var orphanMergeCommands = []; - var workerRewards = {}; - rounds.forEach(function(round, i){ var workerShares = allWorkerShares[i]; @@ -352,282 +303,191 @@ function SetupForPool(logger, poolOptions, setupFinished){ miners still get a reward for their work. This seems unfair to those that just started mining during this current round, but over time it balances out and rewards loyal miners. */ - Object.keys(workerShares).forEach(function(worker){ - orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', - worker, workerShares[worker]]); - }); + round.workerShares = workerShares; break; case 'generate': /* We found a confirmed block! Now get the reward for it and calculate how much we owe each miner based on the shares they submitted during that block round. */ - var reward = round.reward * (1 - processingConfig.feePercent); + var reward = parseInt(round.reward * magnitude); var totalShares = Object.keys(workerShares).reduce(function(p, c){ return p + parseFloat(workerShares[c]) }, 0); - for (var worker in workerShares){ - var percent = parseFloat(workerShares[worker]) / totalShares; + for (var workerAddress in workerShares){ + var percent = parseFloat(workerShares[workerAddress]) / totalShares; var workerRewardTotal = Math.floor(reward * percent); - if (!(worker in workerRewards)) workerRewards[worker] = 0; - workerRewards[worker] += workerRewardTotal; + var worker = workers[workerAddress] = (workers[workerAddress] || {}); + worker.reward = (worker.reward || 0) + workerRewardTotal; } break; } - }); - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount); + callback(null, workers, rounds, addressAccount); }); }, - /* Does a batch call to redis to get worker existing balances from coin_balances*/ - function(rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount, callback){ - - var workers = Object.keys(workerRewards); - - redisClient.hmget([coin + '_balances'].concat(workers), function(error, results){ - if (error && workers.length !== 0){ - callback('Check finished - redis error with multi get balances ' + JSON.stringify(error)); - return; - } - - - var workerBalances = {}; - - for (var i = 0; i < workers.length; i++){ - workerBalances[workers[i]] = (parseInt(results[i]) || 0); - } - - - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount); - }); - - }, - - /* Calculate if any payments are ready to be sent and trigger them sending Get balance different for each address and pass it along as object of latest balances such as {worker1: balance1, worker2, balance2} when deciding the sent balance, it the difference should be -1*amount they had in db, if not sending the balance, the differnce should be +(the amount they earned this round) */ - function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount, callback){ + function(workers, rounds, addressAccount, callback) { - //number of satoshis in a single coin unit - this can be different for coins so we calculate it :) - - daemon.cmd('getbalance', [addressAccount || ''], function(results){ - - var totalBalance = results[0].response * magnitude; - var toBePaid = 0; - var workerPayments = {}; - - - var balanceUpdateCommands = []; - var workerPayoutsCommand = []; - - /* Here we add up all workers' previous unpaid balances plus their current rewards as we are - about to check if they reach the payout threshold. */ - for (var worker in workerRewards){ - workerPayments[worker] = ((workerPayments[worker] || 0) + workerRewards[worker]); - } - for (var worker in workerBalances){ - workerPayments[worker] = ((workerPayments[worker] || 0) + workerBalances[worker]); - } - - /* Here we check if any of the workers reached their payout threshold, or delete them from the - pending payment ledger (the workerPayments object). */ - if (Object.keys(workerPayments).length > 0){ - var coinPrecision = magnitude.toString().length - 1; - for (var worker in workerPayments){ - if (workerPayments[worker] < processingConfig.minimumPayment * magnitude){ - /* The workers total earnings (balance + current reward) was not enough to warrant - a transaction, so we will store their balance in the database. Next time they - are rewarded it might reach the payout threshold. */ - balanceUpdateCommands.push([ - 'hincrby', - coin + '_balances', - worker, - workerRewards[worker] - ]); - delete workerPayments[worker]; - } - else{ - //If worker had a balance that is about to be paid out, subtract it from the database - if (workerBalances[worker] !== 0){ - balanceUpdateCommands.push([ - 'hincrby', - coin + '_balances', - worker, - -1 * workerBalances[worker] - ]); - } - var rewardInPrecision = (workerRewards[worker] / magnitude).toFixed(coinPrecision); - workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', worker, rewardInPrecision]); - toBePaid += workerPayments[worker]; - } - } - - } - - // txfee included in feeAmountToBeCollected - var leftOver = toBePaid / (1 - processingConfig.feePercent); - var feeAmountToBeCollected = toPrecision(leftOver * processingConfig.feePercent, coinPrecision); - var balanceLeftOver = totalBalance - toBePaid - feeAmountToBeCollected; - var minReserveSatoshis = processingConfig.minimumReserve * magnitude; - if (balanceLeftOver < minReserveSatoshis){ - /* TODO: Need to convert all these variables into whole coin units before displaying because - humans aren't good at reading satoshi units. */ - callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' + - (toBePaid/magnitude) + ' and collect ' + (feeAmountToBeCollected/magnitude) + ' as fees' + - ' but only have ' + (totalBalance/magnitude) + '. Left over balance would be ' + (balanceLeftOver/magnitude) + - ', needs to be at least ' + (minReserveSatoshis/magnitude)); - return; - } - - - /* Move pending blocks into either orphan for confirmed sets, and delete their no longer - required round/shares data. */ - var movePendingCommands = []; - var roundsToDelete = []; - rounds.forEach(function(r){ - - var destinationSet = (function(){ - switch(r.category){ - case 'orphan': return '_blocksOrphaned'; - case 'generate': return '_blocksConfirmed'; - case 'dropkicked': return '_blocksDropKicked'; - } - })(); - movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]); - if (r.category === 'generate') - roundsToDelete.push(coin + '_shares:round' + r.height) - }); - - var finalRedisCommands = []; - - if (movePendingCommands.length > 0) - finalRedisCommands = finalRedisCommands.concat(movePendingCommands); - - if (orphanMergeCommands.length > 0) - finalRedisCommands = finalRedisCommands.concat(orphanMergeCommands); - - if (balanceUpdateCommands.length > 0) - finalRedisCommands = finalRedisCommands.concat(balanceUpdateCommands); - - if (workerPayoutsCommand.length > 0) - finalRedisCommands = finalRedisCommands.concat(workerPayoutsCommand); - - if (roundsToDelete.length > 0) - finalRedisCommands.push(['del'].concat(roundsToDelete)); - - if (toBePaid !== 0) - finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', (toBePaid / magnitude).toFixed(coinPrecision)]); - - finalRedisCommands.push(['del', coin + '_finalRedisCommands']); - - finalRedisCommands.push(['bgsave']); - - callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); - - }); - }, - - function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback) { - /* Save final redis cleanout commands in case something goes wrong during payments */ - redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { - if (error){ - callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); - return; - } - callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); - }); - }, - - function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback){ - - //This does the final all-or-nothing atom transaction if block deamon sent payments - var finalizeRedisTx = function(){ - redisClient.multi(finalRedisCommands).exec(function(error, results){ - if (error){ - callback('Error with final redis commands for cleaning up ' + JSON.stringify(error)); - return; - } - processingPayments = false; - logger.debug(logSystem, logComponent, 'Payments processing performed an interval'); - }); - }; - - if (Object.keys(workerPayments).length === 0){ - finalizeRedisTx(); - } - else{ - - //This is how many decimal places to round a coin down to - var coinPrecision = magnitude.toString().length - 1; + var trySend = function (withholdPercent) { var addressAmounts = {}; - var totalAmountUnits = 0; - for (var address in workerPayments){ - var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision); - var properAddress = getProperAddress(address); - if (!properAddress){ - logger.error(logSystem, logComponent, 'Could not convert pubkey ' + address + ' into address'); - continue; + var totalSent = 0; + for (var w in workers) { + var worker = workers[w]; + worker.balance = worker.balance || 0; + worker.reward = worker.reward || 0; + var toSend = (worker.balance + worker.reward) * (1 - withholdPercent); + if (toSend >= minPaymentSatoshis) { + totalSent += toSend; + var address = worker.address = (worker.address || getProperAddress(w)); + worker.sent = addressAmounts[address] = satoshisToCoins(toSend); + worker.balanceChange = Math.min(worker.balance, toSend) * -1; + } + else { + worker.balanceChange = Math.max(toSend - worker.balance, 0); + worker.sent = 0; } - addressAmounts[properAddress] = coinUnits; - totalAmountUnits += coinUnits; } - logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts)); + if (Object.keys(addressAmounts).length === 0){ + callback(null, workers, rounds); + return; + } - processingPayments = true; - daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function(results){ - - if (results[0].error){ - callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error)); - return; + daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function (result) { + if (result.error && result.error.code === -6) { + var higherPercent = withholdPercent + 0.01; + console.log('asdfasdfsadfasdf'); + logger.warning(logSystem, logComponent, 'Not enough funds to send out payments, decreasing rewards by ' + + (higherPercent * 100) + '% and retrying'); + trySend(higherPercent); } - - finalizeRedisTx(); - - var totalWorkers = Object.keys(workerPayments).length; - - logger.debug(logSystem, logComponent, 'Payments sent, a total of ' + totalAmountUnits - + ' ' + poolOptions.coin.symbol + ' was sent to ' + totalWorkers + ' miners'); - - daemon.cmd('gettransaction', [results[0].response], function(results){ - if (results[0].error){ - callback('Check finished - error with gettransaction ' + JSON.stringify(results[0].error)); - return; + else if (result.error) { + logger.error(logSystem, logComponent, 'Error trying to send payments wtih RCP sendmany ' + + JSON.stringify(result.error)); + callback(true); + } + else { + logger.debug(logSystem, logComponent, 'Sent out a total of ' + (totalSent / magnitude) + + ' to ' + Object.keys(addressAmounts).length + ' workers'); + if (withholdPercent > 0) { + logger.warning(logSystem, logComponent, 'Had to withhold ' + (withholdPercent * 100) + + '% of reward from miners to cover transaction fees. ' + + 'Fund pool wallet with coins to prevent this from happening'); } - var feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision)); - var poolFees = feeAmountUnits - results[0].response.fee; - daemon.cmd('move', [addressAccount || '', processingConfig.feeCollectAccount, poolFees], function(results){ - if (results[0].error){ - callback('Check finished - error with move ' + JSON.stringify(results[0].error)); - return; - } - callback(null, poolFees + ' ' + poolOptions.coin.symbol + ' collected as pool fee'); - }); - }); - }); - } - } - ], function(error, result){ + callback(null, workers, rounds); + } + }, true, true); + }; + trySend(0); + }, + function(workers, rounds, callback){ + + var totalPaid = 0; + + var balanceUpdateCommands = []; + var workerPayoutsCommand = []; + + for (var w in workers) { + var worker = workers[w]; + if (worker.balanceChange !== 0){ + balanceUpdateCommands.push([ + 'hincrby', + coin + '_balances', + w, + worker.balanceChange + ]); + } + if (worker.sent !== 0){ + workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', w, worker.sent]); + totalPaid += worker.sent; + } + } + + + + var movePendingCommands = []; + var roundsToDelete = []; + var orphanMergeCommands = []; + + rounds.forEach(function(r){ + + switch(r.category){ + case 'orphan': + movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksOrphaned', r.serialized]); + var workerShares = r.workerShares; + Object.keys(workerShares).forEach(function(worker){ + orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', + worker, workerShares[worker]]); + }); + break; + case 'generate': + movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksConfirmed', r.serialized]); + roundsToDelete.push(coin + '_shares:round' + r.height); + break; + } + + }); + + var finalRedisCommands = []; + + if (movePendingCommands.length > 0) + finalRedisCommands = finalRedisCommands.concat(movePendingCommands); + + if (orphanMergeCommands.length > 0) + finalRedisCommands = finalRedisCommands.concat(orphanMergeCommands); + + if (balanceUpdateCommands.length > 0) + finalRedisCommands = finalRedisCommands.concat(balanceUpdateCommands); + + if (workerPayoutsCommand.length > 0) + finalRedisCommands = finalRedisCommands.concat(workerPayoutsCommand); + + if (roundsToDelete.length > 0) + finalRedisCommands.push(['del'].concat(roundsToDelete)); + + if (totalPaid !== 0) + finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', totalPaid]); + + if (finalRedisCommands.length === 0){ + callback(); + return; + } + + startRedisTimer(); + redisClient.multi(finalRedisCommands).exec(function(error, results){ + endRedisTimer(); + if (error){ + clearInterval(paymentInterval); + logger.error(logSystem, logComponent, + 'Payments sent but could not update redis. ' + JSON.stringify(error) + + ' Disabling payment processing to prevent possible double-payouts. The redis commands in ' + + coin + '_finalRedisCommands.txt must be ran manually'); + fs.writeFile(coin + '_finalRedisCommands.txt', JSON.stringify(finalRedisCommands), function(err){ + logger.error('Could not write finalRedisCommands.txt, you are fucked.'); + }); + } + callback(); + }); + } + + ], function(){ var paymentProcessTime = Date.now() - startPaymentProcess; + logger.debug(logSystem, logComponent, 'Finished interval - time spent: ' + + paymentProcessTime + 'ms total, ' + timeSpentRedis + 'ms redis, ' + + timeSpentRPC + 'ms daemon RPC'); - if (error) - logger.debug(logSystem, logComponent, '[Took ' + paymentProcessTime + 'ms] ' + error); - - else{ - logger.debug(logSystem, logComponent, '[' + paymentProcessTime + 'ms] ' + result); - // not sure if we need some time to let daemon update the wallet balance - setTimeout(withdrawalProfit, 1000); - } }); }; @@ -639,37 +499,5 @@ function SetupForPool(logger, poolOptions, setupFinished){ else return address; }; - var withdrawalProfit = function(){ - if (!processingConfig.feeWithdrawalThreshold) return; - - logger.debug(logSystem, logComponent, 'Profit withdrawal started'); - daemon.cmd('getbalance', [processingConfig.feeCollectAccount], function(results){ - - // We have to pay some tx fee here too but maybe we shoudn't really care about it too much as long as fee is less - // then minimumReserve value. Because in this case even if feeCollectAccount account will have negative balance - // total wallet balance will be positive and feeCollectAccount account will be refilled during next payment processing. - var withdrawalAmount = results[0].response; - - if (withdrawalAmount < processingConfig.feeWithdrawalThreshold){ - logger.debug(logSystem, logComponent, 'Not enough profit to withdraw yet'); - } - else{ - - var withdrawal = {}; - withdrawal[processingConfig.feeReceiveAddress] = withdrawalAmount; - - daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){ - if (results[0].error){ - logger.debug(logSystem, logComponent, 'Profit withdrawal of ' + withdrawalAmount + ' failed - error with sendmany ' - + JSON.stringify(results[0].error)); - return; - } - logger.debug(logSystem, logComponent, 'Profit sent, a total of ' + withdrawalAmount - + ' ' + poolOptions.coin.symbol + ' was sent to ' + processingConfig.feeReceiveAddress); - }); - } - }); - - }; } \ No newline at end of file diff --git a/libs/poolWorker.js b/libs/poolWorker.js index fc310b7..faeb247 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -108,10 +108,8 @@ module.exports = function(logger){ diff: function(){} }; - var shareProcessing = poolOptions.shareProcessing; - //Functions required for MPOS compatibility - if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){ + if (poolOptions.mposMode && poolOptions.mposMode.enabled){ var mposCompat = new MposCompatibility(logger, poolOptions); handlers.auth = function(port, workerName, password, authCallback){ @@ -128,12 +126,12 @@ module.exports = function(logger){ } //Functions required for internal payment processing - else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){ + else{ var shareProcessor = new ShareProcessor(logger, poolOptions); handlers.auth = function(port, workerName, password, authCallback){ - if (shareProcessing.internal.validateWorkerAddress !== true) + if (poolOptions.validateWorkerUsername !== true) authCallback(true); else { port = port.toString(); @@ -238,10 +236,7 @@ module.exports = function(logger){ });*/ redisClient.hgetall("proxyState", function(error, obj) { - if (error || obj == null) { - //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); - } - else { + if (!error && obj) { proxyState = obj; logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); } @@ -258,64 +253,49 @@ module.exports = function(logger){ var algorithm = portalConfig.switching[switchName].algorithm; - if (portalConfig.switching[switchName].enabled === true) { - var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); - proxySwitch[switchName] = { - algorithm: algorithm, - ports: portalConfig.switching[switchName].ports, - currentPool: initalPool, - servers: [] - }; + if (!portalConfig.switching[switchName].enabled) return; - // Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up - // - // Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was - // routed into instead. - // - /*if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { - proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff); - proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff; - }*/ + var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); + proxySwitch[switchName] = { + algorithm: algorithm, + ports: portalConfig.switching[switchName].ports, + currentPool: initalPool, + servers: [] + }; - - Object.keys(pools).forEach(function (coinName) { - var p = pools[coinName]; - if (poolConfigs[coinName].coin.algorithm === algorithm) { - for (var port in portalConfig.switching[switchName].ports) { - if (portalConfig.switching[switchName].ports[port].varDiff) - p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); - } + Object.keys(pools).forEach(function (coinName) { + var p = pools[coinName]; + if (poolConfigs[coinName].coin.algorithm === algorithm) { + for (var port in portalConfig.switching[switchName].ports) { + if (portalConfig.switching[switchName].ports[port].varDiff) + p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); } + } + }); + + + Object.keys(proxySwitch[switchName].ports).forEach(function(port){ + var f = net.createServer(function(socket) { + var currentPool = proxySwitch[switchName].currentPool; + + logger.debug(logSystem, 'Connect', logSubCat, 'Connection to ' + + switchName + ' from ' + + socket.remoteAddress + ' on ' + + port + ' routing to ' + currentPool); + + pools[currentPool].getStratumServer().handleNewClient(socket); + + }).listen(parseInt(port), function() { + logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName + + '" listening for ' + algorithm + + ' on port ' + port + + ' into ' + proxySwitch[switchName].currentPool); }); + proxySwitch[switchName].servers.push(f); + }); - - Object.keys(proxySwitch[switchName].ports).forEach(function(port){ - var f = net.createServer(function(socket) { - var currentPool = proxySwitch[switchName].currentPool; - - logger.debug(logSystem, 'Connect', logSubCat, 'Connection to ' - + switchName + ' from ' - + socket.remoteAddress + ' on ' - + port + ' routing to ' + currentPool); - - pools[currentPool].getStratumServer().handleNewClient(socket); - - }).listen(parseInt(port), function() { - logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName - + '" listening for ' + algorithm - + ' on port ' + port - + ' into ' + proxySwitch[switchName].currentPool); - }); - proxySwitch[switchName].servers.push(f); - }); - - - } - else { - //logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); - } }); }); } diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 6e0e6c6..9836f2f 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -399,7 +399,7 @@ module.exports = function(logger){ Object.keys(profitStatus[algo]).forEach(function(symbol){ var coinName = profitStatus[algo][symbol].name; var poolConfig = poolConfigs[coinName]; - var daemonConfig = poolConfig.shareProcessing.internal.daemon; + var daemonConfig = poolConfig.paymentProcessing.daemon; daemonTasks.push(function(callback){ _this.getDaemonInfoForCoin(symbol, daemonConfig, callback) }); diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index a4e88c9..62d6444 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -16,8 +16,7 @@ value: a hash with.. module.exports = function(logger, poolConfig){ - var internalConfig = poolConfig.shareProcessing.internal; - var redisConfig = internalConfig.redis; + var redisConfig = poolConfig.redis; var coin = poolConfig.coin.name; var forkId = process.env.forkId; @@ -60,7 +59,7 @@ module.exports = function(logger, poolConfig){ if (isValidBlock){ redisCommands.push(['rename', coin + '_shares:roundCurrent', coin + '_shares:round' + shareData.height]); - redisCommands.push(['sadd', coin + '_blocksPending', [shareData.blockHash, shareData.txHash, shareData.height, shareData.reward].join(':')]); + redisCommands.push(['sadd', coin + '_blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); redisCommands.push(['hincrby', coin + '_stats', 'validBlocks', 1]); } else if (shareData.blockHash){ @@ -70,8 +69,6 @@ module.exports = function(logger, poolConfig){ connection.multi(redisCommands).exec(function(err, replies){ if (err) logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err)); - //else - //logger.debug(logSystem, logComponent, logSubCat, 'Share data and stats recorded'); }); diff --git a/libs/stats.js b/libs/stats.js index 527e19d..340f2d0 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -35,14 +35,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ var poolConfig = poolConfigs[coin]; - if (!poolConfig.shareProcessing || !poolConfig.shareProcessing.internal){ - logger.error(logSystem, coin, 'Cannot do stats without internal share processing setup'); - canDoStats = false; - return; - } - - var internalConfig = poolConfig.shareProcessing.internal; - var redisConfig = internalConfig.redis; + var redisConfig = poolConfig.redis; for (var i = 0; i < redisClients.length; i++){ var client = redisClients[i]; @@ -115,7 +108,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ var redisCommands = []; - var redisComamndTemplates = [ + var redisCommandTemplates = [ ['zremrangebyscore', '_hashrate', '-inf', '(' + windowTime], ['zrangebyscore', '_hashrate', windowTime, '+inf'], ['hgetall', '_stats'], @@ -124,10 +117,10 @@ module.exports = function(logger, portalConfig, poolConfigs){ ['scard', '_blocksOrphaned'] ]; - var commandsPerCoin = redisComamndTemplates.length; + var commandsPerCoin = redisCommandTemplates.length; client.coins.map(function(coin){ - redisComamndTemplates.map(function(t){ + redisCommandTemplates.map(function(t){ var clonedTemplates = t.slice(0); clonedTemplates[1] = coin + clonedTemplates[1]; redisCommands.push(clonedTemplates); diff --git a/libs/website.js b/libs/website.js index 3af59c5..500428b 100644 --- a/libs/website.js +++ b/libs/website.js @@ -151,7 +151,7 @@ module.exports = function(logger){ for (var pName in poolConfigs){ if (pName.toLowerCase() === c) return { - daemon: poolConfigs[pName].shareProcessing.internal.daemon, + daemon: poolConfigs[pName].paymentProcessing.daemon, address: poolConfigs[pName].address } } diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index 7f75c58..a2cdcdf 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -3,6 +3,13 @@ "coin": "litecoin.json", "address": "n4jSe18kZMCdGcZqaYprShXW6EH1wivUK1", + + "rewardRecipients": { + "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, + "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, + "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 + }, + "blockRefreshInterval": 1000, "txRefreshInterval": 20000, "jobRebroadcastTimeout": 55, @@ -12,50 +19,47 @@ "tcpProxyProtocol": false, - "shareProcessing": { - "internal": { - "enabled": true, - "validateWorkerAddress": true, - "paymentInterval": 20, - "minimumPayment": 70, - "minimumReserve": 10, - "feePercent": 0.05, - "feeCollectAccount": "feesCollected", - "feeReceiveAddress": "mppaGeNaSbG1Q7S6V3gL5uJztMhucgL9Vh", - "feeWithdrawalThreshold": 5, - "daemon": { - "host": "127.0.0.1", - "port": 19332, - "user": "litecoinrpc", - "password": "testnet" - }, - "redis": { - "host": "127.0.0.1", - "port": 6379 - } - }, - "mpos": { - "enabled": false, + "validateWorkerUsername": true, + + "paymentProcessing": { + "enabled": true, + "paymentInterval": 20, + "minimumPayment": 70, + "daemon": { "host": "127.0.0.1", - "port": 3306, - "user": "me", - "password": "mypass", - "database": "ltc", - "stratumAuth": "password" + "port": 19332, + "user": "litecoinrpc", + "password": "testnet" } }, + "redis": { + "host": "127.0.0.1", + "port": 6379 + }, + + "mposMode": { + "enabled": false, + "host": "127.0.0.1", + "port": 3306, + "user": "me", + "password": "mypass", + "database": "ltc", + "checkPassword": true, + "autoCreateWorker": false + }, + "banning": { "enabled": true, - "time": 600, + "time": 300, "invalidPercent": 50, - "checkThreshold": 500, + "checkThreshold": 10, "purgeInterval": 300 }, "ports": { "3008": { - "diff": 8 + "diff": 4 }, "3032": { "diff": 32, @@ -78,20 +82,14 @@ "port": 19332, "user": "litecoinrpc", "password": "testnet" - }, - { - "host": "127.0.0.1", - "port": 19344, - "user": "litecoinrpc", - "password": "testnet" } ], "p2p": { - "enabled": false, + "enabled": true, "host": "127.0.0.1", "port": 19333, - "disableTransactions": true, - "magic": "fcc1b7dc" + "disableTransactions": true } -} + +} \ No newline at end of file From c417fdf96b82f1a58052855b3a84be2b65594dc4 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 2 May 2014 17:01:57 -0600 Subject: [PATCH 068/150] Typo fixes in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85333f5..d6c3d8f 100644 --- a/README.md +++ b/README.md @@ -317,7 +317,7 @@ Description of options: "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. + except for a percentage 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 @@ -391,7 +391,7 @@ Description of options: "redis": { "host": "127.0.0.1", "port": 6379 - } + }, /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want to use the "emitInvalidBlockHashes" option below if you require it. The config options From 6984c4fccc8a213d6d3f48e8adf6151cbc66e8a9 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 3 May 2014 04:09:51 -0600 Subject: [PATCH 069/150] Typo fixes in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6c3d8f..747ae00 100644 --- a/README.md +++ b/README.md @@ -361,7 +361,7 @@ Description of options: /* To receive payments, miners must connect with their address or mining key as their username. This option will only authenticate miners using an address or mining key. */ - "validateWorkerAddress": true, + "validateWorkerUsername": true, "paymentProcessing": { "enabled": true, From 3a5fc1e281ba582ee5acbe84254448e13358662d Mon Sep 17 00:00:00 2001 From: Ondalf Date: Sat, 3 May 2014 13:19:53 +0300 Subject: [PATCH 070/150] Update dogecoin.json Updated peerMagic and peerMagicTestnet for Dogecoin according to latest updates of main nomp repo. --- coins/dogecoin.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coins/dogecoin.json b/coins/dogecoin.json index 88e28dc..50962d3 100644 --- a/coins/dogecoin.json +++ b/coins/dogecoin.json @@ -1,5 +1,7 @@ { "name": "Dogecoin", "symbol": "DOGE", - "algorithm": "scrypt" -} \ No newline at end of file + "algorithm": "scrypt", + "peerMagic": "c0c0c0c0", + "peerMagicTestnet": "fcc1b7dc" +} From 7cdf87d9607774681591037b40b1663e5c56b762 Mon Sep 17 00:00:00 2001 From: Ondalf Date: Sat, 3 May 2014 13:22:41 +0300 Subject: [PATCH 071/150] Create saffroncoin.json Added saffroncoin into /coins. --- coins/saffroncoin.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 coins/saffroncoin.json diff --git a/coins/saffroncoin.json b/coins/saffroncoin.json new file mode 100644 index 0000000..af79a75 --- /dev/null +++ b/coins/saffroncoin.json @@ -0,0 +1,7 @@ +{ + "name": "saffroncoin", + "symbol": "SAF", + "algorithm": "scrypt", + "peerMagic": "fbc0b6db", + "peerMagicTestnet": "01f555a4" +} From b07fd03898c0eadf775c42dbe6009ece9c5093c7 Mon Sep 17 00:00:00 2001 From: Ari Velakoski Date: Sat, 3 May 2014 16:04:55 +0300 Subject: [PATCH 072/150] Update saffroncoin.json Typo in SFR coin. --- coins/saffroncoin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coins/saffroncoin.json b/coins/saffroncoin.json index af79a75..67f72df 100644 --- a/coins/saffroncoin.json +++ b/coins/saffroncoin.json @@ -1,6 +1,6 @@ { "name": "saffroncoin", - "symbol": "SAF", + "symbol": "SFR", "algorithm": "scrypt", "peerMagic": "fbc0b6db", "peerMagicTestnet": "01f555a4" From af3115ba7cb24ee31d37852b5c017a491f8ff194 Mon Sep 17 00:00:00 2001 From: Ari Velakoski Date: Sat, 3 May 2014 16:26:50 +0300 Subject: [PATCH 073/150] Update saffroncoin.json Since I seem to lost this every time... https://github.com/saffroncoin/saffroncoin/blob/master/src/chainparams.cpp#L25 --- coins/saffroncoin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coins/saffroncoin.json b/coins/saffroncoin.json index 67f72df..e96e720 100644 --- a/coins/saffroncoin.json +++ b/coins/saffroncoin.json @@ -2,6 +2,6 @@ "name": "saffroncoin", "symbol": "SFR", "algorithm": "scrypt", - "peerMagic": "fbc0b6db", + "peerMagic": "cf0567ea", "peerMagicTestnet": "01f555a4" } From 29d1cc455f06e7151927f219e3c5c0297319aab7 Mon Sep 17 00:00:00 2001 From: Lucas Jones Date: Sat, 3 May 2014 17:29:31 +0100 Subject: [PATCH 074/150] Fix typo in paymentProcessor.js --- libs/paymentProcessor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index d3f2b51..6536a9d 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -372,7 +372,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ trySend(higherPercent); } else if (result.error) { - logger.error(logSystem, logComponent, 'Error trying to send payments wtih RCP sendmany ' + logger.error(logSystem, logComponent, 'Error trying to send payments with RPC sendmany ' + JSON.stringify(result.error)); callback(true); } @@ -500,4 +500,4 @@ function SetupForPool(logger, poolOptions, setupFinished){ }; -} \ No newline at end of file +} From 7b5462dbb0c1860080c45d7e8e36b12740845d48 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sat, 3 May 2014 17:07:27 -0300 Subject: [PATCH 075/150] Added peerMagic to GlobalDenomination --- coins/globaldenomination.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coins/globaldenomination.json b/coins/globaldenomination.json index d43c28b..69cb10d 100644 --- a/coins/globaldenomination.json +++ b/coins/globaldenomination.json @@ -1,5 +1,7 @@ { "name": "GlobalDenomination", "symbol": "GDN", - "algorithm": "x11" + "algorithm": "x11", + "peerMagic": "fec3b9de", + "peerMagicTestnet": "fec4bade" } From 4e872b97769cdde22c1e63e92614f44fdd79defa Mon Sep 17 00:00:00 2001 From: SweetJustice Date: Sun, 4 May 2014 02:26:43 +0100 Subject: [PATCH 076/150] Update battlecoin.json Magic bits --- coins/battlecoin.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coins/battlecoin.json b/coins/battlecoin.json index 1e7fd9e..8bc5498 100644 --- a/coins/battlecoin.json +++ b/coins/battlecoin.json @@ -1,5 +1,7 @@ { "name": "Battlecoin", "symbol": "BCX", - "algorithm": "sha256" + "algorithm": "sha256", + "peerMagic": "03e803e4", + "peerMagicTestnet": "cdf2c0ef" } From cc3fe3ba8eda51f128cd80a681d643919b7394a2 Mon Sep 17 00:00:00 2001 From: zacons Date: Sun, 4 May 2014 09:44:53 +0200 Subject: [PATCH 077/150] Update cliListener.js --- libs/cliListener.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/cliListener.js b/libs/cliListener.js index efb18cf..aa4a0e9 100644 --- a/libs/cliListener.js +++ b/libs/cliListener.js @@ -26,6 +26,9 @@ var listener = module.exports = function listener(port){ }); c.on('end', function () { + }); + c.on('error', function () { + }); } catch(e){ From 81c3a66bde743e2ca9bfdc0b9dcb08e084641143 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 02:21:15 -0600 Subject: [PATCH 078/150] Fixed "missing output details to pool address" error messages for some types of coins --- libs/paymentProcessor.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 6536a9d..12513fc 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -235,6 +235,11 @@ function SetupForPool(logger, poolOptions, setupFinished){ return tx.address === poolOptions.address; })[0]; + + if (!generationTx && tx.result.details.length === 1){ + generationTx = tx.results.details[0]; + } + if (!generationTx){ logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + round.txHash); @@ -243,7 +248,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ round.category = generationTx.category; if (round.category === 'generate') { - round.reward = generationTx.amount; + round.reward = generationTx.amount || generationTx.value; } From b1e05dda1d1edaa8c3a3c09ea0b2e8c31a9b6614 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 4 May 2014 19:55:12 +1000 Subject: [PATCH 079/150] Added Globalcoin --- coins/globalcoin.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 coins/globalcoin.json diff --git a/coins/globalcoin.json b/coins/globalcoin.json new file mode 100644 index 0000000..d2d3427 --- /dev/null +++ b/coins/globalcoin.json @@ -0,0 +1,7 @@ +{ + "name": "Globalcoin", + "symbol": "GLC", + "algorithm": "scrypt", + "peerMagic": "fcd9b7dd", + "peerMagicTestnet": "fbc0b8db" +} From fd7bf439e5d3396936f952b343bae8271197953e Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 13:51:01 -0600 Subject: [PATCH 080/150] Fixed typo in my last commit that fixed payment processing for some coins. --- libs/paymentProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 12513fc..bcccb74 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -237,7 +237,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ if (!generationTx && tx.result.details.length === 1){ - generationTx = tx.results.details[0]; + generationTx = tx.result.details[0]; } if (!generationTx){ From 964335340b604427e5b1b6d7e39831230c319f09 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 15:29:59 -0600 Subject: [PATCH 081/150] More informative error when failing to parse satoshis in coin for payment processing --- libs/paymentProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index bcccb74..1e48e49 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -90,7 +90,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ callback(); } catch(e){ - logger.error(logSystem, logComponent, 'Error detecting number of satoshis in a coin, cannot do payment processing'); + logger.error(logSystem, logComponent, 'Error detecting number of satoshis in a coin, cannot do payment processing. Tried parsing: ' + result.data); callback(true); } From cbebb94a57ce7a571ff571458c9b0932b130b84a Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 15:51:48 -0600 Subject: [PATCH 082/150] Slight readme update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 747ae00..63afaf5 100644 --- a/README.md +++ b/README.md @@ -317,13 +317,15 @@ Description of options: "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 percentage that can go to pool operator(s) as pool fees or donations. - Addresses or hashed public keys can be used. */ + except for a percentage that can go to, for examples, pool operator(s) as pool fees or + or to donations address. Addresses or hashed public keys can be used. Here is an example + of rewards going to the main pool op, a pool co-owner, and NOMP donation. */ "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 + //This NOMP pubkey and accept any type of coin "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, From 8ccf9474823d583aa0413f2173422e7246c8ed3d Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Sun, 4 May 2014 18:53:06 -0600 Subject: [PATCH 083/150] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63afaf5..80b57fa 100644 --- a/README.md +++ b/README.md @@ -324,8 +324,8 @@ Description of options: "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 - //This NOMP pubkey and accept any type of coin + //0.1% donation to NOMP. This pubkey can accept any type of coin, please leave this in + your config to help support NOMP development. "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, From d562bb2a7e1b68ee879d60e56f47228d5ef22def Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Sun, 4 May 2014 18:56:13 -0600 Subject: [PATCH 084/150] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 80b57fa..34ccac4 100644 --- a/README.md +++ b/README.md @@ -324,8 +324,8 @@ Description of options: "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. This pubkey can accept any type of coin, please leave this in - your config to help support NOMP development. + /* 0.1% donation to NOMP. This pubkey can accept any type of coin, please leave this in + your config to help support NOMP development. */ "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, From 86bd8d91fae7706ac00b290380a3c1598cfdc4ca Mon Sep 17 00:00:00 2001 From: mcpackin Date: Mon, 5 May 2014 08:55:36 +0000 Subject: [PATCH 085/150] Added/updated coins. --- coins/fastcoin.json | 3 ++- coins/starcoin.json | 3 ++- coins/ultimatecoin.json | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 coins/ultimatecoin.json diff --git a/coins/fastcoin.json b/coins/fastcoin.json index 077f767..c1af320 100644 --- a/coins/fastcoin.json +++ b/coins/fastcoin.json @@ -1,5 +1,6 @@ { "name": "Fastcoin", "symbol": "FST", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "fbc0b6db" } \ No newline at end of file diff --git a/coins/starcoin.json b/coins/starcoin.json index ca65a81..65f5f42 100644 --- a/coins/starcoin.json +++ b/coins/starcoin.json @@ -1,5 +1,6 @@ { "name": "Starcoin", "symbol": "STR", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "e4e8effd" } diff --git a/coins/ultimatecoin.json b/coins/ultimatecoin.json new file mode 100644 index 0000000..1df3370 --- /dev/null +++ b/coins/ultimatecoin.json @@ -0,0 +1,6 @@ +{ + "name": "Ultimatecoin", + "symbol": "ULT", + "algorithm": "scrypt", + "peerMagic": "f9f7c0e8" +} From 5d8138c3a2d45670f9ca1e65f8d8466220c95e0b Mon Sep 17 00:00:00 2001 From: mcpackin Date: Mon, 5 May 2014 09:04:29 +0000 Subject: [PATCH 086/150] Added grandcoin.json --- coins/grandcoin.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 coins/grandcoin.json diff --git a/coins/grandcoin.json b/coins/grandcoin.json new file mode 100644 index 0000000..748d3cf --- /dev/null +++ b/coins/grandcoin.json @@ -0,0 +1,7 @@ +{ + "name": "Grandcoin", + "symbol": "GDC", + "algorithm": "scrypt", + "peerMagic": "fdc1a5db", + "txMessages": true +} From c3a15ad200d6e5095931af06af48e43a31676e6b Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 6 May 2014 20:30:31 -0600 Subject: [PATCH 087/150] Updated to support slightly different logging implemented while doing new Darkcoin masternode features --- libs/paymentProcessor.js | 4 +++- libs/profitSwitch.js | 7 +++---- libs/website.js | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 1e48e49..ecffef6 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -49,7 +49,9 @@ function SetupForPool(logger, poolOptions, setupFinished){ var logSystem = 'Payments'; var logComponent = coin; - var daemon = new Stratum.daemon.interface([processingConfig.daemon]); + var daemon = new Stratum.daemon.interface([processingConfig.daemon], function(severity, message){ + logger[severity](logSystem, logComponent, message); + }); var redisClient = redis.createClient(poolOptions.redis.port, poolOptions.redis.host); var magnitude; diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 9836f2f..5650ffa 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -419,11 +419,10 @@ module.exports = function(logger){ }); }; this.getDaemonInfoForCoin = function(symbol, cfg, callback){ - var daemon = new Stratum.daemon.interface([cfg]); - daemon.on('error', function(error){ - logger.error(logSystem, symbol, JSON.stringify(error)); + var daemon = new Stratum.daemon.interface([cfg], function(severity, message){ + logger[severity](logSystem, symbol, message); callback(null); // fail gracefully for each coin - }).init(); + }); daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result) { if (result[0].error != null) { diff --git a/libs/website.js b/libs/website.js index 500428b..6db3ed4 100644 --- a/libs/website.js +++ b/libs/website.js @@ -156,10 +156,12 @@ module.exports = function(logger){ } } })(); - var daemon = new Stratum.daemon.interface([coinInfo.daemon]); + var daemon = new Stratum.daemon.interface([coinInfo.daemon], function(severity, message){ + logger[severity](logSystem, c, message); + }); daemon.cmd('dumpprivkey', [coinInfo.address], function(result){ if (result[0].error){ - logger.error(logSystem, 'daemon', 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error)); + logger.error(logSystem, c, 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error)); cback(); return; } From baadebd97da91ca938a9b85b5ef58ce06298465c Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 01:11:27 -0600 Subject: [PATCH 088/150] Detect kicked blocks and deal with them appropriately --- libs/paymentProcessor.js | 67 ++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index ecffef6..b90f646 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -213,8 +213,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ var round = rounds[i]; if (tx.error && tx.error.code === -5){ - logger.error(logSystem, logComponent, 'Daemon reports invalid transaction ' + round.txHash + ' ' - + JSON.stringify(tx.error)); + logger.warning(logSystem, logComponent, 'Daemon reports invalid transaction: ' + round.txHash); + round.category = 'kicked'; + return; + } + else if (!tx.result.details || (tx.result.details && tx.result.details.length === 0)){ + logger.warning(logSystem, logComponent, 'Daemon reports no details for transaction: ' + round.txHash); + round.category = 'kicked'; return; } else if (tx.error || !tx.result){ @@ -222,16 +227,6 @@ function SetupForPool(logger, poolOptions, setupFinished){ + JSON.stringify(tx)); return; } - else if (round.blockHash !== tx.result.blockhash){ - logger.error(logSystem, logComponent, 'Daemon reports blockhash ' + tx.result.blockhash - + ' for tx ' + round.txHash + ' is not the one we have stored: ' + round.blockHash); - return; - } - else if (!(tx.result.details instanceof Array)){ - logger.error(logSystem, logComponent, 'Details array missing from transaction ' - + round.txHash); - return; - } var generationTx = tx.result.details.filter(function(tx){ return tx.address === poolOptions.address; @@ -253,16 +248,29 @@ function SetupForPool(logger, poolOptions, setupFinished){ round.reward = generationTx.amount || generationTx.value; } - }); + var canDeleteShares = function(r){ + for (var i = 0; i < rounds.length; i++){ + var compareR = rounds[i]; + if ((compareR.height === r.height) + && (compareR.category !== 'kicked') + && (compareR.category !== 'orphan') + && (compareR.serialized !== r.serialized)){ + return false; + } + } + return true; + }; + //Filter out all rounds that are immature (not confirmed or orphaned yet) rounds = rounds.filter(function(r){ switch (r.category) { - case 'generate': - return true; case 'orphan': + case 'kicked': + r.canDeleteShares = canDeleteShares(r); + case 'generate': return true; default: return false; @@ -305,11 +313,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ } switch (round.category){ + case 'kicked': case 'orphan': - /* Each block that gets orphaned, all the shares go into the current round so that - miners still get a reward for their work. This seems unfair to those that just - started mining during this current round, but over time it balances out and rewards - loyal miners. */ round.workerShares = workerShares; break; @@ -373,7 +378,6 @@ function SetupForPool(logger, poolOptions, setupFinished){ daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function (result) { if (result.error && result.error.code === -6) { var higherPercent = withholdPercent + 0.01; - console.log('asdfasdfsadfasdf'); logger.warning(logSystem, logComponent, 'Not enough funds to send out payments, decreasing rewards by ' + (higherPercent * 100) + '% and retrying'); trySend(higherPercent); @@ -427,21 +431,30 @@ function SetupForPool(logger, poolOptions, setupFinished){ var roundsToDelete = []; var orphanMergeCommands = []; + var moveSharesToCurrent = function(r){ + var workerShares = r.workerShares; + Object.keys(workerShares).forEach(function(worker){ + orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', + worker, workerShares[worker]]); + }); + }; + rounds.forEach(function(r){ switch(r.category){ + case 'kicked': + movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksKicked', r.serialized]); case 'orphan': movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksOrphaned', r.serialized]); - var workerShares = r.workerShares; - Object.keys(workerShares).forEach(function(worker){ - orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', - worker, workerShares[worker]]); - }); - break; + if (r.canDeleteShares){ + moveSharesToCurrent(r); + roundsToDelete.push(coin + '_shares:round' + r.height); + } + return; case 'generate': movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksConfirmed', r.serialized]); roundsToDelete.push(coin + '_shares:round' + r.height); - break; + return; } }); From 036f246ab7d51debbade4a6b49e6d2eae17db46e Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 01:23:50 -0600 Subject: [PATCH 089/150] Allow stratum authentication with mining key on non-switching ports --- libs/poolWorker.js | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index faeb247..dc48282 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -134,30 +134,24 @@ module.exports = function(logger){ if (poolOptions.validateWorkerUsername !== true) authCallback(true); else { - port = port.toString(); - if (portalConfig.switching) { - for (var switchName in portalConfig.switching) { - if (portalConfig.switching[switchName].enabled && Object.keys(portalConfig.switching[switchName].ports).indexOf(port) !== -1) { - if (workerName.length === 40) { - try { - new Buffer(workerName, 'hex'); - authCallback(true); - } - catch (e) { - authCallback(false); - } - } - else - authCallback(false); - return; - } + if (workerName.length === 40) { + try { + new Buffer(workerName, 'hex'); + authCallback(true); + } + catch (e) { + authCallback(false); } } + else { + pool.daemon.cmd('validateaddress', [workerName], function (results) { + var isValid = results.filter(function (r) { + return r.response.isvalid + }).length > 0; + authCallback(isValid); + }); + } - pool.daemon.cmd('validateaddress', [workerName], function(results){ - var isValid = results.filter(function(r){return r.response.isvalid}).length > 0; - authCallback(isValid); - }); } }; From 7ae0107eea04b81ff09c5c730290fa6baa2a99f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Wed, 7 May 2014 12:45:25 +0300 Subject: [PATCH 090/150] added totalPaid to /api/stats/ --- libs/stats.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/stats.js b/libs/stats.js index 340f2d0..75cfc91 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -144,7 +144,8 @@ module.exports = function(logger, portalConfig, poolConfigs){ poolStats: { validShares: replies[i + 2] ? (replies[i + 2].validShares || 0) : 0, validBlocks: replies[i + 2] ? (replies[i + 2].validBlocks || 0) : 0, - invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0 + invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0, + totalPaid: replies[i + 2] ? (replies[i + 2].totalPaid || 0) : 0 }, blocks: { pending: replies[i + 3], From 3fb907f6821d99662b583aa9097f9473cc944492 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 11:58:56 -0600 Subject: [PATCH 091/150] Changing _balances value to be stored in coin units rather than satoshi units --- libs/paymentProcessor.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index b90f646..2fd2fad 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -121,6 +121,10 @@ function SetupForPool(logger, poolOptions, setupFinished){ return parseFloat((satoshis / magnitude).toFixed(coinPrecision)); }; + var coinsToSatoshies = function(coins){ + return coins * magnitude; + }; + /* Deal with numbers in smallest possible units (satoshis) as much as possible. This greatly helps with accuracy when rounding and whatnot. When we are storing numbers for only humans to see, store in whole coin units. */ @@ -163,7 +167,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var workers = {}; for (var w in results[0]){ - workers[w] = {balance: parseInt(results[0][w])}; + workers[w] = {balance: coinsToSatoshies(parseInt(results[0][w]))}; } var rounds = results[1].map(function(r){ @@ -376,9 +380,10 @@ function SetupForPool(logger, poolOptions, setupFinished){ } daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function (result) { + //Check if payments failed because wallet doesn't have enough coins to pay for tx fees if (result.error && result.error.code === -6) { var higherPercent = withholdPercent + 0.01; - logger.warning(logSystem, logComponent, 'Not enough funds to send out payments, decreasing rewards by ' + logger.warning(logSystem, logComponent, 'Not enough funds to cover the tx fees for sending out payments, decreasing rewards by ' + (higherPercent * 100) + '% and retrying'); trySend(higherPercent); } @@ -413,10 +418,10 @@ function SetupForPool(logger, poolOptions, setupFinished){ var worker = workers[w]; if (worker.balanceChange !== 0){ balanceUpdateCommands.push([ - 'hincrby', + 'hincrbyfloat', coin + '_balances', w, - worker.balanceChange + satoshisToCoins(worker.balanceChange) ]); } if (worker.sent !== 0){ From 57462c3de103ec5ebeb98aef1c460d1049008819 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 11:59:37 -0600 Subject: [PATCH 092/150] Better error handling for when website cannot start listening on configured port --- libs/website.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libs/website.js b/libs/website.js index 6db3ed4..1b246be 100644 --- a/libs/website.js +++ b/libs/website.js @@ -274,9 +274,15 @@ module.exports = function(logger){ res.send(500, 'Something broke!'); }); - app.listen(portalConfig.website.port, function(){ - logger.debug(logSystem, 'Server', 'Website started on port ' + portalConfig.website.port); - }); + try { + app.listen(portalConfig.website.port, function () { + logger.debug(logSystem, 'Server', 'Website started on port ' + portalConfig.website.port); + }); + } + catch(e){ + logger.error(logSystem, 'Server', 'Could not start website on port ' + portalConfig.website.port + + ' - its either in use or you do not have permission'); + } }; From 537fddfc433c650b38e34ad5ead6285a8694a161 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:00:06 -0600 Subject: [PATCH 093/150] Removed "txRefreshInterval" option from readme as its no longer used --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 34ccac4..e668861 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,6 @@ Description of options: "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 - job broadcast. */ - "txRefreshInterval": 20000, /* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast From 1f7b7c36a8b9d1d6cc38dead1030bf5f8ddb7d9b Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:00:40 -0600 Subject: [PATCH 094/150] Remove txRefreshInterval from example config as its not longer used --- pool_configs/litecoin_example.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index a2cdcdf..e9512b8 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -11,7 +11,6 @@ }, "blockRefreshInterval": 1000, - "txRefreshInterval": 20000, "jobRebroadcastTimeout": 55, "connectionTimeout": 600, "emitInvalidBlockHashes": false, From d8444e6a6caea6be8525028e542cdbdfea28ab4c Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:22:32 -0600 Subject: [PATCH 095/150] Removed "shareVariancePercent" option as its no longer useful --- README.md | 6 ------ libs/shareProcessor.js | 4 +++- pool_configs/litecoin_example.json | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e668861..da4b2ed 100644 --- a/README.md +++ b/README.md @@ -347,12 +347,6 @@ Description of options: /* Sometimes you want the block hashes even for shares that aren't block candidates. */ "emitInvalidBlockHashes": false, - /* We use proper maximum algorithm difficulties found in the coin daemon source code. Most - miners/pools that deal with scrypt use a guesstimated one that is about 5.86% off from the - actual one. So here we can set a tolerable threshold for if a share is slightly too low - due to mining apps using incorrect max diffs and this pool using correct max diffs. */ - "shareVariancePercent": 2, - /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy protocol enabled, such as HAProxy with 'send-proxy' param: http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 62d6444..aeeda49 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -19,6 +19,7 @@ module.exports = function(logger, poolConfig){ var redisConfig = poolConfig.redis; var coin = poolConfig.coin.name; + var forkId = process.env.forkId; var logSystem = 'Pool'; var logComponent = coin; @@ -51,7 +52,8 @@ module.exports = function(logger, poolConfig){ doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to generate hashrate for each worker and pool. */ var dateNow = Date.now(); - redisCommands.push(['zadd', coin + '_hashrate', dateNow / 1000 | 0, [shareData.difficulty, shareData.worker, dateNow].join(':')]); + var hashrateData = [shareData.difficulty, shareData.worker, dateNow]; + redisCommands.push(['zadd', coin + '_hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); } else{ redisCommands.push(['hincrby', coin + '_stats', 'invalidShares', 1]); diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index e9512b8..ed5f414 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -14,7 +14,6 @@ "jobRebroadcastTimeout": 55, "connectionTimeout": 600, "emitInvalidBlockHashes": false, - "shareVariancePercent": 15, "tcpProxyProtocol": false, From 81b7f5053693dcc8b7e4a009c0f45ef24f754024 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:38:39 -0600 Subject: [PATCH 096/150] Coin names are now converted to lowercase. --- init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.js b/init.js index aacb92e..dc01c7a 100644 --- a/init.js +++ b/init.js @@ -56,7 +56,6 @@ catch(e){ } - if (cluster.isWorker){ switch(process.env.workerType){ @@ -132,6 +131,7 @@ var buildPoolConfigs = function(){ var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'}))); poolOptions.coin = coinProfile; + poolOptions.coin.name = poolOptions.coin.name.toLowerCase(); if (poolOptions.coin.name in configs){ From c5ba95f1d90796d360dcfe9385fd523057c1816f Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 7 May 2014 13:27:24 -0600 Subject: [PATCH 097/150] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index da4b2ed..5602aac 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ This portal is an extremely efficient, highly scalable, all-in-one, easy to setu entirely in Node.js. It contains a stratum poolserver; reward/payment/share processor; and a (*not yet completed*) responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. +#### Production Usage Notice +This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuratoin files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. + + #### Table of Contents * [Features](#features) * [Attack Mitigation](#attack-mitigation) From d7db8a3af6ff631365fb3c7df9e4d1f4267a66e7 Mon Sep 17 00:00:00 2001 From: Elbandi Date: Thu, 8 May 2014 14:54:44 +0200 Subject: [PATCH 098/150] Allow disable log colors --- config_example.json | 1 + init.js | 3 ++- libs/logUtil.js | 27 ++++++++++++++++++++------- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/config_example.json b/config_example.json index bc09c9b..7f49ad7 100644 --- a/config_example.json +++ b/config_example.json @@ -1,5 +1,6 @@ { "logLevel": "debug", + "logColors": true, "cliPort": 17117, diff --git a/init.js b/init.js index dc01c7a..4ed6d90 100644 --- a/init.js +++ b/init.js @@ -26,7 +26,8 @@ var poolConfigs; var logger = new PoolLogger({ - logLevel: portalConfig.logLevel + logLevel: portalConfig.logLevel, + logColors: portalConfig.logColors }); diff --git a/libs/logUtil.js b/libs/logUtil.js index 7d25275..7b56b98 100644 --- a/libs/logUtil.js +++ b/libs/logUtil.js @@ -30,6 +30,7 @@ var PoolLogger = function (configuration) { var logLevelInt = severityValues[configuration.logLevel]; + var logColors = configuration.logColors; @@ -45,16 +46,28 @@ var PoolLogger = function (configuration) { } var entryDesc = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss') + ' [' + system + ']\t'; - entryDesc = severityToColor(severity, entryDesc); + if (logColors) { + entryDesc = severityToColor(severity, entryDesc); - var logString = - entryDesc + - ('[' + component + '] ').italic; + var logString = + entryDesc + + ('[' + component + '] ').italic; - if (subcat) - logString += ('(' + subcat + ') ').bold.grey + if (subcat) + logString += ('(' + subcat + ') ').bold.grey; - logString += text.grey; + logString += text.grey; + } + else { + var logString = + entryDesc + + '[' + component + '] '; + + if (subcat) + logString += '(' + subcat + ') '; + + logString += text; + } console.log(logString); From 0de705abb1626ebd9cebc795a06f5177d56f2549 Mon Sep 17 00:00:00 2001 From: Ari Velakoski Date: Thu, 8 May 2014 22:38:26 +0300 Subject: [PATCH 099/150] Update tbs.html Small typo fixed. Firefox kindly pointed this out. --- website/pages/tbs.html | 1 - 1 file changed, 1 deletion(-) diff --git a/website/pages/tbs.html b/website/pages/tbs.html index 9ca8352..09a5d27 100644 --- a/website/pages/tbs.html +++ b/website/pages/tbs.html @@ -46,7 +46,6 @@ Orphaned Hashrate - {{ for(var pool in it.stats.pools) { }} From bc8a40a1c9f05acc3d12d6f262836c5f4968807b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Fri, 9 May 2014 00:21:55 +0300 Subject: [PATCH 100/150] added peer magic values for lottocoin and vertcoin --- coins/lottocoin.json | 4 +++- coins/vertcoin.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/coins/lottocoin.json b/coins/lottocoin.json index 188238a..4d8a28b 100644 --- a/coins/lottocoin.json +++ b/coins/lottocoin.json @@ -1,5 +1,7 @@ { "name": "Lottocoin", "symbol": "LOT", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "a5fdb6c1", + "peerMagicTestnet": "fdc3b6f1" } \ No newline at end of file diff --git a/coins/vertcoin.json b/coins/vertcoin.json index 5e74691..35cfdd4 100644 --- a/coins/vertcoin.json +++ b/coins/vertcoin.json @@ -1,5 +1,7 @@ { "name": "Vertcoin", "symbol": "VTC", - "algorithm": "scrypt-n" + "algorithm": "scrypt-n", + "peerMagic": "fabfb5da", + "peerMagicTestnet": "76657274" } \ No newline at end of file From 647060e1ed909b4d9db1c67ad60772d858a6c801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Fri, 9 May 2014 00:23:57 +0300 Subject: [PATCH 101/150] fixed tabs --- coins/lottocoin.json | 4 ++-- coins/vertcoin.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coins/lottocoin.json b/coins/lottocoin.json index 4d8a28b..3b05499 100644 --- a/coins/lottocoin.json +++ b/coins/lottocoin.json @@ -2,6 +2,6 @@ "name": "Lottocoin", "symbol": "LOT", "algorithm": "scrypt", - "peerMagic": "a5fdb6c1", - "peerMagicTestnet": "fdc3b6f1" + "peerMagic": "a5fdb6c1", + "peerMagicTestnet": "fdc3b6f1" } \ No newline at end of file diff --git a/coins/vertcoin.json b/coins/vertcoin.json index 35cfdd4..fc001d4 100644 --- a/coins/vertcoin.json +++ b/coins/vertcoin.json @@ -2,6 +2,6 @@ "name": "Vertcoin", "symbol": "VTC", "algorithm": "scrypt-n", - "peerMagic": "fabfb5da", - "peerMagicTestnet": "76657274" + "peerMagic": "fabfb5da", + "peerMagicTestnet": "76657274" } \ No newline at end of file From ffa2d26b5f3c654750744aa049ee467ef27003ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Fri, 9 May 2014 12:58:36 +0300 Subject: [PATCH 102/150] added einsteinium, feathercoin, groestlcoin, myriadcoin and fixed hirocoin's symbol --- coins/einsteinium.json | 5 +++++ coins/feathercoin.json | 5 +++++ coins/groestlcoin.json | 5 +++++ coins/hirocoin.json | 2 +- coins/myriadcoin.json | 5 +++++ 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 coins/einsteinium.json create mode 100644 coins/feathercoin.json create mode 100644 coins/groestlcoin.json create mode 100644 coins/myriadcoin.json diff --git a/coins/einsteinium.json b/coins/einsteinium.json new file mode 100644 index 0000000..d2575ef --- /dev/null +++ b/coins/einsteinium.json @@ -0,0 +1,5 @@ +{ + "name": "Einsteinium", + "symbol": "EMC2", + "algorithm": "scrypt" +} diff --git a/coins/feathercoin.json b/coins/feathercoin.json new file mode 100644 index 0000000..26959d8 --- /dev/null +++ b/coins/feathercoin.json @@ -0,0 +1,5 @@ +{ + "name": "Feathercoin", + "symbol": "FTC", + "algorithm": "scrypt" +} diff --git a/coins/groestlcoin.json b/coins/groestlcoin.json new file mode 100644 index 0000000..50feee4 --- /dev/null +++ b/coins/groestlcoin.json @@ -0,0 +1,5 @@ +{ + "name": "GroestlCoin", + "symbol": "GRS", + "algorithm": "groestl" +} diff --git a/coins/hirocoin.json b/coins/hirocoin.json index cd7da82..3adec2c 100644 --- a/coins/hirocoin.json +++ b/coins/hirocoin.json @@ -1,6 +1,6 @@ { "name": "Hirocoin", - "symbol": "hic", + "symbol": "HIRO", "algorithm": "x11", "mposDiffMultiplier": 256 } diff --git a/coins/myriadcoin.json b/coins/myriadcoin.json new file mode 100644 index 0000000..bd0f0ee --- /dev/null +++ b/coins/myriadcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Myriadcoin", + "symbol": "MYR", + "algorithm": "scrypt" +} \ No newline at end of file From da0264f8de8bab1bdad58880c1ed663a6d2acf05 Mon Sep 17 00:00:00 2001 From: Elbandi Date: Fri, 9 May 2014 13:41:16 +0200 Subject: [PATCH 103/150] Bind website listener to host --- config_example.json | 1 + libs/website.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config_example.json b/config_example.json index 7f49ad7..c473771 100644 --- a/config_example.json +++ b/config_example.json @@ -11,6 +11,7 @@ "website": { "enabled": true, + "host": "0.0.0.0", "port": 80, "stratumHost": "cryppit.com", "stats": { diff --git a/libs/website.js b/libs/website.js index 1b246be..4156d1f 100644 --- a/libs/website.js +++ b/libs/website.js @@ -275,12 +275,12 @@ module.exports = function(logger){ }); try { - app.listen(portalConfig.website.port, function () { - logger.debug(logSystem, 'Server', 'Website started on port ' + portalConfig.website.port); + app.listen(portalConfig.website.port, portalConfig.website.host, function () { + logger.debug(logSystem, 'Server', 'Website started on ' + portalConfig.website.host + ':' + portalConfig.website.port); }); } catch(e){ - logger.error(logSystem, 'Server', 'Could not start website on port ' + portalConfig.website.port + logger.error(logSystem, 'Server', 'Could not start website on ' + portalConfig.website.host + ':' + portalConfig.website.port + ' - its either in use or you do not have permission'); } From f9c739f123da02532f1e6589f6ffe1a84cdd8061 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Fri, 9 May 2014 09:47:59 -0600 Subject: [PATCH 104/150] Updated with new config options --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5602aac..19fa907 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ entirely in Node.js. It contains a stratum poolserver; reward/payment/share proc responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. #### Production Usage Notice -This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuratoin files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. +This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuration files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. #### Table of Contents @@ -171,6 +171,10 @@ Explanation for each field: /* Specifies the level of log output verbosity. Anything more severy than the level specified will also be logged. */ "logLevel": "debug", //or "warning", "error" + + /* By default NOMP logs to console and gives pretty colors. If you direct that output to a + log file then disable this feature to avoid nasty characters in your log file. */ + "logColors": true, /* The NOMP CLI (command-line interface) will listen for commands on this port. For example, @@ -189,6 +193,9 @@ Explanation for each field: /* This is the front-end. Its not finished. When it is finished, this comment will say so. */ "website": { "enabled": true, + /* If you are using a reverse-proxy like nginx to display the website then set this to + 127.0.0.1 to not expose the port. */ + "host": "0.0.0.0", "port": 80, /* Used for displaying stratum connection data on the Getting Started page. */ "stratumHost": "cryppit.com", From f91ff797183564d60940d00a0b27f8d40e2ce405 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 11:51:08 -0600 Subject: [PATCH 105/150] Added inheritable pool options in global config --- README.md | 155 ++++++++++++++--------------- config_example.json | 27 +++-- init.js | 36 +++---- libs/redisblocknotifyListener.js | 36 ------- package.json | 3 +- pool_configs/litecoin_example.json | 55 +++------- 6 files changed, 127 insertions(+), 185 deletions(-) delete mode 100644 libs/redisblocknotifyListener.js diff --git a/README.md b/README.md index 5602aac..fdbd0e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ entirely in Node.js. It contains a stratum poolserver; reward/payment/share proc responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. #### Production Usage Notice -This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuratoin files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. +This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuration files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. #### Table of Contents @@ -171,6 +171,10 @@ Explanation for each field: /* Specifies the level of log output verbosity. Anything more severy than the level specified will also be logged. */ "logLevel": "debug", //or "warning", "error" + + /* By default NOMP logs to console and gives pretty colors. If you direct that output to a + log file then disable this feature to avoid nasty characters in your log file. */ + "logColors": true, /* The NOMP CLI (command-line interface) will listen for commands on this port. For example, @@ -185,10 +189,54 @@ Explanation for each field: "enabled": true, "forks": "auto" }, + + /* Pool config file will inherit these default values if they are not set. */ + "defaultPoolConfigs": { + + /* Poll RPC daemons for new blocks every this many milliseconds. */ + "blockRefreshInterval": 1000, + + /* If no new blocks are available for this many seconds update and rebroadcast job. */ + "jobRebroadcastTimeout": 55, + + /* Disconnect workers that haven't submitted shares for this many seconds. */ + "connectionTimeout": 600, + + /* (For MPOS mode) Store the block hashes for shares that aren't block candidates. */ + "emitInvalidBlockHashes": false, + + /* This option will only authenticate miners using an address or mining key. */ + "validateWorkerUsername": true, + + /* Enable for client IP addresses to be detected when using a load balancer with TCP + proxy protocol enabled, such as HAProxy with 'send-proxy' param: + http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ + "tcpProxyProtocol": false, + + /* If under low-diff share attack we can ban their IP to reduce system/network load. If + running behind HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up + banning your own IP address (and therefore all workers). */ + "banning": { + "enabled": true, + "time": 600, //How many seconds to ban worker for + "invalidPercent": 50, //What percent of invalid shares triggers ban + "checkThreshold": 500, //Perform check when this many shares have been submitted + "purgeInterval": 300 //Every this many seconds clear out the list of old bans + }, + + /* Used for storing share and block submission data and payment processing. */ + "redis": { + "host": "127.0.0.1", + "port": 6379 + } + }, /* This is the front-end. Its not finished. When it is finished, this comment will say so. */ "website": { "enabled": true, + /* If you are using a reverse-proxy like nginx to display the website then set this to + 127.0.0.1 to not expose the port. */ + "host": "0.0.0.0", "port": 80, /* Used for displaying stratum connection data on the Getting Started page. */ "stratumHost": "cryppit.com", @@ -333,33 +381,6 @@ Description of options: "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, - "blockRefreshInterval": 1000, //How often to poll RPC daemons for new blocks, in milliseconds - - - /* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs - for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast - in this many seconds unless we find a new job. Set to zero or remove to disable this. */ - "jobRebroadcastTimeout": 55, - - //instanceId: 37, //Recommend not using this because a crypto-random one will be generated - - /* Some attackers will create thousands of workers that use up all available socket connections, - usually the workers are zombies and don't submit shares after connecting. This feature - detects those and disconnects them. */ - "connectionTimeout": 600, //Remove workers that haven't been in contact for this many seconds - - /* Sometimes you want the block hashes even for shares that aren't block candidates. */ - "emitInvalidBlockHashes": false, - - /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy - protocol enabled, such as HAProxy with 'send-proxy' param: - http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ - "tcpProxyProtocol": false, - - /* To receive payments, miners must connect with their address or mining key as their username. - This option will only authenticate miners using an address or mining key. */ - "validateWorkerUsername": true, - "paymentProcessing": { "enabled": true, @@ -379,50 +400,11 @@ Description of options: "daemon": { "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } }, - /* Redis database used for storing share and block submission data and payment processing. */ - "redis": { - "host": "127.0.0.1", - "port": 6379 - }, - - /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want - to use the "emitInvalidBlockHashes" option below if you require it. The config options - "redis" and "paymentProcessing" will be ignored/unused if this is enabled. */ - "mposMode": { - "enabled": false, - "host": "127.0.0.1", //MySQL db host - "port": 3306, //MySQL db port - "user": "me", //MySQL db user - "password": "mypass", //MySQL db password - "database": "ltc", //MySQL db database name - - /* Checks for valid password in database when miners connect. */ - "checkPassword": true, - - /* Unregistered workers can automatically be registered (added to database) on stratum - worker authentication if this is true. */ - "autoCreateWorker": false - - - }, - - /* If a worker is submitting a high threshold of invalid shares we can temporarily ban their IP - to reduce system/network load. Also useful to fight against flooding attacks. If running - behind something like HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up - banning your own IP address (and therefore all workers). */ - "banning": { - "enabled": true, - "time": 600, //How many seconds to ban worker for - "invalidPercent": 50, //What percent of invalid shares triggers ban - "checkThreshold": 500, //Check invalid percent when this many shares have been submitted - "purgeInterval": 300 //Every this many seconds clear out the list of old bans - }, - /* Each pool can have as many ports for your miners to connect to as you wish. Each port can be configured to use its own pool difficulty and variable difficulty settings. varDiff is optional and will only be used for the ports you configure it for. */ @@ -445,24 +427,16 @@ Description of options: } }, - /* For redundancy, recommended to have at least two daemon instances running in case one - drops out-of-sync or offline. */ + /* More than one daemon instances can be setup in case one drops out-of-sync or dies. */ "daemons": [ { //Main daemon instance "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" - }, - { //Backup daemon instance - "host": "127.0.0.1", - "port": 19344, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } ], - /* 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 intensive than blocknotify script). It requires the additional field "peerMagic" in @@ -480,6 +454,25 @@ Description of options: 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. */ "disableTransactions": true + }, + + /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want + to use the "emitInvalidBlockHashes" option below if you require it. The config options + "redis" and "paymentProcessing" will be ignored/unused if this is enabled. */ + "mposMode": { + "enabled": false, + "host": "127.0.0.1", //MySQL db host + "port": 3306, //MySQL db port + "user": "me", //MySQL db user + "password": "mypass", //MySQL db password + "database": "ltc", //MySQL db database name + + /* Checks for valid password in database when miners connect. */ + "checkPassword": true, + + /* Unregistered workers can automatically be registered (added to database) on stratum + worker authentication if this is true. */ + "autoCreateWorker": false } } @@ -562,4 +555,4 @@ License ------- Released under the GNU General Public License v2 -http://www.gnu.org/licenses/gpl-2.0.html +http://www.gnu.org/licenses/gpl-2.0.html \ No newline at end of file diff --git a/config_example.json b/config_example.json index c473771..9534651 100644 --- a/config_example.json +++ b/config_example.json @@ -9,6 +9,26 @@ "forks": "auto" }, + "defaultPoolConfigs": { + "blockRefreshInterval": 1000, + "jobRebroadcastTimeout": 55, + "connectionTimeout": 600, + "emitInvalidBlockHashes": false, + "validateWorkerUsername": true, + "tcpProxyProtocol": false, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 500, + "purgeInterval": 300 + }, + "redis": { + "host": "127.0.0.1", + "port": 6379 + } + }, + "website": { "enabled": true, "host": "0.0.0.0", @@ -81,12 +101,5 @@ "usePoloniex": true, "useCryptsy": true, "useMintpal": true - }, - - "redisBlockNotifyListener": { - "enabled": false, - "redisPort": 6379, - "redisHost": "hostname", - "psubscribeKey": "newblocks:*" } } diff --git a/init.js b/init.js index 4ed6d90..f6e21cc 100644 --- a/init.js +++ b/init.js @@ -4,9 +4,10 @@ var os = require('os'); var cluster = require('cluster'); var async = require('async'); +var extend = require('extend'); + var PoolLogger = require('./libs/logUtil.js'); var CliListener = require('./libs/cliListener.js'); -var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js'); var PoolWorker = require('./libs/poolWorker.js'); var PaymentProcessor = require('./libs/paymentProcessor.js'); var Website = require('./libs/website.js'); @@ -145,6 +146,19 @@ var buildPoolConfigs = function(){ return; } + for (var option in portalConfig.defaultPoolConfigs){ + if (!(option in poolOptions)){ + var toCloneOption = portalConfig.defaultPoolConfigs[option]; + var clonedOption = {}; + if (toCloneOption.constructor === Object) + extend(true, clonedOption, toCloneOption); + else + clonedOption = toCloneOption; + poolOptions[option] = clonedOption; + } + } + + configs[poolOptions.coin.name] = poolOptions; if (!(coinProfile.algorithm in algos)){ @@ -334,24 +348,6 @@ var processCoinSwitchCommand = function(params, options, reply){ }; -var startRedisBlockListener = function(){ - //block notify options - //setup block notify here and use IPC to tell appropriate pools - - if (!portalConfig.redisBlockNotifyListener.enabled) return; - - var listener = new RedisBlocknotifyListener(portalConfig.redisBlockNotifyListener); - listener.on('log', function(text){ - logger.debug('Master', 'blocknotify', text); - }).on('hash', function (message) { - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); - }); - listener.start(); -}; - var startPaymentProcessor = function(){ @@ -429,8 +425,6 @@ var startProfitSwitch = function(){ startPaymentProcessor(); - startRedisBlockListener(); - startWebsite(); startProfitSwitch(); diff --git a/libs/redisblocknotifyListener.js b/libs/redisblocknotifyListener.js deleted file mode 100644 index 05b9c7f..0000000 --- a/libs/redisblocknotifyListener.js +++ /dev/null @@ -1,36 +0,0 @@ -var events = require('events'); -var redis = require('redis'); - -var listener = module.exports = function listener(options){ - - var _this = this; - var redisConnection; - - var emitLog = function(text){ - _this.emit('log', text); - }; - - - this.start = function(){ - redisConnection = redis.createClient(options.redisPort, options.redisHost); - redisConnection.on("pmessage", function (pattern, channel, message) { - var coinname = channel.split(':')[1]; - var blockhash = message; - //emitLog("Redis: Received block for "+coinname+" - hash: "+blockhash); - _this.emit('hash', { - "coin" : coinname, - "hash" : blockhash - }); - }); - redisConnection.on('connect', function (err, data) { - emitLog("Redis connected"); - }); - redisConnection.psubscribe(options.psubscribeKey); - emitLog("Connecting to redis!"); - } - - - -}; - -listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/package.json b/package.json index 133d279..f6467a2 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "node-watch": "*", "request": "*", "nonce": "*", - "bignum": "*" + "bignum": "*", + "extend": "*" }, "engines": { "node": ">=0.10" diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index ed5f414..f0647e0 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -6,19 +6,9 @@ "rewardRecipients": { "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, - "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, - "blockRefreshInterval": 1000, - "jobRebroadcastTimeout": 55, - "connectionTimeout": 600, - "emitInvalidBlockHashes": false, - - "tcpProxyProtocol": false, - - "validateWorkerUsername": true, - "paymentProcessing": { "enabled": true, "paymentInterval": 20, @@ -26,38 +16,14 @@ "daemon": { "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } }, - "redis": { - "host": "127.0.0.1", - "port": 6379 - }, - - "mposMode": { - "enabled": false, - "host": "127.0.0.1", - "port": 3306, - "user": "me", - "password": "mypass", - "database": "ltc", - "checkPassword": true, - "autoCreateWorker": false - }, - - "banning": { - "enabled": true, - "time": 300, - "invalidPercent": 50, - "checkThreshold": 10, - "purgeInterval": 300 - }, - "ports": { "3008": { - "diff": 4 + "diff": 8 }, "3032": { "diff": 32, @@ -78,8 +44,8 @@ { "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } ], @@ -88,6 +54,17 @@ "host": "127.0.0.1", "port": 19333, "disableTransactions": true + }, + + "mposMode": { + "enabled": false, + "host": "127.0.0.1", + "port": 3306, + "user": "me", + "password": "mypass", + "database": "ltc", + "checkPassword": true, + "autoCreateWorker": false } } \ No newline at end of file From 04f769a5a66dadc680c490257c02f988d013c39a Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 11:59:22 -0600 Subject: [PATCH 106/150] Add credit to Fcases (triplef) for ordering me a pizza! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b87889b..ec048e2 100644 --- a/README.md +++ b/README.md @@ -548,6 +548,7 @@ Credits * [Alex Petrov / sysmanalex](https://github.com/sysmanalex) - contributed the pure C block notify script * [svirusxxx](//github.com/svirusxxx) - sponsored development of MPOS mode * [icecube45](//github.com/icecube45) - helping out with the repo wiki +* [Fcases](//github.com/Fcases) - ordered me a pizza <3 * Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool#credits) From e6556d416ecb63b3dbcf63939d8e54401973b54d Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 17:43:11 -0600 Subject: [PATCH 107/150] [REDIS BREAKING UPDATE] Changed coins to use redis branching for cleaner management --- libs/paymentProcessor.js | 24 ++++++++++++------------ libs/shareProcessor.js | 16 ++++++++-------- libs/stats.js | 12 ++++++------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 2fd2fad..8e7866e 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -152,8 +152,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ startRedisTimer(); redisClient.multi([ - ['hgetall', coin + '_balances'], - ['smembers', coin + '_blocksPending'] + ['hgetall', coin + ':balances'], + ['smembers', coin + ':blocksPending'] ]).exec(function(error, results){ endRedisTimer(); @@ -294,7 +294,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var shareLookups = rounds.map(function(r){ - return ['hgetall', coin + '_shares:round' + r.height] + return ['hgetall', coin + ':shares:round' + r.height] }); startRedisTimer(); @@ -419,13 +419,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ if (worker.balanceChange !== 0){ balanceUpdateCommands.push([ 'hincrbyfloat', - coin + '_balances', + coin + ':balances', w, satoshisToCoins(worker.balanceChange) ]); } if (worker.sent !== 0){ - workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', w, worker.sent]); + workerPayoutsCommand.push(['hincrbyfloat', coin + ':payouts', w, worker.sent]); totalPaid += worker.sent; } } @@ -439,7 +439,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var moveSharesToCurrent = function(r){ var workerShares = r.workerShares; Object.keys(workerShares).forEach(function(worker){ - orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', + orphanMergeCommands.push(['hincrby', coin + ':shares:roundCurrent', worker, workerShares[worker]]); }); }; @@ -448,17 +448,17 @@ function SetupForPool(logger, poolOptions, setupFinished){ switch(r.category){ case 'kicked': - movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksKicked', r.serialized]); + movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksKicked', r.serialized]); case 'orphan': - movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksOrphaned', r.serialized]); + movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksOrphaned', r.serialized]); if (r.canDeleteShares){ moveSharesToCurrent(r); - roundsToDelete.push(coin + '_shares:round' + r.height); + roundsToDelete.push(coin + ':shares:round' + r.height); } return; case 'generate': - movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksConfirmed', r.serialized]); - roundsToDelete.push(coin + '_shares:round' + r.height); + movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksConfirmed', r.serialized]); + roundsToDelete.push(coin + ':shares:round' + r.height); return; } @@ -482,7 +482,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ finalRedisCommands.push(['del'].concat(roundsToDelete)); if (totalPaid !== 0) - finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', totalPaid]); + finalRedisCommands.push(['hincrbyfloat', coin + ':stats', 'totalPaid', totalPaid]); if (finalRedisCommands.length === 0){ callback(); diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index aeeda49..9dface3 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -45,27 +45,27 @@ module.exports = function(logger, poolConfig){ var redisCommands = []; if (isValidShare){ - redisCommands.push(['hincrbyfloat', coin + '_shares:roundCurrent', shareData.worker, shareData.difficulty]); - redisCommands.push(['hincrby', coin + '_stats', 'validShares', 1]); + redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]); + redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]); /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to generate hashrate for each worker and pool. */ var dateNow = Date.now(); var hashrateData = [shareData.difficulty, shareData.worker, dateNow]; - redisCommands.push(['zadd', coin + '_hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); + redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); } else{ - redisCommands.push(['hincrby', coin + '_stats', 'invalidShares', 1]); + redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]); } if (isValidBlock){ - redisCommands.push(['rename', coin + '_shares:roundCurrent', coin + '_shares:round' + shareData.height]); - redisCommands.push(['sadd', coin + '_blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); - redisCommands.push(['hincrby', coin + '_stats', 'validBlocks', 1]); + redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + '_shares:round' + shareData.height]); + redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); + redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); } else if (shareData.blockHash){ - redisCommands.push(['hincrby', coin + '_stats', 'invalidBlocks', 1]); + redisCommands.push(['hincrby', coin + ':stats', 'invalidBlocks', 1]); } connection.multi(redisCommands).exec(function(err, replies){ diff --git a/libs/stats.js b/libs/stats.js index 75cfc91..977002e 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -109,12 +109,12 @@ module.exports = function(logger, portalConfig, poolConfigs){ var redisCommandTemplates = [ - ['zremrangebyscore', '_hashrate', '-inf', '(' + windowTime], - ['zrangebyscore', '_hashrate', windowTime, '+inf'], - ['hgetall', '_stats'], - ['scard', '_blocksPending'], - ['scard', '_blocksConfirmed'], - ['scard', '_blocksOrphaned'] + ['zremrangebyscore', ':hashrate', '-inf', '(' + windowTime], + ['zrangebyscore', ':hashrate', windowTime, '+inf'], + ['hgetall', ':stats'], + ['scard', ':blocksPending'], + ['scard', ':blocksConfirmed'], + ['scard', ':blocksOrphaned'] ]; var commandsPerCoin = redisCommandTemplates.length; From 936b723c6665401a7b7c16847ba0ea31a6b2eefe Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 17:48:19 -0600 Subject: [PATCH 108/150] Missed an underscore for last commit --- libs/shareProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 9dface3..2632174 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -60,7 +60,7 @@ module.exports = function(logger, poolConfig){ } if (isValidBlock){ - redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + '_shares:round' + shareData.height]); + redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]); redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); } From e08d313b1a80f91c2c876c6a3a6351a01102ba8c Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 18:31:38 -0600 Subject: [PATCH 109/150] Added redis security warning --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ec048e2..912eaa4 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,8 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing +[*Redis security warning*](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file + Clone the repository and run `npm update` for all the dependencies to be installed: ```bash From 6681bf3de66bc6d9079920d080d1f619977ef7ea Mon Sep 17 00:00:00 2001 From: Nicholas Orr Date: Sat, 10 May 2014 08:31:59 +0800 Subject: [PATCH 110/150] Allow ability to shed sudo --- init.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/init.js b/init.js index f6e21cc..75bdc61 100644 --- a/init.js +++ b/init.js @@ -51,6 +51,15 @@ try{ if (cluster.isMaster) logger.warning('POSIX', 'Connection Limit', '(Safe to ignore) Must be ran as root to increase resource limits'); } + finally { + // Find out which user used sudo through the environment variable + var uid = parseInt(process.env.SUDO_UID); + // Set our server's uid to that user + if (uid) { + process.setuid(uid); + logger.debug('POSIX', 'Connection Limit', 'Raised to 100K concurrent connections, now running as non-root user: ' + process.getuid()); + } + } } catch(e){ if (cluster.isMaster) From a63a206ff68b5b64ea8f503f761d2071f9293566 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 18:32:54 -0600 Subject: [PATCH 111/150] Added redis security warning --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 912eaa4..2efd64f 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing -[*Redis security warning*](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file +[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file Clone the repository and run `npm update` for all the dependencies to be installed: From 2eef180751bd2238274df4629065fcc96b3d13e2 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 18:36:27 -0600 Subject: [PATCH 112/150] Added redis security warning --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2efd64f..c7bd804 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing -[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file +[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis - an easy way is to include `bind 127.0.0.1` in your `redis.conf` file Clone the repository and run `npm update` for all the dependencies to be installed: From 621e13b17fc1814dedf01d0337faba35a9e2cd63 Mon Sep 17 00:00:00 2001 From: qazzac Date: Sat, 10 May 2014 17:23:15 +0900 Subject: [PATCH 113/150] modified for my repositry --- coins/kumacoin.json | 6 ++++++ coins/monacoin.json | 5 +++++ coins/sayacoin.json | 5 +++++ coins/sha1coin.json | 5 +++++ package.json | 4 ++-- 5 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 coins/kumacoin.json create mode 100644 coins/monacoin.json create mode 100644 coins/sayacoin.json create mode 100644 coins/sha1coin.json diff --git a/coins/kumacoin.json b/coins/kumacoin.json new file mode 100644 index 0000000..ab7e895 --- /dev/null +++ b/coins/kumacoin.json @@ -0,0 +1,6 @@ +{ + "name": "Kumacoin", + "symbol": "KUMA", + "algorithm": "quark", + "mposDiffMultiplier": 256 +} diff --git a/coins/monacoin.json b/coins/monacoin.json new file mode 100644 index 0000000..d8897d4 --- /dev/null +++ b/coins/monacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Monacoin", + "symbol": "MONA", + "algorithm": "scrypt" +} \ No newline at end of file diff --git a/coins/sayacoin.json b/coins/sayacoin.json new file mode 100644 index 0000000..933c879 --- /dev/null +++ b/coins/sayacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Sayacoin", + "symbol": "SYC", + "algorithm": "sha256" +} \ No newline at end of file diff --git a/coins/sha1coin.json b/coins/sha1coin.json new file mode 100644 index 0000000..81e0fd4 --- /dev/null +++ b/coins/sha1coin.json @@ -0,0 +1,5 @@ +{ + "name": "Sha1coin", + "symbol": "SHA", + "algorithm": "sha1coin" +} diff --git a/package.json b/package.json index f6467a2..328db26 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ }, "repository": { "type": "git", - "url": "https://github.com/zone117x/node-open-mining-portal.git" + "url": "https://github.com/qazzxc/node-open-mining-portal.git" }, "dependencies": { - "stratum-pool": "git://github.com/zone117x/node-stratum-pool.git", + "stratum-pool": "git://github.com/qazzxc/node-stratum-pool.git", "dateformat": "*", "node-json-minify": "*", "redis": "*", From f49728c461513e5db10b3fba5145ffb3c60ed342 Mon Sep 17 00:00:00 2001 From: qazzac Date: Mon, 12 May 2014 10:15:39 +0900 Subject: [PATCH 114/150] Revert --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 328db26..7b65914 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ }, "repository": { "type": "git", - "url": "https://github.com/qazzxc/node-open-mining-portal.git" + "url": "https://github.com/zone117x/node-open-mining-portal.git" }, "dependencies": { - "stratum-pool": "git://github.com/qazzxc/node-stratum-pool.git", + "stratum-pool": "git://github.com/zone117x/node-stratum-pool.git", "dateformat": "*", "node-json-minify": "*", "redis": "*", @@ -51,4 +51,4 @@ "engines": { "node": ">=0.10" } -} +} \ No newline at end of file From 7abfb5151b057153d026042a3d4e8025208b657c Mon Sep 17 00:00:00 2001 From: qazzac Date: Mon, 12 May 2014 10:18:34 +0900 Subject: [PATCH 115/150] add lf --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b65914..f6467a2 100644 --- a/package.json +++ b/package.json @@ -51,4 +51,4 @@ "engines": { "node": ">=0.10" } -} \ No newline at end of file +} From ac7d7780fdeb3e98e9cbb090f756a6ff64471800 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 12 May 2014 11:15:17 -0600 Subject: [PATCH 116/150] Add redis version check so errors are thrown if you use too old of a redis version --- README.md | 2 +- libs/shareProcessor.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7bd804..95146bc 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ Inside the `config_example.json` file, ensure the default configuration will wor Explanation for each field: ````javascript { - /* Specifies the level of log output verbosity. Anything more severy than the level specified + /* Specifies the level of log output verbosity. Anything more severe than the level specified will also be logged. */ "logLevel": "debug", //or "warning", "error" diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 2632174..64fc188 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -14,6 +14,7 @@ value: a hash with.. */ + module.exports = function(logger, poolConfig){ var redisConfig = poolConfig.redis; @@ -38,6 +39,31 @@ module.exports = function(logger, poolConfig){ logger.error(logSystem, logComponent, logSubCat, 'Connection to redis database as been ended'); }); + connection.info(function(error, response){ + if (error){ + logger.error(logSystem, logComponent, logSubCat, 'Redis version check failed'); + return; + } + var parts = response.split('\r\n'); + var version; + var versionString; + for (var i = 0; i < parts.length; i++){ + if (parts[i].indexOf(':') !== -1){ + var valParts = parts[i].split(':'); + if (valParts[0] === 'redis_version'){ + versionString = valParts[1]; + version = parseFloat(versionString); + break; + } + } + } + if (!version){ + logger.error(logSystem, logComponent, logSubCat, 'Could not detect redis version - but be super old or broken'); + } + else if (version < 2.6){ + logger.error(logSystem, logComponent, logSubCat, "You're using redis version " + versionString + " the minimum required version is 2.6. Follow the damn usage instructions..."); + } + }); this.handleShare = function(isValidShare, isValidBlock, shareData){ From 5d15fe9dbf42dfb01cca531612fc60d0b07a3f75 Mon Sep 17 00:00:00 2001 From: Elbandi Date: Mon, 12 May 2014 23:17:00 +0200 Subject: [PATCH 117/150] Fix typo --- libs/website.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/website.js b/libs/website.js index 4156d1f..3b41490 100644 --- a/libs/website.js +++ b/libs/website.js @@ -240,7 +240,7 @@ module.exports = function(logger){ next(); }); - app.get('/key.html', function(reg, res, next){ + app.get('/key.html', function(req, res, next){ res.end(keyScriptProcessed); }); From 257a981c7a23375ce2f79d173d86ce00c42e0ec2 Mon Sep 17 00:00:00 2001 From: Elbandi Date: Tue, 13 May 2014 17:30:54 +0200 Subject: [PATCH 118/150] Add workers tab --- libs/shareProcessor.js | 13 ++++----- libs/stats.js | 30 +++++++++++++++---- libs/website.js | 1 + website/index.html | 6 ++++ website/pages/wks.html | 66 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 website/pages/wks.html diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 64fc188..4495a00 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -73,17 +73,16 @@ module.exports = function(logger, poolConfig){ if (isValidShare){ redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]); redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]); - - /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it - doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to - generate hashrate for each worker and pool. */ - var dateNow = Date.now(); - var hashrateData = [shareData.difficulty, shareData.worker, dateNow]; - redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); } else{ redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]); } + /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it + doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to + generate hashrate for each worker and pool. */ + var dateNow = Date.now(); + var hashrateData = [ isValidShare ? shareData.difficulty : -shareData.difficulty, shareData.worker, dateNow]; + redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); if (isValidBlock){ redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]); diff --git a/libs/stats.js b/libs/stats.js index 977002e..e128190 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -182,12 +182,28 @@ module.exports = function(logger, portalConfig, poolConfigs){ coinStats.hashrates.forEach(function(ins){ var parts = ins.split(':'); var workerShares = parseFloat(parts[0]); - coinStats.shares += workerShares; var worker = parts[1]; - if (worker in coinStats.workers) - coinStats.workers[worker] += workerShares; - else - coinStats.workers[worker] = workerShares; + if (workerShares > 0) { + coinStats.shares += workerShares; + if (worker in coinStats.workers) + coinStats.workers[worker].shares += workerShares; + else + coinStats.workers[worker] = { + shares: workerShares, + invalidshares: 0, + hashrateString: null + }; + } + else { + if (worker in coinStats.workers) + coinStats.workers[worker].invalidshares -= workerShares; // workerShares is negative number! + else + coinStats.workers[worker] = { + shares: 0, + invalidshares: -workerShares, + hashrateString: null + }; + } }); var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier; @@ -208,6 +224,10 @@ module.exports = function(logger, portalConfig, poolConfigs){ portalStats.algos[algo].hashrate += coinStats.hashrate; portalStats.algos[algo].workers += Object.keys(coinStats.workers).length; + for (var worker in coinStats.workers) { + coinStats.workers[worker].hashrateString = _this.getReadableHashRateString(shareMultiplier * coinStats.workers[worker].shares / portalConfig.website.stats.hashrateWindow); + } + delete coinStats.hashrates; delete coinStats.shares; coinStats.hashrateString = _this.getReadableHashRateString(coinStats.hashrate); diff --git a/libs/website.js b/libs/website.js index 3b41490..b13b65a 100644 --- a/libs/website.js +++ b/libs/website.js @@ -38,6 +38,7 @@ module.exports = function(logger){ 'getting_started.html': 'getting_started', 'stats.html': 'stats', 'tbs.html': 'tbs', + 'wks.html': 'wks', 'api.html': 'api', 'admin.html': 'admin', 'mining_key.html': 'mining_key' diff --git a/website/index.html b/website/index.html index 03544ac..77f9879 100644 --- a/website/index.html +++ b/website/index.html @@ -51,6 +51,12 @@ Tab Stats
  • +
  • + +   + Workers Stats + +
  •   diff --git a/website/pages/wks.html b/website/pages/wks.html new file mode 100644 index 0000000..31b0eb4 --- /dev/null +++ b/website/pages/wks.html @@ -0,0 +1,66 @@ + + + +
  • -
  • - +
  • +   Workers Stats diff --git a/website/pages/wks.html b/website/pages/workers.html similarity index 100% rename from website/pages/wks.html rename to website/pages/workers.html From 01de7222b65da2de54fec3596295e4d2f0f464f3 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 16 May 2014 14:52:29 -0500 Subject: [PATCH 120/150] Update opensourcecoin.json --- coins/opensourcecoin.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coins/opensourcecoin.json b/coins/opensourcecoin.json index 9b86d03..48baf8b 100644 --- a/coins/opensourcecoin.json +++ b/coins/opensourcecoin.json @@ -1,5 +1,6 @@ { "name": "OpenSourcecoin", "symbol": "OSC", - "algorithm": "sha256" + "algorithm": "sha256", + "txMessages" : true } From c3555ec95963440f88ff765e980e8e0d52ac4d34 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 21 May 2014 19:58:41 -0600 Subject: [PATCH 121/150] Force content type on doT generated html strings --- README.md | 8 ++++++-- init.js | 2 +- libs/website.js | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 95146bc..85d400c 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,12 @@ Usage ##### Seriously Those are legitimate requirements. If you use old versions of Node.js or Redis that may come with your system package manager then you will have problems. Follow the linked instructions to get the last stable versions. + +[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis - an easy way is to +include `bind 127.0.0.1` in your `redis.conf` file. Also it's a good idea to learn about and understand software that +you are using - a good place to start with redis is [data persistence](http://redis.io/topics/persistence). + + #### 0) Setting up coin daemon Follow the build/install instructions for your coin daemon. Your coin.conf file should end up looking something like this: ``` @@ -152,8 +158,6 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing -[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis - an easy way is to include `bind 127.0.0.1` in your `redis.conf` file - Clone the repository and run `npm update` for all the dependencies to be installed: ```bash diff --git a/init.js b/init.js index 75bdc61..fc8a81b 100644 --- a/init.js +++ b/init.js @@ -223,7 +223,7 @@ var spawnPoolWorkers = function(){ worker.type = 'pool'; poolWorkers[forkId] = worker; worker.on('exit', function(code, signal){ - logger.error('Master', 'PoolSpanwer', 'Fork ' + forkId + ' died, spawning replacement worker...'); + logger.error('Master', 'PoolSpawner', 'Fork ' + forkId + ' died, spawning replacement worker...'); setTimeout(function(){ createPoolWorker(forkId); }, 2000); diff --git a/libs/website.js b/libs/website.js index 7922dac..b570c24 100644 --- a/libs/website.js +++ b/libs/website.js @@ -218,6 +218,7 @@ module.exports = function(logger){ var route = function(req, res, next){ var pageId = req.params.page || ''; if (pageId in indexesProcessed){ + res.header('Content-Type', 'text/html'); res.end(indexesProcessed[pageId]); } else From 3f6cba7a969a1980f1f47a4a50fe1f3ec8e074ff Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:20:01 -0500 Subject: [PATCH 122/150] Create arkenstone.json --- coins/arkenstone.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/arkenstone.json diff --git a/coins/arkenstone.json b/coins/arkenstone.json new file mode 100644 index 0000000..d767bdd --- /dev/null +++ b/coins/arkenstone.json @@ -0,0 +1,5 @@ +{ + "name": "Arkenstone", + "symbol": "ARS", + "algorithm": "sha256" +} From 17d3170aa2d6c4d59422049cd7b34827513b3fee Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:21:23 -0500 Subject: [PATCH 123/150] Create 21coin.json --- coins/21coin.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/21coin.json diff --git a/coins/21coin.json b/coins/21coin.json new file mode 100644 index 0000000..8aa9ea5 --- /dev/null +++ b/coins/21coin.json @@ -0,0 +1,5 @@ +{ + "name": "21coin", + "symbol": "21", + "algorithm": "sha256" +} From e95a4e3a764f4bab14ff2ca667113ee858b589b2 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:23:10 -0500 Subject: [PATCH 124/150] Create arkhash.json --- coins/arkhash.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 coins/arkhash.json diff --git a/coins/arkhash.json b/coins/arkhash.json new file mode 100644 index 0000000..a8fbb9e --- /dev/null +++ b/coins/arkhash.json @@ -0,0 +1,6 @@ +{ + "name": "Arkhash", + "symbol": "ARK", + "algorithm": "sha256", + "txMessages" : true +} From a1f9b7dc89b27c5d3b9ea7ffaa117391926536a6 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:23:51 -0500 Subject: [PATCH 125/150] Create bitraam.json --- coins/bitraam.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/bitraam.json diff --git a/coins/bitraam.json b/coins/bitraam.json new file mode 100644 index 0000000..4a46ae7 --- /dev/null +++ b/coins/bitraam.json @@ -0,0 +1,5 @@ +{ + "name": "BitRaam", + "symbol": "BRM", + "algorithm": "sha256" +} From 5a44c548543ed65e0c79f9bd956c5fbdd739736c Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:25:03 -0500 Subject: [PATCH 126/150] Create continuumcoin.json --- coins/continuumcoin.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/continuumcoin.json diff --git a/coins/continuumcoin.json b/coins/continuumcoin.json new file mode 100644 index 0000000..21a3b6e --- /dev/null +++ b/coins/continuumcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Continuumcoin", + "symbol": "CTM", + "algorithm": "sha256" +} From e65984fe7cb3b1a7ffe74c2a356a4027dec11c71 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:25:37 -0500 Subject: [PATCH 127/150] Create fastcoinsha.json --- coins/fastcoinsha.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/fastcoinsha.json diff --git a/coins/fastcoinsha.json b/coins/fastcoinsha.json new file mode 100644 index 0000000..d4e0882 --- /dev/null +++ b/coins/fastcoinsha.json @@ -0,0 +1,5 @@ +{ + "name": "Fastcoinsha", + "symbol": "FSS", + "algorithm": "sha256" +} From ee0815befa3f4a084490a1cdbcf7358abd3c20ad Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:27:05 -0500 Subject: [PATCH 128/150] Create stashcoin.json --- coins/stashcoin.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/stashcoin.json diff --git a/coins/stashcoin.json b/coins/stashcoin.json new file mode 100644 index 0000000..6c2a50a --- /dev/null +++ b/coins/stashcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Stashcoin", + "symbol": "STA", + "algorithm": "sha256" +} From 5b308168bf32eaab34c34ecc70c6fb3a0101baea Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 19:28:06 -0500 Subject: [PATCH 129/150] Create wearesatoshi.json --- coins/wearesatoshi.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coins/wearesatoshi.json diff --git a/coins/wearesatoshi.json b/coins/wearesatoshi.json new file mode 100644 index 0000000..01ed43d --- /dev/null +++ b/coins/wearesatoshi.json @@ -0,0 +1,5 @@ +{ + "name": "WeAreSatoshi", + "symbol": "WAS", + "algorithm": "sha256" +} From 5983b3a7de3852f4bfea690e70904e7bfd8258c7 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Thu, 22 May 2014 20:22:36 -0500 Subject: [PATCH 130/150] Update arkhash.json --- coins/arkhash.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coins/arkhash.json b/coins/arkhash.json index a8fbb9e..da4d780 100644 --- a/coins/arkhash.json +++ b/coins/arkhash.json @@ -1,6 +1,5 @@ { "name": "Arkhash", "symbol": "ARK", - "algorithm": "sha256", - "txMessages" : true + "algorithm": "sha256" } From b5da4f9428c820f19f57bd199a17cdfcc86d22f6 Mon Sep 17 00:00:00 2001 From: Russel Waters Date: Fri, 23 May 2014 09:05:34 -0700 Subject: [PATCH 131/150] Update tekcoin.json --- coins/tekcoin.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coins/tekcoin.json b/coins/tekcoin.json index 44ba31b..4b3a276 100644 --- a/coins/tekcoin.json +++ b/coins/tekcoin.json @@ -1,5 +1,6 @@ { "name": "Tekcoin", "symbol": "TEK", - "algorithm": "sha256" + "algorithm": "sha256", + "txMessages": "true" } From b016c751069d375c8820fb55f5ae43ecd8d3ae15 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 23 May 2014 11:13:30 -0500 Subject: [PATCH 132/150] Update tekcoin.json --- coins/tekcoin.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coins/tekcoin.json b/coins/tekcoin.json index 44ba31b..f63f94b 100644 --- a/coins/tekcoin.json +++ b/coins/tekcoin.json @@ -2,4 +2,6 @@ "name": "Tekcoin", "symbol": "TEK", "algorithm": "sha256" + "algorithm": "sha256", + "txMessages": "true" } From c42a64742f1faf2d41fa95b7b5c50d085c97ad5b Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 23 May 2014 11:18:33 -0500 Subject: [PATCH 133/150] Update tekcoin.json --- coins/tekcoin.json | 1 - 1 file changed, 1 deletion(-) diff --git a/coins/tekcoin.json b/coins/tekcoin.json index f63f94b..4b3a276 100644 --- a/coins/tekcoin.json +++ b/coins/tekcoin.json @@ -1,7 +1,6 @@ { "name": "Tekcoin", "symbol": "TEK", - "algorithm": "sha256" "algorithm": "sha256", "txMessages": "true" } From 64b48388fc2fd7e81112ab930d446db8764f6082 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 23 May 2014 11:31:49 -0500 Subject: [PATCH 134/150] Create apiCoinWarz.js --- libs/apiCoinWarz.js | 204 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 libs/apiCoinWarz.js diff --git a/libs/apiCoinWarz.js b/libs/apiCoinWarz.js new file mode 100644 index 0000000..2153df4 --- /dev/null +++ b/libs/apiCoinWarz.js @@ -0,0 +1,204 @@ +ar request = require('request'); +var nonce = require('nonce'); + +module.exports = function() { + 'use strict'; + + // Module dependencies + + // Constants + var version = '0.1.0', + PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', + PRIVATE_API_URL = 'https://api.cryptsy.com/api', + USER_AGENT = 'nomp/node-open-mining-portal' + + // Constructor + function Cryptsy(key, secret){ + // Generate headers signed by this user's key and secret. + // The secret is encapsulated and never exposed + this._getPrivateHeaders = function(parameters){ + var paramString, signature; + + if (!key || !secret){ + throw 'Cryptsy: Error. API key and secret required'; + } + + // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` + paramString = Object.keys(parameters).sort().map(function(param){ + return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); + }).join('&'); + + signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); + + return { + Key: key, + Sign: signature + }; + }; + } + + // If a site uses non-trusted SSL certificates, set this value to false + Cryptsy.STRICT_SSL = true; + + // Helper methods + function joinCurrencies(currencyA, currencyB){ + return currencyA + '_' + currencyB; + } + + // Prototype + Cryptsy.prototype = { + constructor: Cryptsy, + + // Make an API request + _request: function(options, callback){ + if (!('headers' in options)){ + options.headers = {}; + } + + options.headers['User-Agent'] = USER_AGENT; + options.json = true; + options.strictSSL = Cryptsy.STRICT_SSL; + + request(options, function(err, response, body) { + callback(err, body); + }); + + return this; + }, + + // Make a public API request + _public: function(parameters, callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL, + qs: parameters + }; + + return this._request(options, callback); + }, + + // Make a private API request + _private: function(parameters, callback){ + var options; + + parameters.nonce = nonce(); + options = { + method: 'POST', + url: PRIVATE_API_URL, + form: parameters, + headers: this._getPrivateHeaders(parameters) + }; + + return this._request(options, callback); + }, + + + ///// + + + // PUBLIC METHODS + + getTicker: function(callback){ + var parameters = { + method: 'marketdatav2' + }; + + return this._public(parameters, callback); + }, + + getOrderBook: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOrderBook', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + getTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + + ///// + + + // PRIVATE METHODS + + myBalances: function(callback){ + var parameters = { + command: 'returnBalances' + }; + + return this._private(parameters, callback); + }, + + myOpenOrders: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOpenOrders', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + myTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + buy: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'buy', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + sell: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'sell', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + cancelOrder: function(currencyA, currencyB, orderNumber, callback){ + var parameters = { + command: 'cancelOrder', + currencyPair: joinCurrencies(currencyA, currencyB), + orderNumber: orderNumber + }; + + return this._private(parameters, callback); + }, + + withdraw: function(currency, amount, address, callback){ + var parameters = { + command: 'withdraw', + currency: currency, + amount: amount, + address: address + }; + + return this._private(parameters, callback); + } + }; + + return Cryptsy; +}(); From 248507b5eeb4478741477c40426077d4bb146959 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 23 May 2014 11:46:51 -0500 Subject: [PATCH 135/150] Update apiCoinWarz.js --- libs/apiCoinWarz.js | 107 ++++---------------------------------------- 1 file changed, 9 insertions(+), 98 deletions(-) diff --git a/libs/apiCoinWarz.js b/libs/apiCoinWarz.js index 2153df4..dd0a550 100644 --- a/libs/apiCoinWarz.js +++ b/libs/apiCoinWarz.js @@ -7,9 +7,8 @@ module.exports = function() { // Module dependencies // Constants - var version = '0.1.0', - PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', - PRIVATE_API_URL = 'https://api.cryptsy.com/api', + var version = '0.0.1', + PUBLIC_API_URL = 'http://www.coinwarz.com/v1/api/profitability/?apikey=YOUR_API_KEY&algo=all', USER_AGENT = 'nomp/node-open-mining-portal' // Constructor @@ -20,7 +19,7 @@ module.exports = function() { var paramString, signature; if (!key || !secret){ - throw 'Cryptsy: Error. API key and secret required'; + throw 'CoinWarz: Error. API key and secret required'; } // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` @@ -46,8 +45,8 @@ module.exports = function() { } // Prototype - Cryptsy.prototype = { - constructor: Cryptsy, + CoinWarz.prototype = { + constructor: CoinWarz, // Make an API request _request: function(options, callback){ @@ -57,7 +56,7 @@ module.exports = function() { options.headers['User-Agent'] = USER_AGENT; options.json = true; - options.strictSSL = Cryptsy.STRICT_SSL; + options.strictSSL = CoinWarz.STRICT_SSL; request(options, function(err, response, body) { callback(err, body); @@ -77,21 +76,6 @@ module.exports = function() { return this._request(options, callback); }, - // Make a private API request - _private: function(parameters, callback){ - var options; - - parameters.nonce = nonce(); - options = { - method: 'POST', - url: PRIVATE_API_URL, - form: parameters, - headers: this._getPrivateHeaders(parameters) - }; - - return this._request(options, callback); - }, - ///// @@ -125,80 +109,7 @@ module.exports = function() { }, - ///// - - - // PRIVATE METHODS - - myBalances: function(callback){ - var parameters = { - command: 'returnBalances' - }; - - return this._private(parameters, callback); - }, - - myOpenOrders: function(currencyA, currencyB, callback){ - var parameters = { - command: 'returnOpenOrders', - currencyPair: joinCurrencies(currencyA, currencyB) - }; - - return this._private(parameters, callback); - }, - - myTradeHistory: function(currencyA, currencyB, callback){ - var parameters = { - command: 'returnTradeHistory', - currencyPair: joinCurrencies(currencyA, currencyB) - }; - - return this._private(parameters, callback); - }, - - buy: function(currencyA, currencyB, rate, amount, callback){ - var parameters = { - command: 'buy', - currencyPair: joinCurrencies(currencyA, currencyB), - rate: rate, - amount: amount - }; - - return this._private(parameters, callback); - }, - - sell: function(currencyA, currencyB, rate, amount, callback){ - var parameters = { - command: 'sell', - currencyPair: joinCurrencies(currencyA, currencyB), - rate: rate, - amount: amount - }; - - return this._private(parameters, callback); - }, - - cancelOrder: function(currencyA, currencyB, orderNumber, callback){ - var parameters = { - command: 'cancelOrder', - currencyPair: joinCurrencies(currencyA, currencyB), - orderNumber: orderNumber - }; - - return this._private(parameters, callback); - }, - - withdraw: function(currency, amount, address, callback){ - var parameters = { - command: 'withdraw', - currency: currency, - amount: amount, - address: address - }; - - return this._private(parameters, callback); - } - }; - - return Cryptsy; + //// + + return CoinWarz; }(); From 2b2887eca1dba2898997b1972770cd6b56a2c429 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 23 May 2014 11:51:24 -0500 Subject: [PATCH 136/150] Update profitSwitch.js --- libs/profitSwitch.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 9836f2f..2a99928 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -4,6 +4,7 @@ var bignum = require('bignum'); var algos = require('stratum-pool/lib/algoProperties.js'); var util = require('stratum-pool/lib/util.js'); +var CoinWarz = require('./apiCoinWarz.js'); var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); var Mintpal = require('./apiMintpal.js'); @@ -64,6 +65,10 @@ module.exports = function(logger){ // // setup APIs // + var coinwarzApi = new CpinWarz( + // 'API_KEY', + // 'API_SECRET' + ); var poloApi = new Poloniex( // 'API_KEY', // 'API_SECRET' From f9f329fb1b026f167d6c20ca65cd650352f42237 Mon Sep 17 00:00:00 2001 From: ob1100 Date: Fri, 23 May 2014 11:55:20 -0500 Subject: [PATCH 137/150] Update apiCoinWarz.js --- libs/apiCoinWarz.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/apiCoinWarz.js b/libs/apiCoinWarz.js index dd0a550..172d2b7 100644 --- a/libs/apiCoinWarz.js +++ b/libs/apiCoinWarz.js @@ -1,4 +1,4 @@ -ar request = require('request'); +var request = require('request'); var nonce = require('nonce'); module.exports = function() { From 57873f7226b8ca493614587319b6f6b9a2d115ac Mon Sep 17 00:00:00 2001 From: Damian Black Date: Tue, 27 May 2014 17:05:12 -0400 Subject: [PATCH 138/150] Add Bittrex API for profitability checks --- libs/apiBittrex.js | 222 +++++++++++++++++++++++++++++++++++++++++++ libs/profitSwitch.js | 121 +++++++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 libs/apiBittrex.js diff --git a/libs/apiBittrex.js b/libs/apiBittrex.js new file mode 100644 index 0000000..9a46d58 --- /dev/null +++ b/libs/apiBittrex.js @@ -0,0 +1,222 @@ +var request = require('request'); +var nonce = require('nonce'); + +module.exports = function() { + 'use strict'; + + // Module dependencies + + // Constants + var version = '0.1.0', + PUBLIC_API_URL = 'https://bittrex.com/api/v1/public', + PRIVATE_API_URL = 'https://bittrex.com/api/v1/market', + USER_AGENT = 'nomp/node-open-mining-portal' + + // Constructor + function Bittrex(key, secret){ + // Generate headers signed by this user's key and secret. + // The secret is encapsulated and never exposed + this._getPrivateHeaders = function(parameters){ + var paramString, signature; + + if (!key || !secret){ + throw 'Bittrex: Error. API key and secret required'; + } + + // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` + paramString = Object.keys(parameters).sort().map(function(param){ + return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); + }).join('&'); + + signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); + + return { + Key: key, + Sign: signature + }; + }; + } + + // If a site uses non-trusted SSL certificates, set this value to false + Bittrex.STRICT_SSL = true; + + // Helper methods + function joinCurrencies(currencyA, currencyB){ + return currencyA + '-' + currencyB; + } + + // Prototype + Bittrex.prototype = { + constructor: Bittrex, + + // Make an API request + _request: function(options, callback){ + if (!('headers' in options)){ + options.headers = {}; + } + + options.headers['User-Agent'] = USER_AGENT; + options.json = true; + options.strictSSL = Bittrex.STRICT_SSL; + + request(options, function(err, response, body) { + callback(err, body); + }); + + return this; + }, + + // Make a public API request + _public: function(parameters, callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL, + qs: parameters + }; + + return this._request(options, callback); + }, + + // Make a private API request + _private: function(parameters, callback){ + var options; + + parameters.nonce = nonce(); + options = { + method: 'POST', + url: PRIVATE_API_URL, + form: parameters, + headers: this._getPrivateHeaders(parameters) + }; + + return this._request(options, callback); + }, + + + ///// + + + // PUBLIC METHODS + + getTicker: function(callback){ + var options = { + method: 'GET', + url: PUBLIC_API_URL + '/getmarketsummaries', + qs: null + }; + + return this._request(options, callback); + }, + + // getBuyOrderBook: function(currencyA, currencyB, callback){ + // var options = { + // method: 'GET', + // url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY', + // qs: null + // }; + + // return this._request(options, callback); + // }, + + getOrderBook: function(currencyA, currencyB, callback){ + var parameters = { + market: joinCurrencies(currencyA, currencyB), + type: 'buy', + depth: '50' + } + var options = { + method: 'GET', + url: PUBLIC_API_URL + '/getorderbook', + qs: parameters + } + + return this._request(options, callback); + }, + + getTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._public(parameters, callback); + }, + + + ///// + + + // PRIVATE METHODS + + myBalances: function(callback){ + var parameters = { + command: 'returnBalances' + }; + + return this._private(parameters, callback); + }, + + myOpenOrders: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnOpenOrders', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + myTradeHistory: function(currencyA, currencyB, callback){ + var parameters = { + command: 'returnTradeHistory', + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private(parameters, callback); + }, + + buy: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'buy', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + sell: function(currencyA, currencyB, rate, amount, callback){ + var parameters = { + command: 'sell', + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private(parameters, callback); + }, + + cancelOrder: function(currencyA, currencyB, orderNumber, callback){ + var parameters = { + command: 'cancelOrder', + currencyPair: joinCurrencies(currencyA, currencyB), + orderNumber: orderNumber + }; + + return this._private(parameters, callback); + }, + + withdraw: function(currency, amount, address, callback){ + var parameters = { + command: 'withdraw', + currency: currency, + amount: amount, + address: address + }; + + return this._private(parameters, callback); + } + }; + + return Bittrex; +}(); diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 5650ffa..f680936 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -7,6 +7,7 @@ var util = require('stratum-pool/lib/util.js'); var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); var Mintpal = require('./apiMintpal.js'); +var Bittrex = require('./apiBittrex.js'); var Stratum = require('stratum-pool'); module.exports = function(logger){ @@ -77,6 +78,11 @@ module.exports = function(logger){ // 'API_SECRET' ); + var bittrexApi = new Bittrex( + // 'API_KEY' + // 'API_SECRET' + ); + // // market data collection from Poloniex // @@ -393,6 +399,118 @@ module.exports = function(logger){ }; + this.getProfitDataBittrex = function(callback){ + async.series([ + function(taskCallback){ + bittrexApi.getTicker(function(err, response){ + if (err || !response.result){ + taskCallback(err); + return; + } + + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + response.result.forEach(function(market){ + var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; + if (!exchangeInfo.hasOwnProperty('Bittrex')) + exchangeInfo['Bittrex'] = {}; + + var marketData = exchangeInfo['Bittrex']; + var marketPair = market.MarketName.match(/([\w]+)-([\w-_]+)/) + market.exchange = marketPair[1] + market.code = marketPair[2] + if (market.exchange == 'BTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; + + marketData['BTC'].last = new Number(market.Last); + marketData['BTC'].baseVolume = new Number(market.BaseVolume); + marketData['BTC'].quoteVolume = new Number(market.BaseVolume / market.Last); + marketData['BTC'].ask = new Number(market.Ask); + marketData['BTC'].bid = new Number(market.Bid); + } + + if (market.exchange == 'LTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; + + marketData['LTC'].last = new Number(market.Last); + marketData['LTC'].baseVolume = new Number(market.BaseVolume); + marketData['LTC'].quoteVolume = new Number(market.BaseVolume / market.Last); + marketData['LTC'].ask = new Number(market.Ask); + marketData['LTC'].bid = new Number(market.Bid); + } + + }); + }); + taskCallback(); + }); + }, + function(taskCallback){ + var depthTasks = []; + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Bittrex']; + if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromBittrex('BTC', symbol, marketData['BTC'].bid, callback) + }); + } + if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromBittrex('LTC', symbol, marketData['LTC'].bid, callback) + }); + } + }); + + if (!depthTasks.length){ + taskCallback(); + return; + } + async.series(depthTasks, function(err){ + if (err){ + taskCallback(err); + return; + } + taskCallback(); + }); + } + ], function(err){ + if (err){ + callback(err); + return; + } + callback(null); + }); + }; + this.getMarketDepthFromBittrex = function(symbolA, symbolB, coinPrice, callback){ + bittrexApi.getOrderBook(symbolA, symbolB, function(err, response){ + if (err){ + callback(err); + return; + } + var depth = new Number(0); + if (response.hasOwnProperty('result')){ + var totalQty = new Number(0); + response['result'].forEach(function(order){ + var price = new Number(order.Rate); + var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); + var qty = new Number(order.Quantity); + // only measure the depth down to configured depth + if (price >= limit){ + depth += (qty * price); + totalQty += qty; + } + }); + } + + var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Bittrex']; + marketData[symbolA].depth = depth; + if (totalQty > 0) + marketData[symbolA].weightedBid = new Number(depth / totalQty); + callback(); + }); + }; + + this.getCoindDaemonInfo = function(callback){ var daemonTasks = []; Object.keys(profitStatus).forEach(function(algo){ @@ -525,6 +643,9 @@ module.exports = function(logger){ if (portalConfig.profitSwitch.useMintpal) profitabilityTasks.push(_this.getProfitDataMintpal); + if (portalConfig.profitSwitch.useBittrex) + profitabilityTasks.push(_this.getProfitDataBittrex); + profitabilityTasks.push(_this.getCoindDaemonInfo); profitabilityTasks.push(_this.getMiningRate); From 5cd6f653eba59f4754b0c79473e3c1615a981120 Mon Sep 17 00:00:00 2001 From: Damian Black Date: Tue, 27 May 2014 17:13:14 -0400 Subject: [PATCH 139/150] add bitstar, bluecoin, octocoin, pawncoin, plncoin, zedcoin --- coins/bitstar.json | 7 +++++++ coins/bluecoin.json | 7 +++++++ coins/einsteinium.json | 5 ++++- coins/octocoin.json | 7 +++++++ coins/pawncoin.json | 7 +++++++ coins/plncoin.json | 7 +++++++ coins/suncoin.json | 4 +++- coins/zedcoin.json | 7 +++++++ 8 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 coins/bitstar.json create mode 100644 coins/bluecoin.json create mode 100644 coins/octocoin.json create mode 100644 coins/pawncoin.json create mode 100644 coins/plncoin.json create mode 100644 coins/zedcoin.json diff --git a/coins/bitstar.json b/coins/bitstar.json new file mode 100644 index 0000000..7ee054d --- /dev/null +++ b/coins/bitstar.json @@ -0,0 +1,7 @@ +{ + "name": "bitstar", + "symbol": "bits", + "algorithm": "scrypt", + "peerMagic": "cef1dbfa", + "peerMagicTestnet": "cdf1c0ef" +} diff --git a/coins/bluecoin.json b/coins/bluecoin.json new file mode 100644 index 0000000..361c28f --- /dev/null +++ b/coins/bluecoin.json @@ -0,0 +1,7 @@ +{ + "name": "bluecoin", + "symbol": "blu", + "algorithm": "scrypt", + "peerMagic": "fef5abaa", + "peerMagicTestnet": "eaceedcd" +} diff --git a/coins/einsteinium.json b/coins/einsteinium.json index d2575ef..c75bad6 100644 --- a/coins/einsteinium.json +++ b/coins/einsteinium.json @@ -1,5 +1,8 @@ { "name": "Einsteinium", "symbol": "EMC2", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "e8f1c4ac", + "peerMagicTestnet": "faa2f0c1", + "txMessages": true } diff --git a/coins/octocoin.json b/coins/octocoin.json new file mode 100644 index 0000000..99867b4 --- /dev/null +++ b/coins/octocoin.json @@ -0,0 +1,7 @@ +{ + "name": "Octocoin", + "symbol": "888", + "algorithm": "scrypt", + "peerMagic": "fbc0b6db", + "peerMagicTestnet": "fcc1b7dc" +} diff --git a/coins/pawncoin.json b/coins/pawncoin.json new file mode 100644 index 0000000..e7aa6f8 --- /dev/null +++ b/coins/pawncoin.json @@ -0,0 +1,7 @@ +{ + "name": "pawncoin", + "symbol": "pawn", + "algorithm": "scrypt", + "peerMagic": "fcc1b7dc", + "peerMagicTestnet": "c0c0c0c0" +} diff --git a/coins/plncoin.json b/coins/plncoin.json new file mode 100644 index 0000000..36ac28e --- /dev/null +++ b/coins/plncoin.json @@ -0,0 +1,7 @@ +{ + "name": "plncoin", + "symbol": "plnc", + "algorithm": "scrypt", + "peerMagic": "fbc0b6db", + "peerMagicTestnet": "fcc1b7dc" +} diff --git a/coins/suncoin.json b/coins/suncoin.json index fea2aed..33cca6e 100644 --- a/coins/suncoin.json +++ b/coins/suncoin.json @@ -1,5 +1,7 @@ { "name": "Suncoin", "symbol": "SUN", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic":"fcd9b7dd", + "peerMagicTestnet":"fbc0b8db" } diff --git a/coins/zedcoin.json b/coins/zedcoin.json new file mode 100644 index 0000000..be6894d --- /dev/null +++ b/coins/zedcoin.json @@ -0,0 +1,7 @@ +{ + "name": "zedcoin", + "symbol": "zed", + "algorithm": "scrypt", + "peerMagic": "c0dbf1fd", + "peerMagicTestnet": "fdc2b6f1" +} From fb12b7373ef7d7bb68519539a1208f30394cc4ce Mon Sep 17 00:00:00 2001 From: Damian Black Date: Tue, 27 May 2014 17:52:42 -0400 Subject: [PATCH 140/150] revert change to einsteinium --- coins/einsteinium.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/coins/einsteinium.json b/coins/einsteinium.json index c75bad6..d2575ef 100644 --- a/coins/einsteinium.json +++ b/coins/einsteinium.json @@ -1,8 +1,5 @@ { "name": "Einsteinium", "symbol": "EMC2", - "algorithm": "scrypt", - "peerMagic": "e8f1c4ac", - "peerMagicTestnet": "faa2f0c1", - "txMessages": true + "algorithm": "scrypt" } From d7120a1f2222059bd013740548097544f398ee87 Mon Sep 17 00:00:00 2001 From: Damian Black Date: Wed, 28 May 2014 10:17:04 -0400 Subject: [PATCH 141/150] add useBittrex to config_example.json --- config_example.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config_example.json b/config_example.json index 9534651..ce057cd 100644 --- a/config_example.json +++ b/config_example.json @@ -100,6 +100,7 @@ "depth": 0.90, "usePoloniex": true, "useCryptsy": true, - "useMintpal": true + "useMintpal": true, + "useBittrex": true } } From 5a0afa660ca946191a518f304257e0d139ab3b3b Mon Sep 17 00:00:00 2001 From: Damian Black Date: Wed, 28 May 2014 10:17:32 -0400 Subject: [PATCH 142/150] add missing comma to bittrexApi --- libs/profitSwitch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index f680936..990314e 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -79,7 +79,7 @@ module.exports = function(logger){ ); var bittrexApi = new Bittrex( - // 'API_KEY' + // 'API_KEY', // 'API_SECRET' ); From 116983fc85b9b1e6db47fa7a7dd810d5ebdb428f Mon Sep 17 00:00:00 2001 From: Jerry Brady Date: Wed, 28 May 2014 20:56:56 +0000 Subject: [PATCH 143/150] Fixed starting difficulty issues for proxy ports, including X11 --- config_example.json | 9 +++++++- libs/poolWorker.js | 50 +++++++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/config_example.json b/config_example.json index 9534651..9083c59 100644 --- a/config_example.json +++ b/config_example.json @@ -88,7 +88,14 @@ "algorithm": "x11", "ports": { "5555": { - "diff": 0.001 + "diff": 0.001, + "varDiff": { + "minDiff": 0.001, + "maxDiff": 1, + "targetTime": 15, + "retargetTime": 60, + "variancePercent": 30 + } } } } diff --git a/libs/poolWorker.js b/libs/poolWorker.js index dc48282..d85d016 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -202,6 +202,8 @@ module.exports = function(logger){ logger[severity](logSystem, logComponent, logSubCat, text); }).on('banIP', function(ip, worker){ process.send({type: 'banIP', ip: ip}); + }).on('started', function(){ + _this.setDifficultyForProxyPort(pool, poolOptions.coin.name, poolOptions.coin.algorithm); }); pool.start(); @@ -259,17 +261,6 @@ module.exports = function(logger){ }; - Object.keys(pools).forEach(function (coinName) { - var p = pools[coinName]; - if (poolConfigs[coinName].coin.algorithm === algorithm) { - for (var port in portalConfig.switching[switchName].ports) { - if (portalConfig.switching[switchName].ports[port].varDiff) - p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); - } - } - }); - - Object.keys(proxySwitch[switchName].ports).forEach(function(port){ var f = net.createServer(function(socket) { var currentPool = proxySwitch[switchName].currentPool; @@ -278,8 +269,11 @@ module.exports = function(logger){ + switchName + ' from ' + socket.remoteAddress + ' on ' + port + ' routing to ' + currentPool); - - pools[currentPool].getStratumServer().handleNewClient(socket); + + if (pools[currentPool]) + pools[currentPool].getStratumServer().handleNewClient(socket); + else + pools[initialPool].getStratumServer().handleNewClient(socket); }).listen(parseInt(port), function() { logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName @@ -304,4 +298,34 @@ module.exports = function(logger){ }); return foundCoin; }; + + // + // Called when stratum pool emits its 'started' event to copy the initial diff and vardiff + // configuation for any proxy switching ports configured into the stratum pool object. + // + this.setDifficultyForProxyPort = function(pool, coin, algo) { + + logger.debug(logSystem, logComponent, algo, 'Setting proxy difficulties after pool start'); + + Object.keys(portalConfig.switching).forEach(function(switchName) { + if (!portalConfig.switching[switchName].enabled) return; + + var switchAlgo = portalConfig.switching[switchName].algorithm; + if (pool.options.coin.algorithm !== switchAlgo) return; + + // we know the switch configuration matches the pool's algo, so setup the diff and + // vardiff for each of the switch's ports + for (var port in portalConfig.switching[switchName].ports) { + + if (portalConfig.switching[switchName].ports[port].varDiff) + pool.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); + + if (portalConfig.switching[switchName].ports[port].diff){ + if (!pool.options.ports.hasOwnProperty(port)) + pool.options.ports[port] = {}; + pool.options.ports[port].diff = portalConfig.switching[switchName].ports[port].diff; + } + } + }); + }; }; From 0f8c3bc8476dfc1b4d2aa6bc3ba29f65ba0128a6 Mon Sep 17 00:00:00 2001 From: PCPerfect Date: Wed, 28 May 2014 21:46:04 -0700 Subject: [PATCH 144/150] Update maxcoin.json Added P2P peerMagic values from wallet source. --- coins/maxcoin.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coins/maxcoin.json b/coins/maxcoin.json index 33c286e..715955f 100644 --- a/coins/maxcoin.json +++ b/coins/maxcoin.json @@ -2,4 +2,7 @@ "name": "Maxcoin", "symbol": "MAX", "algorithm": "keccak" -} \ No newline at end of file + + "peerMagic": "f9bebbd2", + "peerMagicTestNet": "0b11bb07" +} From 649ce1dfba0b0e34733bdbbd26398b03a3eade4c Mon Sep 17 00:00:00 2001 From: PCPerfect Date: Wed, 28 May 2014 21:49:37 -0700 Subject: [PATCH 145/150] Update maxcoin.json --- coins/maxcoin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coins/maxcoin.json b/coins/maxcoin.json index 715955f..aa81270 100644 --- a/coins/maxcoin.json +++ b/coins/maxcoin.json @@ -1,7 +1,7 @@ { "name": "Maxcoin", "symbol": "MAX", - "algorithm": "keccak" + "algorithm": "keccak", "peerMagic": "f9bebbd2", "peerMagicTestNet": "0b11bb07" From 8e727f9e36a660915db7885a25ca6236cbef9aca Mon Sep 17 00:00:00 2001 From: PCPerfect Date: Wed, 28 May 2014 21:55:57 -0700 Subject: [PATCH 146/150] Update cryptometh.json Added peerMagic values from wallet source. --- coins/cryptometh.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/coins/cryptometh.json b/coins/cryptometh.json index 6015361..a116c12 100644 --- a/coins/cryptometh.json +++ b/coins/cryptometh.json @@ -1,5 +1,8 @@ { "name": "Cryptometh", "symbol": "METH", - "algorithm": "keccak" -} \ No newline at end of file + "algorithm": "keccak", + + "peerMagic": "2bf2ed4f" , + "peerMagicTestNet": "b28cfda7" +} From 1578ad87c0f3c243166473c21f26048755b2b4fe Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 29 May 2014 12:52:31 -0600 Subject: [PATCH 147/150] Fixed bug with missing file system module for payment processing fail safe --- libs/paymentProcessor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 8e7866e..30054a3 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -1,3 +1,5 @@ +var fs = require('fs'); + var redis = require('redis'); var async = require('async'); From 9457c6ed90ae288c9bc074a4b5d8a8e95345affb Mon Sep 17 00:00:00 2001 From: Alejandro Reyero Date: Thu, 29 May 2014 22:41:52 +0200 Subject: [PATCH 148/150] Update profitSwitch.js CoinWarz Api incomplete, instancing wrong class at profitSwitch, name of class Cryptsy in it's own .js ... --- libs/profitSwitch.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index c4b712d..990314e 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -4,7 +4,6 @@ var bignum = require('bignum'); var algos = require('stratum-pool/lib/algoProperties.js'); var util = require('stratum-pool/lib/util.js'); -var CoinWarz = require('./apiCoinWarz.js'); var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); var Mintpal = require('./apiMintpal.js'); @@ -66,10 +65,6 @@ module.exports = function(logger){ // // setup APIs // - var coinwarzApi = new CpinWarz( - // 'API_KEY', - // 'API_SECRET' - ); var poloApi = new Poloniex( // 'API_KEY', // 'API_SECRET' From f999ebff188612641aae68393c316edfdb20887f Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 29 May 2014 16:56:36 -0600 Subject: [PATCH 149/150] Fixed bug in parsing balances for payment processing --- libs/paymentProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 30054a3..ecdce13 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -169,7 +169,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var workers = {}; for (var w in results[0]){ - workers[w] = {balance: coinsToSatoshies(parseInt(results[0][w]))}; + workers[w] = {balance: coinsToSatoshies(parseFloat(results[0][w]))}; } var rounds = results[1].map(function(r){ From 952a5cb196dffd071811eaccf094a6bc4c0bd45a Mon Sep 17 00:00:00 2001 From: Daniel Storjordet Date: Fri, 30 May 2014 12:23:11 +0200 Subject: [PATCH 150/150] Added fixminer.com pool --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 85d400c..a9afa54 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ If your pool uses NOMP let us know and we will list your website here. * http://mining.theminingpools.com * http://www.omargpools.ca/pools.html * http://pool.trademybit.com/ +* http://fixminer.com Usage =====