Lightning explorer base structure

This commit is contained in:
softsimon 2022-04-18 18:22:00 +04:00
parent 8487548271
commit 93b398a54f
No known key found for this signature in database
GPG key ID: 488D7DCFB5A430D7
10 changed files with 478 additions and 0 deletions

44
lightning-backend/.gitignore vendored Normal file
View file

@ -0,0 +1,44 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# production config and external assets
*.json
!mempool-config.sample.json
# compiled output
/dist
/tmp
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
#System Files
.DS_Store
Thumbs.db

View file

@ -0,0 +1,27 @@
{
"MEMPOOL": {
"NETWORK": "mainnet",
"BACKEND": "lnd",
"HTTP_PORT": 8999,
"STDOUT_LOG_MIN_PRIORITY": "debug"
},
"SYSLOG": {
"ENABLED": false,
"HOST": "127.0.0.1",
"PORT": 514,
"MIN_PRIORITY": "info",
"FACILITY": "local7"
},
"LN_NODE_AUTH": {
"TSL_CERT_PATH": "",
"MACAROON_PATH": ""
},
"DATABASE": {
"HOST": "127.0.0.1",
"PORT": 3306,
"SOCKET": "/var/run/mysql/mysql.sock",
"DATABASE": "mempool",
"USERNAME": "mempool",
"PASSWORD": "mempool"
}
}

View file

@ -0,0 +1,6 @@
import { ILightningApi } from './lightning-api.interface';
export interface AbstractLightningApi {
getNetworkInfo(): Promise<ILightningApi.NetworkInfo>;
getNetworkGraph(): Promise<ILightningApi.NetworkGraph>;
}

View file

@ -0,0 +1,13 @@
import config from '../config';
import { AbstractLightningApi } from './lightning-api-abstract-factory';
import LndApi from './lnd/lnd-api';
function lightningApiFactory(): AbstractLightningApi {
switch (config.MEMPOOL.BACKEND) {
case 'lnd':
default:
return new LndApi();
}
}
export default lightningApiFactory();

View file

@ -0,0 +1,46 @@
export namespace ILightningApi {
export interface NetworkInfo {
average_channel_size: number;
channel_count: number;
max_channel_size: number;
median_channel_size: number;
min_channel_size: number;
node_count: number;
not_recently_updated_policy_count: number;
total_capacity: number;
}
export interface NetworkGraph {
channels: Channel[];
nodes: Node[];
}
export interface Channel {
id: string;
capacity: number;
policies: Policy[];
transaction_id: string;
transaction_vout: number;
updated_at: string;
}
interface Policy {
public_key: string;
}
export interface Node {
alias: string;
color: string;
features: Feature[];
public_key: string;
sockets: string[];
updated_at: string;
}
interface Feature {
bit: number;
is_known: boolean;
is_required: boolean;
type: string;
}
}

View file

@ -0,0 +1,37 @@
import { AbstractLightningApi } from '../lightning-api-abstract-factory';
import { ILightningApi } from '../lightning-api.interface';
import * as fs from 'fs';
import * as lnService from 'ln-service';
import config from '../../config';
import logger from '../../logger';
class LndApi implements AbstractLightningApi {
private lnd: any;
constructor() {
try {
const tsl = fs.readFileSync(config.LN_NODE_AUTH.TSL_CERT_PATH).toString('base64');
const macaroon = fs.readFileSync(config.LN_NODE_AUTH.MACAROON_PATH).toString('base64');
const { lnd } = lnService.authenticatedLndGrpc({
cert: tsl,
macaroon: macaroon,
socket: 'localhost:10009',
});
this.lnd = lnd;
} catch (e) {
logger.err('Could not initiate the LND service handler: ' + (e instanceof Error ? e.message : e));
process.exit(1);
}
}
async getNetworkInfo(): Promise<ILightningApi.NetworkInfo> {
return await lnService.getNetworkInfo({ lnd: this.lnd });
}
async getNetworkGraph(): Promise<ILightningApi.NetworkGraph> {
return await lnService.getNetworkGraph({ lnd: this.lnd });
}
}
export default LndApi;

View file

@ -0,0 +1,84 @@
const configFile = require('../mempool-config.json');
interface IConfig {
MEMPOOL: {
NETWORK: 'mainnet' | 'testnet' | 'signet';
BACKEND: 'lnd' | 'cln' | 'ldk';
HTTP_PORT: number;
STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
};
SYSLOG: {
ENABLED: boolean;
HOST: string;
PORT: number;
MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
FACILITY: string;
};
LN_NODE_AUTH: {
TSL_CERT_PATH: string;
MACAROON_PATH: string;
};
DATABASE: {
HOST: string,
SOCKET: string,
PORT: number;
DATABASE: string;
USERNAME: string;
PASSWORD: string;
};
}
const defaults: IConfig = {
'MEMPOOL': {
'NETWORK': 'mainnet',
'BACKEND': 'lnd',
'HTTP_PORT': 8999,
'STDOUT_LOG_MIN_PRIORITY': 'debug',
},
'SYSLOG': {
'ENABLED': true,
'HOST': '127.0.0.1',
'PORT': 514,
'MIN_PRIORITY': 'info',
'FACILITY': 'local7'
},
'LN_NODE_AUTH': {
'TSL_CERT_PATH': '',
'MACAROON_PATH': '',
},
'DATABASE': {
'HOST': '127.0.0.1',
'SOCKET': '',
'PORT': 3306,
'DATABASE': 'mempool',
'USERNAME': 'mempool',
'PASSWORD': 'mempool'
},
};
class Config implements IConfig {
MEMPOOL: IConfig['MEMPOOL'];
SYSLOG: IConfig['SYSLOG'];
LN_NODE_AUTH: IConfig['LN_NODE_AUTH'];
DATABASE: IConfig['DATABASE'];
constructor() {
const configs = this.merge(configFile, defaults);
this.MEMPOOL = configs.MEMPOOL;
this.SYSLOG = configs.SYSLOG;
this.LN_NODE_AUTH = configs.LN_NODE_AUTH;
this.DATABASE = configs.DATABASE;
}
merge = (...objects: object[]): IConfig => {
// @ts-ignore
return objects.reduce((prev, next) => {
Object.keys(prev).forEach(key => {
next[key] = { ...next[key], ...prev[key] };
});
return next;
});
}
}
export default new Config();

View file

@ -0,0 +1,51 @@
import config from './config';
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
import logger from './logger';
import { PoolOptions } from 'mysql2/typings/mysql';
class DB {
constructor() {
if (config.DATABASE.SOCKET !== '') {
this.poolConfig.socketPath = config.DATABASE.SOCKET;
} else {
this.poolConfig.host = config.DATABASE.HOST;
}
}
private pool: Pool | null = null;
private poolConfig: PoolOptions = {
port: config.DATABASE.PORT,
database: config.DATABASE.DATABASE,
user: config.DATABASE.USERNAME,
password: config.DATABASE.PASSWORD,
connectionLimit: 10,
supportBigNumbers: true,
timezone: '+00:00',
};
public async query(query, params?) {
const pool = await this.getPool();
return pool.query(query, params);
}
public async checkDbConnection() {
try {
await this.query('SELECT ?', [1]);
logger.info('Database connection established.');
} catch (e) {
logger.err('Could not connect to database: ' + (e instanceof Error ? e.message : e));
process.exit(1);
}
}
private async getPool(): Promise<Pool> {
if (this.pool === null) {
this.pool = createPool(this.poolConfig);
this.pool.on('connection', function (newConnection: PoolConnection) {
newConnection.query(`SET time_zone='+00:00'`);
});
}
return this.pool;
}
}
export default new DB();

View file

@ -0,0 +1,25 @@
import config from './config';
import logger from './logger';
import DB from './database';
import lightningApi from './api/lightning-api-factory';
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
class LightningServer {
constructor() {
this.init();
}
async init() {
await DB.checkDbConnection();
const networkInfo = await lightningApi.getNetworkInfo();
logger.info(JSON.stringify(networkInfo));
const networkGraph = await lightningApi.getNetworkGraph();
logger.info('Network graph channels: ' + networkGraph.channels.length);
logger.info('Network graph nodes: ' + networkGraph.nodes.length);
}
}
const lightningServer = new LightningServer();

View file

@ -0,0 +1,145 @@
import config from './config';
import * as dgram from 'dgram';
class Logger {
static priorities = {
emerg: 0,
alert: 1,
crit: 2,
err: 3,
warn: 4,
notice: 5,
info: 6,
debug: 7
};
static facilities = {
kern: 0,
user: 1,
mail: 2,
daemon: 3,
auth: 4,
syslog: 5,
lpr: 6,
news: 7,
uucp: 8,
local0: 16,
local1: 17,
local2: 18,
local3: 19,
local4: 20,
local5: 21,
local6: 22,
local7: 23
};
// @ts-ignore
public emerg: ((msg: string) => void);
// @ts-ignore
public alert: ((msg: string) => void);
// @ts-ignore
public crit: ((msg: string) => void);
// @ts-ignore
public err: ((msg: string) => void);
// @ts-ignore
public warn: ((msg: string) => void);
// @ts-ignore
public notice: ((msg: string) => void);
// @ts-ignore
public info: ((msg: string) => void);
// @ts-ignore
public debug: ((msg: string) => void);
private name = 'mempool';
private client: dgram.Socket;
private network: string;
constructor() {
let prio;
for (prio in Logger.priorities) {
if (true) {
this.addprio(prio);
}
}
this.client = dgram.createSocket('udp4');
this.network = this.getNetwork();
}
private addprio(prio): void {
this[prio] = (function(_this) {
return function(msg) {
return _this.msg(prio, msg);
};
})(this);
}
private getNetwork(): string {
if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') {
return config.MEMPOOL.NETWORK;
}
return '';
}
private msg(priority, msg) {
let consolemsg, prionum, syslogmsg;
if (typeof msg === 'string' && msg.length > 0) {
while (msg[msg.length - 1].charCodeAt(0) === 10) {
msg = msg.slice(0, msg.length - 1);
}
}
const network = this.network ? ' <' + this.network + '>' : '';
prionum = Logger.priorities[priority] || Logger.priorities.info;
consolemsg = `${this.ts()} [${process.pid}] ${priority.toUpperCase()}:${network} ${msg}`;
if (config.SYSLOG.ENABLED && Logger.priorities[priority] <= Logger.priorities[config.SYSLOG.MIN_PRIORITY]) {
syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`;
this.syslog(syslogmsg);
}
if (Logger.priorities[priority] > Logger.priorities[config.MEMPOOL.STDOUT_LOG_MIN_PRIORITY]) {
return;
}
if (priority === 'warning') {
priority = 'warn';
}
if (priority === 'debug') {
priority = 'info';
}
if (priority === 'err') {
priority = 'error';
}
return (console[priority] || console.error)(consolemsg);
}
private syslog(msg) {
let msgbuf;
msgbuf = Buffer.from(msg);
this.client.send(msgbuf, 0, msgbuf.length, config.SYSLOG.PORT, config.SYSLOG.HOST, function(err, bytes) {
if (err) {
console.log(err);
}
});
}
private leadZero(n: number): number | string {
if (n < 10) {
return '0' + n;
}
return n;
}
private ts() {
let day, dt, hours, minutes, month, months, seconds;
dt = new Date();
hours = this.leadZero(dt.getHours());
minutes = this.leadZero(dt.getMinutes());
seconds = this.leadZero(dt.getSeconds());
month = dt.getMonth();
day = dt.getDate();
if (day < 10) {
day = ' ' + day;
}
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return months[month] + ' ' + day + ' ' + hours + ':' + minutes + ':' + seconds;
}
}
export default new Logger();