Lightning explorer base structure
This commit is contained in:
parent
8487548271
commit
93b398a54f
44
lightning-backend/.gitignore
vendored
Normal file
44
lightning-backend/.gitignore
vendored
Normal 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
|
27
lightning-backend/mempool-config.sample.json
Normal file
27
lightning-backend/mempool-config.sample.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { ILightningApi } from './lightning-api.interface';
|
||||||
|
|
||||||
|
export interface AbstractLightningApi {
|
||||||
|
getNetworkInfo(): Promise<ILightningApi.NetworkInfo>;
|
||||||
|
getNetworkGraph(): Promise<ILightningApi.NetworkGraph>;
|
||||||
|
}
|
13
lightning-backend/src/api/lightning-api-factory.ts
Normal file
13
lightning-backend/src/api/lightning-api-factory.ts
Normal 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();
|
46
lightning-backend/src/api/lightning-api.interface.ts
Normal file
46
lightning-backend/src/api/lightning-api.interface.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
37
lightning-backend/src/api/lnd/lnd-api.ts
Normal file
37
lightning-backend/src/api/lnd/lnd-api.ts
Normal 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;
|
84
lightning-backend/src/config.ts
Normal file
84
lightning-backend/src/config.ts
Normal 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();
|
51
lightning-backend/src/database.ts
Normal file
51
lightning-backend/src/database.ts
Normal 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();
|
25
lightning-backend/src/index.ts
Normal file
25
lightning-backend/src/index.ts
Normal 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();
|
145
lightning-backend/src/logger.ts
Normal file
145
lightning-backend/src/logger.ts
Normal 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();
|
Loading…
x
Reference in New Issue
Block a user