2022-07-06 13:20:37 +02:00
import DB from '../../database' ;
import logger from '../../logger' ;
import lightningApi from '../../api/lightning/lightning-api-factory' ;
2022-07-06 14:56:10 +02:00
import channelsApi from '../../api/explorer/channels.api' ;
2022-07-06 13:20:37 +02:00
import * as net from 'net' ;
2022-04-19 17:37:06 +04:00
class LightningStatsUpdater {
2022-07-09 17:45:34 +02:00
hardCodedStartTime = '2018-01-12' ;
2022-04-19 17:37:06 +04:00
2022-07-04 12:00:16 +02:00
public async $startService() {
2022-07-06 21:43:47 +02:00
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 ;
}
2022-04-19 17:37:06 +04:00
2022-07-10 20:01:15 +02:00
await this . $populateHistoricalStatistics ( ) ;
await this . $populateHistoricalNodeStatistics ( ) ;
2022-04-19 17:37:06 +04:00
2022-07-06 21:43:47 +02:00
setTimeout ( ( ) = > {
2022-07-10 20:01:15 +02:00
this . $runTasks ( ) ;
} , this . timeUntilMidnight ( ) ) ;
}
2022-05-01 03:01:27 +04:00
2022-07-10 20:01:15 +02:00
private timeUntilMidnight ( ) : number {
const date = new Date ( ) ;
this . setDateMidnight ( date ) ;
date . setUTCHours ( 24 ) ;
return date . getTime ( ) - new Date ( ) . getTime ( ) ;
}
private setDateMidnight ( date : Date ) : void {
date . setUTCHours ( 0 ) ;
date . setUTCMinutes ( 0 ) ;
date . setUTCSeconds ( 0 ) ;
date . setUTCMilliseconds ( 0 ) ;
2022-07-04 12:00:16 +02:00
}
2022-07-06 21:43:47 +02:00
private async $lightningIsSynced ( ) : Promise < boolean > {
const nodeInfo = await lightningApi . $getInfo ( ) ;
return nodeInfo . is_synced_to_chain && nodeInfo . is_synced_to_graph ;
}
2022-07-10 20:01:15 +02:00
private async $runTasks ( ) : Promise < void > {
2022-07-04 12:00:16 +02:00
await this . $logLightningStatsDaily ( ) ;
await this . $logNodeStatsDaily ( ) ;
2022-07-10 20:01:15 +02:00
setTimeout ( ( ) = > {
this . $runTasks ( ) ;
} , this . timeUntilMidnight ( ) ) ;
2022-04-19 17:37:06 +04:00
}
2022-07-10 20:01:15 +02:00
private async $logLightningStatsDaily() {
2022-04-29 03:57:27 +04:00
try {
2022-07-10 20:01:15 +02:00
logger . info ( ` Running lightning daily stats log... ` ) ;
const networkGraph = await lightningApi . $getNetworkGraph ( ) ;
let total_capacity = 0 ;
for ( const channel of networkGraph . channels ) {
if ( channel . capacity ) {
total_capacity += channel . capacity ;
}
2022-04-29 03:57:27 +04:00
}
2022-04-19 17:37:06 +04:00
2022-07-10 20:01:15 +02:00
let clearnetNodes = 0 ;
let torNodes = 0 ;
let unannouncedNodes = 0 ;
for ( const node of networkGraph . nodes ) {
let isUnnanounced = true ;
for ( const socket of node . sockets ) {
const hasOnion = socket . indexOf ( '.onion' ) !== - 1 ;
if ( hasOnion ) {
torNodes ++ ;
isUnnanounced = false ;
}
const hasClearnet = [ 4 , 6 ] . includes ( net . isIP ( socket . split ( ':' ) [ 0 ] ) ) ;
if ( hasClearnet ) {
clearnetNodes ++ ;
isUnnanounced = false ;
}
}
if ( isUnnanounced ) {
unannouncedNodes ++ ;
}
}
const channelStats = await channelsApi . $getChannelsStats ( ) ;
const query = ` INSERT INTO lightning_stats(
added ,
channel_count ,
node_count ,
total_capacity ,
tor_nodes ,
clearnet_nodes ,
unannounced_nodes ,
avg_capacity ,
avg_fee_rate ,
avg_base_fee_mtokens ,
med_capacity ,
med_fee_rate ,
med_base_fee_mtokens
)
VALUES ( NOW ( ) - INTERVAL 1 DAY , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ` ;
await DB . query ( query , [
networkGraph . channels . length ,
networkGraph . nodes . length ,
total_capacity ,
torNodes ,
clearnetNodes ,
unannouncedNodes ,
channelStats . avgCapacity ,
channelStats . avgFeeRate ,
channelStats . avgBaseFee ,
channelStats . medianCapacity ,
channelStats . medianFeeRate ,
channelStats . medianBaseFee ,
] ) ;
logger . info ( ` Lightning daily stats done. ` ) ;
} catch ( e ) {
logger . err ( '$logLightningStatsDaily() error: ' + ( e instanceof Error ? e.message : e ) ) ;
}
}
private async $logNodeStatsDaily() {
try {
2022-07-06 21:43:47 +02:00
logger . info ( ` Running daily node stats update... ` ) ;
2022-05-16 00:01:53 +04:00
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 ` ;
2022-04-29 03:57:27 +04:00
const [ nodes ] : any = await DB . query ( query ) ;
for ( const node of nodes ) {
await DB . query (
2022-07-10 20:01:15 +02:00
` INSERT INTO node_stats(public_key, added, capacity, channels) VALUES (?, NOW() - INTERVAL 1 DAY, ?, ?) ` ,
2022-05-05 23:19:24 +04:00
[ node . public_key , ( parseInt ( node . channels_capacity_left || 0 , 10 ) ) + ( parseInt ( node . channels_capacity_right || 0 , 10 ) ) ,
node . channels_count_left + node . channels_count_right ] ) ;
2022-04-29 03:57:27 +04:00
}
2022-07-06 11:58:06 +02:00
logger . info ( 'Daily node stats has updated.' ) ;
2022-04-29 03:57:27 +04:00
} catch ( e ) {
logger . err ( '$logNodeStatsDaily() error: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-04-27 02:52:23 +04:00
}
}
2022-07-04 12:00:16 +02:00
// We only run this on first launch
2022-07-09 17:45:34 +02:00
private async $populateHistoricalStatistics() {
2022-04-19 17:37:06 +04:00
try {
2022-07-06 11:58:06 +02:00
const [ rows ] : any = await DB . query ( ` SELECT COUNT(*) FROM lightning_stats ` ) ;
2022-07-09 17:45:34 +02:00
// Only run if table is empty
2022-07-04 12:00:16 +02:00
if ( rows [ 0 ] [ 'COUNT(*)' ] > 0 ) {
return ;
}
2022-07-06 21:43:47 +02:00
logger . info ( ` Running historical stats population... ` ) ;
2022-07-04 12:00:16 +02:00
const [ channels ] : any = await DB . query ( ` SELECT capacity, created, closing_date FROM channels ORDER BY created ASC ` ) ;
2022-07-10 20:01:15 +02:00
const [ nodes ] : any = await DB . query ( ` SELECT first_seen, sockets FROM nodes ORDER BY first_seen ASC ` ) ;
2022-07-04 12:00:16 +02:00
2022-07-10 20:01:15 +02:00
const date : Date = new Date ( this . hardCodedStartTime ) ;
2022-07-04 12:00:16 +02:00
const currentDate = new Date ( ) ;
2022-07-10 20:01:15 +02:00
this . setDateMidnight ( currentDate ) ;
2022-07-04 12:00:16 +02:00
while ( date < currentDate ) {
let totalCapacity = 0 ;
let channelsCount = 0 ;
2022-07-10 20:01:15 +02:00
2022-07-04 12:00:16 +02:00
for ( const channel of channels ) {
if ( new Date ( channel . created ) > date ) {
break ;
}
2022-07-10 20:01:15 +02:00
if ( channel . closing_date === null || new Date ( channel . closing_date ) > date ) {
totalCapacity += channel . capacity ;
channelsCount ++ ;
2022-07-04 12:00:16 +02:00
}
}
let nodeCount = 0 ;
2022-07-06 13:20:37 +02:00
let clearnetNodes = 0 ;
let torNodes = 0 ;
let unannouncedNodes = 0 ;
2022-07-10 20:01:15 +02:00
2022-07-04 12:00:16 +02:00
for ( const node of nodes ) {
if ( new Date ( node . first_seen ) > date ) {
break ;
}
nodeCount ++ ;
2022-07-06 13:20:37 +02:00
const sockets = node . sockets . split ( ',' ) ;
let isUnnanounced = true ;
for ( const socket of sockets ) {
const hasOnion = socket . indexOf ( '.onion' ) !== - 1 ;
if ( hasOnion ) {
torNodes ++ ;
isUnnanounced = false ;
}
const hasClearnet = [ 4 , 6 ] . includes ( net . isIP ( socket . split ( ':' ) [ 0 ] ) ) ;
if ( hasClearnet ) {
clearnetNodes ++ ;
isUnnanounced = false ;
}
}
if ( isUnnanounced ) {
unannouncedNodes ++ ;
}
2022-07-04 12:00:16 +02:00
}
2022-07-10 20:01:15 +02:00
const query = ` INSERT INTO lightning_stats(
added ,
channel_count ,
node_count ,
total_capacity ,
tor_nodes ,
clearnet_nodes ,
unannounced_nodes
)
VALUES ( FROM_UNIXTIME ( ? ) , ? , ? , ? , ? , ? , ? ) ` ;
2022-07-04 12:00:16 +02:00
await DB . query ( query , [
2022-07-10 20:01:15 +02:00
date . getTime ( ) / 1000 ,
channelsCount ,
2022-07-04 12:00:16 +02:00
nodeCount ,
2022-07-10 20:01:15 +02:00
totalCapacity ,
2022-07-06 13:20:37 +02:00
torNodes ,
clearnetNodes ,
unannouncedNodes ,
2022-07-04 12:00:16 +02:00
] ) ;
2022-07-10 20:01:15 +02:00
date . setUTCDate ( date . getUTCDate ( ) + 1 ) ;
2022-07-04 12:00:16 +02:00
}
2022-07-06 11:58:06 +02:00
logger . info ( 'Historical stats populated.' ) ;
2022-07-04 12:00:16 +02:00
} catch ( e ) {
logger . err ( '$populateHistoricalData() error: ' + ( e instanceof Error ? e.message : e ) ) ;
}
}
2022-07-09 17:45:34 +02:00
private async $populateHistoricalNodeStatistics() {
try {
const [ rows ] : any = await DB . query ( ` SELECT COUNT(*) FROM node_stats ` ) ;
// Only run if table is empty
if ( rows [ 0 ] [ 'COUNT(*)' ] > 0 ) {
return ;
}
logger . info ( ` Running historical node stats population... ` ) ;
const [ nodes ] : any = await DB . query ( ` SELECT public_key, first_seen, alias FROM nodes ORDER BY first_seen ASC ` ) ;
for ( const node of nodes ) {
const [ channels ] : any = await DB . query ( ` SELECT capacity, created, closing_date FROM channels WHERE node1_public_key = ? OR node2_public_key = ? ORDER BY created ASC ` , [ node . public_key , node . public_key ] ) ;
2022-07-10 20:01:15 +02:00
const date : Date = new Date ( this . hardCodedStartTime ) ;
2022-07-09 17:45:34 +02:00
const currentDate = new Date ( ) ;
2022-07-10 20:01:15 +02:00
this . setDateMidnight ( currentDate ) ;
2022-07-09 17:45:34 +02:00
let lastTotalCapacity = 0 ;
let lastChannelsCount = 0 ;
while ( date < currentDate ) {
let totalCapacity = 0 ;
let channelsCount = 0 ;
for ( const channel of channels ) {
if ( new Date ( channel . created ) > date ) {
break ;
}
if ( channel . closing_date !== null && new Date ( channel . closing_date ) < date ) {
2022-07-10 20:01:15 +02:00
date . setUTCDate ( date . getUTCDate ( ) + 1 ) ;
2022-07-09 17:45:34 +02:00
continue ;
}
totalCapacity += channel . capacity ;
channelsCount ++ ;
}
if ( lastTotalCapacity === totalCapacity && lastChannelsCount === channelsCount ) {
2022-07-10 20:01:15 +02:00
date . setUTCDate ( date . getUTCDate ( ) + 1 ) ;
2022-07-09 17:45:34 +02:00
continue ;
}
lastTotalCapacity = totalCapacity ;
lastChannelsCount = channelsCount ;
const query = ` INSERT INTO node_stats(
public_key ,
added ,
capacity ,
channels
)
VALUES ( ? , FROM_UNIXTIME ( ? ) , ? , ? ) ` ;
2022-07-10 20:01:15 +02:00
2022-07-09 17:45:34 +02:00
await DB . query ( query , [
node . public_key ,
date . getTime ( ) / 1000 ,
totalCapacity ,
channelsCount ,
] ) ;
2022-07-10 20:01:15 +02:00
date . setUTCDate ( date . getUTCDate ( ) + 1 ) ;
2022-07-09 17:45:34 +02:00
}
logger . debug ( 'Updated node_stats for: ' + node . alias ) ;
}
logger . info ( 'Historical stats populated.' ) ;
} catch ( e ) {
logger . err ( '$populateHistoricalNodeData() error: ' + ( e instanceof Error ? e.message : e ) ) ;
}
}
2022-04-19 17:37:06 +04:00
}
export default new LightningStatsUpdater ( ) ;