Merge branch 'master' into nymkappa/bugfix/ln-network-history-chart
This commit is contained in:
		
						commit
						c23aa67b0c
					
				| @ -83,7 +83,7 @@ class NetworkSyncService { | |||||||
|     logger.info(`${progress} nodes updated. ${deletedSockets} sockets deleted`); |     logger.info(`${progress} nodes updated. ${deletedSockets} sockets deleted`); | ||||||
| 
 | 
 | ||||||
|     // If a channel if not present in the graph, mark it as inactive
 |     // If a channel if not present in the graph, mark it as inactive
 | ||||||
|     nodesApi.$setNodesInactive(graphNodesPubkeys); |     await nodesApi.$setNodesInactive(graphNodesPubkeys); | ||||||
| 
 | 
 | ||||||
|     if (config.MAXMIND.ENABLED) { |     if (config.MAXMIND.ENABLED) { | ||||||
|       $lookupNodeLocation(); |       $lookupNodeLocation(); | ||||||
| @ -121,7 +121,7 @@ class NetworkSyncService { | |||||||
|       logger.info(`${progress} channels updated`); |       logger.info(`${progress} channels updated`); | ||||||
| 
 | 
 | ||||||
|       // If a channel if not present in the graph, mark it as inactive
 |       // If a channel if not present in the graph, mark it as inactive
 | ||||||
|       channelsApi.$setChannelsInactive(graphChannelsIds); |       await channelsApi.$setChannelsInactive(graphChannelsIds); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       logger.err(`Cannot update channel list. Reason: ${(e instanceof Error ? e.message : e)}`); |       logger.err(`Cannot update channel list. Reason: ${(e instanceof Error ? e.message : e)}`); | ||||||
|     } |     } | ||||||
| @ -285,44 +285,66 @@ class NetworkSyncService { | |||||||
|       for (const channel of channels) { |       for (const channel of channels) { | ||||||
|         let reason = 0; |         let reason = 0; | ||||||
|         // Only Esplora backend can retrieve spent transaction outputs
 |         // Only Esplora backend can retrieve spent transaction outputs
 | ||||||
|         const outspends = await bitcoinApi.$getOutspends(channel.closing_transaction_id); |         try { | ||||||
|         const lightningScriptReasons: number[] = []; |           let outspends: IEsploraApi.Outspend[] | undefined; | ||||||
|         for (const outspend of outspends) { |           try { | ||||||
|           if (outspend.spent && outspend.txid) { |             outspends = await bitcoinApi.$getOutspends(channel.closing_transaction_id); | ||||||
|             const spendingTx = await bitcoinApi.$getRawTransaction(outspend.txid); |           } catch (e) { | ||||||
|             const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); |             logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + channel.closing_transaction_id + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`); | ||||||
|             lightningScriptReasons.push(lightningScript); |             continue; | ||||||
|           } |           } | ||||||
|         } |           const lightningScriptReasons: number[] = []; | ||||||
|         if (lightningScriptReasons.length === outspends.length |           for (const outspend of outspends) { | ||||||
|           && lightningScriptReasons.filter((r) => r === 1).length === outspends.length) { |             if (outspend.spent && outspend.txid) { | ||||||
|           reason = 1; |               let spendingTx: IEsploraApi.Transaction | undefined; | ||||||
|         } else { |               try { | ||||||
|           const filteredReasons = lightningScriptReasons.filter((r) => r !== 1); |                 spendingTx = await bitcoinApi.$getRawTransaction(outspend.txid); | ||||||
|           if (filteredReasons.length) { |               } catch (e) { | ||||||
|             if (filteredReasons.some((r) => r === 2 || r === 4)) { |                 logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + outspend.txid}. Reason ${e instanceof Error ? e.message : e}`); | ||||||
|               reason = 3; |                 continue; | ||||||
|             } else { |               } | ||||||
|               reason = 2; |               const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); | ||||||
|  |               lightningScriptReasons.push(lightningScript); | ||||||
|             } |             } | ||||||
|  |           } | ||||||
|  |           if (lightningScriptReasons.length === outspends.length | ||||||
|  |             && lightningScriptReasons.filter((r) => r === 1).length === outspends.length) { | ||||||
|  |             reason = 1; | ||||||
|           } else { |           } else { | ||||||
|             /* |             const filteredReasons = lightningScriptReasons.filter((r) => r !== 1); | ||||||
|               We can detect a commitment transaction (force close) by reading Sequence and Locktime |             if (filteredReasons.length) { | ||||||
|               https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction
 |               if (filteredReasons.some((r) => r === 2 || r === 4)) { | ||||||
|             */ |                 reason = 3; | ||||||
|             const closingTx = await bitcoinApi.$getRawTransaction(channel.closing_transaction_id); |               } else { | ||||||
|             const sequenceHex: string = closingTx.vin[0].sequence.toString(16); |                 reason = 2; | ||||||
|             const locktimeHex: string = closingTx.locktime.toString(16); |               } | ||||||
|             if (sequenceHex.substring(0, 2) === '80' && locktimeHex.substring(0, 2) === '20') { |  | ||||||
|               reason = 2; // Here we can't be sure if it's a penalty or not
 |  | ||||||
|             } else { |             } else { | ||||||
|               reason = 1; |               /* | ||||||
|  |                 We can detect a commitment transaction (force close) by reading Sequence and Locktime | ||||||
|  |                 https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction
 | ||||||
|  |               */ | ||||||
|  |               let closingTx: IEsploraApi.Transaction | undefined; | ||||||
|  |               try { | ||||||
|  |                 closingTx = await bitcoinApi.$getRawTransaction(channel.closing_transaction_id); | ||||||
|  |               } catch (e) { | ||||||
|  |                 logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + channel.closing_transaction_id}. Reason ${e instanceof Error ? e.message : e}`); | ||||||
|  |                 continue; | ||||||
|  |               } | ||||||
|  |               const sequenceHex: string = closingTx.vin[0].sequence.toString(16); | ||||||
|  |               const locktimeHex: string = closingTx.locktime.toString(16); | ||||||
|  |               if (sequenceHex.substring(0, 2) === '80' && locktimeHex.substring(0, 2) === '20') { | ||||||
|  |                 reason = 2; // Here we can't be sure if it's a penalty or not
 | ||||||
|  |               } else { | ||||||
|  |                 reason = 1; | ||||||
|  |               } | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |           if (reason) { | ||||||
|         if (reason) { |             logger.debug('Setting closing reason ' + reason + ' for channel: ' + channel.id + '.'); | ||||||
|           logger.debug('Setting closing reason ' + reason + ' for channel: ' + channel.id + '.'); |             await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); | ||||||
|           await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); |           } | ||||||
|  |         } catch (e) { | ||||||
|  |           logger.err(`$runClosedChannelsForensics() failed for channel ${channel.short_id}. Reason: ${e instanceof Error ? e.message : e}`); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         ++progress; |         ++progress; | ||||||
|  | |||||||
| @ -1,129 +1,5 @@ | |||||||
| 
 |  | ||||||
| .main-title { |  | ||||||
|   position: relative; |  | ||||||
|   color: #ffffff91; |  | ||||||
|   margin-top: -13px; |  | ||||||
|   font-size: 10px; |  | ||||||
|   text-transform: uppercase; |  | ||||||
|   font-weight: 500; |  | ||||||
|   text-align: center; |  | ||||||
|   padding-bottom: 3px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .full-container { | .full-container { | ||||||
|   padding: 0px 15px; |   margin-top: 25px; | ||||||
|   width: 100%; |   margin-bottom: 25px; | ||||||
|   /* min-height: 500px; */ |   min-height: 100%; | ||||||
|   height: calc(100% - 150px); |  | ||||||
|   @media (max-width: 992px) { |  | ||||||
|     height: 100%; |  | ||||||
|     padding-bottom: 100px; |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| /* |  | ||||||
| .chart { |  | ||||||
|   width: 100%; |  | ||||||
|   height: 100%; |  | ||||||
|   padding-bottom: 20px; |  | ||||||
|   padding-right: 10px; |  | ||||||
|   @media (max-width: 992px) { |  | ||||||
|     padding-bottom: 25px; |  | ||||||
|   } |  | ||||||
|   @media (max-width: 829px) { |  | ||||||
|     padding-bottom: 50px; |  | ||||||
|   } |  | ||||||
|   @media (max-width: 767px) { |  | ||||||
|     padding-bottom: 25px; |  | ||||||
|   } |  | ||||||
|   @media (max-width: 629px) { |  | ||||||
|     padding-bottom: 55px; |  | ||||||
|   } |  | ||||||
|   @media (max-width: 567px) { |  | ||||||
|     padding-bottom: 55px; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| */ |  | ||||||
| .chart-widget { |  | ||||||
|   width: 100%; |  | ||||||
|   height: 100%; |  | ||||||
|   max-height: 270px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formRadioGroup { |  | ||||||
|   margin-top: 6px; |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column; |  | ||||||
|   @media (min-width: 991px) { |  | ||||||
|     position: relative; |  | ||||||
|     top: -65px; |  | ||||||
|   } |  | ||||||
|   @media (min-width: 830px) and (max-width: 991px) { |  | ||||||
|     position: relative; |  | ||||||
|     top: 0px; |  | ||||||
|   } |  | ||||||
|   @media (min-width: 830px) { |  | ||||||
|     flex-direction: row; |  | ||||||
|     float: right; |  | ||||||
|     margin-top: 0px; |  | ||||||
|   } |  | ||||||
|   .btn-sm { |  | ||||||
|     font-size: 9px; |  | ||||||
|     @media (min-width: 830px) { |  | ||||||
|       font-size: 14px; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .pool-distribution { |  | ||||||
|   min-height: 56px; |  | ||||||
|   display: block; |  | ||||||
|   @media (min-width: 485px) { |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: row; |  | ||||||
|   } |  | ||||||
|   h5 { |  | ||||||
|     margin-bottom: 10px; |  | ||||||
|   } |  | ||||||
|   .item { |  | ||||||
|     width: 50%; |  | ||||||
|     display: inline-block; |  | ||||||
|     margin: 0px auto 20px; |  | ||||||
|     &:nth-child(2) { |  | ||||||
|       order: 2; |  | ||||||
|       @media (min-width: 485px) { |  | ||||||
|         order: 3; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     &:nth-child(3) { |  | ||||||
|       order: 3; |  | ||||||
|       @media (min-width: 485px) { |  | ||||||
|         order: 2; |  | ||||||
|         display: block; |  | ||||||
|       } |  | ||||||
|       @media (min-width: 768px) { |  | ||||||
|         display: none; |  | ||||||
|       } |  | ||||||
|       @media (min-width: 992px) { |  | ||||||
|         display: block; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     .card-title { |  | ||||||
|       font-size: 1rem; |  | ||||||
|       color: #4a68b9; |  | ||||||
|     } |  | ||||||
|     .card-text { |  | ||||||
|       font-size: 18px; |  | ||||||
|       span { |  | ||||||
|         color: #ffffff66; |  | ||||||
|         font-size: 12px; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .skeleton-loader { |  | ||||||
|   width: 100%; |  | ||||||
|   display: block; |  | ||||||
|   max-width: 80px; |  | ||||||
|   margin: 15px auto 3px; |  | ||||||
| } | } | ||||||
| @ -25,7 +25,7 @@ import { ActivatedRoute, ParamMap } from '@angular/router'; | |||||||
| export class NodeStatisticsChartComponent implements OnInit { | export class NodeStatisticsChartComponent implements OnInit { | ||||||
|   @Input() publicKey: string; |   @Input() publicKey: string; | ||||||
|   @Input() right: number | string = 65; |   @Input() right: number | string = 65; | ||||||
|   @Input() left: number | string = 55; |   @Input() left: number | string = 45; | ||||||
|   @Input() widget = false; |   @Input() widget = false; | ||||||
| 
 | 
 | ||||||
|   miningWindowPreference: string; |   miningWindowPreference: string; | ||||||
| @ -96,7 +96,7 @@ export class NodeStatisticsChartComponent implements OnInit { | |||||||
|       ], |       ], | ||||||
|       grid: { |       grid: { | ||||||
|         top: 30, |         top: 30, | ||||||
|         bottom: 70, |         bottom: 20, | ||||||
|         right: this.right, |         right: this.right, | ||||||
|         left: this.left, |         left: this.left, | ||||||
|       }, |       }, | ||||||
|  | |||||||
| @ -119,10 +119,14 @@ | |||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|   <div *ngIf="!error"> |   <div *ngIf="!error"> | ||||||
|     <app-nodes-channels-map [style]="'nodepage'" [publicKey]="node.public_key"></app-nodes-channels-map> |     <div class="row"> | ||||||
| 
 |       <div class="col-sm"> | ||||||
|     <h2 i18n="lightning.node-history">Node history</h2> |         <app-nodes-channels-map [style]="'nodepage'" [publicKey]="node.public_key"></app-nodes-channels-map> | ||||||
|     <app-node-statistics-chart [publicKey]="node.public_key"></app-node-statistics-chart> |       </div> | ||||||
|  |       <div class="col-sm"> | ||||||
|  |         <app-node-statistics-chart [publicKey]="node.public_key"></app-node-statistics-chart> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
| 
 | 
 | ||||||
|     <h2 i18n="lightning.active-channels-map">Active channels map</h2> |     <h2 i18n="lightning.active-channels-map">Active channels map</h2> | ||||||
|     <app-node-channels style="display:block;margin-bottom: 40px" [publicKey]="node.public_key"></app-node-channels> |     <app-node-channels style="display:block;margin-bottom: 40px" [publicKey]="node.public_key"></app-node-channels> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <div [style]="style === 'widget' ? 'height: 250px' : ''"> | <div class="map-wrapper" [class]="style"> | ||||||
|   <div *ngIf="channelsObservable | async"> |   <ng-container *ngIf="channelsObservable | async"> | ||||||
|     <div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')"> |     <div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')"> | ||||||
|       <div *ngIf="style === 'graph'" class="card-header"> |       <div *ngIf="style === 'graph'" class="card-header"> | ||||||
|         <div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px"> |         <div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px"> | ||||||
| @ -8,15 +8,15 @@ | |||||||
|         <small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small> |         <small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)" |       <div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions" | ||||||
|         (chartFinished)="onChartFinished($event)"> |         (chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)"> | ||||||
|       </div> |       </div> | ||||||
|  | 
 | ||||||
|  |       <div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div> |     <div class="text-center loading-spinner" [class]="style" *ngIf="isLoading"> | ||||||
|   </div> |       <div class="spinner-border text-light"></div> | ||||||
| 
 |     </div> | ||||||
|   <div class="text-center loading-spinner" [class]="style" *ngIf="isLoading"> |   </ng-container> | ||||||
|     <div class="spinner-border text-light"></div> |  | ||||||
|   </div> |  | ||||||
| </div> | </div> | ||||||
| @ -1,3 +1,15 @@ | |||||||
|  | .map-wrapper { | ||||||
|  |   height: 100%; | ||||||
|  | 
 | ||||||
|  |   &.widget { | ||||||
|  |     height: 250px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &.graph { | ||||||
|  |     height: auto; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .card-header { | .card-header { | ||||||
|   border-bottom: 0; |   border-bottom: 0; | ||||||
|   font-size: 18px; |   font-size: 18px; | ||||||
| @ -12,11 +24,6 @@ | |||||||
|   width: 100%; |   width: 100%; | ||||||
|   min-height: 600px; |   min-height: 600px; | ||||||
|   height: calc(100% - 150px); |   height: calc(100% - 150px); | ||||||
| 
 |  | ||||||
|   @media (max-width: 992px) { |  | ||||||
|     height: 100%; |  | ||||||
|     padding-bottom: 100px; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| .full-container.nodepage { | .full-container.nodepage { | ||||||
|   min-height: 400px; |   min-height: 400px; | ||||||
| @ -27,6 +34,7 @@ | |||||||
|   min-height: 400px; |   min-height: 400px; | ||||||
|   margin-top: 25px; |   margin-top: 25px; | ||||||
|   margin-bottom: 25px; |   margin-bottom: 25px; | ||||||
|  |   min-height: 100%; | ||||||
| } | } | ||||||
| .full-container.widget { | .full-container.widget { | ||||||
|   height: 250px; |   height: 250px; | ||||||
| @ -68,21 +76,21 @@ | |||||||
|   min-height: 600px; |   min-height: 600px; | ||||||
| } | } | ||||||
| .chart.nodepage { | .chart.nodepage { | ||||||
|   min-height: 400px; |   min-height: 100%; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   padding-bottom: 0px; | ||||||
| } | } | ||||||
| .chart.channelpage { | .chart.channelpage { | ||||||
|   min-height: 400px; |   min-height: 400px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .widget { | .widget { | ||||||
|   width: 90vw; |   width: 100%; | ||||||
|   margin-left: auto; |   margin-left: auto; | ||||||
|   margin-right: auto; |   margin-right: auto; | ||||||
|   height: 250px; |   height: 250px; | ||||||
|   -webkit-mask: linear-gradient(0deg, #11131f00 5%, #11131fff 25%); |   -webkit-mask: linear-gradient(0deg, #11131f00 5%, #11131fff 25%); | ||||||
|   @media (max-width: 767.98px) { |  | ||||||
|     width: 100vw; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| .widget > .chart { | .widget > .chart { | ||||||
|   min-height: 250px; |   min-height: 250px; | ||||||
|  | |||||||
| @ -165,7 +165,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
| 
 | 
 | ||||||
|           if (this.style === 'nodepage' && thisNodeGPS) { |           if (this.style === 'nodepage' && thisNodeGPS) { | ||||||
|             this.center = [thisNodeGPS[0], thisNodeGPS[1]]; |             this.center = [thisNodeGPS[0], thisNodeGPS[1]]; | ||||||
|             this.zoom = 10; |             this.zoom = 5; | ||||||
|             this.channelWidth = 1; |             this.channelWidth = 1; | ||||||
|             this.channelOpacity = 1; |             this.channelOpacity = 1; | ||||||
|           } |           } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user