经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
nomp矿池源码详解
来源:cnblogs  作者:weiwei22844  时间:2024/6/17 15:09:56  对本文有异议

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 };
buildPoolConfigs

9~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内容。

 

原文链接:https://www.cnblogs.com/zhaoweiwei/p/18246280/nompdetail

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号