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. 基于EPCLYPS的DDS控制器(二)

    关于ZmodAWGController ZmodAWGController 介绍 双击IP核,进入的第一个界面会有Ch1 Gain Static Configuration的选项修改为 "0 ...

  2. 3.Exporter概述

    一.Exporter概述 所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter.而Exporter的一个实例称为target,如下所示,Prometheus通过轮询的方 ...

  3. 使用open webui+ollama部署本地大模型

    使用open webui+ollama部署本地大模型 上次使用了angthingllm + ollama部署了本地大模型,详情见:https://www.cnblogs.com/jokingremar ...

  4. github只下载某个文件或文件夹(使用GitZip插件)

    安装GitZip插件 (此安装过程需要梯子(不懂"梯子",百度一下就明白)) 1. 打开插件管理页面 方法一:打开Chrome浏览器(Edge浏览器同理),在Chrom地址栏输入c ...

  5. 钉消息Markdown语法

    支持的Markdown语法 1 标题 2 # 一级标题 3 ## 二级标题 4 ### 三级标题 5 #### 四级标题 6 ##### 五级标题 7 ###### 六级标题 8 9 引用 10 &g ...

  6. 羽夏闲谈——解决 MSI 安装包指定账户已存在

    序   前几天用VS2022,升级到17.1.0版本,发现模板用不了了,但能正常打开之前用它创建的项目.我重装试图修复该问题,解决雪上加霜,报错如下: 未能安装包"Microsoft.Vis ...

  7. 16、数据库加固-mysql 加固

    1.修改 DBA 登录密码 shell 下执行: mysqladmin -u root password 非首次修改:mysqladmin -u root password -p原密码 在 mysql ...

  8. 使用Lagent AgentLego 搭建智能体-书生浦语大模型实战营第二期第6节作业

    书生浦语大模型实战营第二期第6节作业 对于这个作业,这里只给出截图,不给详细过程,因为确实没有什么好写的,会做Demo那个作业就会做这个作业.具体的步骤可以查看官方教程. 基础作业 完成 Lagent ...

  9. java里面的方法。

    java里面的方法. java方法是语句的组合,他们在一起执行一个功能. 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建在其他地方被引用 方法类似于其他语言里面的函数 e ...

  10. kettle使用3-增量同步(插入的时候判断数据是否存在,存在就更新,不存在就插入)

    1.新建转换 2.在DB连接中,新建2个数据库连接 3.在输入中,新建:表输入 4.在输入中,新建:表输入 5.在输出中,新建:插入/更新 说明:更新字段: 是说更新目的表时候,哪些列更新,哪些不更新 ...