Replacing ln-service library and wait for graph sync.

This commit is contained in:
softsimon 2022-07-06 21:43:47 +02:00
parent 1f2254681a
commit 73c4a934ce
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
7 changed files with 73 additions and 35 deletions

View File

@ -37,7 +37,7 @@
"bolt07": "^1.8.1", "bolt07": "^1.8.1",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"express": "^4.18.0", "express": "^4.18.0",
"ln-service": "^53.17.4", "lightning": "^5.16.3",
"mysql2": "2.3.3", "mysql2": "2.3.3",
"node-worker-threads-pool": "^1.5.1", "node-worker-threads-pool": "^1.5.1",
"socks-proxy-agent": "~7.0.0", "socks-proxy-agent": "~7.0.0",

View File

@ -3,5 +3,5 @@ import { ILightningApi } from './lightning-api.interface';
export interface AbstractLightningApi { export interface AbstractLightningApi {
$getNetworkInfo(): Promise<ILightningApi.NetworkInfo>; $getNetworkInfo(): Promise<ILightningApi.NetworkInfo>;
$getNetworkGraph(): Promise<ILightningApi.NetworkGraph>; $getNetworkGraph(): Promise<ILightningApi.NetworkGraph>;
$getChanInfo(id: string): Promise<ILightningApi.Channel>; $getInfo(): Promise<ILightningApi.Info>;
} }

View File

@ -21,17 +21,17 @@ export namespace ILightningApi {
policies: Policy[]; policies: Policy[];
transaction_id: string; transaction_id: string;
transaction_vout: number; transaction_vout: number;
updated_at: string; updated_at?: string;
} }
interface Policy { interface Policy {
public_key: string; public_key: string;
base_fee_mtokens?: number; base_fee_mtokens?: string;
cltv_delta?: number; cltv_delta?: number;
fee_rate?: number; fee_rate?: number;
is_disabled?: boolean; is_disabled?: boolean;
max_htlc_mtokens?: number; max_htlc_mtokens?: string;
min_htlc_mtokens?: number; min_htlc_mtokens?: string;
updated_at?: string; updated_at?: string;
} }
@ -41,13 +41,31 @@ export namespace ILightningApi {
features: Feature[]; features: Feature[];
public_key: string; public_key: string;
sockets: string[]; sockets: string[];
updated_at: string; updated_at?: string;
} }
interface Feature { export interface Info {
chains: string[];
color: string;
active_channels_count: number;
alias: string;
current_block_hash: string;
current_block_height: number;
features: Feature[];
is_synced_to_chain: boolean;
is_synced_to_graph: boolean;
latest_block_at: string;
peers_count: number;
pending_channels_count: number;
public_key: string;
uris: any[];
version: string;
}
export interface Feature {
bit: number; bit: number;
is_known: boolean; is_known: boolean;
is_required: boolean; is_required: boolean;
type: string; type?: string;
} }
} }

View File

@ -1,7 +1,7 @@
import { AbstractLightningApi } from '../lightning-api-abstract-factory'; import { AbstractLightningApi } from '../lightning-api-abstract-factory';
import { ILightningApi } from '../lightning-api.interface'; import { ILightningApi } from '../lightning-api.interface';
import * as fs from 'fs'; import * as fs from 'fs';
import * as lnService from 'ln-service'; import { authenticatedLndGrpc, getWalletInfo, getNetworkGraph, getNetworkInfo } from 'lightning';
import config from '../../../config'; import config from '../../../config';
import logger from '../../../logger'; import logger from '../../../logger';
@ -15,7 +15,7 @@ class LndApi implements AbstractLightningApi {
const tls = fs.readFileSync(config.LND.TLS_CERT_PATH).toString('base64'); const tls = fs.readFileSync(config.LND.TLS_CERT_PATH).toString('base64');
const macaroon = fs.readFileSync(config.LND.MACAROON_PATH).toString('base64'); const macaroon = fs.readFileSync(config.LND.MACAROON_PATH).toString('base64');
const { lnd } = lnService.authenticatedLndGrpc({ const { lnd } = authenticatedLndGrpc({
cert: tls, cert: tls,
macaroon: macaroon, macaroon: macaroon,
socket: config.LND.SOCKET, socket: config.LND.SOCKET,
@ -29,15 +29,16 @@ class LndApi implements AbstractLightningApi {
} }
async $getNetworkInfo(): Promise<ILightningApi.NetworkInfo> { async $getNetworkInfo(): Promise<ILightningApi.NetworkInfo> {
return await lnService.getNetworkInfo({ lnd: this.lnd }); return await getNetworkInfo({ lnd: this.lnd });
}
async $getInfo(): Promise<ILightningApi.Info> {
// @ts-ignore
return await getWalletInfo({ lnd: this.lnd });
} }
async $getNetworkGraph(): Promise<ILightningApi.NetworkGraph> { async $getNetworkGraph(): Promise<ILightningApi.NetworkGraph> {
return await lnService.getNetworkGraph({ lnd: this.lnd }); return await getNetworkGraph({ lnd: this.lnd });
}
async $getChanInfo(id: string): Promise<ILightningApi.Channel> {
return await lnService.getChannel({ lnd: this.lnd, id });
} }
} }

View File

@ -1,5 +1,5 @@
import express from "express"; import express from "express";
import { Application, Request, Response, NextFunction, Express } from 'express'; import { Application, Request, Response, NextFunction } from 'express';
import * as http from 'http'; import * as http from 'http';
import * as WebSocket from 'ws'; import * as WebSocket from 'ws';
import cluster from 'cluster'; import cluster from 'cluster';
@ -28,12 +28,12 @@ import { Common } from './api/common';
import poolsUpdater from './tasks/pools-updater'; import poolsUpdater from './tasks/pools-updater';
import indexer from './indexer'; import indexer from './indexer';
import priceUpdater from './tasks/price-updater'; import priceUpdater from './tasks/price-updater';
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
import nodeSyncService from './tasks/lightning/node-sync.service';
import lightningStatsUpdater from './tasks/lightning/stats-updater.service';
import nodesRoutes from './api/explorer/nodes.routes'; import nodesRoutes from './api/explorer/nodes.routes';
import channelsRoutes from './api/explorer/channels.routes'; import channelsRoutes from './api/explorer/channels.routes';
import generalLightningRoutes from './api/explorer/general.routes'; import generalLightningRoutes from './api/explorer/general.routes';
import lightningStatsUpdater from './tasks/lightning/stats-updater.service';
import nodeSyncService from './tasks/lightning/node-sync.service';
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
class Server { class Server {
private wss: WebSocket.Server | undefined; private wss: WebSocket.Server | undefined;
@ -137,9 +137,7 @@ class Server {
if (config.LIGHTNING.ENABLED) { if (config.LIGHTNING.ENABLED) {
nodeSyncService.$startService() nodeSyncService.$startService()
.then(() => { .then(() => lightningStatsUpdater.$startService());
lightningStatsUpdater.$startService();
});
} }
this.server.listen(config.MEMPOOL.HTTP_PORT, () => { this.server.listen(config.MEMPOOL.HTTP_PORT, () => {

View File

@ -245,7 +245,6 @@ class NodeSyncService {
const fromChannel = chanNumber({ channel: channel.id }).number; const fromChannel = chanNumber({ channel: channel.id }).number;
try { try {
const d = new Date(Date.parse(channel.updated_at));
const query = `INSERT INTO channels const query = `INSERT INTO channels
( (
id, id,
@ -360,7 +359,7 @@ class NodeSyncService {
private async $saveNode(node: ILightningApi.Node): Promise<void> { private async $saveNode(node: ILightningApi.Node): Promise<void> {
try { try {
const updatedAt = this.utcDateToMysql(node.updated_at); const updatedAt = node.updated_at ? this.utcDateToMysql(node.updated_at) : '0000-00-00 00:00:00';
const sockets = node.sockets.join(','); const sockets = node.sockets.join(',');
const query = `INSERT INTO nodes( const query = `INSERT INTO nodes(
public_key, public_key,

View File

@ -6,21 +6,43 @@ class LightningStatsUpdater {
constructor() {} constructor() {}
public async $startService() { public async $startService() {
logger.info('Starting Stats service'); logger.info('Starting Lightning Stats service');
let isInSync = false;
let error: any;
try {
error = null;
isInSync = await this.$lightningIsSynced();
} catch (e) {
error = e;
}
if (!isInSync) {
if (error) {
logger.warn('Was not able to fetch Lightning Node status: ' + (error instanceof Error ? error.message : error) + '. Retrying in 1 minute...');
} else {
logger.notice('The Lightning graph is not yet in sync. Retrying in 1 minute...');
}
setTimeout(() => this.$startService(), 60 * 1000);
return;
}
const now = new Date(); const now = new Date();
const nextHourInterval = new Date(now.getFullYear(), now.getMonth(), now.getDate(), Math.floor(now.getHours() / 1) + 1, 0, 0, 0); const nextHourInterval = new Date(now.getFullYear(), now.getMonth(), now.getDate(), Math.floor(now.getHours() / 1) + 1, 0, 0, 0);
const difference = nextHourInterval.getTime() - now.getTime(); const difference = nextHourInterval.getTime() - now.getTime();
// setTimeout(() => { setTimeout(() => {
setInterval(async () => { setInterval(async () => {
await this.$runTasks(); await this.$runTasks();
}, 1000 * 60 * 60); }, 1000 * 60 * 60);
//}, difference); }, difference);
await this.$runTasks(); await this.$runTasks();
} }
private async $lightningIsSynced(): Promise<boolean> {
const nodeInfo = await lightningApi.$getInfo();
return nodeInfo.is_synced_to_chain && nodeInfo.is_synced_to_graph;
}
private async $runTasks() { private async $runTasks() {
await this.$populateHistoricalData(); await this.$populateHistoricalData();
await this.$logLightningStatsDaily(); await this.$logLightningStatsDaily();
@ -28,8 +50,6 @@ class LightningStatsUpdater {
} }
private async $logNodeStatsDaily() { private async $logNodeStatsDaily() {
logger.info(`Running daily node stats update...`);
const currentDate = new Date().toISOString().split('T')[0]; const currentDate = new Date().toISOString().split('T')[0];
try { try {
const [state]: any = await DB.query(`SELECT string FROM state WHERE name = 'last_node_stats'`); const [state]: any = await DB.query(`SELECT string FROM state WHERE name = 'last_node_stats'`);
@ -38,6 +58,8 @@ class LightningStatsUpdater {
return; return;
} }
logger.info(`Running daily node stats update...`);
const query = `SELECT nodes.public_key, c1.channels_count_left, c2.channels_count_right, c1.channels_capacity_left, c2.channels_capacity_right FROM nodes LEFT JOIN (SELECT node1_public_key, COUNT(id) AS channels_count_left, SUM(capacity) AS channels_capacity_left FROM channels WHERE channels.status < 2 GROUP BY node1_public_key) c1 ON c1.node1_public_key = nodes.public_key LEFT JOIN (SELECT node2_public_key, COUNT(id) AS channels_count_right, SUM(capacity) AS channels_capacity_right FROM channels WHERE channels.status < 2 GROUP BY node2_public_key) c2 ON c2.node2_public_key = nodes.public_key`; const query = `SELECT nodes.public_key, c1.channels_count_left, c2.channels_count_right, c1.channels_capacity_left, c2.channels_capacity_right FROM nodes LEFT JOIN (SELECT node1_public_key, COUNT(id) AS channels_count_left, SUM(capacity) AS channels_capacity_left FROM channels WHERE channels.status < 2 GROUP BY node1_public_key) c1 ON c1.node1_public_key = nodes.public_key LEFT JOIN (SELECT node2_public_key, COUNT(id) AS channels_count_right, SUM(capacity) AS channels_capacity_right FROM channels WHERE channels.status < 2 GROUP BY node2_public_key) c2 ON c2.node2_public_key = nodes.public_key`;
const [nodes]: any = await DB.query(query); const [nodes]: any = await DB.query(query);
@ -61,8 +83,6 @@ class LightningStatsUpdater {
// We only run this on first launch // We only run this on first launch
private async $populateHistoricalData() { private async $populateHistoricalData() {
logger.info(`Running historical stats population...`);
const startTime = '2018-01-13'; const startTime = '2018-01-13';
try { try {
const [rows]: any = await DB.query(`SELECT COUNT(*) FROM lightning_stats`); const [rows]: any = await DB.query(`SELECT COUNT(*) FROM lightning_stats`);
@ -70,6 +90,8 @@ class LightningStatsUpdater {
if (rows[0]['COUNT(*)'] > 0) { if (rows[0]['COUNT(*)'] > 0) {
return; return;
} }
logger.info(`Running historical stats population...`);
const [channels]: any = await DB.query(`SELECT capacity, created, closing_date FROM channels ORDER BY created ASC`); const [channels]: any = await DB.query(`SELECT capacity, created, closing_date FROM channels ORDER BY created ASC`);
let date: Date = new Date(startTime); let date: Date = new Date(startTime);
@ -138,8 +160,6 @@ class LightningStatsUpdater {
} }
private async $logLightningStatsDaily() { private async $logLightningStatsDaily() {
logger.info(`Running lightning daily stats log...`);
const currentDate = new Date().toISOString().split('T')[0]; const currentDate = new Date().toISOString().split('T')[0];
try { try {
const [state]: any = await DB.query(`SELECT string FROM state WHERE name = 'last_node_stats'`); const [state]: any = await DB.query(`SELECT string FROM state WHERE name = 'last_node_stats'`);
@ -148,6 +168,8 @@ class LightningStatsUpdater {
return; return;
} }
logger.info(`Running lightning daily stats log...`);
const networkGraph = await lightningApi.$getNetworkGraph(); const networkGraph = await lightningApi.$getNetworkGraph();
let total_capacity = 0; let total_capacity = 0;
for (const channel of networkGraph.channels) { for (const channel of networkGraph.channels) {