Node stats updates
This commit is contained in:
		
							parent
							
								
									b0b73e6c70
								
							
						
					
					
						commit
						07821769cd
					
				| @ -13,6 +13,7 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy { | ||||
|   intervals = {}; | ||||
| 
 | ||||
|   @Input() time: number; | ||||
|   @Input() dateString: number; | ||||
|   @Input() fastRender = false; | ||||
| 
 | ||||
|   constructor( | ||||
| @ -52,7 +53,13 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy { | ||||
|   } | ||||
| 
 | ||||
|   calculate() { | ||||
|     const seconds = Math.floor((+new Date() - +new Date(this.time * 1000)) / 1000); | ||||
|     let date: Date; | ||||
|     if (this.dateString) { | ||||
|       date = new Date(this.dateString) | ||||
|     } else { | ||||
|       date = new Date(this.time * 1000); | ||||
|     } | ||||
|     const seconds = Math.floor((+new Date() - +date) / 1000); | ||||
|     if (seconds < 60) { | ||||
|       return $localize`:@@date-base.just-now:Just now`; | ||||
|     } | ||||
|  | ||||
| @ -32,6 +32,10 @@ export class LightningApiService { | ||||
|     return this.httpClient.get<any>(API_BASE_URL + '/statistics/latest'); | ||||
|   } | ||||
| 
 | ||||
|   listNodeStats$(publicKey: string): Observable<any> { | ||||
|     return this.httpClient.get<any>(API_BASE_URL + '/nodes/' + publicKey + '/statistics'); | ||||
|   } | ||||
| 
 | ||||
|   listTopNodes$(): Observable<any> { | ||||
|     return this.httpClient.get<any>(API_BASE_URL + '/nodes/top'); | ||||
|   } | ||||
|  | ||||
| @ -18,12 +18,16 @@ | ||||
|           <table class="table table-borderless table-striped"> | ||||
|             <tbody> | ||||
|               <tr> | ||||
|                 <td i18n="address.total-received">First Seen</td> | ||||
|                 <td>{{ node.first_seen | date:'yyyy-MM-dd HH:mm' }}</td> | ||||
|                 <td i18n="address.total-received">First seen</td> | ||||
|                 <td> | ||||
|                   <app-time-since [dateString]="node.first_seen"></app-time-since> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <td i18n="address.total-sent">Updated At</td> | ||||
|                 <td>{{ node.updated_at | date:'yyyy-MM-dd HH:mm' }}</td> | ||||
|                 <td i18n="address.total-sent">Last update</td> | ||||
|                 <td> | ||||
|                   <app-time-since [dateString]="node.updated_at"></app-time-since> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <td i18n="address.balance">Color</td> | ||||
|  | ||||
| @ -12,6 +12,7 @@ import { LightningApiService } from '../lightning-api.service'; | ||||
| }) | ||||
| export class NodeComponent implements OnInit { | ||||
|   node$: Observable<any>; | ||||
|   statistics$: Observable<any>; | ||||
|   publicKey$: Observable<string>; | ||||
| 
 | ||||
|   constructor( | ||||
| @ -26,6 +27,13 @@ export class NodeComponent implements OnInit { | ||||
|           return this.lightningApiService.getNode$(params.get('public_key')); | ||||
|         }) | ||||
|       ); | ||||
| 
 | ||||
|     this.statistics$ = this.activatedRoute.paramMap | ||||
|       .pipe( | ||||
|         switchMap((params: ParamMap) => { | ||||
|           return this.lightningApiService.listNodeStats$(params.get('public_key')); | ||||
|         }) | ||||
|       ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -12,10 +12,10 @@ | ||||
|           <a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.alias }}</a> | ||||
|         </td> | ||||
|         <td class="capacity text-right"> | ||||
|           <app-amount [satoshis]="node.capacity_left + node.capacity_right" digitsInfo="1.2-2"></app-amount> | ||||
|           <app-amount [satoshis]="node.capacity" digitsInfo="1.2-2"></app-amount> | ||||
|         </td> | ||||
|         <td class="channels text-right"> | ||||
|           {{ node.channels_left + node.channels_right | number }} | ||||
|           {{ node.channels | number }} | ||||
|         </td> | ||||
|       </tr> | ||||
|     </tbody> | ||||
|  | ||||
| @ -24,6 +24,17 @@ class ChannelsApi { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public async $getChannelsWithoutCreatedDate(): Promise<any[]> { | ||||
|     try { | ||||
|       const query = `SELECT * FROM channels WHERE created IS NULL`; | ||||
|       const [rows]: any = await DB.query(query); | ||||
|       return rows; | ||||
|     } catch (e) { | ||||
|       logger.err('$getChannelsWithoutCreatedDate error: ' + (e instanceof Error ? e.message : e)); | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public async $getChannel(shortId: string): Promise<any> { | ||||
|     try { | ||||
|       const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.* FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key WHERE channels.id = ?`; | ||||
|  | ||||
| @ -4,8 +4,8 @@ import DB from '../../database'; | ||||
| class NodesApi { | ||||
|   public async $getNode(public_key: string): Promise<any> { | ||||
|     try { | ||||
|       const query = `SELECT * FROM nodes WHERE public_key = ?`; | ||||
|       const [rows]: any = await DB.query(query, [public_key]); | ||||
|       const query = `SELECT nodes.*, (SELECT COUNT(*) FROM channels WHERE channels.status < 2 AND (channels.node1_public_key = ? OR channels.node2_public_key = ?)) AS channel_count, (SELECT SUM(capacity) FROM channels WHERE channels.status < 2 AND (channels.node1_public_key = ? OR channels.node2_public_key = ?)) AS capacity FROM nodes WHERE public_key = ?`; | ||||
|       const [rows]: any = await DB.query(query, [public_key, public_key, public_key, public_key, public_key]); | ||||
|       return rows[0]; | ||||
|     } catch (e) { | ||||
|       logger.err('$getNode error: ' + (e instanceof Error ? e.message : e)); | ||||
| @ -13,9 +13,20 @@ class NodesApi { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public async $getNodeStats(public_key: string): Promise<any> { | ||||
|     try { | ||||
|       const query = `SELECT * FROM nodes_stats WHERE public_key = ? ORDER BY added DESC`; | ||||
|       const [rows]: any = await DB.query(query, [public_key]); | ||||
|       return rows; | ||||
|     } catch (e) { | ||||
|       logger.err('$getNodeStats error: ' + (e instanceof Error ? e.message : e)); | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public async $getTopCapacityNodes(): Promise<any> { | ||||
|     try { | ||||
|       const query = `SELECT nodes.*, nodes_stats.capacity_left, nodes_stats.capacity_right, nodes_stats.channels_left, nodes_stats.channels_right FROM nodes LEFT JOIN nodes_stats ON nodes_stats.public_key = nodes.public_key ORDER BY nodes_stats.added DESC, nodes_stats.capacity_left + nodes_stats.capacity_right DESC LIMIT 10`; | ||||
|       const query = `SELECT nodes.*, nodes_stats.capacity, nodes_stats.channels FROM nodes LEFT JOIN nodes_stats ON nodes_stats.public_key = nodes.public_key ORDER BY nodes_stats.added DESC, nodes_stats.capacity DESC LIMIT 10`; | ||||
|       const [rows]: any = await DB.query(query); | ||||
|       return rows; | ||||
|     } catch (e) { | ||||
| @ -26,7 +37,7 @@ class NodesApi { | ||||
| 
 | ||||
|   public async $getTopChannelsNodes(): Promise<any> { | ||||
|     try { | ||||
|       const query = `SELECT nodes.*, nodes_stats.capacity_left, nodes_stats.capacity_right, nodes_stats.channels_left, nodes_stats.channels_right FROM nodes LEFT JOIN nodes_stats ON nodes_stats.public_key = nodes.public_key ORDER BY nodes_stats.added DESC, nodes_stats.channels_left + nodes_stats.channels_right DESC LIMIT 10`; | ||||
|       const query = `SELECT nodes.*, nodes_stats.capacity, nodes_stats.channels FROM nodes LEFT JOIN nodes_stats ON nodes_stats.public_key = nodes.public_key ORDER BY nodes_stats.added DESC, nodes_stats.channels DESC LIMIT 10`; | ||||
|       const [rows]: any = await DB.query(query); | ||||
|       return rows; | ||||
|     } catch (e) { | ||||
|  | ||||
| @ -8,6 +8,7 @@ class NodesRoutes { | ||||
|     app | ||||
|     .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats) | ||||
|     .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes) | ||||
|     .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key/statistics', this.$getHistoricalNodeStats) | ||||
|     .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key', this.$getNode) | ||||
|   ; | ||||
|   } | ||||
| @ -25,6 +26,15 @@ class NodesRoutes { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private async $getHistoricalNodeStats(req: Request, res: Response) { | ||||
|     try { | ||||
|       const statistics = await nodesApi.$getNodeStats(req.params.public_key); | ||||
|       res.json(statistics); | ||||
|     } catch (e) { | ||||
|       res.status(500).send(e instanceof Error ? e.message : e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private async $getGeneralStats(req: Request, res: Response) { | ||||
|     try { | ||||
|       const statistics = await nodesApi.$getLatestStatistics(); | ||||
|  | ||||
| @ -238,10 +238,8 @@ class DatabaseMigration { | ||||
|       id int(11) unsigned NOT NULL AUTO_INCREMENT, | ||||
|       public_key varchar(66) NOT NULL DEFAULT '', | ||||
|       added date NOT NULL, | ||||
|       capacity_left bigint(11) unsigned DEFAULT NULL, | ||||
|       capacity_right bigint(11) unsigned DEFAULT NULL, | ||||
|       channels_left int(11) unsigned DEFAULT NULL, | ||||
|       channels_right int(11) unsigned DEFAULT NULL, | ||||
|       capacity bigint(11) unsigned DEFAULT NULL, | ||||
|       channels int(11) unsigned DEFAULT NULL, | ||||
|       PRIMARY KEY (id), | ||||
|       KEY public_key (public_key) | ||||
|     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | ||||
|  | ||||
| @ -26,21 +26,71 @@ class NodeSyncService { | ||||
|       for (const node of networkGraph.nodes) { | ||||
|         await this.$saveNode(node); | ||||
|       } | ||||
|       logger.debug(`Nodes updated`); | ||||
| 
 | ||||
|       await this.$setChannelsInactive(); | ||||
| 
 | ||||
|       for (const channel of networkGraph.channels) { | ||||
|         await this.$saveChannel(channel); | ||||
|       } | ||||
|       logger.debug(`Channels updated`); | ||||
| 
 | ||||
|       await this.$findInactiveNodesAndChannels(); | ||||
|       logger.debug(`Inactive channels scan complete`); | ||||
| 
 | ||||
|       await this.$scanForClosedChannels(); | ||||
|       logger.debug(`Closed channels scan complete`); | ||||
| 
 | ||||
|       await this.$lookUpCreationDateFromChain(); | ||||
|       logger.debug(`Channel creation dates scan complete`); | ||||
| 
 | ||||
|       await this.$updateNodeFirstSeen(); | ||||
|       logger.debug(`Node first seen dates scan complete`); | ||||
| 
 | ||||
|     } catch (e) { | ||||
|       logger.err('$updateNodes() error: ' + (e instanceof Error ? e.message : e)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // This method look up the creation date of the earliest channel of the node
 | ||||
|   // and update the node to that date in order to get the earliest first seen date
 | ||||
|   private async $updateNodeFirstSeen() { | ||||
|     try { | ||||
|       const [nodes]: any[] = await DB.query(`SELECT nodes.public_key, UNIX_TIMESTAMP(nodes.first_seen) AS first_seen, (SELECT UNIX_TIMESTAMP(created) FROM channels WHERE channels.node1_public_key = nodes.public_key ORDER BY created ASC LIMIT 1) AS created1, (SELECT UNIX_TIMESTAMP(created) FROM channels WHERE channels.node2_public_key = nodes.public_key ORDER BY created ASC LIMIT 1) AS created2 FROM nodes`); | ||||
|       for (const node of nodes) { | ||||
|         let lowest = 0; | ||||
|         if (node.created1) { | ||||
|           if (node.created2 && node.created2 < node.created1) { | ||||
|             lowest = node.created2; | ||||
|           } else { | ||||
|             lowest = node.created1; | ||||
|           } | ||||
|         } else if (node.created2) { | ||||
|           lowest = node.created2; | ||||
|         } | ||||
|         if (lowest && lowest < node.first_seen) { | ||||
|           const query = `UPDATE nodes SET first_seen = FROM_UNIXTIME(?) WHERE public_key = ?`; | ||||
|           const params = [lowest, node.public_key]; | ||||
|           await DB.query(query, params); | ||||
|         } | ||||
|       } | ||||
|     } catch (e) { | ||||
|       logger.err('$updateNodeFirstSeen() error: ' + (e instanceof Error ? e.message : e)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private async $lookUpCreationDateFromChain() { | ||||
|     try { | ||||
|       const channels = await channelsApi.$getChannelsWithoutCreatedDate(); | ||||
|       for (const channel of channels) { | ||||
|         const transaction = await bitcoinClient.getRawTransaction(channel.transaction_id, 1); | ||||
|         await DB.query(`UPDATE channels SET created = FROM_UNIXTIME(?) WHERE channels.id = ?`, [transaction.blocktime, channel.id]); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       logger.err('$setCreationDateFromChain() error: ' + (e instanceof Error ? e.message : e)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Looking for channels whos nodes are inactive
 | ||||
|   private async $findInactiveNodesAndChannels(): Promise<void> { | ||||
|     try { | ||||
| @ -190,23 +240,27 @@ class NodeSyncService { | ||||
|   private async $saveNode(node: ILightningApi.Node): Promise<void> { | ||||
|     try { | ||||
|       const updatedAt = this.utcDateToMysql(node.updated_at); | ||||
|       const sockets = node.sockets.join(', '); | ||||
|       const query = `INSERT INTO nodes(
 | ||||
|           public_key, | ||||
|           first_seen, | ||||
|           updated_at, | ||||
|           alias, | ||||
|           color | ||||
|           color, | ||||
|           sockets | ||||
|         ) | ||||
|         VALUES (?, NOW(), ?, ?, ?) ON DUPLICATE KEY UPDATE updated_at = ?, alias = ?, color = ?;`;
 | ||||
|         VALUES (?, NOW(), ?, ?, ?, ?) ON DUPLICATE KEY UPDATE updated_at = ?, alias = ?, color = ?, sockets = ?;`;
 | ||||
| 
 | ||||
|       await DB.query(query, [ | ||||
|         node.public_key, | ||||
|         updatedAt, | ||||
|         node.alias, | ||||
|         node.color, | ||||
|         sockets, | ||||
|         updatedAt, | ||||
|         node.alias, | ||||
|         node.color, | ||||
|         sockets, | ||||
|       ]); | ||||
|     } catch (e) { | ||||
|       logger.err('$saveNode() error: ' + (e instanceof Error ? e.message : e)); | ||||
|  | ||||
| @ -38,9 +38,9 @@ class LightningStatsUpdater { | ||||
| 
 | ||||
|       for (const node of nodes) { | ||||
|         await DB.query( | ||||
|           `INSERT INTO nodes_stats(public_key, added, capacity_left, capacity_right, channels_left, channels_right) VALUES (?, NOW(), ?, ?, ?, ?)`, | ||||
|           [node.public_key, node.channels_capacity_left, node.channels_capacity_right, | ||||
|             node.channels_count_left, node.channels_count_right]); | ||||
|           `INSERT INTO nodes_stats(public_key, added, capacity, channels) VALUES (?, NOW(), ?, ?)`, | ||||
|           [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]); | ||||
|       } | ||||
|       await DB.query(`UPDATE state SET string = ? WHERE name = 'last_node_stats'`, [currentDate]); | ||||
|       logger.debug('Daily node stats has updated.'); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user