nomp矿池源码详解

打印 上一主题 下一主题

主题 678|帖子 678|积分 2034

1 项目简介

Node Open Mining Portal(简称NOMP)是一个由Node.js编写的高效、可扩展的加密货币挖矿池软件,专为履历丰富的系统管理员和开辟者计划。它包含了Stratum挖矿池服务器、奖励处理与支付功能以及一个相应式前端网站,提供实时统计和管理中心。NOMP基于node-stratum-pool模块,支持动态难度调解(vardiff)、工作证实(POW)和权益证实(POS)。它的安全特性包括DDoS攻击防护、IP禁止列表,并采用了Redis举行内存中的数据存储以优化性能。此外,其多币种挖掘和负载均衡能力使得管理多个币种的矿池变得简单。
该项目的安装配置不再举行详细介绍,感兴趣的请参考之前写的文章:https://www.cnblogs.com/zhaoweiwei/p/nomp.html
2 源码详解

2.1 目次


coins目次里是各个币种的名称及算法配置,libs目次中是各大功能模块的源码,node_modules目次中是各个nodejs模块,pool_configs目次中是矿池支持币种的配置,scripts目次中是两个关键性脚本文件,website目次中是前段相关代码及资源;
config.json用于用于设置矿池全局配置,如监听端口、连接超时、任务更新隔断等,init.js是nodejs入口文件,package.json用于记载依赖包及版本等相关信息。
2.2 入口函数

 执行node init.js将会根据配置启动主步伐,首先会解析当前目次下的config.json配置文件,并将结果存储在portalConfig变量中,还会创建PoolLogger对象(源码对应libs\logUtil.js文件)统一管理log信息

之后,会里用cluster模块,来判断当进步程是主历程(通常称为“master”),还是工作历程(“workers”),对于工作历程,按照类型创建不同的实例
  1. 1 if (cluster.isWorker){
  2. 2
  3. 3     switch(process.env.workerType){
  4. 4         case 'pool':
  5. 5             new PoolWorker(logger);
  6. 6             break;
  7. 7         case 'paymentProcessor':
  8. 8             new PaymentProcessor(logger);
  9. 9             break;
  10. 10         case 'website':
  11. 11             new Website(logger);
  12. 12             break;
  13. 13         case 'profitSwitch':
  14. 14             new ProfitSwitch(logger);
  15. 15             break;
  16. 16     }
  17. 17
  18. 18     return;
  19. 19 }
复制代码
假如是主历程则会调用以下功能模块函数,创建不同的工作历程
  1. 1 (function init(){
  2. 2
  3. 3     poolConfigs = buildPoolConfigs();
  4. 4
  5. 5     spawnPoolWorkers();
  6. 6
  7. 7     startPaymentProcessor();
  8. 8
  9. 9     startWebsite();
  10. 10
  11. 11     startProfitSwitch();
  12. 12
  13. 13     startCliListener();
  14. 14
  15. 15 })();
复制代码
buildPoolConfigs函数会对相关配置文件举行解析整合
spawnPoolWorkers函数会创建PoolWorker历程,功能函数对应libs\poolWorker.js
startPaymentProcessor函数会创建paymentProcessor历程,功能函数对应libs\paymentProcessor.js
startWebsite函数会创建Website历程,功能函数对应libs\website.js
startProfitSwitch函数会创建ProfitSwitch历程,功能函数对应libs\profitSwitch.js
startCliListener函数会创建CliListener对象在cliPort端口举行监听并处理收到的消息,功能函数对应libs\cliListener.js
2.3 buildPoolConfigs

  1. 1 var buildPoolConfigs = function(){
  2. 2     var configs = {};
  3. 3     var configDir = 'pool_configs/';
  4. 4
  5. 5     var poolConfigFiles = [];
  6. 6
  7. 7
  8. 8     /* Get filenames of pool config json files that are enabled */
  9. 9     fs.readdirSync(configDir).forEach(function(file){
  10. 10         if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return;
  11. 11         var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'})));
  12. 12         if (!poolOptions.enabled) return;
  13. 13         poolOptions.fileName = file;
  14. 14         poolConfigFiles.push(poolOptions);
  15. 15     });
  16. 16
  17. 17
  18. 18     /* Ensure no pool uses any of the same ports as another pool */
  19. 19     for (var i = 0; i < poolConfigFiles.length; i++){
  20. 20         var ports = Object.keys(poolConfigFiles[i].ports);
  21. 21         for (var f = 0; f < poolConfigFiles.length; f++){
  22. 22             if (f === i) continue;
  23. 23             var portsF = Object.keys(poolConfigFiles[f].ports);
  24. 24             for (var g = 0; g < portsF.length; g++){
  25. 25                 if (ports.indexOf(portsF[g]) !== -1){
  26. 26                     logger.error('Master', poolConfigFiles[f].fileName, 'Has same configured port of ' + portsF[g] + ' as ' + poolConfigFiles[i].fileName);
  27. 27                     process.exit(1);
  28. 28                     return;
  29. 29                 }
  30. 30             }
  31. 31
  32. 32             if (poolConfigFiles[f].coin === poolConfigFiles[i].coin){
  33. 33                 logger.error('Master', poolConfigFiles[f].fileName, 'Pool has same configured coin file coins/' + poolConfigFiles[f].coin + ' as ' + poolConfigFiles[i].fileName + ' pool');
  34. 34                 process.exit(1);
  35. 35                 return;
  36. 36             }
  37. 37
  38. 38         }
  39. 39     }
  40. 40
  41. 41
  42. 42     poolConfigFiles.forEach(function(poolOptions){
  43. 43
  44. 44         poolOptions.coinFileName = poolOptions.coin;
  45. 45
  46. 46         var coinFilePath = 'coins/' + poolOptions.coinFileName;
  47. 47         if (!fs.existsSync(coinFilePath)){
  48. 48             logger.error('Master', poolOptions.coinFileName, 'could not find file: ' + coinFilePath);
  49. 49             return;
  50. 50         }
  51. 51
  52. 52         var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'})));
  53. 53         poolOptions.coin = coinProfile;
  54. 54         poolOptions.coin.name = poolOptions.coin.name.toLowerCase();
  55. 55
  56. 56         if (poolOptions.coin.name in configs){
  57. 57
  58. 58             logger.error('Master', poolOptions.fileName, 'coins/' + poolOptions.coinFileName
  59. 59                 + ' has same configured coin name ' + poolOptions.coin.name + ' as coins/'
  60. 60                 + configs[poolOptions.coin.name].coinFileName + ' used by pool config '
  61. 61                 + configs[poolOptions.coin.name].fileName);
  62. 62
  63. 63             process.exit(1);
  64. 64             return;
  65. 65         }
  66. 66
  67. 67         for (var option in portalConfig.defaultPoolConfigs){
  68. 68             if (!(option in poolOptions)){
  69. 69                 var toCloneOption = portalConfig.defaultPoolConfigs[option];
  70. 70                 var clonedOption = {};
  71. 71                 if (toCloneOption.constructor === Object)
  72. 72                     extend(true, clonedOption, toCloneOption);
  73. 73                 else
  74. 74                     clonedOption = toCloneOption;
  75. 75                 poolOptions[option] = clonedOption;
  76. 76             }
  77. 77         }
  78. 78
  79. 79
  80. 80         configs[poolOptions.coin.name] = poolOptions;
  81. 81
  82. 82         if (!(coinProfile.algorithm in algos)){
  83. 83             logger.error('Master', coinProfile.name, 'Cannot run a pool for unsupported algorithm "' + coinProfile.algorithm + '"');
  84. 84             delete configs[poolOptions.coin.name];
  85. 85         }
  86. 86
  87. 87     });
  88. 88     return configs;
  89. 89 };
复制代码
buildPoolConfigs9~15行会依次解析pool_configs中不同币种的配置文件,并将配置中使能状态为true的币种配置存储在poolConfigFiles边量。
19~39行检查每个币种会唯一的对应于coins目次的算法配置文件,且每个币种在矿池中使用不同的监听端口。
42~87行根据config.json中的全局配置,更新每个币种对应的配置(假如相应的配置项不存在),此外相应算法要在node_modules\stratum-pool\lib\algoProperties.js已实现,否则会删除对应算法的配置,即矿池不支持该算法。
88行将全局配置返回,并赋值给全局边量poolConfigs。
2.4 spawnPoolWorkers

  1. 1 var spawnPoolWorkers = function(){
  2. 2
  3. 3     Object.keys(poolConfigs).forEach(function(coin){
  4. 4         var p = poolConfigs[coin];
  5. 5
  6. 6         if (!Array.isArray(p.daemons) || p.daemons.length < 1){
  7. 7             logger.error('Master', coin, 'No daemons configured so a pool cannot be started for this coin.');
  8. 8             delete poolConfigs[coin];
  9. 9         }
  10. 10     });
  11. 11
  12. 12     if (Object.keys(poolConfigs).length === 0){
  13. 13         logger.warning('Master', 'PoolSpawner', 'No pool configs exists or are enabled in pool_configs folder. No pools spawned.');
  14. 14         return;
  15. 15     }
  16. 16
  17. 17
  18. 18     var serializedConfigs = JSON.stringify(poolConfigs);
  19. 19
  20. 20     var numForks = (function(){
  21. 21         if (!portalConfig.clustering || !portalConfig.clustering.enabled)
  22. 22             return 1;
  23. 23         if (portalConfig.clustering.forks === 'auto')
  24. 24             return os.cpus().length;
  25. 25         if (!portalConfig.clustering.forks || isNaN(portalConfig.clustering.forks))
  26. 26             return 1;
  27. 27         return portalConfig.clustering.forks;
  28. 28     })();
  29. 29
  30. 30     var poolWorkers = {};
  31. 31
  32. 32     var createPoolWorker = function(forkId){
  33. 33         var worker = cluster.fork({
  34. 34             workerType: 'pool',
  35. 35             forkId: forkId,
  36. 36             pools: serializedConfigs,
  37. 37             portalConfig: JSON.stringify(portalConfig)
  38. 38         });
  39. 39         worker.forkId = forkId;
  40. 40         worker.type = 'pool';
  41. 41         poolWorkers[forkId] = worker;
  42. 42         worker.on('exit', function(code, signal){
  43. 43             logger.error('Master', 'PoolSpawner', 'Fork ' + forkId + ' died, spawning replacement worker...');
  44. 44             setTimeout(function(){
  45. 45                 createPoolWorker(forkId);
  46. 46             }, 2000);
  47. 47         }).on('message', function(msg){
  48. 48             switch(msg.type){
  49. 49                 case 'banIP':
  50. 50                     Object.keys(cluster.workers).forEach(function(id) {
  51. 51                         if (cluster.workers[id].type === 'pool'){
  52. 52                             cluster.workers[id].send({type: 'banIP', ip: msg.ip});
  53. 53                         }
  54. 54                     });
  55. 55                     break;
  56. 56             }
  57. 57         });
  58. 58     };
  59. 59
  60. 60     var i = 0;
  61. 61     var spawnInterval = setInterval(function(){
  62. 62         createPoolWorker(i);
  63. 63         i++;
  64. 64         if (i === numForks){
  65. 65             clearInterval(spawnInterval);
  66. 66             logger.debug('Master', 'PoolSpawner', 'Spawned ' + Object.keys(poolConfigs).length + ' pool(s) on ' + numForks + ' thread(s)');
  67. 67         }
  68. 68     }, 250);
  69. 69
  70. 70 };
复制代码
spawnPoolWorkers 33行会创建pool类型的worker历程,这又会对应在2.2节介绍的内容,根据worker历程类型,创建PoolWorker实例。在PoolWorker中,首先会使用process来处理其他模块发送的IPC消息;之后创建ShareProcessor对象,用于管理客户端的share提交;本地handlers对象中不同函数处理与矿池stratum交互消息,如auth、share、diff等;最后通过Stratum.createPool创建矿池对象pool,并通过pool.start启动矿池,该部分详细内容请参考node_modules\stratum-pool\lib\pool.js文件内容。
  1. 1 this.start = function(){
  2. 2         SetupVarDiff();
  3. 3         SetupApi();
  4. 4         SetupDaemonInterface(function(){
  5. 5             DetectCoinData(function(){
  6. 6                 SetupRecipients();
  7. 7                 SetupJobManager();
  8. 8                 OnBlockchainSynced(function(){
  9. 9                     GetFirstJob(function(){
  10. 10                         SetupBlockPolling();
  11. 11                         SetupPeer();
  12. 12                         StartStratumServer(function(){
  13. 13                             OutputPoolInfo();
  14. 14                             _this.emit('started');
  15. 15                         });
  16. 16                     });
  17. 17                 });
  18. 18             });
  19. 19         });
  20. 20     };
复制代码
第2行用于设置可变难度,即会根据客户端share的提交修改下发任务的难度。
第4行SetupDaemonInterface根据配置文件中钱包配置(钱包地点host的IP地址及监听端口,rpc用户名及密码),创建与钱包rpc通信的保卫线程daemon(参看node_modules\stratum-pool\lib\daemon.js)

之后在DetectCoinData函数中,通过validateaddress、getdifficulty、getmininginfo等rpc调用来对全局配置中类似hasSubmitMethod的关键项举行初始化,在OnBlockchainSynced函数中会等候钱包数据同步,同步完成后,调用GetFirstJob函数获取第一个job,在该函数中通过调用GetBlockTemplate从钱包获取block信息,然后通过jobManager.processTemplate来处理返回值,生成blockTemplate(node_modules\stratum-pool\lib\blockTemplate.js),在通过newBlock消息关照jobManager,jobManager再通过StartStratumServer将job广播出去,这里的jobParams对应于stratum协议的mining.notify中的params内容,如下图:

   至于其他内容如任务提交、难度修改等处理都可以看node_modules\stratum-pool\lib相关内容。
2.5 startPaymentProcessor

  1. 1 var startPaymentProcessor = function(){
  2. 2
  3. 3     var enabledForAny = false;
  4. 4     for (var pool in poolConfigs){
  5. 5         var p = poolConfigs[pool];
  6. 6         var enabled = p.enabled && p.paymentProcessing && p.paymentProcessing.enabled;
  7. 7         if (enabled){
  8. 8             enabledForAny = true;
  9. 9             break;
  10. 10         }
  11. 11     }
  12. 12
  13. 13     if (!enabledForAny)
  14. 14         return;
  15. 15
  16. 16     var worker = cluster.fork({
  17. 17         workerType: 'paymentProcessor',
  18. 18         pools: JSON.stringify(poolConfigs)
  19. 19     });
  20. 20     worker.on('exit', function(code, signal){
  21. 21         logger.error('Master', 'Payment Processor', 'Payment processor died, spawning replacement...');
  22. 22         setTimeout(function(){
  23. 23             startPaymentProcessor(poolConfigs);
  24. 24         }, 2000);
  25. 25     });
  26. 26 };
复制代码
startPaymentProcessor 这部分内容是关于挖矿付款的处理,由于本人对这部分内容也没有深入了解,所以不再举行详细介绍。
2.6 startWebsite

  1. 1 var startWebsite = function(){
  2. 2
  3. 3     if (!portalConfig.website.enabled) return;
  4. 4
  5. 5     var worker = cluster.fork({
  6. 6         workerType: 'website',
  7. 7         pools: JSON.stringify(poolConfigs),
  8. 8         portalConfig: JSON.stringify(portalConfig)
  9. 9     });
  10. 10     worker.on('exit', function(code, signal){
  11. 11         logger.error('Master', 'Website', 'Website process died, spawning replacement...');
  12. 12         setTimeout(function(){
  13. 13             startWebsite(portalConfig, poolConfigs);
  14. 14         }, 2000);
  15. 15     });
  16. 16 };
复制代码
startWebsite 该部分利用express模块生成web前端,这部分内容相对比力独立,不再举行详细介绍,相关功能请直接参考源码。
2.7 startProfitSwitch

  1. 1 var startProfitSwitch = function(){
  2. 2
  3. 3     if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){
  4. 4         //logger.error('Master', 'Profit', 'Profit auto switching disabled');
  5. 5         return;
  6. 6     }
  7. 7
  8. 8     var worker = cluster.fork({
  9. 9         workerType: 'profitSwitch',
  10. 10         pools: JSON.stringify(poolConfigs),
  11. 11         portalConfig: JSON.stringify(portalConfig)
  12. 12     });
  13. 13     worker.on('exit', function(code, signal){
  14. 14         logger.error('Master', 'Profit', 'Profit switching process died, spawning replacement...');
  15. 15         setTimeout(function(){
  16. 16             startWebsite(portalConfig, poolConfigs);
  17. 17         }, 2000);
  18. 18     });
  19. 19 };
复制代码
startProfitSwitch 该部分用于获取各大交易网站的实时价格信息,这部分代码已经不再更新,这里也不再详细介绍,有兴趣的请直接查看源码。
2.8 startCliListener

  1. 1 var startCliListener = function(){
  2. 2
  3. 3     var cliPort = portalConfig.cliPort;
  4. 4
  5. 5     var listener = new CliListener(cliPort);
  6. 6     listener.on('log', function(text){
  7. 7         logger.debug('Master', 'CLI', text);
  8. 8     }).on('command', function(command, params, options, reply){
  9. 9
  10. 10         switch(command){
  11. 11             case 'blocknotify':
  12. 12                 Object.keys(cluster.workers).forEach(function(id) {
  13. 13                     cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]});
  14. 14                 });
  15. 15                 reply('Pool workers notified');
  16. 16                 break;
  17. 17             case 'coinswitch':
  18. 18                 processCoinSwitchCommand(params, options, reply);
  19. 19                 break;
  20. 20             case 'reloadpool':
  21. 21                 Object.keys(cluster.workers).forEach(function(id) {
  22. 22                     cluster.workers[id].send({type: 'reloadpool', coin: params[0] });
  23. 23                 });
  24. 24                 reply('reloaded pool ' + params[0]);
  25. 25                 break;
  26. 26             default:
  27. 27                 reply('unrecognized command "' + command + '"');
  28. 28                 break;
  29. 29         }
  30. 30     }).start();
  31. 31 };
复制代码
startCliListener 第3行根据配置中的cliPort端口创建监听,在10~25行依次处理矿池详细业务相关的blocknotfy、coinswitch、reloadpool命令。
3 总结

NOMP以stratum-pool高性能Stratum池服务器为核心,该部分主要对象可以用下图举行表示:

在stratum-pool基础上,nomp增长网站前端、数据库层、多币种/池支持以及主动切换矿工在不同币种/池之间的操作等功能,如想单纯的查看stratum服务器核心功能,请直接参考该项目
https://github.com/zone117x/node-stratum-pool
也即NOMP项目下node_modules\stratum-pool内容。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

科技颠覆者

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表