Bitcoin Forum
November 02, 2024, 08:33:12 AM *
News: Latest Bitcoin Core release: 28.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1] 2 3 4 5 6 7 »  All
  Print  
Author Topic: How to build your own Multipool - the Open Source Way  (Read 35456 times)
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 03:17:30 PM
Last edit: July 19, 2014, 05:07:50 PM by paradigmflux
 #1

In this thread, I am going to go through the step required to build your very own POS multipool, complete with pretty front end stats and automatic exchange trading..  This will only work for bitcoin related coins.  I don't promise to share all the secrets, but enough to get a site up and running.

I am going to tell you all this completely free.
I am not going to polute this with donation solicitations - if you would like to send me some btc for writing this once you're done reading it, pm me and I'll send you my address.

 I will make some effort to support to help with the implementation of this if anyone has trouble.



It might not be the prettiest, but it works - and I won't blackmail coins out of any of you. Smiley  This is a big screw you to all the cunt MP operators who have forgotten where this entire scene came from. Satoshi releaed the bitcoin client opensource, quit charging people a bitcoin to set them up a multipool and quit trying to screw everyone over.
This implementation btw, does not fudge any of the stats like some of the other major pools, and this offers complete visibility into stats.  It also doesn't do any of that "everbody contributes shares to an algo specific bucket that pays out once per day" crap where the pool operator can steal 1/3 of the money off the top before anyone can tell.

This uses a combination of python, node.js and even basic cronjobs to get the job done.  


Step #1 - start with a basic NOMP install. git clone from the NOMP repo: https://github.com/zone117x/node-open-mining-portal.git
Step #2 - Set up your coin daemons as if this was a basic NOMP build.  There are a few exceptions.  In the /coins/ directory you are going to be limited to only using coins posted on either Cryptsy or Mintpal - I could eaily have added some other markets to this, so feel free.  For each coin, in the /coins directiory include an additional line: "ID": "<either the market ID from Cryptsy, or the word "mintpal" if the coin is only on mintpal>"

IE:
Quote
root@blackcoinpool# cat feathercoin.json
{
"name": "Feathercoin",
"ID": "5",
"symbol": "FTC",
"algorithm": "scrypt"
}
The pool is going to use this to help it look up the values for the coin when it is calculating some of the stats later.

Also, when setting up the coin daemons - we aren't going to use the NOMP payment processor for anything more than calculating the balances tables in redis.  So make sure to set every coin's minimum payout to 999999999999999 and in the main config.json disable the worker name authentication.
Quote
main config.json...

    "defaultPoolConfigs": {
        "blockRefreshInterval": 1000,
        "jobRebroadcastTimeout": 55,
        "connectionTimeout": 600,
       "emitInvalidBlockHashes": false,
       "validateWorkerUsername": false,
        "tcpProxyProtocol": false,
        "banning": {
            "enabled": true,
            "time": 600,
            "invalidPercent": 30,
            "checkThreshold": 500,
            "purgeInterval": 300
        },
        "redis": {
            "host": "127.0.0.1",
            "port": 6379
        }
    },

    "website": {
        "enabled": true,
        "host": "0.0.0.0",
        "port": 80,
        "stratumHost": "everypool.com",
        "stats": {
           "updateInterval": 60,
            "historicalRetention": 86400,

            "hashrateWindow": 300
    },

Quote
this is from inside on the of the /pool_config/ coin configurations
    "paymentProcessing": {
        "enabled": true,
       "paymentInterval": 75,
        "minimumPayment": 99999999999,

        "daemon": {
            "host": "127.0.0.1",
            "port": <whatever port your coin daemon is listening on>,
            "user": "birdsofafeather",
            "password": "flocktogether"
        }
    },

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 03:17:48 PM
Last edit: July 19, 2014, 05:06:24 PM by paradigmflux
 #2

Next up, we are going to extend the stats.js file to extend the API to have some new functionality. This is going to be a long post.

In the /libs/ directory, rename stats.js entirely to stats.old
Open up a new stats.js and paste the following three snippets - modify the SECOND sections.  If your target coin is only on mintpal, uncomment the lines calling the mintpal API and fill in your ticker symbol.  If your coin is on cryptsy, fill in the appropriate market ID and the Cryptsy ticker symbol.  This file will extend your API in a few ways.

Most importantly, it will make <your url>/api/payout/<worker name> return the estimated number of coins a worker has earned during the current shift (in the payout coin of your choice).
It will also extend the 'my miner' page so that every worker can have a complete list of exactly how many coins of what type they have earned during that current shift.

Code:
var zlib = require('zlib');

var redis = require('redis');
var async = require('async');
var request = require('request');

var os = require('os');

var algos = require('stratum-pool/lib/algoProperties.js');


module.exports = function(logger, portalConfig, poolConfigs){

    var _this = this;

    var logSystem = 'Stats';

    var redisClients = [];
    var redisStats;

    this.statHistory = [];
    this.statPoolHistory = [];

    this.stats = {};
    this.statsString = '';

    setupStatsRedis();
    gatherStatHistory();

    var canDoStats = true;

    Object.keys(poolConfigs).forEach(function(coin){

        if (!canDoStats) return;

        var poolConfig = poolConfigs[coin];

        var redisConfig = poolConfig.redis;

        for (var i = 0; i < redisClients.length; i++){
            var client = redisClients[i];
            if (client.client.port === redisConfig.port && client.client.host === redisConfig.host){
                client.coins.push(coin);
                return;
            }
        }
        redisClients.push({
            coins: [coin],
            client: redis.createClient(redisConfig.port, redisConfig.host)
        });
    });


    function setupStatsRedis(){
        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));
        });
    }

    function gatherStatHistory(){

        var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0).toString();

        redisStats.zrangebyscore(['statHistory', retentionTime, '+inf'], function(err, replies){
            if (err) {
                logger.error(logSystem, 'Historics', 'Error when trying to grab historical stats ' + JSON.stringify(err));
                return;
            }
            for (var i = 0; i < replies.length; i++){
                _this.statHistory.push(JSON.parse(replies[i]));
            }
            _this.statHistory = _this.statHistory.sort(function(a, b){
                return a.time - b.time;
            });
            _this.statHistory.forEach(function(stats){
                addStatPoolHistory(stats);
            });
        });
    }

    function addStatPoolHistory(stats){
        var data = {
            time: stats.time,
            pools: {}
        };
        for (var pool in stats.pools){
            data.pools[pool] = {
                hashrate: stats.pools[pool].hashrate,
                workerCount: stats.pools[pool].workerCount,
                blocks: stats.pools[pool].blocks
            }
        }
        _this.statPoolHistory.push(data);
    }


this.getCoins = function(cback){
        _this.stats.coins = redisClients[0].coins;
        cback();
    };

    this.getPayout = function(address, cback){

        async.waterfall([

            function(callback){

                _this.getBalanceByAddress(address, function(){

                    callback(null, 'test');
                });

            },

            function(msg, callback){

                var totaltargetcoin = 0;

                async.each(_this.stats.balances, function(balance, cb){

                    _this.getCoinTotals(balance.coin, balance.balance, function(targetcoin){

                        if(typeof(targetcoin) != "undefined"){
                            totaltargetcoin += targetcoin;
                        }

                        cb();
                    });

                }, function(err){
                    callback(null, totaltargetcoin);
                });
            }

        ], function(err, total){

            cback(total.toFixed());

        });
    };


    this.getBalanceByAddress = function(address, cback){

        var client = redisClients[0].client,
            coins = redisClients[0].coins,
            balances = [];
          payouts = [];



                    client.hgetall('Payouts:' + address, function(error, txns){
                                                                         //logger.error(logSystem, 'TEMP', 'txnid variable is:' + txnid);

                                                                        if (error) {
                                                                                callback ('There was no payouts found');
                                                                                return;
                                                                        }
                                                                        if(txns === null){
                                                                               var index = [];
                                                                               } else{
                                                                               payouts=txns;

                                                                               }

                                                                        });


        async.each(coins, function(coin, cb){

            client.hget(coin + ':balances', address, function(error, result){
                if (error){
                    callback('There was an error getting balances');
                    return;
                }

                if(result === null) {
                    result = 0;
                }else{
                    result = result;
                }

                balances.push({
                    coin:coin,
                    balance:result
                });

                cb();
            });

        }, function(err){
            _this.stats.balances = balances;
            _this.stats.address = address;

 
            cback();
        });
    };

this.getCoinTotals = function(coin, balance, cback){
        var client = redisClients[0].client,
        coinData = poolConfigs[coin].coin;
       logger.error(logSystem, 'TEMP', 'var is' + JSON.stringify(poolConfigs[coin].coin));
         //logger.error(logSystem, 'TEMP', 'coinData.ID variable is:' + coinData.ID);

        async.waterfall([

  // Get all balances from redis if no balance was provided already
            function(callback){

                if(balance) {
                    callback(null, balance);
                    return;
                }

                client.hgetall(coin + ':balances', function(error, results){
                    if (error){
                        callback('There was an error getting balances');
                        return;
                    }

                    callback(null, results);
                });
            },
THIS NEXT PART OF THE FILE YOU NEED TO MAKE SOME CHANGES TO - this is a continuation of the file above though
Code:
            // make a call to Mintpal to get targetcoin exchange rate
            function(balances_results, callback){
                var options = {
                  // url:'https://api.mintpal.com/market/stats/<TICKER SYMBOL OF YOUR TARGET COIN HERE>/BTC',
                url:'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=<CRYPTSY MARKET ID OF YOUR TARGET COIN>',
                json:true
                }

                request(options, function (error, response, body) {
                  if (!error && response.statusCode == 200) {
                   // var targetcoin_price = parseFloat(body[0].last_price);
                        var targetcoin_price =  body['return'].markets['<YOUR POS TARGET COIN TICKET SYMBOL HERE>'].lasttradeprice;
                    callback(null, targetcoin_price, balances_results);

                  } else {
                    callback('There was an error getting mintpal targetcoin exchange rate');
                  }
                });
            },

The rest of the stats.js is below - just paste all three of these into the same file, remembering in to fill in your info into the second part.
Code:

            // make call to get coin's exchange rate
            function(targetcoin_price, balances_results, callback){


               // logger.error(logSystem, 'TEMP', '#1 ---- coinData.ID variable is:' + coinData.ID);

                if(coinData.ID === 'mintpal') {

                        var optionsB = {
                   url:'https://api.mintpal.com/market/stats/' + coinData.symbol + '/BTC',
                json:true
                }

                request(optionsB, function (error, responseB, bodyB) {
            
                  if (!error && responseB.statusCode == 200) {
                   var coinB_price = parseFloat(bodyB[0].last_price);
                        logger.error(logSystem, 'TEMP', 'coinB_price variable is:' + coinB_price);

                    callback(null, targetcoin_price, coinB_price, balances_results);

                  } else {
                    callback('There was an error getting mintpal exchange rate');
                  }
                });

                } else if (coinData.ID) {

                    var options = {
                        url:'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=' + coinData.ID,
                        json:true
                    }

                    request(options, function (error, response, body) {
                      if (!error && response.statusCode == 200) {
                        var coin_price = body['return'].markets[coinData.symbol].lasttradeprice;

                        /*
                        if(coin_price.toString().indexOf('-') === -1) {
                            // Good it doesn't have a dash.. no need to convert it to a fixed number
                        }
                        else {
                            var decimal_places = coin_price.toString().split('-')[1];
                            coin_price = coin_price.toFixed(parseInt(decimal_places));
                        }
                        */

                        callback(null, targetcoin_price, coin_price, balances_results);

                      } else {
                        callback('There was an error getting mintpal targetcoin exchange rate');
                      }
                    });
                }
                else {
                    callback(null, targetcoin_price, coinData.rate, balances_results);
                }
            },

            // Calculate the amount of targetcoin earned from the worker's balance
            function(targetcoin_price, coin_price, balances_results, callback){

                if(typeof balances_results !== 'object') {
                    var total_coins = balances_results
                    var bitcoins = parseFloat(total_coins) * coin_price;
                    var balance = (bitcoins / targetcoin_price);

                    callback(null, balance);
                    return;
                }

                var balances = [];

                for(var worker in balances_results){
                    var total_coins = parseFloat(balances_results[worker]) / 1;
                    var bitcoins = total_coins.toFixed() * coin_price;
                    var balance = (bitcoins / targetcoin_price);
                    balances.push({worker:worker, balance:balance.toFixed( 8 )});
                }

                callback(null, balances);
            }

        ], function(err, balances){

            if(balance) {
                cback(balances);
                return;
            }

            _this.stats.balances = balances;
         _this.stats.payout  = payouts;
        //logger.error(logSystem, 'TEMP', '_this.stats right before CB variable is:' + JSON.stringify(_this.stats));

    cback();
        });

    };





    this.getGlobalStats = function(callback){

        var statGatherTime = Date.now() / 1000 | 0;

        var allCoinStats = {};

        async.each(redisClients, function(client, callback){
            var windowTime = (((Date.now() / 1000) - portalConfig.website.stats.hashrateWindow) | 0).toString();
            var redisCommands = [];


            var redisCommandTemplates = [
                ['zremrangebyscore', ':hashrate', '-inf', '(' + windowTime],
                ['zrangebyscore', ':hashrate', windowTime, '+inf'],
                ['hgetall', ':stats'],
                ['scard', ':blocksPending'],
                ['scard', ':blocksConfirmed'],
                ['scard', ':blocksOrphaned']
            ];

            var commandsPerCoin = redisCommandTemplates.length;

            client.coins.map(function(coin){
                redisCommandTemplates.map(function(t){
                    var clonedTemplates = t.slice(0);
                    clonedTemplates[1] = coin + clonedTemplates[1];
                    redisCommands.push(clonedTemplates);
                });
            });


            client.client.multi(redisCommands).exec(function(err, replies){
                if (err){
                    logger.error(logSystem, 'Global', 'error with getting global stats ' + JSON.stringify(err));
                    callback(err);
                }
                else{
                    for(var i = 0; i < replies.length; i += commandsPerCoin){
                        var coinName = client.coins[i / commandsPerCoin | 0];
                        var coinStats = {
                            name: coinName,
                            symbol: poolConfigs[coinName].coin.symbol.toUpperCase(),
                            algorithm: poolConfigs[coinName].coin.algorithm,
                            hashrates: replies[i + 1],
                            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,
                                totalPaid: replies[i + 2] ? (replies[i + 2].totalPaid || 0) : 0
                            },
                            blocks: {
                                pending: replies[i + 3],
                                confirmed: replies[i + 4],
                                orphaned: replies[i + 5]
                            }
                        };
                        allCoinStats[coinStats.name] = (coinStats);
                    }
                    callback();
                }
            });
        }, function(err){
            if (err){
                logger.error(logSystem, 'Global', 'error getting all stats' + JSON.stringify(err));
                callback();
                return;
            }

            var portalStats = {
                time: statGatherTime,
                global:{
                    workers: 0,
                    hashrate: 0
                },
                algos: {},
                pools: allCoinStats
            };

            Object.keys(allCoinStats).forEach(function(coin){
                var coinStats = allCoinStats[coin];
                coinStats.workers = {};
                coinStats.shares = 0;
                coinStats.hashrates.forEach(function(ins){
                    var parts = ins.split(':');
                    var workerShares = parseFloat(parts[0]);
                    var worker = parts[1];
                    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;
                coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow;

                coinStats.workerCount = Object.keys(coinStats.workers).length;
                portalStats.global.workers += coinStats.workerCount;

                /* algorithm specific global stats */
                var algo = coinStats.algorithm;
                if (!portalStats.algos.hasOwnProperty(algo)){
                    portalStats.algos[algo] = {
                        workers: 0,
                        hashrate: 0,
                        hashrateString: null
                    };
                }
                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);
            });

            Object.keys(portalStats.algos).forEach(function(algo){
                var algoStats = portalStats.algos[algo];
                algoStats.hashrateString = _this.getReadableHashRateString(algoStats.hashrate);
            });

            _this.stats = portalStats;
            _this.statsString = JSON.stringify(portalStats);



            _this.statHistory.push(portalStats);
            addStatPoolHistory(portalStats);

            var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0);

            for (var i = 0; i < _this.statHistory.length; i++){
                if (retentionTime < _this.statHistory[i].time){
                    if (i > 0) {
                        _this.statHistory = _this.statHistory.slice(i);
                        _this.statPoolHistory = _this.statPoolHistory.slice(i);
                    }
                    break;
                }
            }

            redisStats.multi([
                ['zadd', 'statHistory', statGatherTime, _this.statsString],
                ['zremrangebyscore', 'statHistory', '-inf', '(' + retentionTime]
            ]).exec(function(err, replies){
                if (err)
                    logger.error(logSystem, 'Historics', 'Error adding stats to historics ' + JSON.stringify(err));
            });
            callback();
        });

    };

    this.getReadableHashRateString = function(hashrate){
        var i = -1;
        var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH' ];
        do {
            hashrate = hashrate / 1024;
                        i++;
        } while (hashrate > 1024);
        return hashrate.toFixed(2) + byteUnits[i];
    };

};
[/code

Delete the stock website.js file as well, make a new one:
[code]

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 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);

    var websiteConfig = portalConfig.website;

    var portalApi = new api(logger, portalConfig, poolConfigs);
    var portalStats = portalApi.stats;

    var logSystem = 'Website';


var pageFiles = {
        'index.html': 'index',
        'home.html': '',
        'tbs.html': 'tbs',
        'workers.html': 'workers',
        'api.html': 'api',
        'admin.html': 'admin',
        'mining_key.html': 'mining_key',
        'miner.html': 'miner',
        'miner_stats.html': 'miner_stats',
        'user_shares.html': 'user_shares',
        'getting_started.html': 'getting_started'
    };

    var pageTemplates = {};

    var pageProcessed = {};
    var indexesProcessed = {};

    var keyScriptTemplate = '';
    var keyScriptProcessed = '';


    var processTemplates = function(){

        for (var pageName in pageTemplates){
            if (pageName === 'index') continue;
            pageProcessed[pageName] = pageTemplates[pageName]({
                poolsConfigs: poolConfigs,
                stats: portalStats.stats,
                portalConfig: portalConfig
            });
            indexesProcessed[pageName] = pageTemplates.index({
                page: pageProcessed[pageName],
                selected: pageName,
                stats: portalStats.stats,
                poolConfigs: poolConfigs,
                portalConfig: portalConfig
            });
        }

        //logger.debug(logSystem, 'Stats', 'Website updated to latest stats');
    };



    var readPageFiles = function(files){
        async.each(files, function(fileName, callback){
            var filePath = 'website/' + (fileName === 'index.html' ? '' : 'pages/') + fileName;
            fs.readFile(filePath, 'utf8', function(err, data){
                var pTemp = dot.template(data);
                pageTemplates[pageFiles[fileName]] = pTemp
                callback();
            });
        }, function(err){
            if (err){
                console.log('error reading files for creating dot templates: '+ JSON.stringify(err));
                return;
            }
            processTemplates();
        });
    };


    //If an html file was changed reload it
    watch('website', function(filename){
        var basename = path.basename(filename);
        if (basename in pageFiles){
            console.log(filename);
            readPageFiles([basename]);
            logger.debug(logSystem, 'Server', 'Reloaded file ' + basename);
        }
    });

    portalStats.getGlobalStats(function(){
        readPageFiles(Object.keys(pageFiles));
    });

    var buildUpdatedWebsite = function(){
        portalStats.getGlobalStats(function(){
            processTemplates();

            var statData = 'data: ' + JSON.stringify(portalStats.stats) + '\n\n';
            for (var uid in portalApi.liveStatConnections){
                var res = portalApi.liveStatConnections[uid];
                res.write(statData);
            }

        });
    };

    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].paymentProcessing.daemon,
                                    address: poolConfigs[pName].address
                                }
                        }
                    })();
                    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, c, '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){
            var requestedPage = pageProcessed[pageId];
            return requestedPage;
        }
    };




    var minerpage = function(req, res, next){
        var address = req.params.address || null;

        if (address != null){
            portalStats.getBalanceByAddress(address, function(){
                processTemplates();

                res.end(indexesProcessed['miner_stats']);

            });
        }
        else
            next();
    };

    var payout = function(req, res, next){
        var address = req.params.address || null;

        if (address != null){
            portalStats.getPayout(address, function(data){
                res.write(data.toString());
                res.end();
            });
        }
        else
            next();
    };


    var shares = function(req, res, next){
        portalStats.getCoins(function(){
            processTemplates();

            res.end(indexesProcessed['user_shares']);

        });
    };

    var usershares = function(req, res, next){

        var coin = req.params.coin || null;

        if(coin != null){
            portalStats.getCoinTotals(coin, null, function(){
                processTemplates();

                res.end(indexesProcessed['user_shares']);

            });
        }
        else
            next();
    };


    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
            next();

    };



    var app = express();


    app.use(bodyParser.json());

    app.get('/get_page', function(req, res, next){
        var requestedPage = getPage(req.query.id);
        if (requestedPage){
            res.end(requestedPage);
            return;
        }
        next();
    });

    app.get('/key.html', function(req, res, next){
        res.end(keyScriptProcessed);
    });


        app.get('/stats/shares/:coin', usershares);
    app.get('/stats/shares', shares);
    app.get('/miner/:address', minerpage);
    app.get('/payout/:address', payout);



    app.get('/:page', route);
    app.get('/', route);

    app.get('/api/:method', function(req, res, next){
        portalApi.handleApiRequest(req, res, next);
    });

    app.post('/api/admin/:method', function(req, res, next){
        if (portalConfig.website
            && portalConfig.website.adminCenter
            && portalConfig.website.adminCenter.enabled){
            if (portalConfig.website.adminCenter.password === req.body.password)
                portalApi.handleAdminApiRequest(req, res, next);
            else
                res.send(401, JSON.stringify({error: 'Incorrect Password'}));

        }
        else
            next();

    });

    app.use(compress());
    app.use('/static', express.static('website/static'));

    app.use(function(err, req, res, next){
        console.error(err.stack);
        res.send(500, 'Something broke!');
    });

    try {
        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 ' + portalConfig.website.host + ':' + portalConfig.website.port
            +  ' - its either in use or you do not have permission');
    }


};

Then inside the <nomp instal>/website/pages folder create two new files.. Three actually.  First while in the <nomp install>/website/pages type 'touch user_shares.html'  just to create the file so NOMP won't puke for now.

Open a new file named miner.html:
Code:
<div class="row">
        <div class="col-md-2"> </div>
        <div class="col-md-4">
                <p class="lead">Enter Your <YOUR COIN> Wallet address</p>
                <div class="input-group">
                        <input type="text" class="form-control input-lg">
                        <span class="input-group-btn">
                                <button class="btn btn-default btn-lg" type="button">Go!</button>
                        </span>
                </div>
        </div>
<div class="col-md-4"></div>
</div> <!--- end row --!>


<script type="text/javascript">
        $(document).ready(function(){

                $('.btn-lg').click(function(){
                        window.location = "miner/" + $('.input-lg').val();
                });
        });
</script>
[/code]

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 03:18:03 PM
Last edit: July 19, 2014, 04:18:48 PM by paradigmflux
 #3

As well as a miner_stat.html

Code:
                <div class="row">
                                                <div class="col-md-6">
{{? it.stats.balances }}

                                <div class="row"> </div>
                                <div class="row">
                                                        <div class="panel panel-default">
                            <div class="panel-heading"> <h1><strong><span id="address">{{=it.stats.address}}<span></strong> </h1></div>
                            <div class="panel-body">
                                <p class="lead">So far this shift you have earned: <strong><span class="payout"></span></strong> <<INSERT UR TARGET SYMBOL HERE> (estimate).</p>

                                <p class="lead">Your previous balance with the pool is currently: <strong><span class="owed"></span></strong>  <INSERT UR TARGET SYMBOL HERE> </p?
                            </div>
                        </div>
                </div>
                                </div> <!-- end row -->
                        <div class="row"> </div>



                                                </div>
                                </div>
                        <div class="row"> &nbsp;</div>
                        <center>
                        <div class="panel panel-default">
                                <div class="panel-body">
                                <div class="stat-panel row">

<div>

    <!-- content -->
        <div class="row">

        <!-- main col left -->
        <div class="col-sm-12">

                    <div class="panel panel-default">
                            <div class="panel-body">

                        <div class="stat-panel row">
                                                              {{ for(var balance in it.stats.balances) { }}
                                                                               <div class="panel panel-default col-md-2 col-xs-4">
                                                                                  <div class="panel-heading" style="background-color:#3d3d3d;color:white;"><center><span class="text-xs"> {{=it.stats.balances[balance].coin}}</span></center></div>
                                                                               <div class="panel-body"><span class="text-bg"><strong>{{=it.stats.balances[balance].balance}}</strong></span> &nbsp</div>
                                                                               </div>
                                              {{ } }}
                                                                               </div>
                        </div>


                </div>



        <!-- Debug -->
        <div class="debug" style="display:none;">
                {{=JSON.stringify(it.stats)}}
        </div>

</div>
{{?}}

<script type="text/javascript">

$(document).ready(function(){

        var addr = $('#address').text();

        $.get('http://ururl:7379/hget/Pool_Stats:CurrentShift:WorkerTgtCoin/' + addr + '.txt', function(payoutdata){
                $('.payout').text(payoutdata);
        });



/*
        $.each($('.blockAmount'), function(i, v){
                if($(v).html() === "undefined") {
                        $(v).html(' --- ');
                }
        });

        $.each($('.blockShares'), function(i, v){
                if($(v).html() === "undefined") {
                        $(v).html(' --- ');
                }
        });
        */
});


$(function() {
                   var addr = $('#address').text();

                $.get("http://ururl:7379/ZSCORE/Pool_Stats:Balances/" + addr, function( payoutdata ) {
                                                $('.owed').append(payoutdata.ZSCORE);
                                });
                });


</script>

next install Webdis from here https://github.com/nicolasff/webdis
and launch it with a .json config file like this:
Code:
root@blackcoinpool:/FORFREEDOM/# cat ~/webdis-home/webdis.json
{
        "redis_host":   "127.0.0.1",

        "redis_port":   6379,
        "redis_auth":   null,

        "http_host":    "0.0.0.0",
        "http_port":    7379,

        "threads":      5,
        "pool_size": 20,

        "daemonize":    true,
        "websockets":   true,
        "default_root": "/GET/index.html",
        "database":     0,

        "acl": [
                {
                        "disabled":     ["*"]
                },

                {
                        "enabled":              ["GET", "HGET", "ZRANGE", "ZCARD", "ZRANGEBYSCORE", "ZVAL", "LRANGE", "HGETALL", "HKEYS", "HVALS", "GETALL", "HGETALL", "ZRANGE", "SMEMBERS", "ZSCORE"]
                }
        ],

        "verbosity": 6,
        "logfile": "/root/webdis-home/webdis.log"
}

Next, you should be able to restart your nomp install and browse to <url>/miner and input your worker address and see all the coin that worker has mined that shift.  the balance and outstanding amount will be blank till.  you can manually browe to <url>/payout/<worker> to see an estimated next payout.

Next up, let's do a deep dive into redis and how NOMP uses it (as well a re-write the entire payment processor)

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
albertdros
Hero Member
*****
Offline Offline

Activity: 602
Merit: 500


View Profile
July 19, 2014, 03:18:13 PM
 #4

that's great! Looking forward to your guide.
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 03:18:17 PM
Last edit: July 19, 2014, 05:16:50 PM by paradigmflux
 #5

How NOMP uses REDIS, and how we can use it to get what we want (and improve it)
All coin are given a redis key named <coin> - this breaks down into subkeys, :blocksCompleted, blocksPending, blocksOrphaned, blocksKicked
It also has a :balances key that keeps track of each workers current earnings per key, and a :payouts (which keeps track of the number of coins paid out to each user).

If we set up the coin configs right, we will never ever see a :payouts key in our install.
We are also going to create a new subkey, :blocksPaid that we are going to be moving every paid for round into, every time we run payouts.

Now the only stats that NOMP keeps are brutal.

We are going to implement a whole new level of logging into redis.

A master key, Pool_Stats contains everything.
We also have a kew named Coin_Names that contains (lowercase) a list of every coin we support.
We have Coin_Algos that lists every algo we support (lowercase)
We have Coin_Names_<algo> that lists every coin of that algo.  These are all stored as hashes, by the way, with values of 1 (although the value doesn't matter, these are all accessed via the redis-command HVALS)

ie:
Coin_Names consists of two hashes:
feathercoin 1
terracoin 1

Coin_Algos consists of two hashes:
scrypt 1
sha256 1

Coin_Names_scrypt consists of 1 hash
feathercoin 1
Coin_Name_sha256 consists of 1 hash:
terracoin 1

Set these up like this:
 root@blackcoinpool:/: redis-cli hset Coin_Names feathercoin 1
 root@blackcoinpool:/: redis-cli hset Coin_Algos scrypt 1
 root@blackcoinpool:/: redis-cli hset Coin_Names_scrypt feathercoin 1
and so on for all of your coins/algos....

We are going to keep track of every users historical hashrate in a key named Pool_Stats:WorkerHRS:<Algo>:<Worker> as part of a redis SORTED SET.  The format for the sorted set will be to use epoch time as the score and the value will be set to: <Users current hashrate>:<epoch time> -  the epoch time is requierd to allow redis to store duplicate hashrates (as it makes them all unique).
Pool_Stats will have seperate subkeys for every shift, but all of that will get created automatically through the next series of scripts I will provide.

We will keep CurrentShift datat in Pool_Stats:CurrentShift and current shift profitability data in Pool_Stats:CurrentShift:Profitability
Inside CurrentShift we will have:

Pool_Stats:CurrentShift:AlgosBTC - hash field listing each algos total value, in BTC, of each algo mined so far this shift.
Pool_Stats:CurrentShift:AlgosTgtCoin - hash field listing each algos total value, in target coin, of each algo mined so far this this shift.
Pool_Stats:CurrentShift:CoinsBTC - hash field listing each coins  total BTC that have been earned so far this shift.
Pool_Stats:CurrentShift:CoinsTgtCoin - hash field listing each coins total target coins that have been earned so far this shift.
Pool_Stats:CurrentShift:WorkersBTC - hash field listing each workers  total BTC they have earned so far this shift
Pool_Stats:CurrentShift:WorkersTgtCoin - hash field listing each workers  total target coin they have earned so far this shift.

We will keep track of historical stats in:
Pool_Stats:Profitability_<algo> - set of profitabilities, using shift number as field and profitability a value.
Pool_Stats:WorkerHRs:<algo>:<worker> - sorted set list of hashrates, per worker, per algo.  Epoch time as field, value is hashrate:epochtime to ensure uniqueness.
Pool_Stats:Balances - outstanding balances in target coin
Pool_Stats:DetailedPayouts:<worker> - sorted set list of payouts, using epoch time as field and value as txn ids in target coin.
Pool_Stats:DetailedPayouts:<worker>:<txn> - Hash key named Date: with value as epoch time, hash key as Amount: and value as txn amount, hash key as URL and value set to full URL for txn in target coin's block explorer.
Pool_Stats:TotalPaid - hash key listing every worker ID as a key name and the total amount of target coins they have been paid in total as the value.

.

These values are all automatically calculated by the payment processor, which is coming up in a few posts - the reason for the duplicate storage (in both BTC and TgtCoin is it provides a level of sanity checking for the payment processor before it decides to pay out the shift automatically or not).


Pool_Stats:CurrentShift will always have a starttime set to the epoch time that shift started at.
We will have an incrementer named This_Shift in Pool_Stats (a hash value) that will be incremented by one whenver a shift ends.  
Whenever a shift end, we will rename all of the Pool_Stats:CurrentShift keys to be Pool_Stats:<value of ThisShift variable> instead, and then start a fresh Pool_Stats:CurrentShift set of keys.


Let's start off with calculating and storing the users hashrates into the redis db.


---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 03:19:04 PM
 #6

lol you guy are dinks for stealting the first posts

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
cryptoangel
Sr. Member
****
Offline Offline

Activity: 700
Merit: 250



View Profile
July 19, 2014, 03:35:59 PM
 #7

Yep, I got lost at

Quote
Step #1 - start with a basic NOMP install. git clone from the NOMP repo: https://github.com/zone117x/node-open-mining-portal.git

Haha

▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄
█                         █
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄        █
           ▄▄███████▄▄   █
    ▄▄▄▄▄▄█████████████  █
         ████▀     ▀████
         ████       ████
         ████▄     ▄████
    ▀▀▀▀▀▀█████████████  █
           ▀▀███████▀▀   █
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀        █
█                         █
▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀
ROLLBIT●  Instant Deposits & Withdrawals
●  Rakeback & Level Up Bonuses
●  Live Customer Support
█▀▀▀▀▀











█▄▄▄▄▄
.
PLAY NOW
▀▀▀▀▀█











▄▄▄▄▄█
bathrobehero
Legendary
*
Offline Offline

Activity: 2002
Merit: 1051


ICO? Not even once.


View Profile
July 19, 2014, 03:50:17 PM
 #8

lol you guy are dinks for stealting the first posts

There's an Edit button for a reason.

Not your keys, not your coins!
MV120
Newbie
*
Offline Offline

Activity: 6
Merit: 0


View Profile
July 19, 2014, 04:07:26 PM
 #9

These are probably noob questions, but here goes anyways.

How much machine would this require (RAM, storage)?

What is the minimum hashrate you would recommend for before setting up your own Multipool?
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 04:12:23 PM
 #10

These are probably noob questions, but here goes anyways.

How much machine would this require (RAM, storage)?

What is the minimum hashrate you would recommend for before setting up your own Multipool?
SSD backing is important, but you can run a MP easily on a machine with 4 GB of ram and 40 GB of disk space.

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 04:34:23 PM
 #11

Set up a cronjob to run every 10 minutes.
Have it run a bash file, and this is the bash file:
Code:
#!/bin/bash
workercounter=0
arraycounter=0
now="`date +%s`"
TenMins=$((now - 600))
interval=600
modifier=65536
SHAmodifier=4294967296
redis-cli del tmpkey

while read Algo
do
        TotalWorkers=0
        WorkerTotals=0
        unset arrWorkerTotals
        unset arrWorkerCounts
        unset arrWorkerNames
        typeset -A arrWorkerTotals
        typeset -A arrWorkerCounts
        typeset -A arrWorkerNames
        AlgoCounter=0
        workercounter=0
        redis-cli del tmpkey
        while read CoinType
        do
                echo "$CoinType"

                counter=0
                CoinKeyName=$CoinType":hashrate"
                totalhashes=`redis-cli zcard $CoinKeyName`
        if [ -z "$totalhashes" ]
        then
                echo "no hashes" >/dev/null
        else
                while read LineItem
                do
#                       echo "$LineItem"
        counter=$(($counter + 1))
                        AlgoCounter=$(($AlgoCounter + 1))
                        IN=$LineItem
            arrIN=(${IN//:/ })
            preworker=(${arrIN[1]})
            #strip HTML tags out to ensure safe displaying later
            workername=`echo "$preworker," | tr -d '<>,'`
                        echo "$workername"
                        if [[ $workername == "" ]]
                        then
                                echo "ignore worker"
                        else
                               share=(${arrIN[0]})
                                arrWorkerCounts[$workername]=$((${arrWorkerCounts[$workername]} + 1))
if [[ ${arrWorkerCounts[$workername]} -eq 1 ]]
                                  then
                                #must have been this workers first share, so thi                                                                                                             s is a new worker
                                TotalWorkers=$(($TotalWorkers + 1))
                workercounter=$(($workercounter + 1))
                        arrWorkerNames[$workercounter]=$workername
                                echo "TotalWorkers - $TotalWorkers ~~~ workercounter - $workercounter ~~~ arrWorkerNames -" ${arrWorkerNames[$workercounter]}
                                  else
                                        #this was a duplicate worker, do nothing
                                        echo " " >/dev/null
                                fi
                        if [ -z "${arrWorkerTotals[$workername]}" ]
                        then
                                tempvar=0
                        else
                                tempvar=${arrWorkerTotals[$workername]}
                        fi


                         arrWorkerTotals[$workername]=`echo "scale=6;$tempvar + $share" | bc -l`

                        echo "${arrWorkerNames[$workercounter]}"



            fi
                        done< <(redis-cli zrangebyscore $CoinKeyName $TenMins $now)

                        TotalHash=`echo "$TotalHash + $share" | bc -l`
fi
                done< <(redis-cli hkeys Coin_Names_$Algo)


                  # break it down - sha is stored a GH, everything else is stored a MH
                                if [ $Algo = "sha256" ]
                                then
                                        modifier=4294967296
                                        divisor=1073741824
                                elif [ $Algo = "keccak" ]
                                then
                                        modifier=16777216
                                        divisor=1048576
                                elif [ $Algo = "x11" ]
                                then
                                        modifier=4294967296
                                        divisor=1048576
                                else
                                        modifier=65536
                                        divisor=1048576
                                fi

                                TotalHR=`echo "scale=3;$TotalHash * $modifier / $interval / $divisor" | bc`
                redis-cli zadd Pool_Stats:avgHR:$Algo $now $TotalHR":"$now
                #go over the array of WorkerNames and calculate each workers HR
                                counterB=0
                while [[ $counterB -lt $workercounter ]]
                do
                        counterB=$(($counterB + 1))
                        workerName=${arrWorkerNames[$counterB]}
                        echo $workerName
                        temporary=`echo "scale=6;${arrWorkerTotals[$workerName]} * $modifier" | bc -l`
                                        #               number=`echo "$interval / $divisor" | bc -l`
                          arrWorkerHashRates[$counterB]=`echo "$temporary / $interval / $divisor" | bc -l`
                                                workerName=${arrWorkerNames[$counterB]}
                                                rate=${arrWorkerHashRates[$counterB]}
                                                echo $workerName $rate
                                                string=$rate":"$now
                                                redis-cli zadd Pool_Stats:WorkerHRs:$Algo:$workerName $now $string
                                                echo "$Algo - $workerName -"$arrWorkerHashRates[$counterB]}
                done


done< <(redis-cli hkeys Coin_Algos)

Go ahead and run it a few times at the command line and see how it works - it's a bit spammy, but notice how the redis keys are being populated with all of the users hashrate information now?  The line near the top can be adjuted from 600 to a lower number if you would rather users not have to wait 10 min to see an accurate hash rate.  Make sure you have your Coin_Algos and Coin_Names_<algo> redis values filled in appropriately for this to work.

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 04:44:11 PM
 #12

Now let's set up a cronjob, and start grabbing a local copy of all of the coin exchange rates.
This is a simplfied version, I have a better version that aggregates prices from multiple exchanges but we all have to keep some secrets, right? Smiley I never said I'd give away how to make the most  profitable pool, but I did say i'd show you how to set up A multipool.

Code:
#!/bin/bash
BC2BTC=`curl -G 'https://api.mintpal.com/v1/market/stats/BC/BTC/' | jq -r .[].last_price`
FTC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=5' | jq -r .return.markets.FTC.lasttradeprice`
MZC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=164' | jq -r .return.markets.MZC.lasttradeprice`
NET2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=134' | jq -r .return.markets.NET.lasttradeprice`
DRK2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=155' | jq -r .return.markets.DRK.lasttradeprice`
WDC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=14' | jq -r .return.markets.WDC.lasttradeprice`
STR2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=83' | jq -r .return.markets.STR.lasttradeprice`
KDC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=178' | jq -r .return.markets.KDC.lasttradeprice`
NYAN2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=184' | jq -r .return.markets.NYAN.lasttradeprice`
MNC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=7' | jq -r .return.markets.MNC.lasttradeprice`
POT2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=173' | jq -r .return.markets.POT.lasttradeprice`
GDC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=82' | jq -r .return.markets.GDC.lasttradeprice`
GLC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=76' | jq -r .return.markets.GLC.lasttradeprice`
BTE2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=49' | jq -r .return.markets.BTE.lasttradeprice`
UNO2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=133' | jq -r .return.markets.UNO.lasttradeprice`
USDE2BTC=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_USDE.last`
HIRO2BTC=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_HIRO.last`
GDN2BTC=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_GDN.last`
cinni2btc=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_CINNI.last`
NOBL2BTC=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_NOBL.last`
REDD2BTC=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_REDD.last`
LGC2BTC=`curl -G 'https://poloniex.com/public?command=returnTicker' | jq -r .BTC_LGC.last`
EAC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=139' | jq -r .return.markets.EAC.lasttradeprice`
CAP2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=53' | jq -r .return.markets.CAP.lasttradeprice`
RBY2BTC=`curl -G 'https://bittrex.com/api/v1/public/getticker?market=BTC-RBY' | jq -r .result.Last`
tips2ltc=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=147' | jq -r .return.markets.TIPS.lasttradeprice`
ltc2btc=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=3' | jq -r .return.markets.LTC.lasttradeprice`
nxt2btc=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=159' | jq -r .return.markets.NXT.lasttradeprice`
tips2btc=`echo "$tips2ltc * $ltc2btc" | bc -l`
TRC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=27' | jq -r .return.markets.TRC.lasttradeprice`
LOT2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=137' | jq -r .return.markets.LOT.lasttradeprice`
GLD2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=30' | jq -r .return.markets.GLD.lasttradeprice`
MEC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=45' | jq -r .return.markets.MEC.lasttradeprice`
MYR2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=200' | jq -r .return.markets.MYR.lasttradeprice`
MEOW2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=149' | jq -r .return.markets.MEOW.lasttradeprice`
EXE2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=183' | jq -r .return.markets.EXE.lasttradeprice`
VTC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=151' | jq -r .return.markets.VTC.lasttradeprice`
SAT2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=168' | jq -r .return.markets.SAT.lasttradeprice`
NXT2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=159' | jq -r .return.markets.NXT.lasttradeprice`
MAX2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=152' | jq -r .return.markets.MAX.lasttradeprice`
THREE2BTC=`curl -G 'https://api.mintpal.com/v1/market/stats/365/BTC/' | jq -r .[].last_price`
ZET2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=85' | jq -r .return.markets.ZET.lasttradeprice`
XC2BTC=`curl -G 'http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=210' | jq -r .return.markets.XC.lasttradeprice`

redis-cli hset Exchange_Rates blackcoin $BC2BTC
redis-cli hset Exchange_Rates globaldenomination $GDN2BTC
redis-cli hset Exchange_Rates zetacoin $ZET2BTC
redis-cli hset Exchange_Rates logicoin $LGC2BTC
redis-cli hset Exchange_Rates cinnicoin $cinni2btc
redis-cli hset Exchange_Rates 365coin $THREE2BTC
redis-cli hset Exchange_Rates NXT $NXT2BTC
redis-cli hset Exchange_Rates execoin $EXE2BTC
redis-cli hset Exchange_Rates vertcoin $VTC2BTC
redis-cli hset Exchange_Rates kittehcoin $MEOW2BTC
redis-cli hset Exchange_Rates megacoin $MEC2BTC
redis-cli hset Exchange_Rates netcoin $NET2BTC
redis-cli hset Exchange_Rates globalcoin $GLC2BTC
redis-cli hset Exchange_Rates grandcoin $GDC2BTC
redis-cli hset Exchange_Rates goldcoin $GLD2BTC
redis-cli hset Exchange_Rates fedoracoin $tips2btc
redis-cli hset Exchange_Rates litecoin $ltc2btc
redis-cli hset Exchange_Rates nxt $NXT2BTC
redis-cli hset Exchange_Rates terracoin $TRC2BTC
redis-cli hset Exchange_Rates feathercoin $FTC2BTC
redis-cli hset Exchange_Rates reddcoin $REDD2BTC
redis-cli hset Exchange_Rates earthcoin $EAC2BTC
redis-cli hset Exchange_Rates bottlecaps $CAP2BTC
redis-cli hset Exchange_Rates rubycoin $RBY2BTC
redis-cli hset Exchange_Rates terracoin $TRC2BTC
redis-cli hset Exchange_Rates noblecoin $NOBL2BTC
redis-cli hset Exchange_Rates mincoin $MNC2BTC
redis-cli hset Exchange_Rates klondikecoin $KDC2BTC
redis-cli hset Exchange_Rates darkcoin $DRK2BTC
redis-cli hset Exchange_Rates mazacoin $MZC2BTC
redis-cli hset Exchange_Rates unobtanium $UNO2BTC
redis-cli hset Exchange_Rates hirocoin $HIRO2BTC
redis-cli hset Exchange_Rates cinnicoin $cinni2btc
redis-cli hset Exchange_Rates usde $USDE2BTC
redis-cli hset Exchange_Rates lottocoin $LOT2BTC
redis-cli hset Exchange_Rates nyancoin $NYAN2BTC
redis-cli hset Exchange_Rates worldcoin $WDC2BTC
redis-cli hset Exchange_Rates potcoin $POT2BTC
redis-cli hset Exchange_Rates myriadcoin $MYR2BTC
redis-cli hset Exchange_Rates myriad-scrypt $MYR2BTC
redis-cli hset Exchange_Rates myriadsha $MYR2BTC
redis-cli hset Exchange_Rates saturncoin $SAT2BTC
redis-cli hset Exchange_Rates maxcoin $MAX2BTC
redis-cli hset Exchange_Rates rubycoin $RBY2BTC
redis-cli hset Exchange_Rates xccoin $XC2BTC

Get this cronjob to run every 5 minutes, this will keep track of the most current exchange prices.  (the final step will be syncing all the cronjobs into a single script, but we'll get to that still in a few posts. Next up is re-writing the payment processor to properly handle the start and end of 'shifts' as well as to handle the coin moving to the exchange.


---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 04:55:32 PM
 #13

Let me know if anyone is even reading this far. Smiley

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 05:17:43 PM
 #14

Naw, feel free to post - I'm not going to waste my time putting all this up if nobody is interested.
I will be linking to a .zip with all of the files I am referring to as well, once I get all these written.

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
Kergekoin
Hero Member
*****
Offline Offline

Activity: 546
Merit: 500


View Profile
July 19, 2014, 05:19:52 PM
 #15

Im probably going to set one back-end up for my miners. Thanks for your effort!
At the moment im switching manually on my NOMP pool back-end.

******  NB! The links below are affiliate - friend type links, which bring additional benefits both, to you and me  ******
Binance - Best Crypto Trading Platform          CoinBase - Fastest way from FIAT to Crypto
Windscribe - The quickest and easyest way to secure and anonymize your internet traffic
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 05:23:08 PM
 #16

STILL TO COME:

--> custom payment processor
---> front end javascript reporting
---> Exchange integration
---> Cryptonote integration
--> Sanity Checking
--> Future Plans

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
billotronic
Legendary
*
Offline Offline

Activity: 1610
Merit: 1000


Crackpot Idealist


View Profile
July 19, 2014, 05:54:00 PM
 #17

Naw, feel free to post - I'm not going to waste my time putting all this up if nobody is interested.
I will be linking to a .zip with all of the files I am referring to as well, once I get all these written.


No sir, please share! I've been very curious of how MP's work and this guide so far has been great!

This post sums up why all this bullshit is a scam
Read It. Hate It. Change the facts that it represents.
https://bitcointalk.org/index.php?topic=1606638.msg16139644#msg16139644
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 07:28:12 PM
 #18

BTW, I invite anyone intereted to check out http://www.btcdpool.com  Send some hash it's way and see how it works and if you like the feel of it.
I will be posting all of it's source in time, this thread is inspired by it.

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 07:39:30 PM
 #19

Calculating Profitability

Calculating profitability can be done fairly easily like this!  This also calculates each worker's current earnings for the round, as well as updates each of the respective keys under the Pool_Stats:CurrentShift key for the coin/algo earning totals.
Set via cronjob, runs in only a few seconds on a pool even when it is getting mutli GH of scrypt/x11 traffic. You can run it manually to verify the output (it's a bit spammy, but it prints what it's doing to the screen as you do it.  If run via cronjob this would all just go to null.

Code:
#!/bin/bash
declare -A ProArr
declare -A NameArr
AlgoCounter=0
now="`date +%s`"
ShiftNumber=`redis-cli hget Pool_Stats This_Shift`
echo "Test"
startstring="Pool_Stats:"$ShiftNumber
starttime=`redis-cli hget $startstring starttime`
endtime=$now
length=$(($endtime - $starttime))
redis-cli hset Pool_Stats CurLength $length
dayslength=`echo "scale=3;$length / 86400" | bc -l`
TgtCoinPrice=`redis-cli hget Exchange_Rates <TARGET COIN NAME HERE>`
TotalEarned=0
TotalEarnedTgtCoin=0
redis-cli hset Pool_Stats CurDaysLength $dayslength
redis-cli del Pool_Stats:CurrentShift:WorkerBTC
redis-cli del Pool_Stats:CurrentShift:WorkerTgtCoin

# START CALCULATING COIN PROFIT FOR CURRENT ROUND - THIS ALSO CALCULATES WORKER EARNINGS MID SHIFT.
# PLEASE NOTE ALL COIN NAMES IN COIN_ALGO REDIS KEY MUST MATCH KEY NAMES IN EXCHANGE_RATES KEY CASE-WISE
while read line
do
        AlgoTotal=0
        AlgoTotalTgtCoin=0
        logkey2="Pool_Stats:CurrentShift:Algos"
        logkey2TgtCoin="Pool_Stats:CurrentShift:AlgosTgtCoin"
        echo "LOGKEY2: $logkey2"
        # loop through each coin for that algo
        while read CoinName
        do
                coinTotal=0
                coinTotalTgtCoin=0
                thiskey=$CoinName":balances"
                logkey="Pool_Stats:CurrentShift:Coins"
                logkeyTgtCoin="Pool_Stats:CurrentShift:CoinsTgtCoin"
                #Determine price for Coin
                coin2btc=`redis-cli hget Exchange_Rates $CoinName`
#               echo "$CoinName - $coin2btc"
                workersPerCoin=`redis-cli hlen $thiskey`
                if [ $workersPerCoin = 0 ]
                then
                        echo "do nothing" > /dev/null
                else

                        while read WorkerName
                        do
                                thisBalance=`redis-cli hget $thiskey $WorkerName`
                                thisEarned=`echo "scale=6;$thisBalance * $coin2btc" | bc -l`
                                coinTotal=`echo "scale=6;$coinTotal + $thisEarned" | bc -l`
                                AlgoTotal=`echo "scale=6;$AlgoTotal + $thisEarned" | bc -l`
                                TgtCoinEarned=`echo "scale=6;$thisEarned / $TgtCoinPrice" | bc -l`
                                coinTotalTgtCoin=`echo "scale=6;$coinTotalTgtCoin + $TgtCoinEarned" | bc -l`
                                AlgoTotalTgtCoin=`echo "scale=6;$AlgoTotalTgtCoin + $TgtCoinEarned" | bc -l`

#                               echo "$WorkerName earned $TgtCoinEarned from $CoinName"
                                redis-cli hincrbyfloat Pool_Stats:CurrentShift:WorkerTgtCoin $WorkerName $TgtCoinEarned
                                redis-cli hincrbyfloat Pool_Stats:CurrentShift:WorkerBTC $WorkerName $thisEarned
                        done< <(redis-cli hkeys $CoinName:balances)
                        redis-cli hset $logkey $CoinName $coinTotal
                        redis-cli hset $logkeyTgtCoin $CoinName $coinTotalTgtCoin
                        echo "$CoinName: $coinTotal"

                fi
        done< <(redis-cli hkeys Coin_Names_$line)
          redis-cli hset $logkey2 $line $AlgoTotal
        redis-cli hset $logkey2TgtCoin $line $AlgoTotalTgtCoin
TotalEarned=`echo "scale=6;$TotalEarned + $AlgoTotal" | bc -l`
TotalEarnedTgtCoin=`echo "scale=6;$TotalEarnedTgtCoin + $AlgoTotalTgtCoin" | bc -l`

done< <(redis-cli hkeys Coin_Algos)


# END CALCULATING COIN PROFITS FOR CURRENT SHIFT


# START CALCULATIN AVERAGE HASHRATES SO FAR THIS SHIFT
echo "Start: $starttime End: $endtime"
        AlgoCounter=0
        while read Algo
        do
                AlgoCounter=$(($AlgoCounter + 1))
                if [ $Algo = "sha256" ]
                then
                        Algo="sha256"
                fi
                AlgoHRTotal=0
                counter=0
                loopstring="Pool_Stats:AvgHRs:"$Algo
                while read HR
                do
                        IN=$HR
                        arrIN=(${IN//:/ })
                        amt=${arrIN[0]}
                        counter=`echo "$counter + 1" | bc`
                        AlgoHRTotal=`echo "$AlgoHRTotal + $amt" | bc -l`
               done< <(redis-cli zrangebyscore $loopstring $starttime $endtime)

                if [ $Algo = "sha" ]
                then
                        Algo="sha256"
                fi
                thisalgoAVG=`echo "scale=8;$AlgoHRTotal / $counter" |  bc -l`
                string="average_"$Algo
                redis-cli hset Pool_Stats:CurrentShift $string $thisalgoAVG
                string3="Pool_Stats:CurrentShift:Algos"
                thisalgoEarned=`redis-cli hget $string3 $Algo`
                thisalgoP=`echo "scale=8;$thisalgoEarned / $thisalgoAVG / $dayslength" | bc -l`
                string2="Profitability_$Algo"
                redis-cli hset Pool_Stats:CurrentShift $string2 $thisalgoP
                if [ $Algo = "keccak" ]
                then
                        thisalgoP=`echo "scale=8;$thisalgoP * 500" | bc -l`
                elif [ $Algo = "sha256" ]
                then
                        thisalgoP=`echo "scale=8;$thisalgoP * 100" | bc -l`
                elif [ $Algo = "x11" ]
                then
                        thisalgoP=`echo "scale=8;$thisalgoP * 4" | bc -l`
                else
                        echo "done" >/dev/null
                fi
                if [ -z "$thisalgoP" ]
                then
                        thisalgoP=0
                fi

                ProArr[$AlgoCounter]=$thisalgoP
                NameArr[$AlgoCounter]=$Algo
                redis-cli hset Pool_Stats:CurrentShift $string2 $thisalgoP

                echo "For Current Shift Algo $Algo had an average of $thisalgoAVG - profitability was $thisalgoP"
        done< <(redis-cli hkeys Coin_Algos)

                profitstring=${ProArr[1]}":"${ProArr[2]}":"${ProArr[3]}":"${ProArr[4]}":"${ProArr[5]}
                stringnames=${NameArr[1]}":"${NameArr[2]}":"${NameArr[3]}":"${NameArr[4]}":"${NameArr[5]}
redis-cli hset Pool_Stats:CurrentShift:Profitability $now $profitstring
redis-cli hset Pool_Stats:CurrentShift  NameString $stringnames



Ugly, but it works.
It even gives you all the data that you require to be able to pull pretty charts of current shift profitability.
I will try and clean up the commenting and repost soon.

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
paradigmflux (OP)
Sr. Member
****
Offline Offline

Activity: 378
Merit: 254

small fry


View Profile WWW
July 19, 2014, 08:00:59 PM
 #20

I'll post the next bit about the pool once the hashrate on btcdpool.com goes up a bit.

---
NXT Multipool! Mine Scrypt, SHA, Keccak or X11 for NXT! http://hashrate.org
http://hashrate.org/getting_started for port info!
Pages: [1] 2 3 4 5 6 7 »  All
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!