app v1.0
This commit is contained in:
57
README
Normal file
57
README
Normal file
@@ -0,0 +1,57 @@
|
||||
app module
|
||||
hashrate
|
||||
distribution
|
||||
report_block
|
||||
confirm_block
|
||||
clear
|
||||
balance
|
||||
|
||||
############# start common #############
|
||||
pm2 start app.js --name nexa-hashratev2 -- hashrate nexa
|
||||
pm2 start app.js --name nexa-reportv2 -- report nexa
|
||||
pm2 start app.js --name nexa-confirm -- confirm nexa
|
||||
pm2 start app.js --name nexa-distributionv2 -- distribution nexa
|
||||
pm2 start app.js --name email -- notice nexa
|
||||
pm2 start app.js --name nexa-balance -- balance nexa
|
||||
|
||||
pm2 start app.js --name grs-hashratev2 -- hashrate grs
|
||||
pm2 start app.js --name grs-reportv2 -- report grs
|
||||
pm2 start app.js --name grs-confirm -- confirm grs
|
||||
pm2 start app.js --name grs-distributionv2 -- distribution grs
|
||||
pm2 start app.js --name grs-balance -- balance grs
|
||||
|
||||
pm2 start app.js --name mona-hashratev2 -- hashrate mona
|
||||
pm2 start app.js --name mona-reportv2 -- report mona
|
||||
pm2 start app.js --name mona-confirm -- confirm mona
|
||||
pm2 start app.js --name mona-distributionv2 -- distribution mona
|
||||
pm2 start app.js --name mona-balance -- balance mona
|
||||
|
||||
pm2 start app.js --name dgbs-hashratev2 -- hashrate dgbs
|
||||
pm2 start app.js --name dgbs-reportv2 -- report dgbs
|
||||
pm2 start app.js --name dgbs-confirm -- confirm dgbs
|
||||
pm2 start app.js --name dgbs-distributionv2 -- distribution dgbs
|
||||
# pm2 start app.js --name dgbs-balance -- balance dgbs
|
||||
|
||||
pm2 start app.js --name dgbq-hashratev2 -- hashrate dgbq
|
||||
pm2 start app.js --name dgbq-reportv2 -- report dgbq
|
||||
pm2 start app.js --name dgbq-confirm -- confirm dgbq
|
||||
pm2 start app.js --name dgbq-distributionv2 -- distribution dgbq
|
||||
# pm2 start app.js --name dgbq-balance -- balance dgbq
|
||||
|
||||
pm2 start app.js --name dgbo-hashratev2 -- hashrate dgbo
|
||||
pm2 start app.js --name dgbo-reportv2 -- report dgbo
|
||||
pm2 start app.js --name dgbo-confirm -- confirm dgbo
|
||||
pm2 start app.js --name dgbo-distributionv2 -- distribution dgbo
|
||||
pm2 start app.js --name dgb-balance -- balance dgbo
|
||||
|
||||
pm2 start app.js --name rxd-hashratev2 -- hashrate rxd
|
||||
pm2 start app.js --name rxd-reportv2 -- report rxd
|
||||
pm2 start app.js --name rxd-confirm -- confirm rxd
|
||||
pm2 start app.js --name rxd-distributionv2 -- distribution rxd
|
||||
pm2 start app.js --name rxd-balance -- balance rxd
|
||||
|
||||
pm2 start app.js --name enx-hashrate -- hashrate enx
|
||||
pm2 start app.js --name enx-report -- report enx
|
||||
|
||||
pm2 start app.js --name alph-report -- report alph
|
||||
pm2 start app.js --name alph-hashrate -- hashrate alph
|
||||
189
app.js
Normal file
189
app.js
Normal file
@@ -0,0 +1,189 @@
|
||||
const schedule = require("node-schedule");
|
||||
// const executeWithRetry = require("./public/retry")
|
||||
const Times = require("./public/times");
|
||||
const HashRate = require("./src/hashrate");
|
||||
const {Report, ReportEnx} = require("./src/report");
|
||||
const Confirm = require("./src/confirm");
|
||||
const Distribution = require("./src/distribution");
|
||||
const { Balance, DGBBlance } = require("./src/blanace");
|
||||
const Stats = require("./src/stat");
|
||||
const ClearDBData = require("./src/clear")
|
||||
const Notice = require("./src/notice");
|
||||
|
||||
const method = process.argv[2];
|
||||
const methods = ["hashrate", "report", "clear", "distribution", "confirm", "balance", "stats", "notice"];
|
||||
|
||||
if (!methods.includes(method)) {
|
||||
throw `暂不支持${method}方法`;
|
||||
}
|
||||
|
||||
const coin = process.argv[3];
|
||||
const coins = ["nexa", "mona", "grs", "dgbq", "dgbs", "dgbo", "rxd", "enx", "alph"];
|
||||
if (!coins.includes(coin)) {
|
||||
throw `暂不支持${coin}`;
|
||||
}
|
||||
|
||||
if (method === "hashrate") {
|
||||
const hashrate = new HashRate(coin);
|
||||
schedule.scheduleJob({ minute: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55], second: [30] }, async () => {
|
||||
const ymd_now = Times.utcTime(Date.now().valueOf());
|
||||
const ymd = ymd_now.split(":");
|
||||
const end_time = ymd[0] + ":" + ymd[1] + ":" + "00";
|
||||
await hashrate.insert_hashrate_miners_table(end_time);
|
||||
const currentMinute = new Date().getMinutes();
|
||||
|
||||
const data = await hashrate.query_hashrate_miners_accepts(end_time);
|
||||
if (currentMinute === 0 || currentMinute === 30) {
|
||||
await hashrate.insert_mhs(data);
|
||||
await hashrate.insert_mhs_real(data);
|
||||
} else {
|
||||
await hashrate.insert_mhs_real(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (method === "report") {
|
||||
let report
|
||||
if(coin === "enx"){
|
||||
report = new ReportEnx(coin);
|
||||
} else {
|
||||
report = new Report(coin);
|
||||
}
|
||||
let interval = 60000;
|
||||
if (coin === "rxd") {
|
||||
interval = 300000;
|
||||
} else if (coin === "nexa") {
|
||||
interval = 120000;
|
||||
}
|
||||
setInterval(() => {
|
||||
report.main();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
// if (method === "confirm") {
|
||||
// // schedule.scheduleJob({hour:[1], minute:[30], second:[0]}, async () =>{
|
||||
// // confirm.main();
|
||||
// // })
|
||||
// let interval = 600000;
|
||||
// if (coin === "rxd") {
|
||||
// interval = 1800000;
|
||||
// }
|
||||
// const confirm = new Confirm(coin);
|
||||
// setInterval(() => {
|
||||
// confirm.main();
|
||||
// }, interval);
|
||||
// }
|
||||
|
||||
|
||||
if (method === "confirm") {
|
||||
const confirm = new Confirm(coin);
|
||||
schedule.scheduleJob({hour:[1], minute:[30], second:[0]}, async () =>{
|
||||
confirm.main();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (method === "distribution") {
|
||||
const distribution = new Distribution(coin);
|
||||
schedule.scheduleJob({ hour: [0], minute: [1], second: [0] }, async () => {
|
||||
const now_ts = Date.now().valueOf();
|
||||
const last_ts = now_ts - 1000 * 60 * 60 * 24;
|
||||
const ymd_now = Times.utcTime(now_ts);
|
||||
const ymd_last = Times.utcTime(last_ts);
|
||||
const end_time = ymd_now.split(" ")[0] + " 00:00:00";
|
||||
const start_time = ymd_last.split(" ")[0] + " 00:00:00";
|
||||
await distribution.main(start_time, end_time);
|
||||
});
|
||||
}
|
||||
|
||||
// if (method === "balance") {
|
||||
// const special_coins = ["dgbo", "dgbs", "dgbq"];
|
||||
// let balance;
|
||||
// if (special_coins.includes(coin)) {
|
||||
// balance = new DGBBlance(coin);
|
||||
// } else {
|
||||
// balance = new Balance(coin);
|
||||
// }
|
||||
// balance
|
||||
// .main()
|
||||
// .then(() => {
|
||||
// console.log("ok");
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// console.log(err);
|
||||
// })
|
||||
// .finally(() => {
|
||||
// process.exit(0);
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
if (method === "balance") {
|
||||
const special_coins = ["dgbo", "dgbs", "dgbq"];
|
||||
let balance;
|
||||
if (special_coins.includes(coin)) {
|
||||
balance = new DGBBlance(coin);
|
||||
} else {
|
||||
balance = new Balance(coin);
|
||||
}
|
||||
|
||||
let hour = 4
|
||||
if(coin === "rxd"){
|
||||
hour = 9
|
||||
}
|
||||
|
||||
async function task(balance) {
|
||||
let count = 0; // 让 count 成为 task 内部变量,避免多个 schedule 共享 count
|
||||
const last_height = await balance.node.getblockcount()
|
||||
while (count < 36) { // 最多执行 36 次 (6小时)
|
||||
const enable = await balance.query_now_height(last_height);
|
||||
if (enable) {
|
||||
await balance.main();
|
||||
console.log(`${coin}转账已完成`);
|
||||
return; // 成功执行后退出循环
|
||||
}
|
||||
console.log(`等待中... (已等待 ${count * 10} 分钟)`);
|
||||
await balance.sleep(1000 * 60 * 10); // 休眠 10 分钟
|
||||
count++;
|
||||
}
|
||||
console.log("等待超时,任务结束!");
|
||||
}
|
||||
schedule.scheduleJob({hour: [hour], minute: [10], second: [0]}, async () => {
|
||||
await task(balance);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (method === "stats") {
|
||||
const stats = new Stats(coin);
|
||||
stats.caculate_user_should_distribution("2024-11-28 00:00:00");
|
||||
}
|
||||
|
||||
if (method === "clear") {
|
||||
const clear = new ClearDBData(coin);
|
||||
clear
|
||||
.clearSharesDB(72)
|
||||
.then((res) => {
|
||||
console.log("sharesdb:ok");
|
||||
})
|
||||
.then(() => {
|
||||
clear.clearHashrateDB();
|
||||
})
|
||||
.then(() => {
|
||||
console.log("hashratedb:ok");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
})
|
||||
.finally(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
// clear.clearPoolDB("2024-10-11 00:00:00", "2024-12-23 00:00:00")
|
||||
}
|
||||
|
||||
if (method === "notice") {
|
||||
const notice = new Notice(coin);
|
||||
schedule.scheduleJob({hour:[9], minute:[30], second:[0]}, () =>{
|
||||
notice.main()
|
||||
})
|
||||
}
|
||||
129
config/alph.conf
Normal file
129
config/alph.conf
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "alphpooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "alphsharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbosharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"db":9,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8",
|
||||
"rpcPort":12973,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"0x09e220e226f2feb7a971a2b6f958e7d4b1c187c8",
|
||||
"rpcPort":12973,
|
||||
"rpcHost":"127.0.0.1"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"1CYKPymfTVex9KZ2i48S3v5cAE7xT6hERG1P6GJiHgWJu",
|
||||
"MAX_MATURE":500,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/dgbo.conf
Normal file
128
config/dgbo.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbopooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbosharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbopooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbosharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":14022,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":14022,
|
||||
"rpcHost":"13.214.176.64"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"DSdh4rXmRZizpZh7zKGSsyMqHmFE137G96",
|
||||
"MAX_MATURE":50,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/dgbq.conf
Normal file
128
config/dgbq.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbqpooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbqsharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbqpooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbqsharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":14022,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":14022,
|
||||
"rpcHost":"13.214.176.64"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"DK8ZTp89aahJpmnwdZ8LNsAUNoGA6qS43G",
|
||||
"MAX_MATURE":50,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/dgbs.conf
Normal file
128
config/dgbs.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbspooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbssharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbspooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbssharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":14022,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":14022,
|
||||
"rpcHost":"13.214.176.64"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"DDQVUDkCNfF5TKjJ5n6jh7BG2wZxr9SNnq",
|
||||
"MAX_MATURE":50,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/enx.conf
Normal file
128
config/enx.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "enxpooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "enxsharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbopooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "13.214.176.64",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbosharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":16110,
|
||||
"rpcHost":"10.168.1.162"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":16110,
|
||||
"rpcHost":"10.168.1.162"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"entropyx:qpe7q43ajzuv42az6kjdu84yrzdhzj33w03kpytfg5lx5yk3x0pd6x0r87hhr",
|
||||
"MAX_MATURE":1000,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0
|
||||
}
|
||||
}
|
||||
128
config/grs.conf
Normal file
128
config/grs.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "grspooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "grssharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "grspooldb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "grssharesdb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":1441,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":1441,
|
||||
"rpcHost":"13.213.4.56"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"3BZgQAB1tAN6zRcseavQ6vcMtXXiwThTvL",
|
||||
"MAX_MATURE":20,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/mona.conf
Normal file
128
config/mona.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "monapooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "monasharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "monapooldb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "monasharesdb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":9402,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":9402,
|
||||
"rpcHost":"13.213.4.56"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"PSq64UuybtHykZvV9eQa77bkkAZyLgLkcG",
|
||||
"MAX_MATURE":20,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/nexa.conf
Normal file
128
config/nexa.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "m2pooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "sharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "test",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "m2pooldb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "sharesdb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":7227,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":7227,
|
||||
"rpcHost":"18.139.85.190"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"nexa:nqtsq5g54692xaxstwa8je7u6g83uy2x0mlgl52w75uvrsg4",
|
||||
"MAX_MATURE":20,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/rxd.conf
Normal file
128
config/rxd.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "rxdpooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "rxdsharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "13.213.4.56",
|
||||
"user": "pool190",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbopooldb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "dgbosharesdb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":7332,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":7332,
|
||||
"rpcHost":"18.141.161.129"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"1MBm1CZGSxa8DbDRjNVigyYXo24gU3AEfv",
|
||||
"MAX_MATURE":10,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
128
config/test.conf
Normal file
128
config/test.conf
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"master":{
|
||||
"pooldb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "m2pooldb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "sharesdb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"distribution":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"hashrate":{
|
||||
"host": "127.0.0.1",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "test",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"users_addresses":{
|
||||
"host": "13.214.175.13",
|
||||
"user": "root",
|
||||
"password": "m2Pool2024@!",
|
||||
"database": "pool",
|
||||
"port":3123,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"balance":{
|
||||
"host": "18.143.153.132",
|
||||
"user": "app_user",
|
||||
"password": "8xdZ3~FR$c1mqcxwmLs",
|
||||
"database": "mydb",
|
||||
"port":3306,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"slave":{
|
||||
"pooldb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "m2pooldb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
},
|
||||
"sharesdb_slave":{
|
||||
"host": "172.17.0.1",
|
||||
"user": "m2pool",
|
||||
"password": "pMJzgwrg@Frt8aDXkQAsTGhG!zy!H8Jd",
|
||||
"database": "sharesdb",
|
||||
"port":3307,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"redis_options":{
|
||||
"redis1":{
|
||||
"host":"localhost",
|
||||
"port":6379,
|
||||
"connectTimeout":10000
|
||||
}
|
||||
},
|
||||
"node_options":{
|
||||
"node1":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":7227,
|
||||
"rpcHost":"127.0.0.1"
|
||||
},
|
||||
"node2":{
|
||||
"rpcUser":"test",
|
||||
"rpcPassword":"test",
|
||||
"rpcPort":7227,
|
||||
"rpcHost":"18.139.85.190"
|
||||
}
|
||||
},
|
||||
"retry_options":{
|
||||
"node":{
|
||||
"max_retries":10,
|
||||
"retry_delay":10000
|
||||
}
|
||||
},
|
||||
"REPORT_ADDRESS":"nexa:nqtsq5g54692xaxstwa8je7u6g83uy2x0mlgl52w75uvrsg4",
|
||||
"MAX_MATURE":20,
|
||||
"distribution_conf":{
|
||||
"PPLNS_SIZE":20000,
|
||||
"MODEL_PERCENT":{
|
||||
"SCORE":0,
|
||||
"PPLNS":0.7,
|
||||
"PROPDIF":0.3
|
||||
},
|
||||
"SCORE_PERCENT":{
|
||||
"HASHRATE":1,
|
||||
"STDDEVS":0
|
||||
},
|
||||
"POOL_FEE":0.025
|
||||
}
|
||||
}
|
||||
88
lib/mysql.js
Normal file
88
lib/mysql.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const mysql = require("mysql2/promise");
|
||||
|
||||
class DBPool {
|
||||
constructor(coin, options) {
|
||||
this.coin = coin;
|
||||
this.pool = mysql.createPool(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非事务SQL操作
|
||||
* @param {String} sql
|
||||
* @param {Array} values
|
||||
* @returns
|
||||
*/
|
||||
async exec(sql, values = []) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
const [data] = await con.query(sql, values);
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 单独SQL事务操作
|
||||
* @param {String} sql
|
||||
* @param {Array} values
|
||||
* @returns
|
||||
*/
|
||||
async exec_transaction(sql, values = []) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
await con.beginTransaction();
|
||||
const [data] = await con.query(sql, values);
|
||||
await con.commit();
|
||||
return data;
|
||||
} catch (err) {
|
||||
await con.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一系列 SQL 操作,合并到一个事务中。
|
||||
* @param {Array} params [{sql:"", param:[param1, param2, ...]}]
|
||||
*/
|
||||
async exec_transaction_together(params) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
await con.beginTransaction();
|
||||
|
||||
// 确保所有查询都完成后再提交事务
|
||||
for (const { sql, param } of params) {
|
||||
await con.query(sql, param);
|
||||
}
|
||||
|
||||
await con.commit();
|
||||
} catch (err) {
|
||||
await con.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
|
||||
async exec_write_lock(sql, values = [], table_name) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
await con.beginTransaction();
|
||||
// 使用模板字符串而不是占位符来构建 LOCK TABLES 语句
|
||||
await con.query(`LOCK TABLES ${table_name} WRITE`);
|
||||
await con.query(sql, values);
|
||||
await con.commit();
|
||||
} catch (err) {
|
||||
await con.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
await con.query("UNLOCK TABLES");
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DBPool
|
||||
511
lib/node.js
Normal file
511
lib/node.js
Normal file
@@ -0,0 +1,511 @@
|
||||
const axios = require("axios");
|
||||
const ClientWarapper = require("kaspa-rpc-client").ClientWrapper
|
||||
|
||||
class BaseRPCNode {
|
||||
constructor(NODE_OPTION) {
|
||||
const { rpcUser, rpcHost, rpcPassword, rpcPort } = NODE_OPTION;
|
||||
const request_Rpc = axios.create({
|
||||
baseURL: `http://${rpcHost}:${rpcPort}`, // 修正为 baseURL
|
||||
auth: {
|
||||
username: rpcUser,
|
||||
password: rpcPassword,
|
||||
},
|
||||
timeout: 5000, // 超时时间,5秒
|
||||
});
|
||||
this.request_Rpc = request_Rpc;
|
||||
}
|
||||
async callRpcMethod(method, params = []) {
|
||||
try {
|
||||
const response = await this.request_Rpc.post("/", {
|
||||
jsonrpc: "1.0",
|
||||
id: "testnet",
|
||||
method: method,
|
||||
params: params,
|
||||
});
|
||||
return response.data.result;
|
||||
} catch (error) {
|
||||
console.error("RPC Error:", error.response ? error.response.data : error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async getblockcount() {
|
||||
try {
|
||||
// const blockhash = await getblockhash(height);
|
||||
const data = await this.callRpcMethod("getblockcount", []);
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async getblockhash(height) {
|
||||
try {
|
||||
const data = await this.callRpcMethod("getblockhash", [height]);
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async getblock(param) {
|
||||
try {
|
||||
let data;
|
||||
if (typeof param === "string") {
|
||||
data = await this.callRpcMethod("getblock", [param, 2]);
|
||||
} else if (typeof param === "number") {
|
||||
const hash = await this.getblockhash(param);
|
||||
data = await this.callRpcMethod("getblock", [hash, 2]);
|
||||
}
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NEXARPCNode extends BaseRPCNode {
|
||||
constructor(NODE_OPTION) {
|
||||
super(NODE_OPTION); // 调用父类构造函数
|
||||
}
|
||||
|
||||
async verify_wallet(address) {
|
||||
try {
|
||||
const data = await this.callRpcMethod("getreceivedbyaddress", [address]);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getblock(height) {
|
||||
try {
|
||||
// const blockhash = await getblockhash(height);
|
||||
const data = await this.callRpcMethod("getblock", [height, 2]);
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 校验是否是本矿池报块
|
||||
* @param {Number} height 报块高度
|
||||
* @param {String} address 矿池报块钱包地址
|
||||
* @returns
|
||||
*/
|
||||
async verify_block(height, address) {
|
||||
try {
|
||||
const block_data = await this.getblock(height);
|
||||
const { tx } = block_data;
|
||||
for (let item of tx) {
|
||||
if (item.vin.length === 0) {
|
||||
const { addresses } = item.vout[0].scriptPubKey;
|
||||
if (address === addresses[0]) {
|
||||
return block_data;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
block(block_data) {
|
||||
try {
|
||||
// const block_data = await this.getblock(height);
|
||||
const { tx, time, hash, height } = block_data;
|
||||
let block_fees = 0;
|
||||
let block_reward = 0;
|
||||
for (let item of tx) {
|
||||
if (item.vin.length === 0) {
|
||||
// coinbase交易
|
||||
block_reward = item.sends;
|
||||
} else {
|
||||
block_fees += item.fee;
|
||||
}
|
||||
}
|
||||
const result = { height, hash, time, block_reward, block_fees };
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GRSRPCNode extends BaseRPCNode {
|
||||
constructor(NODE_OPTION) {
|
||||
super(NODE_OPTION); // 调用父类构造函数
|
||||
}
|
||||
async verify_block(_height, REPORT_ADDRESS) {
|
||||
try {
|
||||
const block_data = await this.getblock(_height);
|
||||
const { tx } = block_data;
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
if (value.scriptPubKey.address) {
|
||||
if (value.scriptPubKey.address === REPORT_ADDRESS) {
|
||||
return block_data;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
block(data) {
|
||||
const { hash, tx, height, time } = data;
|
||||
// const littleEndian = Buffer.from(Buffer.from(hash, "hex").reverse());
|
||||
let reward = 0;
|
||||
let fees = 0;
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
reward += value.value;
|
||||
}
|
||||
} else {
|
||||
const { fee } = item;
|
||||
fees += fee;
|
||||
}
|
||||
}
|
||||
// console.log(littleEndian.toString("hex")," ", hash);
|
||||
|
||||
return { height, hash, time, block_reward: reward, block_fees: fees };
|
||||
}
|
||||
}
|
||||
|
||||
class MONARPCNode extends BaseRPCNode {
|
||||
constructor(NODE_OPTION) {
|
||||
super(NODE_OPTION); // 调用父类构造函数
|
||||
}
|
||||
|
||||
async verify_block(_height, REPORT_ADDRESS) {
|
||||
try {
|
||||
const block_data = await this.getblock(_height);
|
||||
const { tx } = block_data;
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
if (value.scriptPubKey.addresses) {
|
||||
if (value.scriptPubKey.addresses["0"] === REPORT_ADDRESS) {
|
||||
return block_data;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
block(data) {
|
||||
const { hash, tx, height, time } = data;
|
||||
|
||||
let reward = 0;
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
if (value.scriptPubKey.addresses) {
|
||||
reward += value.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { height, hash, time, block_reward: reward, block_fees: null };
|
||||
}
|
||||
}
|
||||
|
||||
class DGBRPCNode extends BaseRPCNode {
|
||||
constructor(NODE_OPTION) {
|
||||
super(NODE_OPTION); // 调用父类构造函数
|
||||
}
|
||||
|
||||
async verify_block(_height, REPORT_ADDRESS) {
|
||||
try {
|
||||
const block_data = await this.getblock(_height);
|
||||
const { tx, pow_algo } = block_data;
|
||||
// if (algorithm === pow_algo) {
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
if (value.scriptPubKey.addresses) {
|
||||
if (value.scriptPubKey.addresses[0] === REPORT_ADDRESS) {
|
||||
return block_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async verify_block_with_alogo(_height, REPORT_ADDRESS, algorithm) {
|
||||
try {
|
||||
const block_data = await this.getblock(_height);
|
||||
const { tx, pow_algo } = block_data;
|
||||
if (algorithm === pow_algo) {
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
if (value.scriptPubKey.addresses) {
|
||||
if (value.scriptPubKey.addresses[0] === REPORT_ADDRESS) {
|
||||
return block_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
block(data) {
|
||||
const { hash, tx, height, time } = data;
|
||||
let reward = 0;
|
||||
for (let item of tx) {
|
||||
const { vin, vout } = item;
|
||||
if (vin[0].coinbase) {
|
||||
for (let value of vout) {
|
||||
if (value.scriptPubKey.addresses) {
|
||||
reward += value.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { height, hash, time, block_reward: reward, block_fees: null };
|
||||
}
|
||||
}
|
||||
|
||||
class RXDRPCNode extends BaseRPCNode {
|
||||
constructor(NODE_OPTION) {
|
||||
super(NODE_OPTION);
|
||||
}
|
||||
|
||||
async verify_block(_height, REPORT_ADDRESS) {
|
||||
try {
|
||||
const block_data = await this.getblock(_height);
|
||||
const { tx } = block_data;
|
||||
for (let { vin, vout } of tx) {
|
||||
if (vin[0].coinbase) {
|
||||
for (let { scriptPubKey } of vout) {
|
||||
if (scriptPubKey.addresses[0] === REPORT_ADDRESS) {
|
||||
const hash = await this.getblockhash(_height);
|
||||
const blockstats = await this.callRpcMethod("getblockstats", [hash]);
|
||||
const { blockhash, height, time, subsidy, totalfee } = blockstats;
|
||||
return { height, hash: blockhash, time, block_reward: subsidy + totalfee, block_fees: totalfee };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
block(data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class ENXNode {
|
||||
constructor(NODE_OPTION) {
|
||||
const { rpcHost, rpcPort } = NODE_OPTION;
|
||||
this.rpcHost = rpcHost;
|
||||
this.rpcPort = rpcPort;
|
||||
|
||||
this.state = false
|
||||
}
|
||||
|
||||
async init (){
|
||||
try{
|
||||
const warapper = new ClientWarapper({
|
||||
hosts: [`${this.rpcHost}:${this.rpcPort}`],
|
||||
verbose: true
|
||||
})
|
||||
await warapper.initialize()
|
||||
this.client = await warapper.getClient()
|
||||
this.state = true
|
||||
} catch (err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getblockcount(){
|
||||
if(!this.state){
|
||||
console.log("节点未初始化");
|
||||
return
|
||||
}
|
||||
try{
|
||||
const data = await this.client.getBlockDagInfo()
|
||||
const {virtualDaaScore} = data
|
||||
return Number(virtualDaaScore)
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Number} start_height
|
||||
* @param {String} REPORT_ADDRESS
|
||||
* @returns
|
||||
*
|
||||
{
|
||||
address: 'entropyx:qpe7q43ajzuv42az6kjdu84yrzdhzj33w03kpytfg5lx5yk3x0pd6x0r87hhr',
|
||||
outpoint: {
|
||||
transactionId: '9341ecd11e3cccb06ff09ad8f604aee80f9777aafa0f8858a5501c2736b31cb2',
|
||||
index: 0
|
||||
},
|
||||
utxoEntry: {
|
||||
amount: '50000000000',
|
||||
scriptPublicKey: {
|
||||
version: 0,
|
||||
scriptPublicKey: '2073e0563d90b8caaba2d5a4de1ea4189b714a3173e3609169453e6a12d133c2ddac'
|
||||
},
|
||||
blockDaaScore: '2776996',
|
||||
isCoinbase: false
|
||||
}
|
||||
}
|
||||
*/
|
||||
async getTransactions(start_height, REPORT_ADDRESS){
|
||||
if(!this.state){
|
||||
console.log("节点未初始化");
|
||||
return
|
||||
}
|
||||
try{
|
||||
const {entries} = await this.client.getUtxosByAddresses({addresses:[REPORT_ADDRESS]})
|
||||
const blocks = []
|
||||
for(let item of entries){
|
||||
if(Number(item.utxoEntry.blockDaaScore) > start_height && item.utxoEntry.isCoinbase){
|
||||
blocks.push({
|
||||
height: Number(item.utxoEntry.blockDaaScore),
|
||||
amount: Number(item.utxoEntry.amount) / 10 ** 8,
|
||||
})
|
||||
}
|
||||
}
|
||||
return blocks
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class HttpNode {
|
||||
constructor(NODE_OPTION){
|
||||
const { rpcHost, rpcPassword, rpcPort } = NODE_OPTION;
|
||||
this.host = rpcHost
|
||||
this.prot = rpcPort
|
||||
this.api_key = rpcPassword
|
||||
}
|
||||
|
||||
async Get(api = "", params = {}){
|
||||
const url = `http://${this.host}:${this.prot}/${api}`
|
||||
try{
|
||||
const res = await axios.get(url, { params, headers: { "x-api-key": this.api_key } })
|
||||
return res.data
|
||||
} catch (err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async Post(api = "", params = {}){
|
||||
const url = `http://${this.host}:${this.prot}/${api}`
|
||||
try{
|
||||
const res = await axios.post(url, params, { headers: { "x-api-key": this.api_key } })
|
||||
return res.data
|
||||
} catch (err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ALPHRPCNode extends HttpNode {
|
||||
constructor(NODE_OPTION) {
|
||||
super(NODE_OPTION);
|
||||
}
|
||||
|
||||
async getblockcount(){
|
||||
try{
|
||||
const result = await this.Get("blockflow/chain-info", {fromGroup:0, toGroup:0})
|
||||
return result.currentHeight
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getblockhash(height){
|
||||
try{
|
||||
const result = await this.Get("blockflow/hashes", {fromGroup:0, toGroup:0, height})
|
||||
return result.headers[0]
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getblock(block_hash){
|
||||
try{
|
||||
const result = await this.Get(`blockflow/blocks/${block_hash}`)
|
||||
return result
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async verify_block(height, REPORT_ADDRESS){
|
||||
try{
|
||||
const block_hash = await this.getblockhash(height)
|
||||
const block = await this.getblock(block_hash)
|
||||
const {transactions, timestamp} = block
|
||||
const coinbaseTx = transactions[transactions.length - 1].unsigned
|
||||
if (coinbaseTx.inputs.length === 0) {
|
||||
const {fixedOutputs} = coinbaseTx
|
||||
if(fixedOutputs.length === 1){
|
||||
const {attoAlphAmount, address } = fixedOutputs[0]
|
||||
if(address === REPORT_ADDRESS){
|
||||
return {height, hash: block_hash, time:Math.trunc(timestamp / 1000), block_reward: Number(attoAlphAmount), block_fees: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
block(data){
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
NEXARPCNode,
|
||||
GRSRPCNode,
|
||||
MONARPCNode,
|
||||
DGBRPCNode,
|
||||
RXDRPCNode,
|
||||
ENXNode,
|
||||
ALPHRPCNode
|
||||
};
|
||||
26
lib/redis.js
Normal file
26
lib/redis.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const Redis = require("ioredis");
|
||||
|
||||
class Cache {
|
||||
constructor(options) {
|
||||
this.redis = new Redis(options);
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
try {
|
||||
await this.redis.set(key, value);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
try {
|
||||
const data = await this.redis.get(key);
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Cache;
|
||||
1
m2pool_backend_app
Submodule
1
m2pool_backend_app
Submodule
Submodule m2pool_backend_app added at d880f559f8
28
public/baseResponse.js
Normal file
28
public/baseResponse.js
Normal file
@@ -0,0 +1,28 @@
|
||||
class SuccessResponse {
|
||||
constructor(data) {
|
||||
if (data) {
|
||||
this.code = 0;
|
||||
this.msg = "Success";
|
||||
this.data = data;
|
||||
} else {
|
||||
this.code = 0;
|
||||
this.msg = "Success";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorResponse {
|
||||
constructor(data) {
|
||||
if (data) {
|
||||
this.code = -1;
|
||||
this.msg = "Error";
|
||||
this.data = data;
|
||||
} else {
|
||||
this.code = -1;
|
||||
this.msg = "Error";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SuccessResponse, ErrorResponse };
|
||||
|
||||
10
public/distribution-hashrate.js
Normal file
10
public/distribution-hashrate.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 计算算力占比
|
||||
* @param {Array} list [{user:"user1", mhs24h:100}, {user:"user2", mhs24h:320}]
|
||||
* @returns
|
||||
*/
|
||||
module.exports = (list) => {
|
||||
const total_hashrate = list.reduce((sum, user) => sum + user.mhs24h, 0);
|
||||
const users_weight = list.map((user) => ({ user: user.user, mhs24h: user.mhs24h, weight: user.mhs24h / total_hashrate }));
|
||||
return users_weight
|
||||
};
|
||||
49
public/distribution-shares.js
Normal file
49
public/distribution-shares.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 截取小数点后dots位
|
||||
* @param {Number} number 要截取的小数
|
||||
* @param {Number} dots 小数点位数
|
||||
* @returns
|
||||
*/
|
||||
function truncateToTenDecimals(number, dots) {
|
||||
const str = number.toString();
|
||||
const decimalIndex = str.indexOf(".");
|
||||
// 如果没有小数部分,直接返回原始数值
|
||||
if (decimalIndex === -1) {
|
||||
return str;
|
||||
}
|
||||
// 截取到小数点后 10 位
|
||||
const truncatedStr = str.slice(0, decimalIndex + dots + 1);
|
||||
return Number(truncatedStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据shares队列计算每个user本轮次的分配占比
|
||||
* @param {Array} data [{user:"user1", pool_diff:100, miner_diff:100}, ]
|
||||
* @returns {"1x1":{totalWeight:10, weightRatio:0.1}}
|
||||
*/
|
||||
function calculate_shares_weight(data) {
|
||||
// Step 1: 按照 user 分类计算每个 user 的总 weight
|
||||
const userWeights = data.reduce((acc, item) => {
|
||||
const weight = item.miner_diff / item.pool_diff;
|
||||
if (!acc[item.user]) {
|
||||
acc[item.user] = { totalWeight: 0, data: [] };
|
||||
}
|
||||
acc[item.user].totalWeight += weight;
|
||||
acc[item.user].data.push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Step 2: 计算所有 user 的总 weight
|
||||
const totalWeight = Object.values(userWeights).reduce((acc, user) => acc + user.totalWeight, 0);
|
||||
|
||||
// Step 3: 计算每个 user 的 weight 占总 weight 的比重并返回所需格式
|
||||
const userWeightRatios = Object.keys(userWeights).reduce((acc, user) => {
|
||||
const weightRatio = truncateToTenDecimals(userWeights[user].totalWeight / totalWeight, 10);
|
||||
acc[user] = Number(weightRatio);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return userWeightRatios;
|
||||
}
|
||||
module.exports = { calculate_shares_weight, truncateToTenDecimals };
|
||||
|
||||
7
public/endian.js
Normal file
7
public/endian.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const changeEndian = (hex) =>{
|
||||
const buffer = Buffer.from(hex, 'hex');
|
||||
const endian = Buffer.from(buffer.reverse())
|
||||
const result = endian.toString("hex")
|
||||
return result
|
||||
}
|
||||
module.exports = changeEndian
|
||||
513
public/index.sql
Normal file
513
public/index.sql
Normal file
@@ -0,0 +1,513 @@
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS nexa_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nexa_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nexa_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS grs_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS grs_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS grs_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS mona_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mona_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mona_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS dgbs_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbs_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbs_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS dgbq_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbq_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbq_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS dgbo_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbo_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbo_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nexa_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mona_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS grs_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbq_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbo_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dgbs_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS rxd_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rxd_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rxd_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rxd_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS enx_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS enx_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS enx_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS enx_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(18,8) NOT NULL,
|
||||
fees DECIMAL(18,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
-- 矿工历史算力表
|
||||
CREATE TABLE IF NOT EXISTS alph_mhsv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alph_mhs_realv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
mhs30m DECIMAL(32, 6) NOT NULL,
|
||||
mhs24h DECIMAL(32, 6) NOT NULL,
|
||||
state VARCHAR(15) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alph_minersv2(
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
user VARCHAR(64) NOT NULL,
|
||||
miner VARCHAR(64) NOT NULL,
|
||||
date DATETIME NOT NULL,
|
||||
accepts DECIMAL(16,8) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL,
|
||||
last_submit DATETIME NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alph_blkreportprofitv2(
|
||||
date DATETIME NOT NULL,
|
||||
height INT NOT NULL PRIMARY KEY,
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
reward DECIMAL(32,8) NOT NULL,
|
||||
fees DECIMAL(32,8),
|
||||
state TINYINT NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `alph_pool_blkstats` (
|
||||
`id` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME NOT NULL,
|
||||
`height` INT(10),
|
||||
`hash` VARCHAR(128),
|
||||
`pow` VARCHAR(128),
|
||||
`net_target` VARCHAR(128),
|
||||
`submit` VARCHAR(64),
|
||||
`success` TINYINT(1),
|
||||
`accepts` DECIMAL(32,6),
|
||||
`rejects` DECIMAL(32,6),
|
||||
`reward` DECIMAL(32,6),
|
||||
`fee` DECIMAL(32,6),
|
||||
`nonce` VARCHAR(64),
|
||||
`subidx` INT(10),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `alph_miners` (
|
||||
`id` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME NOT NULL,
|
||||
`fromip` VARCHAR(64),
|
||||
`state` VARCHAR(64),
|
||||
`online` DATETIME,
|
||||
`offline` DATETIME,
|
||||
`retry` INT(10),
|
||||
`duration` DECIMAL(12,6),
|
||||
`protocol` VARCHAR(64),
|
||||
`user` VARCHAR(128),
|
||||
`miner` VARCHAR(128),
|
||||
`refindex` VARCHAR(128),
|
||||
`diff` DECIMAL(32,6),
|
||||
`height` INT(10),
|
||||
`accepts` DECIMAL(32,6),
|
||||
`rejects` DECIMAL(32,6),
|
||||
`ratio` DECIMAL(32,6),
|
||||
`staleds` DECIMAL(32,6),
|
||||
`lows` DECIMAL(32,6),
|
||||
`duplicates` DECIMAL(32,6),
|
||||
`formats` DECIMAL(32,6),
|
||||
`others` DECIMAL(32,6),
|
||||
`is_disabled` TINYINT(1),
|
||||
`last_submit` DATETIME,
|
||||
`submits` INT(10),
|
||||
`blocks` INT(10),
|
||||
`orphans` INT(10),
|
||||
`orphan_ratio` DECIMAL(32,6),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `alph_miners_stats` (
|
||||
`id` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME NOT NULL,
|
||||
`user` VARCHAR(128),
|
||||
`miner` VARCHAR(128),
|
||||
`refindex` VARCHAR(128),
|
||||
`shares5m` DECIMAL(32,6),
|
||||
`shares15m` DECIMAL(32,6),
|
||||
`shares30m` DECIMAL(32,6),
|
||||
`shares1h` DECIMAL(32,6),
|
||||
`shares3h` DECIMAL(32,6),
|
||||
`shares6h` DECIMAL(32,6),
|
||||
`shares12h` DECIMAL(32,6),
|
||||
`shares24h` DECIMAL(32,6),
|
||||
`shares48h` DECIMAL(32,6),
|
||||
`rejects5m` DECIMAL(32,6),
|
||||
`rejects15m` DECIMAL(32,6),
|
||||
`rejects30m` DECIMAL(32,6),
|
||||
`rejects1h` DECIMAL(32,6),
|
||||
`rejects3h` DECIMAL(32,6),
|
||||
`rejects6h` DECIMAL(32,6),
|
||||
`rejects12h` DECIMAL(32,6),
|
||||
`rejects24h` DECIMAL(32,6),
|
||||
`rejects48h` DECIMAL(32,6),
|
||||
`mhs5m` DECIMAL(32,6),
|
||||
`mhs15m` DECIMAL(32,6),
|
||||
`mhs30m` DECIMAL(32,6),
|
||||
`mhs1h` DECIMAL(32,6),
|
||||
`mhs3h` DECIMAL(32,6),
|
||||
`mhs6h` DECIMAL(32,6),
|
||||
`mhs12h` DECIMAL(32,6),
|
||||
`mhs24h` DECIMAL(32,6),
|
||||
`mhs48h` DECIMAL(32,6),
|
||||
`ratio5m` DECIMAL(32,6),
|
||||
`ratio15m` DECIMAL(32,6),
|
||||
`ratio30m` DECIMAL(32,6),
|
||||
`ratio1h` DECIMAL(32,6),
|
||||
`ratio3h` DECIMAL(32,6),
|
||||
`ratio6h` DECIMAL(32,6),
|
||||
`ratio12h` DECIMAL(32,6),
|
||||
`ratio24h` DECIMAL(32,6),
|
||||
`ratio48h` DECIMAL(32,6),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `alph_blk_height_detail` (
|
||||
`id` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME NOT NULL,
|
||||
`from` INT(10),
|
||||
`to` INT(10),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `alph_blk_detail` (
|
||||
`id` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME NOT NULL,
|
||||
`height` INT(10),
|
||||
`hash` VARCHAR(128),
|
||||
`user` VARCHAR(128),
|
||||
`miner` VARCHAR(128),
|
||||
`refindex` VARCHAR(128),
|
||||
`success` TINYINT(1),
|
||||
`miner_diff` DECIMAL(32,6),
|
||||
`pool_diff` DECIMAL(32,6),
|
||||
`nonce` VARCHAR(64),
|
||||
`subidx` INT(10),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `alph_blk_new` (
|
||||
`id` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME NOT NULL,
|
||||
`height` INT(10),
|
||||
`hash` VARCHAR(128),
|
||||
`success` TINYINT(1),
|
||||
`nonce` VARCHAR(64),
|
||||
`subidx` INT(10),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
33
public/retry.js
Normal file
33
public/retry.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 异步任务重试器
|
||||
* @param {Function} task 异步任务
|
||||
* @param {Number} maxRetries 最大重试次数
|
||||
* @param {Number} delay 重试间隔(秒)
|
||||
* @returns
|
||||
*/
|
||||
async function executeWithRetry(task, maxRetries, delay) {
|
||||
let attempts = 0;
|
||||
|
||||
while (attempts < maxRetries) {
|
||||
try {
|
||||
// 尝试执行异步任务
|
||||
const result = await task();
|
||||
// console.log("任务成功");
|
||||
return result; // 成功时返回结果
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
console.error(`尝试 ${attempts} 失败:`, error.message);
|
||||
|
||||
if (attempts >= maxRetries) {
|
||||
console.error("已达最大重试次数,任务失败。");
|
||||
throw error; // 达到最大重试次数时抛出错误
|
||||
}
|
||||
|
||||
console.log(`等待 ${delay} 秒后重试...`);
|
||||
// 等待指定时间后再重试
|
||||
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = executeWithRetry
|
||||
48
public/score.js
Normal file
48
public/score.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// 计算标准差
|
||||
function caculate_standar_deviation(data) {
|
||||
// 计算数学期望(均值)
|
||||
const calculateMean = (values) => {
|
||||
const total = values.reduce((acc, value) => acc + parseFloat(value), 0);
|
||||
return total / values.length;
|
||||
};
|
||||
|
||||
// 计算标准差
|
||||
const calculateStandardDeviation = (values, mean) => {
|
||||
const variance = values.reduce((acc, value) => acc + Math.pow(parseFloat(value) - mean, 2), 0) / values.length;
|
||||
return Math.sqrt(variance);
|
||||
};
|
||||
|
||||
// 计算每个用户的标准差
|
||||
const results = Object.keys(data).reduce((acc, user) => {
|
||||
const values = data[user];
|
||||
const mean = calculateMean(values);
|
||||
const stddev = calculateStandardDeviation(values, mean);
|
||||
acc[user] = stddev;
|
||||
return acc;
|
||||
}, {});
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算每个用户最终得分,满分为100分
|
||||
* @param {Array} alluser_mhs24h 每个用户过去24小时平均算力
|
||||
* @param {Number} hash_percent 24小时平均算力权重
|
||||
* @returns 每个用户最终得分
|
||||
*/
|
||||
function score(alluser_mhs24h, hash_percent = 1) {
|
||||
// 提取 mhs24h 数值
|
||||
const hashrateValues = alluser_mhs24h.map((obj) => obj.mhs24h);
|
||||
|
||||
// 计算总和
|
||||
const totalHashrate = hashrateValues.reduce((sum, value) => sum + value, 0);
|
||||
|
||||
const result = {};
|
||||
|
||||
// 计算每个用户的算力占比
|
||||
for (let { user, mhs24h } of alluser_mhs24h) {
|
||||
result[user] = (mhs24h / totalHashrate) * hash_percent;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
module.exports = { caculate_standar_deviation, score };
|
||||
52
public/times.js
Normal file
52
public/times.js
Normal file
@@ -0,0 +1,52 @@
|
||||
class Times {
|
||||
static bjTime(dateForm) {
|
||||
if (dateForm === "") {
|
||||
//解决deteForm为空传1970-01-01 00:00:00
|
||||
return "";
|
||||
} else {
|
||||
var dateee = new Date(dateForm).toJSON();
|
||||
var date = new Date(+new Date(dateee) + 8 * 3600 * 1000)
|
||||
.toISOString()
|
||||
.replace(/T/g, " ")
|
||||
.replace(/\.[\d]{3}Z/, "");
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
static utcTime(dateForm) {
|
||||
if (dateForm === "") {
|
||||
//解决deteForm为空传1970-01-01 00:00:00
|
||||
return "";
|
||||
} else {
|
||||
var dateee = new Date(dateForm).toJSON();
|
||||
var date = new Date(+new Date(dateee))
|
||||
.toISOString()
|
||||
.replace(/T/g, " ")
|
||||
.replace(/\.[\d]{3}Z/, "");
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
static times() {
|
||||
const date = new Date();
|
||||
const y = date.getFullYear();
|
||||
const M = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const h = String(date.getHours()).padStart(2, '0');
|
||||
const m = String(date.getMinutes()).padStart(2, '0');
|
||||
const s = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return [
|
||||
`${y}-${M}-${d} ${h}:${m}`, // 格式:YYYY-MM-DD HH:mm
|
||||
`${y}-${M}-${d} ${h}:${m}:${s}`, // 格式:YYYY-MM-DD HH:mm:ss
|
||||
`${y}-${M}-${d}`, // 格式:YYYY-MM-DD
|
||||
`${m}`, // 分钟格式:mm
|
||||
`${h}`, // 小时格式:HH
|
||||
`${d}` // 日期格式:DD
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Times;
|
||||
|
||||
239
src/blanace.js
Normal file
239
src/blanace.js
Normal file
@@ -0,0 +1,239 @@
|
||||
const Times = require("../public/times");
|
||||
// const executeWithRetry = require("./public/retry")
|
||||
const Init = require("./init");
|
||||
|
||||
class Balance extends Init {
|
||||
constructor(coin) {
|
||||
const method = "balance";
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
async query_min_height() {
|
||||
try {
|
||||
const sql = `select MIN(max_height) AS min_height from wallet_in where coin = ? and state = ?; `;
|
||||
const result = await this.distribution.exec(sql, [this.coin, 0]);
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_transaction_state(height) {
|
||||
try {
|
||||
const sql = `SELECT * FROM sendinfo WHERE coin = ? AND height >= ?;`;
|
||||
const result = await this.balancedb.exec(sql, [this.coin, height]);
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_now_height(last_height){
|
||||
try{
|
||||
const [chain_height, [db_height]] = await Promise.all([this.node.getblockcount(), this.query_min_height()])
|
||||
return chain_height > db_height.min_height + this.MAX_MATURE && chain_height > last_height + 2// 在成熟高度基础上再+2高度,防止pool_account转账未更新
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async update_wallet_in(ids) {
|
||||
try {
|
||||
const sql = `UPDATE wallet_in SET state = 1 WHERE id IN (?);`;
|
||||
await this.distribution.exec_transaction(sql, [ids]);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_wallet_out_AND_update_wallet_in(data) {
|
||||
try {
|
||||
let sql = `INSERT INTO wallet_outv2(coin, user, address, date, max_height, tx_id, amount, tx_fee) VALUES `;
|
||||
const values = [];
|
||||
let id = [];
|
||||
data.forEach((item) => {
|
||||
const { username, qty, fee, txid, time, userid, height, ids } = item;
|
||||
if (txid) {
|
||||
id = id.concat(ids.split(","));
|
||||
values.push(this.coin, userid, username, Times.utcTime(time * 1000), height, txid, qty, fee);
|
||||
sql += `(?,?,?,?,?,?,?,?), `;
|
||||
}
|
||||
});
|
||||
if (values.length === 0) {
|
||||
console.log(`${Date.now()}: ${this.coin}无新增转账`);
|
||||
return;
|
||||
}
|
||||
sql = sql.slice(0, -2);
|
||||
const wallet_in_sql = `UPDATE wallet_in SET state = 1 WHERE id IN (?);`;
|
||||
await Promise.all([this.distribution.exec_transaction(sql, values)], this.distribution.exec_transaction(wallet_in_sql, [id]));
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async main() {
|
||||
try {
|
||||
const min_height = await this.query_min_height();
|
||||
if (min_height.length === 0 || !min_height[0].min_height) {
|
||||
console.log(`${Times.bjTime(Date.now().valueOf())}: ${this.coin}无需要更新的数据`);
|
||||
return true;
|
||||
}
|
||||
const need_update_data = await this.query_transaction_state(min_height[0].min_height);
|
||||
if (need_update_data.length === 0) {
|
||||
console.log(`${Times.bjTime(Date.now().valueOf())}: ${this.coin}钱包暂无转账信息`);
|
||||
// await executeWithRetry(this.main(), 12, 60 * 5);
|
||||
return true;
|
||||
}
|
||||
await this.insert_wallet_out_AND_update_wallet_in(need_update_data);
|
||||
return false
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DGBBlance extends Init {
|
||||
constructor(coin) {
|
||||
const method = "balance";
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
async query_min_height() {
|
||||
try {
|
||||
const sql = `select MIN(max_height) AS min_height from wallet_in where coin like "dgb%" and state = ?; `;
|
||||
const result = await this.distribution.exec(sql, [0]);
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async check_height() {
|
||||
try {
|
||||
const my_sql = `
|
||||
SELECT height FROM dgbs_blkreportprofitv2 WHERE date >= "2024-11-26 00:00:00" and date < "2024-11-27 00:00:00"
|
||||
UNION
|
||||
SELECT height FROM dgbo_blkreportprofitv2 WHERE date >= "2024-11-26 00:00:00" and date < "2024-11-27 00:00:00"
|
||||
UNION
|
||||
SELECT height FROM dgbq_blkreportprofitv2 WHERE date >= "2024-11-26 00:00:00" and date < "2024-11-27 00:00:00";`;
|
||||
const balance_sql = `SELECT height FROM balanceinfo WHERE height > 20402916 AND height <= 20408691;`;
|
||||
const [my_data, balance_data] = await Promise.all([this.distribution.exec(my_sql), this.balancedb.exec(balance_sql)]);
|
||||
const my_result = [];
|
||||
const balance_result = [];
|
||||
for (let item of my_data) {
|
||||
my_result.push(item.height);
|
||||
}
|
||||
for (let item of balance_data) {
|
||||
balance_result.push(item.height);
|
||||
}
|
||||
// 找出 array1 中不在 array2 的元素
|
||||
const onlyInArray1 = my_result.filter((item) => !balance_result.includes(item));
|
||||
|
||||
// 找出 array2 中不在 array1 的元素
|
||||
const onlyInArray2 = balance_result.filter((item) => !my_result.includes(item));
|
||||
|
||||
// 合并结果
|
||||
const difference = [...onlyInArray1, ...onlyInArray2];
|
||||
return difference;
|
||||
console.log(difference); // 输出: [1, 2, 5, 6]
|
||||
// for(let item of difference){
|
||||
// if(!my_result.includes(item)){
|
||||
// console.log(`${item}不在池子报块中`);
|
||||
// } else {
|
||||
// console.log(`${item}不在钱包报块中`);
|
||||
// }
|
||||
// }
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_now_height(last_height){
|
||||
try{
|
||||
const sql = `SELECT MAX(max_height) AS max_height FROM wallet_in WHERE coin Like "dgb%" AND state = ?;`
|
||||
const [chain_height, [db_height]] = await Promise.all([this.node.getblockcount(), this.distribution.exec(sql, [0])])
|
||||
return chain_height > db_height.max_height + this.MAX_MATURE && chain_height > last_height + 2
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async query_transaction_state(height) {
|
||||
try {
|
||||
const sql = `SELECT * FROM sendinfo WHERE coin LIKE "dgb%" AND height >= ?;`;
|
||||
const result = await this.balancedb.exec(sql, [height]);
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_coin(id) {
|
||||
try {
|
||||
const sql = `SELECT coin FROM wallet_in WHERE id = ?;`;
|
||||
const result = await this.distribution.exec(sql, [id]);
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async update_wallet_in(ids) {
|
||||
try {
|
||||
const sql = `UPDATE wallet_in SET state = 1 WHERE id IN (?);`;
|
||||
await this.distribution.exec_transaction(sql, [ids]);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_wallet_out_AND_update_wallet_in(data) {
|
||||
try {
|
||||
let sql = `INSERT INTO wallet_outv2(coin, user, address, date, max_height, tx_id, amount, tx_fee) VALUES `;
|
||||
const values = [];
|
||||
let id = [];
|
||||
data.forEach((item) => {
|
||||
const { coin, username, qty, fee, txid, time, userid, height, ids } = item;
|
||||
if (txid) {
|
||||
id = id.concat(ids.split(","));
|
||||
values.push(coin, userid, username, Times.utcTime(time * 1000), height, txid, qty, fee);
|
||||
sql += `(?,?,?,?,?,?,?,?), `;
|
||||
}
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
const wallet_in_sql = `UPDATE wallet_in SET state = 1 WHERE id IN (?);`;
|
||||
await Promise.all([this.distribution.exec_transaction(wallet_in_sql, [id]), this.distribution.exec_transaction(sql, values)]);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async main() {
|
||||
try {
|
||||
const min_height = await this.query_min_height();
|
||||
if (min_height.length === 0 || !min_height[0].min_height) {
|
||||
console.log(`${Date.now()}: dgb无需要更新的数据`);
|
||||
return true
|
||||
}
|
||||
const need_update_data = await this.query_transaction_state(min_height[0].min_height);
|
||||
if (need_update_data.length === 0) {
|
||||
console.log(`${Date.now()}: dgb转账未完成`);
|
||||
return true
|
||||
}
|
||||
const data = [];
|
||||
for (let item of need_update_data) {
|
||||
const { username, qty, fee, txid, time, userid, height, ids } = item;
|
||||
const coin = await this.query_coin(ids.split(",")[0]);
|
||||
data.push({ coin: coin[0].coin, username, qty, fee, txid, time, userid, height, ids });
|
||||
}
|
||||
await this.insert_wallet_out_AND_update_wallet_in(data);
|
||||
return false
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Balance, DGBBlance };
|
||||
92
src/clear.js
Normal file
92
src/clear.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const Times = require("../public/times");
|
||||
const Init = require("./init");
|
||||
|
||||
class ClearDBData extends Init {
|
||||
constructor(coin) {
|
||||
const method = "clear"
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除X小时前的sharesdb数据,hours必须大于24小时
|
||||
* @param {Number} hours
|
||||
* @returns
|
||||
*/
|
||||
async clearSharesDB(hours) {
|
||||
if(hours < 24){
|
||||
console.log(`sharesdb最多只能删除24小时之前的数据`);
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 修正查询语句,去掉 'from' 和 'to' 字段的单引号
|
||||
const query_sharesdb_table_sql = `
|
||||
SELECT \`from\`, \`to\`
|
||||
FROM ${this.coin}_blk_height_detail
|
||||
WHERE date <= NOW() - INTERVAL ? HOUR;
|
||||
`;
|
||||
|
||||
// 获取需要删除的高度范围
|
||||
const need_del_height = await this.sharesdb.exec(query_sharesdb_table_sql, [hours]);
|
||||
if (need_del_height.length === 0) {
|
||||
console.log(`${Date.now()}:${this.coin}暂无需要清理的shares detail`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成删除表的 SQL 语句
|
||||
let delete_sql = `DROP TABLE IF EXISTS `;
|
||||
need_del_height.forEach((item) => {
|
||||
const { from, to } = item;
|
||||
const table = `${this.coin}_block_detail_${from}_${Number((to - 1).toFixed(0))}`;
|
||||
delete_sql += `${table}, `;
|
||||
});
|
||||
delete_sql = delete_sql.slice(0, -2) + ";"; // 去掉最后的逗号并加上分号
|
||||
// 执行删除表的 SQL 语句
|
||||
await this.sharesdb.exec(delete_sql);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除pooldb,只支持0时刻
|
||||
* @param {*} start_time 开始时间 yyyy-MM-dd hh-mm-ss格式 例如"2024-11-11 00:00:00"
|
||||
* @param {*} end_time 同上
|
||||
* @returns
|
||||
*/
|
||||
async clearPoolDB(start_time, end_time){
|
||||
try{
|
||||
const interval = 86400000 // 1天的毫秒数
|
||||
const start_ts = new Date(start_time).valueOf()
|
||||
const end_ts = new Date(end_time).valueOf()
|
||||
const count = Number((end_ts - start_ts).toFixed(0)) / interval
|
||||
if (!Number.isInteger(count)){
|
||||
console.log(`给定的${start_time}和${end_time}有误!`);
|
||||
return
|
||||
}
|
||||
let sql = `DROP TABLE IF EXISTS `
|
||||
for(let i=0; i<count; i++){
|
||||
const table_time = Times.utcTime(start_ts + i * interval).split(" ")[0].replace(/\-/g, "")
|
||||
sql += `${this.coin}_miners_${table_time}, ${this.coin}_miners_stats_${table_time}, ${this.coin}_pool_blkstats_${table_time}, `
|
||||
}
|
||||
sql = sql.slice(0, -2) + ";"
|
||||
console.log(sql);
|
||||
return
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除hashrate db的xx_miners表
|
||||
*/
|
||||
async clearHashrateDB(){
|
||||
try{
|
||||
const sql = `DELETE FROM ${this.coin}_minersv2 WHERE date <= NOW() - INTERVAL 25 HOUR;`
|
||||
await this.hashratedb.exec_transaction(sql)
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClearDBData
|
||||
119
src/confirm.js
Normal file
119
src/confirm.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const Init = require("./init");
|
||||
|
||||
class Confirm extends Init {
|
||||
constructor(coin) {
|
||||
const method = "confirm";
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
isPositiveInteger(num) {
|
||||
return Number.isInteger(num) && num > 0;
|
||||
}
|
||||
|
||||
// 查询当前所有满足查验要求的报块,即 <= 最高成熟高度 且 状态为待定(0)的报块
|
||||
async query_need_update_data(mature_height) {
|
||||
try {
|
||||
const sql = `SELECT height FROM ${this.coin}_blkreportprofitv2 WHERE height <= ? AND state = ?;`;
|
||||
const data = await this.distribution.exec(sql, [mature_height, 0]);
|
||||
if (!data || data.length === 0) {
|
||||
console.log(`${mature_height}高度之前暂无需要更新的报块`);
|
||||
return false;
|
||||
}
|
||||
return data.map(item => item.height);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询当前最高成熟高度,即当前最新高度 - MAX_MATURE
|
||||
async query_maxture_height() {
|
||||
try {
|
||||
const now_height = await this.node.getblockcount();
|
||||
const max_height = Number(now_height) - this.MAX_MATURE;
|
||||
if (!this.isPositiveInteger(max_height)) {
|
||||
console.log(`当前节点最大高度为${now_height}, 当前成熟高度为${max_height}`);
|
||||
return false;
|
||||
} else {
|
||||
return max_height;
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 通过报块地址,校验高度是否为本矿池报块
|
||||
async verify_block(height) {
|
||||
try {
|
||||
return await this.node.verify_block(height, this.REPORT_ADDRESS);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// 根据校验通过和校验失败的高度更新blkreportprofit表,1为成功,2为失败
|
||||
async update_blkreporprofit_state(suc_heights, err_heights) {
|
||||
try {
|
||||
const sql = `UPDATE ${this.coin}_blkreportprofitv2 SET state = ? WHERE height IN (?);`;
|
||||
if (err_heights.length === 0 && suc_heights.length !== 0) {
|
||||
// 只有 suc_heights 更新
|
||||
await this.distribution.exec_transaction(sql, [1, suc_heights]);
|
||||
} else if (err_heights.length !== 0 && suc_heights.length === 0) {
|
||||
// 只有 err_heights 更新
|
||||
await this.distribution.exec_transaction(sql, [2, err_heights]);
|
||||
} else if (err_heights.length !== 0 && suc_heights.length !== 0) {
|
||||
// 同时更新 suc_heights 和 err_heights
|
||||
await Promise.all([
|
||||
this.distribution.exec_transaction(sql, [1, suc_heights]),
|
||||
this.distribution.exec_transaction(sql, [2, err_heights]),
|
||||
]);
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1,查验区块链最高成熟高度
|
||||
* 2,查验blk表中所有<=最高成熟高度 且 状态为待定(0)的区块
|
||||
* 3,遍历这些区块高度,并通过node.verify方法校验
|
||||
* 4,将校验通过(正常报块)和校验不通过(孤块)分为两组
|
||||
* 5,将正常报块组的状态改为1,将孤块组状态改为2
|
||||
* @returns
|
||||
*/
|
||||
async main() {
|
||||
try {
|
||||
const mature_max_height = await this.query_maxture_height();
|
||||
if (!mature_max_height) {
|
||||
return;
|
||||
}
|
||||
const need_update_heights = await this.query_need_update_data(mature_max_height);
|
||||
if (!need_update_heights) {
|
||||
return;
|
||||
}
|
||||
const suc_heights = [];
|
||||
const err_heights = [];
|
||||
for (let item of need_update_heights) {
|
||||
const verify_result = await this.verify_block(item);
|
||||
if (!verify_result) {
|
||||
err_heights.push(item);
|
||||
} else {
|
||||
suc_heights.push(item);
|
||||
}
|
||||
}
|
||||
if(err_heights.length === 0 && need_update_heights.length !== 0){
|
||||
console.log(`${mature_max_height}之前有新报块,且无孤块`);
|
||||
} else if(err_heights.length !== 0 && need_update_heights.length === 0){
|
||||
console.log(`${mature_max_height}之前有新报块,但这些报块都是孤块`);
|
||||
} else if(err_heights.length !== 0 && need_update_heights.length !== 0){
|
||||
console.log(`${mature_max_height}之前有新报块,且其中有孤块`);
|
||||
}
|
||||
await this.update_blkreporprofit_state(suc_heights, err_heights);
|
||||
return
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Confirm
|
||||
376
src/distribution.js
Normal file
376
src/distribution.js
Normal file
@@ -0,0 +1,376 @@
|
||||
const Times = require("../public/times");
|
||||
const Decimal = require("decimal");
|
||||
const Init = require("./init");
|
||||
|
||||
/**
|
||||
* 查询规定窗口期内的总报块奖励,查询规定窗口期各个挖矿账户24小时平均算力
|
||||
* 通过各个账户24小时平均算力,计算出各个账户的算力占比
|
||||
* 根据算力占比对总报块奖励进行分配
|
||||
* 将分配的结果写入wallet_in表
|
||||
*/
|
||||
class Distribution extends Init {
|
||||
constructor(coin) {
|
||||
const method = "distribution";
|
||||
super(coin, method);
|
||||
}
|
||||
/**
|
||||
* 计算每个用户最终得分,满分为100分
|
||||
* @param {Array} alluser_mhs24h 每个用户过去24小时平均算力
|
||||
* @param {Number} hash_percent 24小时平均算力权重
|
||||
* @returns 每个用户最终得分
|
||||
*/
|
||||
score(alluser_mhs24h, hash_percent = 1) {
|
||||
// 提取 mhs24h 数值
|
||||
const hashrateValues = alluser_mhs24h.map((obj) => obj.mhs24h);
|
||||
// 计算总和
|
||||
const totalHashrate = hashrateValues.reduce((sum, value) => sum + value, 0);
|
||||
const result = {};
|
||||
// 计算每个用户的算力占比
|
||||
for (let { user, mhs24h } of alluser_mhs24h) {
|
||||
result[user] = (mhs24h / totalHashrate) * hash_percent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定日期用户的24小时总算力
|
||||
* @param {Date} date 采用yyyy-MM-dd格式(只支持0时刻),例如"2024-11-11"
|
||||
*/
|
||||
async query_last_day_hashrate(date) {
|
||||
try {
|
||||
const sql = `SELECT user, SUM(mhs24h) AS mhs24h FROM ${this.coin}_mhsv2 WHERE date = ? GROUP BY user;`;
|
||||
const data = await this.hashratedb.exec(sql, [date + " 00:00:00"]);
|
||||
if (data.length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
const result = [];
|
||||
for (let item of data) {
|
||||
const { user, mhs24h } = item;
|
||||
result.push({ user, mhs24h: Number(mhs24h) });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定日期报块总奖励
|
||||
* @param {Date} start_time 采用yyyy-MM-dd格式,例如"2024-11-11"
|
||||
* @param {Date} end_time 同上
|
||||
* @return {Number} 昨日总奖励
|
||||
*/
|
||||
async query_last_day_reward(start_time, end_time) {
|
||||
try {
|
||||
const sql = `SELECT MAX(height) AS max_height, SUM(reward) AS reward FROM ${this.coin}_blkreportprofitv2 WHERE date >= ? AND date < ? AND state = ?;`;
|
||||
const data = await this.distributiondb.exec(sql, [start_time, end_time, 1]);
|
||||
if (data.length === 0 || !data[0].reward) {
|
||||
return 0;
|
||||
} else {
|
||||
data[0].reward = Number(data[0].reward);
|
||||
return data;
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 校验昨天所有报块是否成熟
|
||||
* @param {Date} start_time 采用yyyy-MM-dd hh:mm:ss格式,例如"2024-11-11 00:00:00"
|
||||
* @param {Date} end_time 同上
|
||||
* @returns
|
||||
*/
|
||||
async query_last_day_if_mature(start_time, end_time) {
|
||||
try {
|
||||
const sql = `SELECT count(*) AS count FROM ${this.coin}_blkreportprofitv2 WHERE date >= ? AND date < ? AND state = ?;`;
|
||||
let data;
|
||||
|
||||
// 使用循环替代递归
|
||||
do {
|
||||
await this.sleep(1000 * 60 * 15); // 等待 15 分钟
|
||||
data = await this.distributiondb.exec(sql, [start_time, end_time, 0]);
|
||||
|
||||
// 动态获取当前小时
|
||||
const currentHour = Number(Times.times()[4]);
|
||||
if(this.coin === "rxd"){
|
||||
if (currentHour >= 9) {
|
||||
console.log("已超过凌晨 9 点,停止检查。");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (currentHour >= 4) {
|
||||
console.log("已超过凌晨 4 点,停止检查。");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (data[0].count !== 0);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前挖矿账户状态,包括起付额和钱包地址
|
||||
* @returns
|
||||
*/
|
||||
async query_users_address() {
|
||||
try {
|
||||
const sql = `
|
||||
SELECT
|
||||
a.miner_user AS 'user',
|
||||
b.balance AS 'address',
|
||||
b.amount AS 'amount',
|
||||
b.active AS 'state'
|
||||
FROM
|
||||
user_account_balance b
|
||||
LEFT JOIN user_miner_account a ON b.ma_id = a.id
|
||||
WHERE
|
||||
a.coin = ?
|
||||
AND b.status = 0;`;
|
||||
const data = await this.users_addresses.exec(sql, [this.coin]);
|
||||
if (!data || data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验报块
|
||||
* @param {Number} height
|
||||
* @returns
|
||||
*/
|
||||
async verify_block(height) {
|
||||
try {
|
||||
const data = await this.node.verify_block(height, this.REPORT_ADDRESS);
|
||||
if (data && typeof data === "object") {
|
||||
return this.node.block(data);
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将漏掉的报块插入到数据库中
|
||||
* @param {Array} data [{time: 1708256800, height: 123456, hash: "0x1234567890", reward: 100, fees: 10}]
|
||||
*/
|
||||
async insert_blkreportprofit(data) {
|
||||
try {
|
||||
let sql = `INSERT INTO ${this.coin}_blkreportprofitv2 (date, height, hash, reward, fees, state) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { time, height, hash, block_reward, block_fees } = item;
|
||||
sql += `(?,?,?,?,?,?), `;
|
||||
values.push(Times.utcTime(time * 1000), height, hash, block_reward, block_fees, 1);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
await this.distributiondb.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验昨日是否有漏掉报块的情况
|
||||
* @param {Date} date 当前时间
|
||||
* @returns
|
||||
*/
|
||||
async check_last_data_blk(date) {
|
||||
try {
|
||||
const ts = new Date(date).valueOf() - 86400000; // 前一天
|
||||
const yMd = Times.utcTime(ts).split(" ")[0]; // "2024-10-11"
|
||||
const ymd = yMd.split("-"); // ["2024", "10", "11"]
|
||||
const table_name = `${this.coin}_pool_blkstats_${ymd[0]}${ymd[1]}${ymd[2]}`;
|
||||
const confirm_if_table = `SHOW TABLES LIKE '${table_name}';`;
|
||||
const confirm_result = await this.pooldb.exec(confirm_if_table);
|
||||
if (!confirm_result || confirm_result.length === 0) {
|
||||
console.log(`pool_blkstats表未更新,退出本次执行,请手动校验`);
|
||||
return false;
|
||||
}
|
||||
const heights = [];
|
||||
const query_blk_pool_sql = `SELECT height FROM ${table_name} WHERE DATE(date) >= ?;`;
|
||||
const [master_data, slave_data] = await Promise.all([this.pooldb.exec(query_blk_pool_sql, [yMd]), this.pooldb_slave.exec(`SELECT height FROM ${this.coin}_pool_blkstats WHERE DATE(date) >= ?;`, [yMd])])
|
||||
const pool_data = master_data.concat(slave_data);
|
||||
for (let item of pool_data) {
|
||||
heights.push(item.height);
|
||||
}
|
||||
const blkreport_heighs = [];
|
||||
const query_blkreport_sql = `SELECT height FROM ${this.coin}_blkreportprofitv2 WHERE DATE(date) = ? AND state = ?;`;
|
||||
const blkreport_data = await this.distributiondb.exec(query_blkreport_sql, [yMd, 1]);
|
||||
for (let item of blkreport_data) {
|
||||
blkreport_heighs.push(item.height);
|
||||
}
|
||||
const setB = new Set(blkreport_heighs);
|
||||
const need_check_heights = heights.filter((item) => !setB.has(item));
|
||||
|
||||
if (need_check_heights.length === 0) {
|
||||
console.log(`${this.coin}check 完成,没有需要重新校验的区块`);
|
||||
return true;
|
||||
}
|
||||
const need_insert_data = [];
|
||||
for (let height of need_check_heights) {
|
||||
const result = await this.verify_block(height);
|
||||
if (result) {
|
||||
need_insert_data.push(result);
|
||||
}
|
||||
}
|
||||
if (need_insert_data.length === 0) {
|
||||
console.log(`${this.coin}check 完成,没有需要insert的区块`);
|
||||
return true;
|
||||
}
|
||||
await this.insert_blkreportprofit(need_insert_data);
|
||||
console.log(`${this.coin}check 完成,已将${this.coin}漏掉的报块全部插入blk表中!`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新wallet_in表中不满足起付额的用户状态
|
||||
* @param {Array} min_amount [{"user": amount}]
|
||||
* @returns
|
||||
*/
|
||||
async update_state(min_amount) {
|
||||
try {
|
||||
const sql = `SELECT user, SUM(amount) AS profit FROM wallet_in WHERE coin = ? AND state = ? GROUP BY user;`;
|
||||
const data = await this.distributiondb.exec(sql, [this.coin, 2]); // []
|
||||
if (!data || data.length === 0) {
|
||||
return
|
||||
}
|
||||
const need_update_state = [];
|
||||
for (let item of data) {
|
||||
if (item.profit >= min_amount[item.user]) {
|
||||
need_update_state.push(item.user);
|
||||
}
|
||||
}
|
||||
if (need_update_state.length === 0) {
|
||||
console.log(`${this.coin}无需要更新状态的用户(2->0)`);
|
||||
return;
|
||||
}
|
||||
const values = [];
|
||||
let update_sql = `UPDATE wallet_in SET state = ? WHERE coin = ? AND user = ?;`;
|
||||
need_update_state.forEach(async (item) => {
|
||||
const { user } = item;
|
||||
update_sql += `(?, ?, ?), `;
|
||||
values.push(0, this.coin, user);
|
||||
});
|
||||
update_sql = update_sql.slice(0, -2);
|
||||
await this.distributiondb.exec_transaction(update_sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将最终分配数据插入wallet_in表中
|
||||
* @param {Array} data [{coin, user, address, create_date, should_out_date, max_height, amount, state}]
|
||||
*/
|
||||
async insert_wallet_in(data) {
|
||||
try {
|
||||
let sql = `INSERT INTO wallet_in(coin, user, address, create_date, should_out_date, max_height, amount, state) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { coin, user, address, create_date, should_out_date, max_height, amount, state } = item;
|
||||
values.push(coin, user, address, create_date, should_out_date, max_height, amount, state);
|
||||
sql += `(?,?,?,?,?,?,?,?), `;
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
await this.distributiondb.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async main(start_time, end_time) {
|
||||
try {
|
||||
const _if = await this.query_last_day_if_mature(start_time, end_time);
|
||||
if (!_if) {
|
||||
return;
|
||||
}
|
||||
const check_result = await this.check_last_data_blk(end_time)
|
||||
if (!check_result) {
|
||||
return;
|
||||
}
|
||||
const [last_day_mhs24h, last_day_reward, users_address] = await Promise.all([this.query_last_day_hashrate(end_time), this.query_last_day_reward(start_time, end_time), this.query_users_address()]);
|
||||
if (!last_day_mhs24h || !last_day_reward || !users_address || !check_result) {
|
||||
console.log(`查询错误`);
|
||||
console.log("last_day_mhs24h:", last_day_mhs24h);
|
||||
console.log("last_day_reward:", last_day_reward);
|
||||
console.log("users_address:", users_address);
|
||||
return;
|
||||
}
|
||||
const min_amount = {};
|
||||
const score_ratio = this.score(last_day_mhs24h, 1);
|
||||
const reward = Number(last_day_reward[0].reward) * (1 - this.POOL_FEE);
|
||||
const max_height = last_day_reward[0].max_height;
|
||||
let should_out_date; // 实际转账时间
|
||||
let accuracy; // user保留小数位数,100为2位,以此类推
|
||||
let count // pool_account 保留小数位数
|
||||
if (this.coin === "nexa") {
|
||||
should_out_date = Times.utcTime(new Date(end_time).valueOf() + 1000 * 60 * 60 * 24 * 7);
|
||||
accuracy = 100;
|
||||
count = 2
|
||||
} else if (this.coin === "rxd") {
|
||||
accuracy = 100;
|
||||
should_out_date = end_time;
|
||||
count = 2
|
||||
} else {
|
||||
should_out_date = end_time;
|
||||
accuracy = 100000000;
|
||||
count = 8
|
||||
}
|
||||
let user_profit = 0;
|
||||
const result = [];
|
||||
let pool_account_address;
|
||||
for (let user in score_ratio) {
|
||||
const profit = Math.floor(score_ratio[user] * reward * accuracy) / accuracy;
|
||||
if(profit === 0){
|
||||
continue
|
||||
}
|
||||
user_profit += profit;
|
||||
for (let item of users_address) {
|
||||
if (item.user === "pool_account") {
|
||||
pool_account_address = item.address;
|
||||
}
|
||||
if (user === item.user) {
|
||||
min_amount[item.user] = item.min_amount; // 今天有变动的账户才会更新最小提现金额
|
||||
let state;
|
||||
if (profit >= item.amount && item.state === 0) {
|
||||
// 账号激活自动提现且当天收益满足起付额,state用0
|
||||
state = 0;
|
||||
} else if (profit < item.amount && item.state === 0) {
|
||||
// 账号激活自动提现但当天收益不满足起付额,state用2
|
||||
state = 2;
|
||||
} else if (profit >= item.amount && item.state === 1) {
|
||||
// 账号未激活自动提现但当天收益满足起付额,state用3
|
||||
state = 3;
|
||||
} else {
|
||||
// 账号未激活自动提现且当天收益不满足起付额,state用4
|
||||
state = 4;
|
||||
}
|
||||
result.push({ coin: this.coin, user, address: item.address, create_date: end_time, should_out_date, max_height, amount: profit, state });
|
||||
}
|
||||
}
|
||||
}
|
||||
const num1 = new Decimal(last_day_reward[0].reward);
|
||||
const num2 = new Decimal(user_profit);
|
||||
const pool_account_amount = num1.sub(num2).toString();
|
||||
result.push({ coin: this.coin, user: "pool_account", address: pool_account_address, create_date: end_time, should_out_date, max_height, amount: Number(Number(pool_account_amount).toFixed(count)), state: 0 });
|
||||
console.log(result);
|
||||
await this.insert_wallet_in(result);
|
||||
await this.update_state(min_amount);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Distribution;
|
||||
366
src/hashrate.js
Normal file
366
src/hashrate.js
Normal file
@@ -0,0 +1,366 @@
|
||||
const Times = require("../public/times");
|
||||
// const executeWithRetry = require("./public/retry")
|
||||
const Init = require("./init");
|
||||
|
||||
class HashRate extends Init {
|
||||
constructor(coin) {
|
||||
const method = "hashrate";
|
||||
super(coin, method);
|
||||
this.diffOneShareHashsAvg = 2 ** 32 - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算hash
|
||||
* @param {Number} accepts 时段内接受总数
|
||||
* @param {Number} seconds 时段秒数
|
||||
* @param {String} unit H/s、KH/s、MH/s、GH/s、TH/s、PH/s、EH/s
|
||||
* @returns
|
||||
*/
|
||||
calculate_hashrate(accepts, seconds, unit) {
|
||||
let num;
|
||||
switch (unit) {
|
||||
case "H/s":
|
||||
num = 1;
|
||||
break;
|
||||
case "KH/s":
|
||||
num = 1_000;
|
||||
break;
|
||||
case "MH/s":
|
||||
num = 1_000_000;
|
||||
break;
|
||||
case "GH/s":
|
||||
num = 1_000_000_000;
|
||||
break;
|
||||
case "TH/s":
|
||||
num = 1_000_000_000_000;
|
||||
break;
|
||||
case "PH/s":
|
||||
num = 1_000_000_000_000_000;
|
||||
break;
|
||||
case "EH/s":
|
||||
num = 10 ** 18;
|
||||
break;
|
||||
default:
|
||||
throw `${unit}不是已知单位`;
|
||||
}
|
||||
const hashrate = (accepts * this.diffOneShareHashsAvg) / seconds / num;
|
||||
if(this.coin === "alph"){
|
||||
return hashrate * 4
|
||||
} else {
|
||||
return hashrate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将主、备查询出来的数据合并
|
||||
* @param {*} data [{user:"", miner:"", accepts:100},{user:"", miner:"", accepts:100}...]
|
||||
* @returns
|
||||
*/
|
||||
merge(data) {
|
||||
// 创建一个 Map 来存储 user 和 miner 组合的结果
|
||||
const results = new Map();
|
||||
|
||||
data.forEach((item) => {
|
||||
const key = `${item.user}-${item.miner}`;
|
||||
|
||||
if (results.has(key)) {
|
||||
const existing = results.get(key);
|
||||
existing.accepts += parseFloat(item.accepts);
|
||||
if (new Date(item.last_submit) > new Date(existing.last_submit)) {
|
||||
existing.last_submit = item.last_submit;
|
||||
}
|
||||
results.set(key, existing);
|
||||
} else {
|
||||
results.set(key, {
|
||||
user: item.user,
|
||||
miner: item.miner,
|
||||
accepts: parseFloat(item.accepts),
|
||||
last_submit: item.last_submit,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 将结果转换为数组
|
||||
const resultArray = Array.from(results.values());
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主库符合时段的表名
|
||||
* @param {String} start_time
|
||||
* @param {String} end_time
|
||||
* @returns
|
||||
*/
|
||||
async query_table(start_time, end_time) {
|
||||
try {
|
||||
const sql = `(SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) UNION (SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;`;
|
||||
const data = await this.sharesdb.exec(sql, [end_time, start_time, end_time])
|
||||
const result = [];
|
||||
if (data.length !== 0) {
|
||||
for (let item of data) {
|
||||
result.push(`${this.coin}_block_detail_${item.from}_${Math.trunc(item.to - 1)}`);
|
||||
}
|
||||
}
|
||||
result.push(`${this.coin}_blk_detail`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询从库符合时段的表名
|
||||
* @param {String} start_time
|
||||
* @param {String} end_time
|
||||
* @returns
|
||||
*/
|
||||
async query_slave_table(start_time, end_time) {
|
||||
try {
|
||||
const sql = `(SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) UNION (SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;`;
|
||||
const data = await this.sharesdb_slave.exec(sql, [end_time, start_time, end_time])
|
||||
const result = [];
|
||||
if (data.length !== 0) {
|
||||
for (let item of data) {
|
||||
result.push(`${this.coin}_block_detail_${item.from}_${Math.trunc(item.to - 1)}`);
|
||||
}
|
||||
}
|
||||
result.push(`${this.coin}_blk_detail`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 查询时段内accepts,主从同时查询
|
||||
async query_accepts(start_time, end_time, enable) {
|
||||
try {
|
||||
if (this.count === undefined) this.count = 0;
|
||||
if (enable) {
|
||||
const [tables_name, slave_tables_name] = await Promise.all([this.query_table(start_time, end_time), this.query_slave_table(start_time, end_time)]);
|
||||
|
||||
// 查询主库符合条件的数据
|
||||
let sql = ``;
|
||||
if (tables_name.length <= 1) {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= "${start_time}" AND date < "${end_time}" GROUP BY user, miner;`;
|
||||
} else {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < tables_name.length; i++) {
|
||||
if (i < tables_name.length - 1) {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}" \nUNION ALL\n`;
|
||||
} else {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}") AS combined_tables GROUP BY user, miner;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
let slave_sql = ``;
|
||||
if (slave_tables_name.length <= 1) {
|
||||
slave_sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= "${start_time}" AND date < "${end_time}" GROUP BY user, miner;`;
|
||||
} else {
|
||||
slave_sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < slave_tables_name.length; i++) {
|
||||
if (i < slave_tables_name.length - 1) {
|
||||
slave_sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${slave_tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}" \nUNION ALL\n`;
|
||||
} else {
|
||||
slave_sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${slave_tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}") AS combined_tables GROUP BY user, miner;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 执行查询,并将结果合并
|
||||
const [accepts_data, slave_accepts] = await Promise.all([this.sharesdb.exec(sql), this.sharesdb_slave.exec(slave_sql)]);
|
||||
const accepts = this.merge(accepts_data.concat(slave_accepts)); // 合并主备accepts
|
||||
return accepts;
|
||||
} else {
|
||||
const tables_name = await this.query_table(start_time, end_time);
|
||||
let sql = ``;
|
||||
if (tables_name.length <= 1) {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= "${start_time}" AND date < "${end_time}" GROUP BY user, miner;`;
|
||||
} else {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < tables_name.length; i++) {
|
||||
if (i < tables_name.length - 1) {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}" \nUNION ALL\n`;
|
||||
} else {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}") AS combined_tables GROUP BY user, miner;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const accepts_data = await this.sharesdb.exec(sql);
|
||||
const slave_accepts = [];
|
||||
const accepts = this.merge(accepts_data.concat(slave_accepts)); // 合并主备accepts
|
||||
return accepts;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error in query_accepts: ${err.message}`);
|
||||
await this.sleep(1000 * 15);
|
||||
if (this.count > 3) { // 重试4次,1分钟
|
||||
this.count = 0;
|
||||
throw err;
|
||||
}
|
||||
this.count++;
|
||||
return this.query_accepts(start_time, end_time, enable);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询当天miners状态,排除掉超过1天没有提交的矿工
|
||||
async query_miners(time) {
|
||||
try {
|
||||
const sql = `SELECT date, user, miner, state, ratio, last_submit FROM ${this.coin}_miners WHERE last_submit >= DATE(?) - INTERVAL 1 DAY;`;
|
||||
const miners_state = await this.pooldb.exec(sql, [time]);
|
||||
return miners_state;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_mhs(data) {
|
||||
if (data.length === 0 || !data || data.size === 0) {
|
||||
console.log(Date.now(), ":30分钟没有新增矿机提交数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let sql = `INSERT INTO ${this.coin}_mhsv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { user, miner, date, mhs30m, mhs24h, state, last_submit } = item;
|
||||
sql += `(?, ?, ?, ?, ?, ?, ?), `;
|
||||
values.push(user, miner, date, mhs30m, mhs24h, state, last_submit);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
await this.hashratedb.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_mhs_real(data) {
|
||||
if (data.length === 0 || !data || data.size === 0) {
|
||||
console.log(Date.now(), ":5分钟没有新增矿机提交数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const del_sql = `DELETE FROM ${this.coin}_mhs_realv2 WHERE id > 0;`;
|
||||
let sql = `INSERT INTO ${this.coin}_mhs_realv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { user, miner, date, mhs30m, mhs24h, state, last_submit } = item;
|
||||
sql += `(?, ?, ?, ?, ?, ? ,?), `;
|
||||
values.push(user, miner, date, mhs30m, mhs24h, state, last_submit);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
// sql += ` AS new_values ON DUPLICATE KEY UPDATE date = new_values.date, mhs30m = new_values.mhs30m, mhs24h = new_values.mhs24h, state = new_values.state, last_submit = new_values.last_submit;`;
|
||||
const sqls = [{ sql: del_sql }, { sql, param: values }];
|
||||
await this.hashratedb.exec_transaction_together(sqls);
|
||||
} catch (err) {
|
||||
// 处理错误
|
||||
console.error("Transaction failed: ", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_hashrate_miners_accepts(end_time) {
|
||||
try {
|
||||
const ymd_last_30m = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 30);
|
||||
const ymd_last_24h = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 60 * 24);
|
||||
const state_sql = `SELECT t1.*
|
||||
FROM ${this.coin}_minersv2 t1
|
||||
INNER JOIN (
|
||||
SELECT user, miner, MAX(date) AS max_date
|
||||
FROM ${this.coin}_minersv2
|
||||
WHERE date <= ?
|
||||
GROUP BY user, miner
|
||||
) t2
|
||||
ON t1.user = t2.user AND t1.miner = t2.miner AND t1.date = t2.max_date;`;
|
||||
const mhs30m_sql = `SELECT SUM(accepts) AS accepts_30min, user, miner FROM ${this.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;`;
|
||||
const mhs24h_sql = `SELECT SUM(accepts) AS accepts_24h, user, miner FROM ${this.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;`;
|
||||
const [state, mhs30m, mhs24h] = await Promise.all([this.hashratedb.exec(state_sql, [end_time]), this.hashratedb.exec(mhs30m_sql, [ymd_last_30m, end_time]), this.hashratedb.exec(mhs24h_sql, [ymd_last_24h, end_time])]);
|
||||
|
||||
const hashrate_map = new Map();
|
||||
|
||||
state.forEach((item) => {
|
||||
const { date, user, miner, state, last_submit } = item;
|
||||
hashrate_map.set(`${user}:${miner}`, { date: end_time, user, miner, state, last_submit, mhs30m: 0, mhs24h: 0 });
|
||||
});
|
||||
|
||||
mhs30m.forEach((item) => {
|
||||
const { accepts_30min, user, miner } = item;
|
||||
|
||||
const values = hashrate_map.get(`${user}:${miner}`);
|
||||
|
||||
values.mhs30m = this.calculate_hashrate(accepts_30min, 60 * 30, "MH/s");
|
||||
|
||||
hashrate_map.set(`${user}:${miner}`, values);
|
||||
});
|
||||
mhs24h.forEach((item) => {
|
||||
const { accepts_24h, user, miner } = item;
|
||||
const values = hashrate_map.get(`${user}:${miner}`);
|
||||
|
||||
values.mhs24h = this.calculate_hashrate(accepts_24h, 60 * 60 * 24, "MH/s");
|
||||
|
||||
hashrate_map.set(`${user}:${miner}`, values);
|
||||
});
|
||||
return hashrate_map;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_hashrate_miners_table(end_time) {
|
||||
try {
|
||||
const ymd = end_time.split(":");
|
||||
const date = ymd[0] + ":" + ymd[1] + ":" + "00";
|
||||
// 计算最近5分钟accepts,最新矿机状态
|
||||
const start_time = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 5);
|
||||
let enable = (await this.redis.get(`${this.coin}:enable`)) || false;
|
||||
|
||||
let [accepts, miners_state] = await Promise.all([this.query_accepts(start_time, end_time, enable), this.query_miners(end_time)]);
|
||||
|
||||
// 创建nexa_miners表所需要的map
|
||||
const miners_map = new Map();
|
||||
// 判断各种情况
|
||||
if (accepts.length === 0 && miners_state.length === 0) {
|
||||
// 历史上没有矿工接入
|
||||
return;
|
||||
} else if (accepts.length !== 0 && miners_state.length === 0) {
|
||||
// 主库出了问题,基本不可能出现这种情况
|
||||
return;
|
||||
} else if (accepts.length === 0 && miners_state.length !== 0) {
|
||||
// 最近5分钟没有矿工接入,直接将m2pooldb-nexa_miners表中所有矿工的accepts更新为0,并放入nexa_miners表需要的map中
|
||||
miners_state.forEach((item) => {
|
||||
const { user, miner, state, last_submit } = item;
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, state: "offline", last_submit, accepts: 0 });
|
||||
});
|
||||
} else {
|
||||
// 先找到所有最近5分钟有提交的矿机
|
||||
accepts.forEach((item) => {
|
||||
const { user, miner, accepts, last_submit } = item;
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, accepts, last_submit, state: "online" });
|
||||
});
|
||||
// 再将stats表有记录矿机,但最近5分钟没有提交的矿机合并进去
|
||||
miners_state.forEach((item) => {
|
||||
const { user, miner, state, last_submit } = item;
|
||||
if (!miners_map.get(`${user}:${miner}`)) {
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, accepts: 0, last_submit, state });
|
||||
}
|
||||
});
|
||||
}
|
||||
// 将指定时段内的数据插入nexa_miners表
|
||||
let insert_miners_table_sql = `INSERT INTO ${this.coin}_minersv2(user, miner, date, accepts, state, last_submit) VALUES `;
|
||||
const miners_table_values = [];
|
||||
miners_map.forEach((item) => {
|
||||
const { user, miner, date, accepts, state, last_submit } = item;
|
||||
insert_miners_table_sql += `(?, ?, ?, ?, ?, ?), `;
|
||||
miners_table_values.push(user, miner, date, accepts, state, last_submit);
|
||||
});
|
||||
insert_miners_table_sql = insert_miners_table_sql.slice(0, -2);
|
||||
await this.hashratedb.exec_transaction(insert_miners_table_sql, miners_table_values);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HashRate
|
||||
96
src/init.js
Normal file
96
src/init.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const fs = require("fs")
|
||||
const DBPool = require("../lib/mysql");
|
||||
const Cache = require("../lib/redis");
|
||||
const { NEXARPCNode, GRSRPCNode, MONARPCNode, DGBRPCNode, RXDRPCNode, ENXNode, ALPHRPCNode } = require("../lib/node");
|
||||
|
||||
class Init{
|
||||
constructor(coin, method){
|
||||
this.coin = coin
|
||||
const config = fs.readFileSync(`./config/${coin}.conf`, "utf-8")
|
||||
const {master, slave, redis_options, node_options, distribution_conf, MAX_MATURE, REPORT_ADDRESS} = JSON.parse(config)
|
||||
const {pooldb, sharesdb, distribution, hashrate, users_addresses, balance} = master
|
||||
const {pooldb_slave, sharesdb_slave} = slave
|
||||
const {node1, node2} = node_options
|
||||
const {redis1} = redis_options
|
||||
const {POOL_FEE} = distribution_conf
|
||||
const node_map = {
|
||||
mona: MONARPCNode,
|
||||
nexa: NEXARPCNode,
|
||||
grs: GRSRPCNode,
|
||||
dgbs: DGBRPCNode,
|
||||
dgbq: DGBRPCNode,
|
||||
dgbo: DGBRPCNode,
|
||||
rxd: RXDRPCNode,
|
||||
enx: ENXNode,
|
||||
alph: ALPHRPCNode
|
||||
};
|
||||
|
||||
switch (method){
|
||||
case "hashrate":
|
||||
this.sharesdb = new DBPool(coin, sharesdb)
|
||||
this.sharesdb_slave = new DBPool(coin, sharesdb_slave)
|
||||
this.pooldb = new DBPool(coin, pooldb)
|
||||
this.redis = new Cache(redis1)
|
||||
// this.pooldb_slave = new DBPool(coin, pooldb_slave)
|
||||
this.hashratedb = new DBPool(coin, hashrate)
|
||||
break
|
||||
case "report":
|
||||
if(this.coin === "enx"){
|
||||
this.distribution = new DBPool(coin, distribution)
|
||||
this.pooldb = new DBPool(coin, pooldb)
|
||||
} else {
|
||||
this.REPORT_ADDRESS = REPORT_ADDRESS
|
||||
this.node = new node_map[coin](node2)
|
||||
this.distribution = new DBPool(coin, distribution)
|
||||
this.redis = new Cache(redis1)
|
||||
}
|
||||
break
|
||||
case "clear":
|
||||
this.pooldb = new DBPool(coin, pooldb)
|
||||
this.sharesdb = new DBPool(coin, sharesdb)
|
||||
this.hashratedb = new DBPool(coin, hashrate)
|
||||
break
|
||||
case "distribution":
|
||||
this.pooldb = new DBPool(coin, pooldb)
|
||||
this.pooldb_slave = new DBPool(coin, pooldb_slave)
|
||||
this.hashratedb = new DBPool(coin, hashrate)
|
||||
this.distributiondb = new DBPool(coin, distribution)
|
||||
this.users_addresses = new DBPool(coin, users_addresses)
|
||||
this.node = new node_map[coin](node2)
|
||||
this.REPORT_ADDRESS = REPORT_ADDRESS
|
||||
this.POOL_FEE = POOL_FEE
|
||||
console.log(`当前手续费率为:${POOL_FEE}`);
|
||||
// this.balance = new DBPool(coin, balance)
|
||||
break
|
||||
case "balance":
|
||||
this.distribution = new DBPool(coin, distribution)
|
||||
this.balancedb = new DBPool(coin, balance)
|
||||
this.node = new node_map[coin](node2)
|
||||
this.MAX_MATURE = MAX_MATURE
|
||||
break
|
||||
case "confirm":
|
||||
this.MAX_MATURE = MAX_MATURE
|
||||
this.REPORT_ADDRESS = REPORT_ADDRESS
|
||||
this.node = new node_map[coin](node2)
|
||||
this.distribution = new DBPool(coin, distribution)
|
||||
this.pooldb = new DBPool(coin, pooldb)
|
||||
break
|
||||
case "stats":
|
||||
this.pooldb = new DBPool(coin, pooldb)
|
||||
this.hashratedb = new DBPool(coin, hashrate)
|
||||
this.distribution = new DBPool(coin, distribution)
|
||||
break
|
||||
case "notice":
|
||||
this.distribution = new DBPool(coin, distribution)
|
||||
break
|
||||
default:
|
||||
throw `暂不支持${method}方法 init`
|
||||
}
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Init
|
||||
85
src/notice.js
Normal file
85
src/notice.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const Init = require("./init");
|
||||
const nodemailer = require("nodemailer");
|
||||
const Times = require("../public/times");
|
||||
|
||||
const conf = {
|
||||
create_config: {
|
||||
service: "qq",
|
||||
secureConnection: true,
|
||||
port: 465,
|
||||
auth: {
|
||||
user: "393768033@qq.com",
|
||||
// pass: "hyaefuvaiudcbihj",
|
||||
pass:"anuflpiziamwcaia",
|
||||
},
|
||||
},
|
||||
symbol:["nexa", "rxd", "dgbs", "dgbq", "dgbo", "grs", "mona", "enx"],
|
||||
receiver: "709387953@qq.com, liarsars@gmail.com"
|
||||
};
|
||||
|
||||
class Notice extends Init {
|
||||
constructor(coin) {
|
||||
super(coin, "notice");
|
||||
this.conf = conf;
|
||||
this.transporter = nodemailer.createTransport(conf.create_config);
|
||||
}
|
||||
|
||||
construct_mail(receiver, subject, text, html) {
|
||||
return {
|
||||
from: this.transporter.options.auth.user,
|
||||
to: receiver,
|
||||
subject: subject,
|
||||
text: text,
|
||||
html: html
|
||||
};
|
||||
}
|
||||
|
||||
async send_mail(mailOptions) {
|
||||
if (!mailOptions.to) {
|
||||
throw new Error("未指定收件人");
|
||||
}
|
||||
try {
|
||||
const info = await this.transporter.sendMail(mailOptions);
|
||||
console.log(`${Date.now()} 检测到未转账信息!邮件ID: ${info.messageId}`);
|
||||
} catch (err) {
|
||||
console.error("邮件发送失败:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async query_walletout(date) {
|
||||
try {
|
||||
const sql = `SELECT * FROM wallet_outv2 WHERE DATE(date) = ?;`;
|
||||
const data = await this.distribution.exec(sql, [date]);
|
||||
return data.length > 0 ? data : false;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async main() {
|
||||
try {
|
||||
const date = Times.times()[2];
|
||||
const data = await this.query_walletout(date);
|
||||
|
||||
if (!data) return;
|
||||
|
||||
const symbol = new Set(data.map(item => item.coin));
|
||||
|
||||
const transfer_err_coins = (this.conf.symbol || []).filter(item => !symbol.has(item));
|
||||
|
||||
if (transfer_err_coins.length === 0) return;
|
||||
|
||||
const subject = `今日有未转账币种`;
|
||||
const text = `今日有未转账币种`;
|
||||
const html = `今日未转账币种:${transfer_err_coins.join(", ")}<h1>测试数据</h1>`;
|
||||
|
||||
const mailOptions = this.construct_mail(this.conf.receiver, subject, text, html);
|
||||
await this.send_mail(mailOptions);
|
||||
return
|
||||
} catch (err) {
|
||||
console.error("主任务执行错误:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Notice
|
||||
182
src/report.js
Normal file
182
src/report.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const Times = require("../public/times");
|
||||
const Init = require("./init");
|
||||
|
||||
class Report extends Init {
|
||||
constructor(coin) {
|
||||
const method = "report";
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
async query_mysql_last_height() {
|
||||
try {
|
||||
const sql = `SELECT MAX(height) AS max_height FROM ${this.coin}_blkreportprofitv2;`;
|
||||
const data = await this.distribution.exec(sql);
|
||||
if (!data || data.length === 0) {
|
||||
throw `${this.coin}当前无报块记录`;
|
||||
}
|
||||
return data[0].max_height;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_redis_last_height() {
|
||||
try {
|
||||
const data = await this.redis.get(`${this.coin}:last_check`);
|
||||
if (!data) {
|
||||
const result = await this.query_mysql_last_height();
|
||||
await this.redis.set(`${this.coin}:last_check`, result);
|
||||
console.log(`redis中无${this.coin} last_check数据,采用最后一个报块高度!`);
|
||||
return result;
|
||||
}
|
||||
return Number(data);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_chain_last_height() {
|
||||
try {
|
||||
const data = await this.node.getblockcount();
|
||||
return Number(data);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_blkreportprofit(data) {
|
||||
try {
|
||||
let sql = `INSERT INTO ${this.coin}_blkreportprofitv2 (date, height, hash, reward, fees, state) VALUES `;
|
||||
const values = [];
|
||||
for (let item of data) {
|
||||
const { date, height, hash, reward, fees } = item;
|
||||
values.push(date, height, hash, reward, fees, 0);
|
||||
sql += `(?,?,?,?,?,?), `;
|
||||
}
|
||||
sql = sql.slice(0, -2);
|
||||
await this.distribution.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async main() {
|
||||
try {
|
||||
const [redis_height, chain_height] = await Promise.all([this.query_redis_last_height(), this.query_chain_last_height()]);
|
||||
// 区块链高度小于last_check高度,节点同步出错
|
||||
if (chain_height < redis_height) {
|
||||
console.log(`${this.coin}节点同步出错,节点高度${chain_height},last_check${redis_height}`);
|
||||
return;
|
||||
}
|
||||
if (chain_height === redis_height) {
|
||||
console.log(`${this.coin}当前节点和last_check高度一致,无需校验`);
|
||||
return;
|
||||
}
|
||||
const suc_data = [];
|
||||
for (let i = redis_height + 1; i <= chain_height; i++) {
|
||||
const check_result = await this.node.verify_block(i, this.REPORT_ADDRESS);
|
||||
if (check_result) {
|
||||
const block = this.node.block(check_result);
|
||||
const { height, hash, time, block_reward, block_fees } = block;
|
||||
suc_data.push({ date: Times.utcTime(time * 1000), height, hash, reward: block_reward, fees: block_fees });
|
||||
}
|
||||
}
|
||||
if (suc_data.length === 0) {
|
||||
console.log(`${redis_height} - ${chain_height} 无报块`);
|
||||
await this.redis.set(`${this.coin}:last_check`, chain_height);
|
||||
return;
|
||||
}
|
||||
await this.insert_blkreportprofit(suc_data);
|
||||
await this.redis.set(`${this.coin}:last_check`, chain_height);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReportEnx extends Report {
|
||||
constructor(coin) {
|
||||
const method = "report";
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
async query_blkstats(height) {
|
||||
try {
|
||||
const yesterday = Times.utcTime(new Date().valueOf() - 24 * 60 * 60 * 1000)
|
||||
.split(" ")[0]
|
||||
.replace(/\-/g, "");
|
||||
const table1 = "enx_pool_blkstats";
|
||||
const table2 = `enx_pool_blkstats_${yesterday}`;
|
||||
|
||||
const queryTableExistSql = `SHOW TABLES LIKE ?;`;
|
||||
const existData = await this.pooldb.exec(queryTableExistSql, [table2]);
|
||||
|
||||
if (existData.length === 0) {
|
||||
const sql = `SELECT date, height, hash FROM ?? WHERE height > ?;`;
|
||||
return await this.pooldb.exec(sql, [table1, height]);
|
||||
} else {
|
||||
const sql = `SELECT date, height, hash FROM ?? WHERE height > ?
|
||||
UNION ALL
|
||||
SELECT date, height, hash FROM ?? WHERE height > ?;`;
|
||||
return await this.pooldb.exec(sql, [table1, height, table2, height]);
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async query_blkreportprofit() {
|
||||
try {
|
||||
const sql = `SELECT MAX(height) AS max_height FROM ${this.coin}_blkreportprofitv2;`;
|
||||
const result = await this.distribution.exec(sql);
|
||||
if (result.length === 0 || result[0].max_height === null) {
|
||||
return 0;
|
||||
} else {
|
||||
return result[0].max_height;
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insertinto_blkreportprofit(data) {
|
||||
try {
|
||||
let sql = `INSERT INTO ${this.coin}_blkreportprofitv2 (date, height, hash, reward, fees, state) VALUES `;
|
||||
const values = [];
|
||||
for (let item of data) {
|
||||
const { date, height, hash, reward } = item;
|
||||
values.push(date, height, hash, reward, 0, 1);
|
||||
sql += `(?,?,?,?,?,?), `;
|
||||
}
|
||||
sql = sql.slice(0, -2);
|
||||
await this.distribution.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async main() {
|
||||
// const start_time = Times.utcTime(new Date().valueOf());
|
||||
const last_height = await this.query_blkreportprofit();
|
||||
const data = await this.query_blkstats(last_height);
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log(`${this.coin} 无报块`);
|
||||
return;
|
||||
}
|
||||
const block_data = []
|
||||
for(let item of data){
|
||||
const {date, height, hash} = item
|
||||
block_data.push({date, height, hash, reward:333})
|
||||
}
|
||||
|
||||
await this.insertinto_blkreportprofit(block_data);
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Report,
|
||||
ReportEnx,
|
||||
}
|
||||
112
src/stat.js
Normal file
112
src/stat.js
Normal file
@@ -0,0 +1,112 @@
|
||||
const Times = require("../public/times");
|
||||
const Init = require("./init");
|
||||
|
||||
/**
|
||||
* earliest date : 2024-11-26 00:00:00
|
||||
*/
|
||||
class Stats extends Init {
|
||||
constructor(coin) {
|
||||
const method = "stats";
|
||||
super(coin, method);
|
||||
}
|
||||
|
||||
async query_pooldb_mhs24h(date) {
|
||||
try {
|
||||
const yMd = Times.utcTime(new Date(date).valueOf() - 1000 * 60 * 60 * 24).split(" ")[0];
|
||||
const table_date = yMd.replace(/\-/g, "");
|
||||
let table = `${this.coin}_miners_stats_${table_date}`;
|
||||
console.log(table);
|
||||
|
||||
const sql = `SELECT user, SUM(mhs24h) AS mhs24h FROM ${table} WHERE date >= ? AND date <= ? GROUP BY user;`;
|
||||
const start = yMd + " 23:55:00";
|
||||
const end = yMd + " 23:59:59";
|
||||
const data = await this.pooldb.exec(sql, [start, end]);
|
||||
if (data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const result = [];
|
||||
for (let item of data) {
|
||||
const { user, mhs24h } = item;
|
||||
result.push({ user, mhs24h: Number(mhs24h) });
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_hashratedb_mhs24h(date) {
|
||||
try {
|
||||
const sql = `SELECT user, SUM(mhs24h) AS mhs24h FROM ${this.coin}_mhsv2 WHERE date = ? GROUP BY user;`;
|
||||
const data = await this.hashratedb.exec(sql, [date]);
|
||||
if (!data || data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const result = [];
|
||||
for (let item of data) {
|
||||
const { user, mhs24h } = item;
|
||||
result.push({ user, mhs24h: Number(mhs24h) });
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_distribution_data(date) {
|
||||
try {
|
||||
const sql = `SELECT user, amount FROM wallet_in WHERE coin = ? AND create_date = ? AND user != "pool_account";`;
|
||||
const data = await this.distribution.exec(sql, [this.coin, date]);
|
||||
if (!data || data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const result = [];
|
||||
for (let item of data) {
|
||||
const { user, amount } = item;
|
||||
result.push({ user, amount: Number(amount) });
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
caculate_mhs24h_percent(data) {
|
||||
const totalAmount = data.reduce((acc, item) => acc + item.mhs24h, 0);
|
||||
const result = data.map((item) => ({
|
||||
user: item.user,
|
||||
mhs24h: item.mhs24h,
|
||||
percentage: Number((item.mhs24h / totalAmount).toFixed(4)), // 保留两位小数
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
|
||||
caculate_distribution_percent(data) {
|
||||
const totalAmount = data.reduce((acc, item) => acc + item.amount, 0);
|
||||
const result = data.map((item) => ({
|
||||
user: item.user,
|
||||
should_receive: Number((item.amount / 0.95).toFixed(8)),
|
||||
actual_receive: item.amount,
|
||||
percentage: Number(((item.amount / totalAmount)).toFixed(4)), // 保留两位小数
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
|
||||
async caculate_user_should_distribution(date) {
|
||||
try {
|
||||
const [user_mhs24h, user_distribution] = await Promise.all([this.query_hashratedb_mhs24h(date), this.query_distribution_data(date)]);
|
||||
const mhs24h_percent = this.caculate_mhs24h_percent(user_mhs24h)
|
||||
const distribution_percent = this.caculate_distribution_percent(user_distribution)
|
||||
let fees = 0
|
||||
for(let item of distribution_percent){
|
||||
const {user, should_receive, actual_receive, percentage} = item
|
||||
fees += should_receive - actual_receive
|
||||
}
|
||||
console.log(fees);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Stats;
|
||||
2
src/web/elementui/axios.min.js
vendored
Normal file
2
src/web/elementui/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/web/elementui/echarts.min.js
vendored
Normal file
1
src/web/elementui/echarts.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
src/web/elementui/fonts/element-icons.woff
Normal file
BIN
src/web/elementui/fonts/element-icons.woff
Normal file
Binary file not shown.
1
src/web/elementui/index.css
Normal file
1
src/web/elementui/index.css
Normal file
File diff suppressed because one or more lines are too long
34986
src/web/elementui/index.js
Normal file
34986
src/web/elementui/index.js
Normal file
File diff suppressed because it is too large
Load Diff
11
src/web/elementui/vue.min.js
vendored
Normal file
11
src/web/elementui/vue.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
154
src/web/index.html
Normal file
154
src/web/index.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verify</title>
|
||||
<script src="./elementui/axios.min.js"></script>
|
||||
<script src="./elementui/echarts.min.js"></script>
|
||||
<!-- 引入vue -->
|
||||
<script src="./elementui/vue.min.js"></script>
|
||||
<!-- 引入样式 -->
|
||||
<link rel="stylesheet" href="./elementui/index.css">
|
||||
<!-- 引入组件库 -->
|
||||
<script src="./elementui/index.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<template>
|
||||
|
||||
<div style="width: 200px;">
|
||||
<el-input v-model="input_date" placeholder="请输入日期(2024-12-01格式)" size="mini" @keyup.enter="get_date"></el-input>
|
||||
<el-button size="mini" type="primary" @click="get_date">查询</el-button>
|
||||
</div>
|
||||
<!-- <h6>1,"分配数据表":按币种分组,每个币种单独计算除"pool_account"账号之外的"分配金额"比例,所有除"pool_account"账号以外的用户总比例为95%,即每个账号比例需要额外*0.95的权重</h6>
|
||||
<h6>2,"算力数据表":按币种分组,每个币种单独计算所有用户算力占比,这个无需权重,直接算即可</h6>
|
||||
<h6>3,比较"分配占比"和"算力占比"是否一致,币种+账号名相同的对比</h6>
|
||||
<h6>4,"报块数据表":将上方算出的算力数据占比,找到对应币种的报块数据,用每个</h6> -->
|
||||
<div style="margin-top:50px;width: 1000px;">
|
||||
<h3>分配数据</h3>
|
||||
<el-table :data="distribution" style="width: 100%">
|
||||
<!-- <el-table-column prop="date" label="日期" width="180">
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="coin" label="币种" width="100">
|
||||
</el-table-column>
|
||||
<el-table-column prop="user" label="用户" width="180">
|
||||
</el-table-column>
|
||||
<el-table-column prop="amount" label="分配金额" width="200">
|
||||
</el-table-column>
|
||||
<el-table-column prop="address" label="钱包地址">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div style="margin-top:150px; width: 800px;">
|
||||
<h3>算力数据</h3>
|
||||
<el-table :data="hashrate" style="width: 100%">
|
||||
<!-- <el-table-column prop="date" label="日期" width="180">
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="coin" label="币种" width="100">
|
||||
</el-table-column>
|
||||
<el-table-column prop="user" label="用户" width="100">
|
||||
</el-table-column>
|
||||
<el-table-column prop="mhs24h" label="过去24小时算力">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div style="margin-top:50px;width: 800px;">
|
||||
<h3>报块数据</h3>
|
||||
<el-table :data="reward" style="width: 100%">
|
||||
<!-- <el-table-column prop="date" label="日期" width="180">
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="coin" label="币种" width="100">
|
||||
</el-table-column>
|
||||
<el-table-column prop="reward" label="总报块奖励">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
async function get(api, params) {
|
||||
try {
|
||||
const response = await axios({
|
||||
method: "GET",
|
||||
url: `http://13.214.175.13:23116/api/${api}`,
|
||||
params
|
||||
});
|
||||
return response.data; // 提取响应数据
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const Main = {
|
||||
data() {
|
||||
return {
|
||||
input_date: "",
|
||||
distribution: [],
|
||||
hashrate: [],
|
||||
reward: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async query_distribution(date) {
|
||||
try {
|
||||
const data = await get("distribution", { date });
|
||||
return data || []; // 确保返回的是数组
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
async query_blockreward(date) {
|
||||
try {
|
||||
const data = await get("reward", { date });
|
||||
return data || [];
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
async query_hashrate(date) {
|
||||
try {
|
||||
const data = await get("hashrate", { date });
|
||||
return data || [];
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
async get_date() {
|
||||
const regex = /^\d{4}-\d{2}-\d{2}$/; // 匹配 yyyy-MM-dd 格式
|
||||
if (!regex.test(this.input_date)) {
|
||||
this.$message.error('日期格式无效,请使用 yyyy-MM-dd 格式'); // 显示错误消息
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 并发请求所有数据
|
||||
const [distribution, hashrate, reward] = await Promise.all([
|
||||
this.query_distribution(this.input_date),
|
||||
this.query_hashrate(this.input_date),
|
||||
this.query_blockreward(this.input_date)
|
||||
]);
|
||||
// 赋值数据
|
||||
this.distribution = distribution;
|
||||
this.hashrate = hashrate;
|
||||
this.reward = reward;
|
||||
} catch (error) {
|
||||
this.$message.error('数据加载失败,请检查网络或接口!');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const Ctor = Vue.extend(Main);
|
||||
new Ctor().$mount('#app'); // 确保 Vue 实例已挂载
|
||||
</script>
|
||||
|
||||
</html>
|
||||
88
src/web/mysql.js
Normal file
88
src/web/mysql.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const mysql = require("mysql2/promise");
|
||||
|
||||
class DBPool {
|
||||
constructor(coin, options) {
|
||||
this.coin = coin;
|
||||
this.pool = mysql.createPool(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非事务SQL操作
|
||||
* @param {String} sql
|
||||
* @param {Array} values
|
||||
* @returns
|
||||
*/
|
||||
async exec(sql, values = []) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
const [data] = await con.query(sql, values);
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 单独SQL事务操作
|
||||
* @param {String} sql
|
||||
* @param {Array} values
|
||||
* @returns
|
||||
*/
|
||||
async exec_transaction(sql, values = []) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
await con.beginTransaction();
|
||||
const [data] = await con.query(sql, values);
|
||||
await con.commit();
|
||||
return data;
|
||||
} catch (err) {
|
||||
await con.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一系列 SQL 操作,合并到一个事务中。
|
||||
* @param {Array} params [{sql:"", param:[param1, param2, ...]}]
|
||||
*/
|
||||
async exec_transaction_together(params) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
await con.beginTransaction();
|
||||
|
||||
// 确保所有查询都完成后再提交事务
|
||||
for (const { sql, param } of params) {
|
||||
await con.query(sql, param);
|
||||
}
|
||||
|
||||
await con.commit();
|
||||
} catch (err) {
|
||||
await con.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
|
||||
async exec_write_lock(sql, values = [], table_name) {
|
||||
const con = await this.pool.getConnection();
|
||||
try {
|
||||
await con.beginTransaction();
|
||||
// 使用模板字符串而不是占位符来构建 LOCK TABLES 语句
|
||||
await con.query(`LOCK TABLES ${table_name} WRITE`);
|
||||
await con.query(sql, values);
|
||||
await con.commit();
|
||||
} catch (err) {
|
||||
await con.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
await con.query("UNLOCK TABLES");
|
||||
con.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DBPool
|
||||
102
src/web/service.js
Normal file
102
src/web/service.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const Times = require("./times")
|
||||
const DBPool = require("./mysql")
|
||||
|
||||
class Service {
|
||||
constructor(){
|
||||
this.hashratedb = new DBPool("nexa", {
|
||||
"host": "13.213.4.56",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "hashrate",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
})
|
||||
|
||||
this.distributiondb = new DBPool("nexa", {
|
||||
"host": "13.213.4.56",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "distribution",
|
||||
"port":6789,
|
||||
"waitForConnections": true,
|
||||
"connectionLimit": 20,
|
||||
"queueLimit": 0
|
||||
})
|
||||
}
|
||||
|
||||
async query_distribution(date){
|
||||
date = date + " 00:00:00"
|
||||
try{
|
||||
const sql = `SELECT create_date AS date, coin, user, amount, address FROM wallet_in WHERE create_date = ?;`
|
||||
const data = await this.distributiondb.exec(sql, [date])
|
||||
if(!data || data.length === 0){
|
||||
return false
|
||||
}
|
||||
return data
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async query_hashrate(date){
|
||||
date = date + " 00:00:00"
|
||||
try{
|
||||
const coins = ["nexa", "grs", "mona", "dgbo", "dgbq", "dgbs", "rxd"]
|
||||
const result = []
|
||||
for(let i=0; i<coins.length; i++){
|
||||
const sql = `SELECT date, user, SUM(mhs24h) AS mhs24h FROM ${coins[i]}_mhsv2 WHERE date = ? GROUP BY date, user;`
|
||||
const data = await this.hashratedb.exec(sql, [date])
|
||||
for(let item of data){
|
||||
result.push({date:item.date, user:item.user, coin: coins[i], mhs24h:item.mhs24h})
|
||||
}
|
||||
}
|
||||
if(!result || result.length === 0){
|
||||
return false
|
||||
}
|
||||
return result
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async query_total_blockreward(date){
|
||||
date = date + " 00:00:00"
|
||||
let param_date = Times.utcTime(new Date(date).valueOf() - 86400000)
|
||||
param_date = param_date.split(" ")[0]
|
||||
try{
|
||||
const coins = ["nexa", "grs", "mona", "dgbo", "dgbq", "dgbs", "rxd"]
|
||||
const result = []
|
||||
for(let i=0; i<coins.length; i++){
|
||||
const sql = `SELECT SUM(reward) AS reward FROM ${coins[i]}_blkreportprofitv2 WHERE DATE(date) = ? AND state = ?;`
|
||||
const data = await this.distributiondb.exec(sql, [param_date, 1])
|
||||
for(let item of data){
|
||||
result.push({date, coin:coins[i], reward:item.reward})
|
||||
}
|
||||
}
|
||||
if(!result || result.length === 0){
|
||||
return false
|
||||
}
|
||||
return result
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async query_walletout(date){
|
||||
date = date + " 00:00:00"
|
||||
try{
|
||||
const sql = `SELECT * FROM wallet_outv2 WHERE date = ?;`
|
||||
const data = await this.distributiondb.exec(sql, [date])
|
||||
if(!data || data.length === 0){
|
||||
return false
|
||||
}
|
||||
return data
|
||||
} catch(err){
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Service
|
||||
52
src/web/times.js
Normal file
52
src/web/times.js
Normal file
@@ -0,0 +1,52 @@
|
||||
class Times {
|
||||
static bjTime(dateForm) {
|
||||
if (dateForm === "") {
|
||||
//解决deteForm为空传1970-01-01 00:00:00
|
||||
return "";
|
||||
} else {
|
||||
var dateee = new Date(dateForm).toJSON();
|
||||
var date = new Date(+new Date(dateee) + 8 * 3600 * 1000)
|
||||
.toISOString()
|
||||
.replace(/T/g, " ")
|
||||
.replace(/\.[\d]{3}Z/, "");
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
static utcTime(dateForm) {
|
||||
if (dateForm === "") {
|
||||
//解决deteForm为空传1970-01-01 00:00:00
|
||||
return "";
|
||||
} else {
|
||||
var dateee = new Date(dateForm).toJSON();
|
||||
var date = new Date(+new Date(dateee))
|
||||
.toISOString()
|
||||
.replace(/T/g, " ")
|
||||
.replace(/\.[\d]{3}Z/, "");
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
static times() {
|
||||
const date = new Date();
|
||||
const y = date.getFullYear();
|
||||
const M = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const h = String(date.getHours()).padStart(2, '0');
|
||||
const m = String(date.getMinutes()).padStart(2, '0');
|
||||
const s = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return [
|
||||
`${y}-${M}-${d} ${h}:${m}`, // 格式:YYYY-MM-DD HH:mm
|
||||
`${y}-${M}-${d} ${h}:${m}:${s}`, // 格式:YYYY-MM-DD HH:mm:ss
|
||||
`${y}-${M}-${d} ${h}`, // 格式:YYYY-MM-DD HH
|
||||
`${m}`, // 分钟格式:mm
|
||||
`${h}`, // 小时格式:HH
|
||||
`${d}` // 日期格式:DD
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Times;
|
||||
|
||||
38
src/web/verify.conf
Normal file
38
src/web/verify.conf
Normal file
@@ -0,0 +1,38 @@
|
||||
server {
|
||||
listen 23116; # 监听的端口号
|
||||
server_name localhost; # 您的域名
|
||||
|
||||
root /var/www/html/verify; # 设置根路径为 /var/www/html/web
|
||||
|
||||
index index.html; # 设置默认索引文件
|
||||
|
||||
location / {
|
||||
root /var/www/html/verify;
|
||||
index index.html index.htm;
|
||||
# try_files $uri $uri/ @router; # 处理请求的方式
|
||||
}
|
||||
|
||||
|
||||
#location @router{
|
||||
# rewrite ^.*$ /index.html last;
|
||||
#}
|
||||
|
||||
|
||||
# 静态资源配置
|
||||
location ~ ^/(images|javascript|js|css|flash|media|static|eot|otf|ttf|woff|svg|woof2|fonts)/{
|
||||
root /var/www/html/verify;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
|
||||
#过期30天,静态文件不怎么更新,过期可以设大一点,如果频繁更新,则可以设置得小一点。
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
location ~ ^/favicon\.ico$ {
|
||||
root /var/www/html/verify;
|
||||
}
|
||||
|
||||
if ($args ~* "select|insert|update|delete|drop|exec|script"){
|
||||
return 403;
|
||||
}
|
||||
|
||||
}
|
||||
88
src/web/web.js
Normal file
88
src/web/web.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const Service = require("./service");
|
||||
|
||||
const service = new Service();
|
||||
const port = process.argv[2] || 23115
|
||||
|
||||
// Middleware to parse JSON and form data
|
||||
app.use(express.json()); // for parsing application/json
|
||||
app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
|
||||
// 跨域处理
|
||||
app.use((req, res, next) => {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||
// res.header('Content-Type', 'application/json')
|
||||
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||
res.header("Access-Control-Allow-Credentials", true);
|
||||
next();
|
||||
});
|
||||
|
||||
// 连通测试
|
||||
app.get("/test", (req, res) => {
|
||||
res.json({ msg: "hello!" });
|
||||
});
|
||||
|
||||
app.get("/distribution", async (req, res) => {
|
||||
const { date } = req.query;
|
||||
const data = await service.query_distribution(date);
|
||||
let result;
|
||||
if (!data) {
|
||||
result = [];
|
||||
} else {
|
||||
result = data;
|
||||
}
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
app.get("/hashrate", async (req, res) => {
|
||||
const { date } = req.query;
|
||||
const data = await service.query_hashrate(date);
|
||||
let result;
|
||||
if (!data) {
|
||||
result = [];
|
||||
} else {
|
||||
result = data;
|
||||
}
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
app.get("/reward", async (req, res) => {
|
||||
const { date } = req.query;
|
||||
const data = await service.query_total_blockreward(date);
|
||||
let result;
|
||||
if (!data) {
|
||||
result = [];
|
||||
} else {
|
||||
result = data;
|
||||
}
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
app.get("/walletout", async (req, res) => {
|
||||
const { date } = req.query;
|
||||
const data = await service.query_walletout(date);
|
||||
let result;
|
||||
if (!data) {
|
||||
result = [];
|
||||
} else {
|
||||
result = data;
|
||||
}
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is listening at http://127.0.0.1:${port}`);
|
||||
});
|
||||
|
||||
[
|
||||
{ date: "2024-12-03", coin: "nexa", reward: "2700041722.75000000" },
|
||||
{ date: "2024-12-03", coin: "grs", reward: "2285.22102434" },
|
||||
{ date: "2024-12-03", coin: "mona", reward: "5644.34314098" },
|
||||
{ date: "2024-12-03", coin: "dgbo", reward: "363953.84122200" },
|
||||
{ date: "2024-12-03", coin: "dgbq", reward: "303936.09476747" },
|
||||
{ date: "2024-12-03", coin: "dgbs", reward: "103023.94566538" },
|
||||
{ date: "2024-12-03", coin: "rxd", reward: "2150203.36261784" },
|
||||
]
|
||||
[({ date: "2024-12-03T00:00:00.000Z", user: "a20", coin: "nexa", mhs24h: "1221097.498765" }, { date: "2024-12-03T00:00:00.000Z", user: "a21", coin: "nexa", mhs24h: "811679.870615" }, { date: "2024-12-03T00:00:00.000Z", user: "a2x", coin: "nexa", mhs24h: "125128.418926" }, { date: "2024-12-03T00:00:00.000Z", user: "n1gminer", coin: "grs", mhs24h: "12952631.953375" }, { date: "2024-12-03T00:00:00.000Z", user: "n3miner", coin: "mona", mhs24h: "36491507.736254" }, { date: "2024-12-03T00:00:00.000Z", user: "b20miner", coin: "dgbo", mhs24h: "14829610.388251" }, { date: "2024-12-03T00:00:00.000Z", user: "w3miner", coin: "dgbo", mhs24h: "3356677.306375" }, { date: "2024-12-03T00:00:00.000Z", user: "a1xqminer", coin: "dgbq", mhs24h: "48256381.780041" }, { date: "2024-12-03T00:00:00.000Z", user: "miner", coin: "dgbq", mhs24h: "0.000000" }, { date: "2024-12-03T00:00:00.000Z", user: "n1qminer", coin: "dgbq", mhs24h: "32414166.811899" }, { date: "2024-12-03T00:00:00.000Z", user: "a1xsminer", coin: "dgbs", mhs24h: "317211396.182949" }, { date: "2024-12-03T00:00:00.000Z", user: "n1sminer", coin: "dgbs", mhs24h: "32972685.343203" }, { date: "2024-12-03T00:00:00.000Z", user: "a1xrminer", coin: "rxd", mhs24h: "1049483151.952393" }, { date: "2024-12-03T00:00:00.000Z", user: "miner", coin: "rxd", mhs24h: "0.000000" })];
|
||||
12
test/caculate.js
Normal file
12
test/caculate.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const Times = require('../public/times')
|
||||
const str = "rxd_miners_stats_20241128"
|
||||
|
||||
let sql = `DROP TABLE IF EXISTS `
|
||||
const start = new Date("2024-11-28 00:00:00").valueOf()
|
||||
for(let i=0; i<70; i++){
|
||||
const t = Times.utcTime(start + i * 86400000)
|
||||
const ymd = t.split(" ")[0].replace(/-/g, "")
|
||||
sql += ``+ `rxd_miners_stats_${ymd},`
|
||||
}
|
||||
sql = sql.slice(0, -1)
|
||||
console.log(sql);
|
||||
340
test/hashratev2-test.js
Normal file
340
test/hashratev2-test.js
Normal file
@@ -0,0 +1,340 @@
|
||||
const Times = require("../public/times");
|
||||
// const executeWithRetry = require("./public/retry")
|
||||
const Init = require("./init");
|
||||
|
||||
class HashRate extends Init {
|
||||
constructor(coin) {
|
||||
const method = "hashrate";
|
||||
super(coin, method);
|
||||
this.count = 0;
|
||||
this.diffOneShareHashsAvg = 2 ** 32 - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算hash
|
||||
* @param {Number} accepts 时段内接受总数
|
||||
* @param {Number} seconds 时段秒数
|
||||
* @param {String} unit H/s、KH/s、MH/s、GH/s、TH/s、PH/s、EH/s
|
||||
* @returns
|
||||
*/
|
||||
calculate_hashrate(accepts, seconds, unit) {
|
||||
let num;
|
||||
switch (unit) {
|
||||
case "H/s":
|
||||
num = 1;
|
||||
break;
|
||||
case "KH/s":
|
||||
num = 1_000;
|
||||
break;
|
||||
case "MH/s":
|
||||
num = 1_000_000;
|
||||
break;
|
||||
case "GH/s":
|
||||
num = 1_000_000_000;
|
||||
break;
|
||||
case "TH/s":
|
||||
num = 1_000_000_000_000;
|
||||
break;
|
||||
case "PH/s":
|
||||
num = 1_000_000_000_000_000;
|
||||
break;
|
||||
case "EH/s":
|
||||
num = 10 ** 18;
|
||||
break;
|
||||
default:
|
||||
throw `${unit}不是已知单位`;
|
||||
}
|
||||
const hashrate = (accepts * this.diffOneShareHashsAvg) / seconds / num;
|
||||
return hashrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将主、备查询出来的数据合并
|
||||
* @param {*} data [{user:"", miner:"", accepts:100},{user:"", miner:"", accepts:100}...]
|
||||
* @returns
|
||||
*/
|
||||
merge(data) {
|
||||
// 创建一个 Map 来存储 user 和 miner 组合的结果
|
||||
const results = new Map();
|
||||
|
||||
data.forEach((item) => {
|
||||
const key = `${item.user}-${item.miner}`;
|
||||
|
||||
if (results.has(key)) {
|
||||
const existing = results.get(key);
|
||||
existing.accepts += parseFloat(item.accepts);
|
||||
if (new Date(item.last_submit) > new Date(existing.last_submit)) {
|
||||
existing.last_submit = item.last_submit;
|
||||
}
|
||||
results.set(key, existing);
|
||||
} else {
|
||||
results.set(key, {
|
||||
user: item.user,
|
||||
miner: item.miner,
|
||||
accepts: parseFloat(item.accepts),
|
||||
last_submit: item.last_submit,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 将结果转换为数组
|
||||
const resultArray = Array.from(results.values());
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主库符合时段的表名
|
||||
* @param {String} start_time
|
||||
* @param {String} end_time
|
||||
* @returns
|
||||
*/
|
||||
async query_table(start_time, end_time) {
|
||||
try {
|
||||
const sql = `(SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) UNION (SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;`;
|
||||
const data = await this.sharesdb.exec(sql, [end_time, start_time, end_time]);
|
||||
const result = [];
|
||||
if (data.length !== 0) {
|
||||
for (let item of data) {
|
||||
result.push(`${this.coin}_block_detail_${item.from}_${Math.trunc(item.to - 1)}`);
|
||||
}
|
||||
}
|
||||
result.push(`${this.coin}_blk_detail`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询从库符合时段的表名
|
||||
* @param {String} start_time
|
||||
* @param {String} end_time
|
||||
* @returns
|
||||
*/
|
||||
async query_slave_table(start_time, end_time) {
|
||||
try {
|
||||
const sql = `(SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) UNION (SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;`;
|
||||
const data = await this.sharesdb_slave.exec(sql, [end_time, start_time, end_time]);
|
||||
const result = [];
|
||||
if (data.length !== 0) {
|
||||
for (let item of data) {
|
||||
result.push(`${this.coin}_block_detail_${item.from}_${Math.trunc(item.to - 1)}`);
|
||||
}
|
||||
}
|
||||
result.push(`${this.coin}_blk_detail`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 查询时段内accepts,主从同时查询
|
||||
async query_accepts(start_time, end_time, enable) {
|
||||
try {
|
||||
if (this.count === undefined) this.count = 0;
|
||||
|
||||
const generateUnionSql = (tables) => {
|
||||
let sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < tables.length; i++) {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables[i]} WHERE date >= ? AND date < ?`;
|
||||
if (i < tables.length - 1) {
|
||||
sql += ` \nUNION ALL\n `;
|
||||
}
|
||||
}
|
||||
sql += `) AS combined_tables GROUP BY user, miner;`;
|
||||
return sql;
|
||||
};
|
||||
|
||||
let sql, slave_sql;
|
||||
if (enable) {
|
||||
const [tables_name, slave_tables_name] = await Promise.all([this.query_table(start_time, end_time), this.query_slave_table(start_time, end_time)]);
|
||||
|
||||
sql = tables_name.length <= 1 ? `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= ? AND date < ? GROUP BY user, miner;` : generateUnionSql(tables_name);
|
||||
|
||||
slave_sql = slave_tables_name.length <= 1 ? `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= ? AND date < ? GROUP BY user, miner;` : generateUnionSql(slave_tables_name);
|
||||
|
||||
const [accepts_data, slave_accepts] = await Promise.all([this.sharesdb.exec(sql, [start_time, end_time]), this.sharesdb_slave.exec(slave_sql, [start_time, end_time])]);
|
||||
|
||||
return this.merge(accepts_data.concat(slave_accepts));
|
||||
} else {
|
||||
const tables_name = await this.query_table(start_time, end_time);
|
||||
|
||||
sql = tables_name.length <= 1 ? `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= ? AND date < ? GROUP BY user, miner;` : generateUnionSql(tables_name);
|
||||
|
||||
const accepts_data = await this.sharesdb.exec(sql, [start_time, end_time]);
|
||||
return this.merge(accepts_data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error in query_accepts: ${err.message}`);
|
||||
await this.sleep(1000 * 15);
|
||||
if (this.count > 3) { // 重试4次,1分钟
|
||||
this.count = 0;
|
||||
throw err;
|
||||
}
|
||||
this.count++;
|
||||
return this.query_accepts(start_time, end_time, enable);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询当天miners状态,排除掉超过1天没有提交的矿工
|
||||
async query_miners(time) {
|
||||
try {
|
||||
const sql = `SELECT date, user, miner, state, ratio, last_submit FROM ${this.coin}_miners WHERE last_submit >= DATE(?) - INTERVAL 1 DAY;`;
|
||||
const miners_state = await this.pooldb.exec(sql, [time]);
|
||||
return miners_state;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_mhs(data) {
|
||||
if (data.length === 0 || !data || data.size === 0) {
|
||||
console.log(Date.now(), ":30分钟没有新增矿机提交数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let sql = `INSERT INTO ${this.coin}_mhsv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { user, miner, date, mhs30m, mhs24h, state, last_submit } = item;
|
||||
sql += `(?, ?, ?, ?, ?, ?, ?), `;
|
||||
values.push(user, miner, date, mhs30m, mhs24h, state, last_submit);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
await this.hashratedb.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_mhs_real(data) {
|
||||
if (data.length === 0 || !data || data.size === 0) {
|
||||
console.log(Date.now(), ":5分钟没有新增矿机提交数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const del_sql = `DELETE FROM ${this.coin}_mhs_realv2 WHERE id > 0;`;
|
||||
let sql = `INSERT INTO ${this.coin}_mhs_realv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { user, miner, date, mhs30m, mhs24h, state, last_submit } = item;
|
||||
sql += `(?, ?, ?, ?, ?, ? ,?), `;
|
||||
values.push(user, miner, date, mhs30m, mhs24h, state, last_submit);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
// sql += ` AS new_values ON DUPLICATE KEY UPDATE date = new_values.date, mhs30m = new_values.mhs30m, mhs24h = new_values.mhs24h, state = new_values.state, last_submit = new_values.last_submit;`;
|
||||
const sqls = [{ sql: del_sql }, { sql, param: values }];
|
||||
await this.hashratedb.exec_transaction_together(sqls);
|
||||
} catch (err) {
|
||||
// 处理错误
|
||||
console.error("Transaction failed: ", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_hashrate_miners_accepts(end_time) {
|
||||
try {
|
||||
const ymd_last_30m = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 30);
|
||||
const ymd_last_24h = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 60 * 24);
|
||||
const state_sql = `SELECT t1.*
|
||||
FROM ${this.coin}_minersv2 t1
|
||||
INNER JOIN (
|
||||
SELECT user, miner, MAX(date) AS max_date
|
||||
FROM ${this.coin}_minersv2
|
||||
WHERE date <= ?
|
||||
GROUP BY user, miner
|
||||
) t2
|
||||
ON t1.user = t2.user AND t1.miner = t2.miner AND t1.date = t2.max_date;`;
|
||||
const mhs30m_sql = `SELECT SUM(accepts) AS accepts_30min, user, miner FROM ${this.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;`;
|
||||
const mhs24h_sql = `SELECT SUM(accepts) AS accepts_24h, user, miner FROM ${this.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;`;
|
||||
const [state, mhs30m, mhs24h] = await Promise.all([this.hashratedb.exec(state_sql, [end_time]), this.hashratedb.exec(mhs30m_sql, [ymd_last_30m, end_time]), this.hashratedb.exec(mhs24h_sql, [ymd_last_24h, end_time])]);
|
||||
|
||||
const hashrate_map = new Map();
|
||||
|
||||
state.forEach((item) => {
|
||||
const { date, user, miner, state, last_submit } = item;
|
||||
hashrate_map.set(`${user}:${miner}`, { date: end_time, user, miner, state, last_submit, mhs30m: 0, mhs24h: 0 });
|
||||
});
|
||||
|
||||
mhs30m.forEach((item) => {
|
||||
const { accepts_30min, user, miner } = item;
|
||||
|
||||
const values = hashrate_map.get(`${user}:${miner}`);
|
||||
|
||||
values.mhs30m = this.calculate_hashrate(accepts_30min, 60 * 30, "MH/s");
|
||||
|
||||
hashrate_map.set(`${user}:${miner}`, values);
|
||||
});
|
||||
mhs24h.forEach((item) => {
|
||||
const { accepts_24h, user, miner } = item;
|
||||
const values = hashrate_map.get(`${user}:${miner}`);
|
||||
|
||||
values.mhs24h = this.calculate_hashrate(accepts_24h, 60 * 60 * 24, "MH/s");
|
||||
|
||||
hashrate_map.set(`${user}:${miner}`, values);
|
||||
});
|
||||
return hashrate_map;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_hashrate_miners_table(end_time) {
|
||||
try {
|
||||
const ymd = end_time.split(":");
|
||||
const date = ymd[0] + ":" + ymd[1] + ":" + "00";
|
||||
// 计算最近5分钟accepts,最新矿机状态
|
||||
const start_time = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 5);
|
||||
let enable = (await this.redis.get(`${this.coin}:enable`)) || false;
|
||||
|
||||
let [accepts, miners_state] = await Promise.all([this.query_accepts(start_time, end_time, enable), this.query_miners(end_time)]);
|
||||
|
||||
// 创建nexa_miners表所需要的map
|
||||
const miners_map = new Map();
|
||||
// 判断各种情况
|
||||
if (accepts.length === 0 && miners_state.length === 0) {
|
||||
// 历史上没有矿工接入
|
||||
return;
|
||||
} else if (accepts.length !== 0 && miners_state.length === 0) {
|
||||
// 主库出了问题,基本不可能出现这种情况
|
||||
return;
|
||||
} else if (accepts.length === 0 && miners_state.length !== 0) {
|
||||
// 最近5分钟没有矿工接入,直接将m2pooldb-nexa_miners表中所有矿工的accepts更新为0,并放入nexa_miners表需要的map中
|
||||
miners_state.forEach((item) => {
|
||||
const { user, miner, state, last_submit } = item;
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, state: "offline", last_submit, accepts: 0 });
|
||||
});
|
||||
} else {
|
||||
// 先找到所有最近5分钟有提交的矿机
|
||||
accepts.forEach((item) => {
|
||||
const { user, miner, accepts, last_submit } = item;
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, accepts, last_submit, state: "online" });
|
||||
});
|
||||
// 再将stats表有记录矿机,但最近5分钟没有提交的矿机合并进去
|
||||
miners_state.forEach((item) => {
|
||||
const { user, miner, state, last_submit } = item;
|
||||
if (!miners_map.get(`${user}:${miner}`)) {
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, accepts: 0, last_submit, state });
|
||||
}
|
||||
});
|
||||
}
|
||||
// 将指定时段内的数据插入nexa_miners表
|
||||
let insert_miners_table_sql = `INSERT INTO ${this.coin}_minersv2(user, miner, date, accepts, state, last_submit) VALUES `;
|
||||
const miners_table_values = [];
|
||||
miners_map.forEach((item) => {
|
||||
const { user, miner, date, accepts, state, last_submit } = item;
|
||||
insert_miners_table_sql += `(?, ?, ?, ?, ?, ?), `;
|
||||
miners_table_values.push(user, miner, date, accepts, state, last_submit);
|
||||
});
|
||||
insert_miners_table_sql = insert_miners_table_sql.slice(0, -2);
|
||||
await this.hashratedb.exec_transaction(insert_miners_table_sql, miners_table_values);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HashRate;
|
||||
463
test/test1.js
Normal file
463
test/test1.js
Normal file
@@ -0,0 +1,463 @@
|
||||
const Times = require("../public/times");
|
||||
const Decimal = require("decimal");
|
||||
const fs = require("fs");
|
||||
const DBPool = require("../lib/mysql");
|
||||
const Cache = require("../lib/redis");
|
||||
const { NEXARPCNode, GRSRPCNode, MONARPCNode, DGBRPCNode, RXDRPCNode } = require("../lib/node");
|
||||
|
||||
class Init {
|
||||
constructor(coin, method) {
|
||||
this.coin = coin;
|
||||
const config = fs.readFileSync(`../config/${coin}.conf`, "utf-8");
|
||||
const { master, slave, redis_options, node_options, distribution_conf, MAX_MATURE, REPORT_ADDRESS } = JSON.parse(config);
|
||||
const { pooldb, sharesdb, distribution, hashrate, users_addresses, balance } = master;
|
||||
const { pooldb_slave, sharesdb_slave } = slave;
|
||||
const { node1, node2 } = node_options;
|
||||
const { redis1 } = redis_options;
|
||||
const { POOL_FEE } = distribution_conf;
|
||||
const node_map = {
|
||||
mona: MONARPCNode,
|
||||
nexa: NEXARPCNode,
|
||||
grs: GRSRPCNode,
|
||||
dgbs: DGBRPCNode,
|
||||
dgbq: DGBRPCNode,
|
||||
dgbo: DGBRPCNode,
|
||||
rxd: RXDRPCNode,
|
||||
};
|
||||
|
||||
switch (method) {
|
||||
case "hashrate":
|
||||
this.sharesdb = new DBPool(coin, sharesdb);
|
||||
this.sharesdb_slave = new DBPool(coin, sharesdb_slave);
|
||||
this.pooldb = new DBPool(coin, pooldb);
|
||||
this.redis = new Cache(redis1);
|
||||
// this.pooldb_slave = new DBPool(coin, pooldb_slave)
|
||||
this.hashratedb = new DBPool(coin, hashrate);
|
||||
break;
|
||||
case "report":
|
||||
this.REPORT_ADDRESS = REPORT_ADDRESS;
|
||||
this.node = new node_map[coin](node2);
|
||||
this.distribution = new DBPool(coin, distribution);
|
||||
this.redis = new Cache(redis1);
|
||||
break;
|
||||
case "clear":
|
||||
this.pooldb = new DBPool(coin, pooldb);
|
||||
this.sharesdb = new DBPool(coin, sharesdb);
|
||||
this.hashratedb = new DBPool(coin, hashrate);
|
||||
break;
|
||||
case "distribution":
|
||||
this.pooldb = new DBPool(coin, pooldb);
|
||||
this.hashratedb = new DBPool(coin, hashrate);
|
||||
this.distributiondb = new DBPool(coin, distribution);
|
||||
this.users_addresses = new DBPool(coin, users_addresses);
|
||||
this.node = new node_map[coin](node2);
|
||||
this.REPORT_ADDRESS = REPORT_ADDRESS;
|
||||
this.POOL_FEE = POOL_FEE;
|
||||
console.log(`当前手续费率为:${POOL_FEE}`);
|
||||
// this.balance = new DBPool(coin, balance)
|
||||
break;
|
||||
case "balance":
|
||||
this.distribution = new DBPool(coin, distribution);
|
||||
this.balancedb = new DBPool(coin, balance);
|
||||
break;
|
||||
case "confirm":
|
||||
this.MAX_MATURE = MAX_MATURE;
|
||||
this.REPORT_ADDRESS = REPORT_ADDRESS;
|
||||
this.node = new node_map[coin](node2);
|
||||
this.distribution = new DBPool(coin, distribution);
|
||||
this.pooldb = new DBPool(coin, pooldb);
|
||||
break;
|
||||
case "stats":
|
||||
this.pooldb = new DBPool(coin, pooldb);
|
||||
this.hashratedb = new DBPool(coin, hashrate);
|
||||
this.distribution = new DBPool(coin, distribution);
|
||||
break;
|
||||
default:
|
||||
throw `暂不支持${method}方法 init`;
|
||||
}
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
class HashRate extends Init {
|
||||
constructor(coin) {
|
||||
const method = "hashrate";
|
||||
super(coin, method);
|
||||
this.diffOneShareHashsAvg = 2 ** 32 - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算hash
|
||||
* @param {Number} accepts 时段内接受总数
|
||||
* @param {Number} seconds 时段秒数
|
||||
* @param {String} unit H/s、KH/s、MH/s、GH/s、TH/s、PH/s、EH/s
|
||||
* @returns
|
||||
*/
|
||||
calculate_hashrate(accepts, seconds, unit) {
|
||||
let num;
|
||||
switch (unit) {
|
||||
case "H/s":
|
||||
num = 1;
|
||||
break;
|
||||
case "KH/s":
|
||||
num = 1_000;
|
||||
break;
|
||||
case "MH/s":
|
||||
num = 1_000_000;
|
||||
break;
|
||||
case "GH/s":
|
||||
num = 1_000_000_000;
|
||||
break;
|
||||
case "TH/s":
|
||||
num = 1_000_000_000_000;
|
||||
break;
|
||||
case "PH/s":
|
||||
num = 1_000_000_000_000_000;
|
||||
break;
|
||||
case "EH/s":
|
||||
num = 10 ** 18;
|
||||
break;
|
||||
default:
|
||||
throw `${unit}不是已知单位`;
|
||||
}
|
||||
const hashrate = (accepts * this.diffOneShareHashsAvg) / seconds / num;
|
||||
return hashrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将主、备查询出来的数据合并
|
||||
* @param {*} data [{user:"", miner:"", accepts:100},{user:"", miner:"", accepts:100}...]
|
||||
* @returns
|
||||
*/
|
||||
merge(data) {
|
||||
// 创建一个 Map 来存储 user 和 miner 组合的结果
|
||||
const results = new Map();
|
||||
|
||||
data.forEach((item) => {
|
||||
const key = `${item.user}-${item.miner}`;
|
||||
|
||||
if (results.has(key)) {
|
||||
const existing = results.get(key);
|
||||
existing.accepts += parseFloat(item.accepts);
|
||||
if (new Date(item.last_submit) > new Date(existing.last_submit)) {
|
||||
existing.last_submit = item.last_submit;
|
||||
}
|
||||
results.set(key, existing);
|
||||
} else {
|
||||
results.set(key, {
|
||||
user: item.user,
|
||||
miner: item.miner,
|
||||
accepts: parseFloat(item.accepts),
|
||||
last_submit: item.last_submit,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 将结果转换为数组
|
||||
const resultArray = Array.from(results.values());
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主库符合时段的表名
|
||||
* @param {String} start_time
|
||||
* @param {String} end_time
|
||||
* @returns
|
||||
*/
|
||||
async query_table(start_time, end_time) {
|
||||
try {
|
||||
const sql = `(SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) UNION (SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;`;
|
||||
const data = await this.sharesdb.exec(sql, [end_time, start_time, end_time])
|
||||
const result = [];
|
||||
if (data.length !== 0) {
|
||||
for (let item of data) {
|
||||
result.push(`${this.coin}_block_detail_${item.from}_${Math.trunc(item.to - 1)}`);
|
||||
}
|
||||
}
|
||||
result.push(`${this.coin}_blk_detail`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询从库符合时段的表名
|
||||
* @param {String} start_time
|
||||
* @param {String} end_time
|
||||
* @returns
|
||||
*/
|
||||
async query_slave_table(start_time, end_time) {
|
||||
try {
|
||||
const sql = `(SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) UNION (SELECT date, \`from\`, \`to\` FROM ${this.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;`;
|
||||
const data = await this.sharesdb_slave.exec(sql, [end_time, start_time, end_time])
|
||||
const result = [];
|
||||
if (data.length !== 0) {
|
||||
for (let item of data) {
|
||||
result.push(`${this.coin}_block_detail_${item.from}_${Math.trunc(item.to - 1)}`);
|
||||
}
|
||||
}
|
||||
result.push(`${this.coin}_blk_detail`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 查询时段内accepts,主从同时查询
|
||||
async query_accepts(start_time, end_time, enable) {
|
||||
try {
|
||||
if (this.count === undefined) this.count = 0;
|
||||
if (enable) {
|
||||
const [tables_name, slave_tables_name] = await Promise.all([this.query_table(start_time, end_time), this.query_slave_table(start_time, end_time)]);
|
||||
|
||||
// 查询主库符合条件的数据
|
||||
let sql = ``;
|
||||
if (tables_name.length <= 1) {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= "${start_time}" AND date < "${end_time}" GROUP BY user, miner;`;
|
||||
} else {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < tables_name.length; i++) {
|
||||
if (i < tables_name.length - 1) {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}" \nUNION ALL\n`;
|
||||
} else {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}") AS combined_tables GROUP BY user, miner;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
let slave_sql = ``;
|
||||
if (slave_tables_name.length <= 1) {
|
||||
slave_sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= "${start_time}" AND date < "${end_time}" GROUP BY user, miner;`;
|
||||
} else {
|
||||
slave_sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < slave_tables_name.length; i++) {
|
||||
if (i < slave_tables_name.length - 1) {
|
||||
slave_sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${slave_tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}" \nUNION ALL\n`;
|
||||
} else {
|
||||
slave_sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${slave_tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}") AS combined_tables GROUP BY user, miner;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 执行查询,并将结果合并
|
||||
const [accepts_data, slave_accepts] = await Promise.all([this.sharesdb.exec(sql), this.sharesdb_slave.exec(slave_sql)]);
|
||||
const accepts = this.merge(accepts_data.concat(slave_accepts)); // 合并主备accepts
|
||||
return accepts;
|
||||
} else {
|
||||
const tables_name = await this.query_table(start_time, end_time);
|
||||
let sql = ``;
|
||||
if (tables_name.length <= 1) {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ${this.coin}_blk_detail WHERE date >= "${start_time}" AND date < "${end_time}" GROUP BY user, miner;`;
|
||||
} else {
|
||||
sql = `SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( `;
|
||||
for (let i = 0; i < tables_name.length; i++) {
|
||||
if (i < tables_name.length - 1) {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}" \nUNION ALL\n`;
|
||||
} else {
|
||||
sql += `SELECT date, user, miner, miner_diff, pool_diff FROM ${tables_name[i]} WHERE date >= "${start_time}" AND date < "${end_time}") AS combined_tables GROUP BY user, miner;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const accepts_data = await this.sharesdb.exec(sql);
|
||||
const slave_accepts = [];
|
||||
const accepts = this.merge(accepts_data.concat(slave_accepts)); // 合并主备accepts
|
||||
return accepts;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error in query_accepts: ${err.message}`);
|
||||
await this.sleep(1000 * 15);
|
||||
if (this.count > 3) { // 重试4次,1分钟
|
||||
this.count = 0;
|
||||
throw err;
|
||||
}
|
||||
this.count++;
|
||||
return this.query_accepts(start_time, end_time, enable);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询当天miners状态,排除掉超过1天没有提交的矿工
|
||||
async query_miners(time) {
|
||||
try {
|
||||
const sql = `SELECT date, user, miner, state, ratio, last_submit FROM ${this.coin}_miners WHERE last_submit >= DATE(?) - INTERVAL 1 DAY;`;
|
||||
const miners_state = await this.pooldb.exec(sql, [time]);
|
||||
return miners_state;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_mhs(data) {
|
||||
if (data.length === 0 || !data || data.size === 0) {
|
||||
console.log(Date.now(), ":30分钟没有新增矿机提交数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let sql = `INSERT INTO ${this.coin}_mhsv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { user, miner, date, mhs30m, mhs24h, state, last_submit } = item;
|
||||
sql += `(?, ?, ?, ?, ?, ?, ?), `;
|
||||
values.push(user, miner, date, mhs30m, mhs24h, state, last_submit);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
await this.hashratedb.exec_transaction(sql, values);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_mhs_real(data) {
|
||||
if (data.length === 0 || !data || data.size === 0) {
|
||||
console.log(Date.now(), ":5分钟没有新增矿机提交数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const del_sql = `DELETE FROM ${this.coin}_mhs_realv2 WHERE id > 0;`;
|
||||
let sql = `INSERT INTO ${this.coin}_mhs_realv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES `;
|
||||
const values = [];
|
||||
data.forEach((item) => {
|
||||
const { user, miner, date, mhs30m, mhs24h, state, last_submit } = item;
|
||||
sql += `(?, ?, ?, ?, ?, ? ,?), `;
|
||||
values.push(user, miner, date, mhs30m, mhs24h, state, last_submit);
|
||||
});
|
||||
sql = sql.slice(0, -2);
|
||||
// sql += ` AS new_values ON DUPLICATE KEY UPDATE date = new_values.date, mhs30m = new_values.mhs30m, mhs24h = new_values.mhs24h, state = new_values.state, last_submit = new_values.last_submit;`;
|
||||
const sqls = [{ sql: del_sql }, { sql, param: values }];
|
||||
await this.hashratedb.exec_transaction_together(sqls);
|
||||
} catch (err) {
|
||||
// 处理错误
|
||||
console.error("Transaction failed: ", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async query_hashrate_miners_accepts(end_time) {
|
||||
try {
|
||||
const ymd_last_30m = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 30);
|
||||
const ymd_last_24h = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 60 * 24);
|
||||
const state_sql = `SELECT t1.*
|
||||
FROM ${this.coin}_minersv2 t1
|
||||
INNER JOIN (
|
||||
SELECT user, miner, MAX(date) AS max_date
|
||||
FROM ${this.coin}_minersv2
|
||||
WHERE date <= ?
|
||||
GROUP BY user, miner
|
||||
) t2
|
||||
ON t1.user = t2.user AND t1.miner = t2.miner AND t1.date = t2.max_date;`;
|
||||
const mhs30m_sql = `SELECT SUM(accepts) AS accepts_30min, user, miner FROM ${this.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;`;
|
||||
const mhs24h_sql = `SELECT SUM(accepts) AS accepts_24h, user, miner FROM ${this.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;`;
|
||||
const [state, mhs30m, mhs24h] = await Promise.all([this.hashratedb.exec(state_sql, [end_time]), this.hashratedb.exec(mhs30m_sql, [ymd_last_30m, end_time]), this.hashratedb.exec(mhs24h_sql, [ymd_last_24h, end_time])]);
|
||||
|
||||
const hashrate_map = new Map();
|
||||
|
||||
state.forEach((item) => {
|
||||
const { date, user, miner, state, last_submit } = item;
|
||||
hashrate_map.set(`${user}:${miner}`, { date: end_time, user, miner, state, last_submit, mhs30m: 0, mhs24h: 0 });
|
||||
});
|
||||
|
||||
mhs30m.forEach((item) => {
|
||||
const { accepts_30min, user, miner } = item;
|
||||
|
||||
const values = hashrate_map.get(`${user}:${miner}`);
|
||||
|
||||
values.mhs30m = this.calculate_hashrate(accepts_30min, 60 * 30, "MH/s");
|
||||
|
||||
hashrate_map.set(`${user}:${miner}`, values);
|
||||
});
|
||||
mhs24h.forEach((item) => {
|
||||
const { accepts_24h, user, miner } = item;
|
||||
const values = hashrate_map.get(`${user}:${miner}`);
|
||||
|
||||
values.mhs24h = this.calculate_hashrate(accepts_24h, 60 * 60 * 24, "MH/s");
|
||||
|
||||
hashrate_map.set(`${user}:${miner}`, values);
|
||||
});
|
||||
return hashrate_map;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async insert_hashrate_miners_table(end_time) {
|
||||
try {
|
||||
const ymd = end_time.split(":");
|
||||
const date = ymd[0] + ":" + ymd[1] + ":" + "00";
|
||||
// 计算最近5分钟accepts,最新矿机状态
|
||||
const start_time = Times.utcTime(new Date(end_time).valueOf() - 1000 * 60 * 5);
|
||||
let enable = (await this.redis.get(`${this.coin}:enable`)) || false;
|
||||
|
||||
let [accepts, miners_state] = await Promise.all([this.query_accepts(start_time, end_time, enable), this.query_miners(end_time)]);
|
||||
|
||||
// 创建nexa_miners表所需要的map
|
||||
const miners_map = new Map();
|
||||
// 判断各种情况
|
||||
if (accepts.length === 0 && miners_state.length === 0) {
|
||||
// 历史上没有矿工接入
|
||||
return;
|
||||
} else if (accepts.length !== 0 && miners_state.length === 0) {
|
||||
// 主库出了问题,基本不可能出现这种情况
|
||||
return;
|
||||
} else if (accepts.length === 0 && miners_state.length !== 0) {
|
||||
// 最近5分钟没有矿工接入,直接将m2pooldb-nexa_miners表中所有矿工的accepts更新为0,并放入nexa_miners表需要的map中
|
||||
miners_state.forEach((item) => {
|
||||
const { user, miner, state, last_submit } = item;
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, state: "offline", last_submit, accepts: 0 });
|
||||
});
|
||||
} else {
|
||||
// 先找到所有最近5分钟有提交的矿机
|
||||
accepts.forEach((item) => {
|
||||
const { user, miner, accepts, last_submit } = item;
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, accepts, last_submit, state: "online" });
|
||||
});
|
||||
// 再将stats表有记录矿机,但最近5分钟没有提交的矿机合并进去
|
||||
miners_state.forEach((item) => {
|
||||
const { user, miner, state, last_submit } = item;
|
||||
if (!miners_map.get(`${user}:${miner}`)) {
|
||||
miners_map.set(`${user}:${miner}`, { date, user, miner, accepts: 0, last_submit, state });
|
||||
}
|
||||
});
|
||||
}
|
||||
// 将指定时段内的数据插入nexa_miners表
|
||||
let insert_miners_table_sql = `INSERT INTO ${this.coin}_minersv2(user, miner, date, accepts, state, last_submit) VALUES `;
|
||||
const miners_table_values = [];
|
||||
miners_map.forEach((item) => {
|
||||
const { user, miner, date, accepts, state, last_submit } = item;
|
||||
insert_miners_table_sql += `(?, ?, ?, ?, ?, ?), `;
|
||||
miners_table_values.push(user, miner, date, accepts, state, last_submit);
|
||||
});
|
||||
insert_miners_table_sql = insert_miners_table_sql.slice(0, -2);
|
||||
await this.hashratedb.exec_transaction(insert_miners_table_sql, miners_table_values);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const coin = "enx";
|
||||
const hashrate = new HashRate(coin);
|
||||
// schedule.scheduleJob({ minute: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55], second: [30] }, async () => {
|
||||
|
||||
// });
|
||||
async function main(){
|
||||
const ymd_now = Times.utcTime(Date.now().valueOf());
|
||||
const ymd = ymd_now.split(":");
|
||||
const end_time = ymd[0] + ":" + ymd[1] + ":" + "00";
|
||||
await hashrate.insert_hashrate_miners_table(end_time);
|
||||
const currentMinute = new Date().getMinutes();
|
||||
|
||||
const data = await hashrate.query_hashrate_miners_accepts(end_time);
|
||||
if (currentMinute === 0 || currentMinute === 30) {
|
||||
await hashrate.insert_mhs(data);
|
||||
await hashrate.insert_mhs_real(data);
|
||||
} else {
|
||||
await hashrate.insert_mhs_real(data);
|
||||
}
|
||||
}
|
||||
main()
|
||||
Reference in New Issue
Block a user