app v1.0
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user