科技颠覆者 发表于 2024-6-17 08:54:27

nomp矿池源码详解

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 目次

https://img2024.cnblogs.com/blog/465567/202406/465567-20240613163625106-1641858453.png
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信息
https://img2024.cnblogs.com/blog/465567/202406/465567-20240614101226555-2146536688.png
之后,会里用cluster模块,来判断当进步程是主历程(通常称为“master”),还是工作历程(“workers”),对于工作历程,按照类型创建不同的实例
1 if (cluster.isWorker){
2
3   switch(process.env.workerType){
4         case 'pool':
5             new PoolWorker(logger);
6             break;
7         case 'paymentProcessor':
8             new PaymentProcessor(logger);
9             break;
10         case 'website':
11             new Website(logger);
12             break;
13         case 'profitSwitch':
14             new ProfitSwitch(logger);
15             break;
16   }
17
18   return;
19 } 假如是主历程则会调用以下功能模块函数,创建不同的工作历程
1 (function init(){
2
3   poolConfigs = buildPoolConfigs();
4
5   spawnPoolWorkers();
6
7   startPaymentProcessor();
8
9   startWebsite();
10
11   startProfitSwitch();
12
13   startCliListener();
14
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

https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif 1 var buildPoolConfigs = function(){
2   var configs = {};
3   var configDir = 'pool_configs/';
4
5   var poolConfigFiles = [];
6
7
8   /* Get filenames of pool config json files that are enabled */
9   fs.readdirSync(configDir).forEach(function(file){
10         if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return;
11         var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'})));
12         if (!poolOptions.enabled) return;
13         poolOptions.fileName = file;
14         poolConfigFiles.push(poolOptions);
15   });
16
17
18   /* Ensure no pool uses any of the same ports as another pool */
19   for (var i = 0; i < poolConfigFiles.length; i++){
20         var ports = Object.keys(poolConfigFiles.ports);
21         for (var f = 0; f < poolConfigFiles.length; f++){
22             if (f === i) continue;
23             var portsF = Object.keys(poolConfigFiles.ports);
24             for (var g = 0; g < portsF.length; g++){
25               if (ports.indexOf(portsF) !== -1){
26                     logger.error('Master', poolConfigFiles.fileName, 'Has same configured port of ' + portsF + ' as ' + poolConfigFiles.fileName);
27                     process.exit(1);
28                     return;
29               }
30             }
31
32             if (poolConfigFiles.coin === poolConfigFiles.coin){
33               logger.error('Master', poolConfigFiles.fileName, 'Pool has same configured coin file coins/' + poolConfigFiles.coin + ' as ' + poolConfigFiles.fileName + ' pool');
34               process.exit(1);
35               return;
36             }
37
38         }
39   }
40
41
42   poolConfigFiles.forEach(function(poolOptions){
43
44         poolOptions.coinFileName = poolOptions.coin;
45
46         var coinFilePath = 'coins/' + poolOptions.coinFileName;
47         if (!fs.existsSync(coinFilePath)){
48             logger.error('Master', poolOptions.coinFileName, 'could not find file: ' + coinFilePath);
49             return;
50         }
51
52         var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'})));
53         poolOptions.coin = coinProfile;
54         poolOptions.coin.name = poolOptions.coin.name.toLowerCase();
55
56         if (poolOptions.coin.name in configs){
57
58             logger.error('Master', poolOptions.fileName, 'coins/' + poolOptions.coinFileName
59               + ' has same configured coin name ' + poolOptions.coin.name + ' as coins/'
60               + configs.coinFileName + ' used by pool config '
61               + configs.fileName);
62
63             process.exit(1);
64             return;
65         }
66
67         for (var option in portalConfig.defaultPoolConfigs){
68             if (!(option in poolOptions)){
69               var toCloneOption = portalConfig.defaultPoolConfigs;
70               var clonedOption = {};
71               if (toCloneOption.constructor === Object)
72                     extend(true, clonedOption, toCloneOption);
73               else
74                     clonedOption = toCloneOption;
75               poolOptions = clonedOption;
76             }
77         }
78
79
80         configs = poolOptions;
81
82         if (!(coinProfile.algorithm in algos)){
83             logger.error('Master', coinProfile.name, 'Cannot run a pool for unsupported algorithm "' + coinProfile.algorithm + '"');
84             delete configs;
85         }
86
87   });
88   return configs;
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

https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif 1 var spawnPoolWorkers = function(){
2
3   Object.keys(poolConfigs).forEach(function(coin){
4         var p = poolConfigs;
5
6         if (!Array.isArray(p.daemons) || p.daemons.length < 1){
7             logger.error('Master', coin, 'No daemons configured so a pool cannot be started for this coin.');
8             delete poolConfigs;
9         }
10   });
11
12   if (Object.keys(poolConfigs).length === 0){
13         logger.warning('Master', 'PoolSpawner', 'No pool configs exists or are enabled in pool_configs folder. No pools spawned.');
14         return;
15   }
16
17
18   var serializedConfigs = JSON.stringify(poolConfigs);
19
20   var numForks = (function(){
21         if (!portalConfig.clustering || !portalConfig.clustering.enabled)
22             return 1;
23         if (portalConfig.clustering.forks === 'auto')
24             return os.cpus().length;
25         if (!portalConfig.clustering.forks || isNaN(portalConfig.clustering.forks))
26             return 1;
27         return portalConfig.clustering.forks;
28   })();
29
30   var poolWorkers = {};
31
32   var createPoolWorker = function(forkId){
33         var worker = cluster.fork({
34             workerType: 'pool',
35             forkId: forkId,
36             pools: serializedConfigs,
37             portalConfig: JSON.stringify(portalConfig)
38         });
39         worker.forkId = forkId;
40         worker.type = 'pool';
41         poolWorkers = worker;
42         worker.on('exit', function(code, signal){
43             logger.error('Master', 'PoolSpawner', 'Fork ' + forkId + ' died, spawning replacement worker...');
44             setTimeout(function(){
45               createPoolWorker(forkId);
46             }, 2000);
47         }).on('message', function(msg){
48             switch(msg.type){
49               case 'banIP':
50                     Object.keys(cluster.workers).forEach(function(id) {
51                         if (cluster.workers.type === 'pool'){
52                           cluster.workers.send({type: 'banIP', ip: msg.ip});
53                         }
54                     });
55                     break;
56             }
57         });
58   };
59
60   var i = 0;
61   var spawnInterval = setInterval(function(){
62         createPoolWorker(i);
63         i++;
64         if (i === numForks){
65             clearInterval(spawnInterval);
66             logger.debug('Master', 'PoolSpawner', 'Spawned ' + Object.keys(poolConfigs).length + ' pool(s) on ' + numForks + ' thread(s)');
67         }
68   }, 250);
69
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 this.start = function(){
2         SetupVarDiff();
3         SetupApi();
4         SetupDaemonInterface(function(){
5             DetectCoinData(function(){
6               SetupRecipients();
7               SetupJobManager();
8               OnBlockchainSynced(function(){
9                     GetFirstJob(function(){
10                         SetupBlockPolling();
11                         SetupPeer();
12                         StartStratumServer(function(){
13                           OutputPoolInfo();
14                           _this.emit('started');
15                         });
16                     });
17               });
18             });
19         });
20   };第2行用于设置可变难度,即会根据客户端share的提交修改下发任务的难度。
第4行SetupDaemonInterface根据配置文件中钱包配置(钱包地点host的IP地址及监听端口,rpc用户名及密码),创建与钱包rpc通信的保卫线程daemon(参看node_modules\stratum-pool\lib\daemon.js)
https://img2024.cnblogs.com/blog/465567/202406/465567-20240614150636540-2027736635.png
之后在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内容,如下图:
https://img2024.cnblogs.com/blog/465567/202406/465567-20240614192209344-330959935.png
   至于其他内容如任务提交、难度修改等处理都可以看node_modules\stratum-pool\lib相关内容。
2.5 startPaymentProcessor

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

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

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

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

NOMP以stratum-pool高性能Stratum池服务器为核心,该部分主要对象可以用下图举行表示:
https://img2024.cnblogs.com/blog/465567/202406/465567-20240617095439025-1335712801.png
在stratum-pool基础上,nomp增长网站前端、数据库层、多币种/池支持以及主动切换矿工在不同币种/池之间的操作等功能,如想单纯的查看stratum服务器核心功能,请直接参考该项目
https://github.com/zone117x/node-stratum-pool
也即NOMP项目下node_modules\stratum-pool内容。
 

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