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

 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[i].ports);
21 for (var f = 0; f < poolConfigFiles.length; f++){
22 if (f === i) continue;
23 var portsF = Object.keys(poolConfigFiles[f].ports);
24 for (var g = 0; g < portsF.length; g++){
25 if (ports.indexOf(portsF[g]) !== -1){
26 logger.error('Master', poolConfigFiles[f].fileName, 'Has same configured port of ' + portsF[g] + ' as ' + poolConfigFiles[i].fileName);
27 process.exit(1);
28 return;
29 }
30 }
31
32 if (poolConfigFiles[f].coin === poolConfigFiles[i].coin){
33 logger.error('Master', poolConfigFiles[f].fileName, 'Pool has same configured coin file coins/' + poolConfigFiles[f].coin + ' as ' + poolConfigFiles[i].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[poolOptions.coin.name].coinFileName + ' used by pool config '
61 + configs[poolOptions.coin.name].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[option];
70 var clonedOption = {};
71 if (toCloneOption.constructor === Object)
72 extend(true, clonedOption, toCloneOption);
73 else
74 clonedOption = toCloneOption;
75 poolOptions[option] = clonedOption;
76 }
77 }
78
79
80 configs[poolOptions.coin.name] = 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[poolOptions.coin.name];
85 }
86
87 });
88 return configs;
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 var spawnPoolWorkers = function(){
2
3 Object.keys(poolConfigs).forEach(function(coin){
4 var p = poolConfigs[coin];
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[coin];
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[forkId] = 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[id].type === 'pool'){
52 cluster.workers[id].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)

之后在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 var startPaymentProcessor = function(){
2
3 var enabledForAny = false;
4 for (var pool in poolConfigs){
5 var p = poolConfigs[pool];
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

 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

 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

 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[id].send({type: 'blocknotify', coin: params[0], hash: params[1]});
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[id].send({type: 'reloadpool', coin: params[0] });
23 });
24 reply('reloaded pool ' + params[0]);
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池服务器为核心,该部分主要对象可以用下图进行表示:

在stratum-pool基础上,nomp增加网站前端、数据库层、多币种/池支持以及自动切换矿工在不同币种/池之间的操作等功能,如想单纯的查看stratum服务器核心功能,请直接参考该项目

https://github.com/zone117x/node-stratum-pool

也即NOMP项目下node_modules\stratum-pool内容。

nomp矿池源码详解的更多相关文章

  1. Java多线程学习之线程池源码详解

    0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...

  2. udhcp源码详解(五) 之DHCP包--options字段

    中间有很长一段时间没有更新udhcp源码详解的博客,主要是源码里的函数太多,不知道要不要一个一个讲下去,要知道讲DHCP的实现理论的话一篇博文也就可以大致的讲完,但实现的源码却要关心很多的问题,比如说 ...

  3. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

  4. 源码详解系列(八) ------ 全面讲解HikariCP的使用和源码

    简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...

  5. RocketMQ源码详解 | Producer篇 · 其二:消息组成、发送链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其一:Start,然后 Send 一条消息 中,我们了解了 Producer 在发送消息的流程.这次我们再来具体下看消息的构成与其 ...

  6. RocketMQ源码详解 | Broker篇 · 其一:线程模型与接收链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其二:消息组成.发送链路 中,我们终于将消息发送出了 Producer,在短暂的 tcp 握手后,很快它就会进入目的 Broker ...

  7. RocketMQ源码详解 | Consumer篇 · 其一:消息的 Pull 和 Push

    概述 当消息被存储后,消费者就会将其消费. 这句话简要的概述了一条消息的最总去向,也引出了本文将讨论的问题: 消息什么时候才对被消费者可见? 是在 page cache 中吗?还是在落盘后?还是像 K ...

  8. RocketMQ源码详解 | Broker篇 · 其四:事务消息、批量消息、延迟消息

    概述 在上文中,我们讨论了消费者对于消息拉取的实现,对于 RocketMQ 这个黑盒的心脏部分,我们顺着消息的发送流程已经将其剖析了大半部分.本章我们不妨乘胜追击,接着讨论各种不同的消息的原理与实现. ...

  9. Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解

    Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...

  10. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

随机推荐

  1. 技术实践第二期|Flutter异常捕获

    ​简介:应用性能稳定是良好用户体验中非常关键的一环,为了更好保障应用性能稳定,异常捕获在保证线上产品稳定中扮演着至关重要的角色.我们团队在推出了U-APM移动应用性能监控的产品后,帮助开发者定位并解决 ...

  2. 阿里巴巴Canal常见问题:重复解析/Filter失效/消费落后

    前言 Canal是阿里巴巴开源的数据库Binlog日志解析框架,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费. 在之前我写的文章阿里开源MySQL中间件Canal快速入门中, ...

  3. git checkout 命令图文详解

    目录 git checkout branchname (切换本地分支) 切换远程分支 放弃修改 git checkout . git checkout – filename git checkout ...

  4. NASM中的ALIGN ALIGNB SECTALIGN

    ALIGN与ALIGNB NASM中的ALIGN与ALIGNB是用来字节对齐的,它们接收2个参数,第一个参数是必须的,表示对齐的字节数(必须是2的幂),第二个参数是可选的,表示为了对齐而进行填充的内容 ...

  5. ansible系列(1)--ansible基础

    目录 1. ansible概述 1.1 ansible的功能 1.2 ansible的特性 1.3 ansible的架构 1.4 ansible注意事项 1. ansible概述 Ansible 是一 ...

  6. neo4j配置文件neo4j.conf详解

    一.dbms配置 dbms.default_database=neo4j 目录路径 dbms.directories.data=datadbms.directories.plugins=plugins ...

  7. pageoffice 6 实现数据区域填充(插入文本、图片、word、excel等)

    在实际的Word文档开发中,经常需要自动填充数据到Word模板中,以生成动态的Word文档. 例如: 1.我们可以根据数据库表中已保存的个人信息,设计好一个简历模板docx文件,然后通过代码将这些个人 ...

  8. linux ls命令的重要用法:按照文件大小排序和按照时间排序

    1.ls命令是list的缩写,用来打印当前目录清单或者打印出指定目录下的文件及文件清单. 2.本文介绍ls的重要用法:按"文件大小"排序列出文件清单和按"时间" ...

  9. 一个基于 Spring Dubbo 微服务的快速开发脚手架,新手入门必备!

    Spring-dubbo-skeleton 这是一个基于 Spring Dubbo 的快速开发脚手架,Github 地址:https://github.com/yxhsea/spring-dubbo- ...

  10. WebView2在WPF中的应用

    开发环境 运行环境:.Net 6 开发环境:Visual Studio 2022 17.1.3 框架语言:WPF 安装WebView2 通过Package Manager控制台安装 Install-P ...