512 lines
12 KiB
JavaScript
512 lines
12 KiB
JavaScript
|
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
|
|||
|
};
|