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`); | ||||
| 
 | ||||
|     // If a channel if not present in the graph, mark it as inactive
 | ||||
|     nodesApi.$setNodesInactive(graphNodesPubkeys); | ||||
|     await nodesApi.$setNodesInactive(graphNodesPubkeys); | ||||
| 
 | ||||
|     if (config.MAXMIND.ENABLED) { | ||||
|       $lookupNodeLocation(); | ||||
| @ -121,7 +121,7 @@ class NetworkSyncService { | ||||
|       logger.info(`${progress} channels updated`); | ||||
| 
 | ||||
|       // If a channel if not present in the graph, mark it as inactive
 | ||||
|       channelsApi.$setChannelsInactive(graphChannelsIds); | ||||
|       await channelsApi.$setChannelsInactive(graphChannelsIds); | ||||
|     } catch (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) { | ||||
|         let reason = 0; | ||||
|         // Only Esplora backend can retrieve spent transaction outputs
 | ||||
|         const outspends = await bitcoinApi.$getOutspends(channel.closing_transaction_id); | ||||
|         const lightningScriptReasons: number[] = []; | ||||
|         for (const outspend of outspends) { | ||||
|           if (outspend.spent && outspend.txid) { | ||||
|             const spendingTx = await bitcoinApi.$getRawTransaction(outspend.txid); | ||||
|             const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); | ||||
|             lightningScriptReasons.push(lightningScript); | ||||
|         try { | ||||
|           let outspends: IEsploraApi.Outspend[] | undefined; | ||||
|           try { | ||||
|             outspends = await bitcoinApi.$getOutspends(channel.closing_transaction_id); | ||||
|           } catch (e) { | ||||
|             logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + channel.closing_transaction_id + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`); | ||||
|             continue; | ||||
|           } | ||||
|         } | ||||
|         if (lightningScriptReasons.length === outspends.length | ||||
|           && lightningScriptReasons.filter((r) => r === 1).length === outspends.length) { | ||||
|           reason = 1; | ||||
|         } else { | ||||
|           const filteredReasons = lightningScriptReasons.filter((r) => r !== 1); | ||||
|           if (filteredReasons.length) { | ||||
|             if (filteredReasons.some((r) => r === 2 || r === 4)) { | ||||
|               reason = 3; | ||||
|             } else { | ||||
|               reason = 2; | ||||
|           const lightningScriptReasons: number[] = []; | ||||
|           for (const outspend of outspends) { | ||||
|             if (outspend.spent && outspend.txid) { | ||||
|               let spendingTx: IEsploraApi.Transaction | undefined; | ||||
|               try { | ||||
|                 spendingTx = await bitcoinApi.$getRawTransaction(outspend.txid); | ||||
|               } catch (e) { | ||||
|                 logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + outspend.txid}. Reason ${e instanceof Error ? e.message : e}`); | ||||
|                 continue; | ||||
|               } | ||||
|               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 { | ||||
|             /* | ||||
|               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
 | ||||
|             */ | ||||
|             const closingTx = await bitcoinApi.$getRawTransaction(channel.closing_transaction_id); | ||||
|             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
 | ||||
|             const filteredReasons = lightningScriptReasons.filter((r) => r !== 1); | ||||
|             if (filteredReasons.length) { | ||||
|               if (filteredReasons.some((r) => r === 2 || r === 4)) { | ||||
|                 reason = 3; | ||||
|               } else { | ||||
|                 reason = 2; | ||||
|               } | ||||
|             } 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) { | ||||
|           logger.debug('Setting closing reason ' + reason + ' for channel: ' + channel.id + '.'); | ||||
|           await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); | ||||
|           if (reason) { | ||||
|             logger.debug('Setting closing reason ' + reason + ' for channel: ' + 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; | ||||
|  | ||||
| @ -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 { | ||||
|   padding: 0px 15px; | ||||
|   width: 100%; | ||||
|   /* min-height: 500px; */ | ||||
|   height: calc(100% - 150px); | ||||
|   @media (max-width: 992px) { | ||||
|     height: 100%; | ||||
|     padding-bottom: 100px; | ||||
|   }; | ||||
|   margin-top: 25px; | ||||
|   margin-bottom: 25px; | ||||
|   min-height: 100%; | ||||
| } | ||||
| /* | ||||
| .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 { | ||||
|   @Input() publicKey: string; | ||||
|   @Input() right: number | string = 65; | ||||
|   @Input() left: number | string = 55; | ||||
|   @Input() left: number | string = 45; | ||||
|   @Input() widget = false; | ||||
| 
 | ||||
|   miningWindowPreference: string; | ||||
| @ -96,7 +96,7 @@ export class NodeStatisticsChartComponent implements OnInit { | ||||
|       ], | ||||
|       grid: { | ||||
|         top: 30, | ||||
|         bottom: 70, | ||||
|         bottom: 20, | ||||
|         right: this.right, | ||||
|         left: this.left, | ||||
|       }, | ||||
|  | ||||
| @ -119,10 +119,14 @@ | ||||
|   </div> | ||||
| 
 | ||||
|   <div *ngIf="!error"> | ||||
|     <app-nodes-channels-map [style]="'nodepage'" [publicKey]="node.public_key"></app-nodes-channels-map> | ||||
| 
 | ||||
|     <h2 i18n="lightning.node-history">Node history</h2> | ||||
|     <app-node-statistics-chart [publicKey]="node.public_key"></app-node-statistics-chart> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm"> | ||||
|         <app-nodes-channels-map [style]="'nodepage'" [publicKey]="node.public_key"></app-nodes-channels-map> | ||||
|       </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> | ||||
|     <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 *ngIf="channelsObservable | async"> | ||||
| <div class="map-wrapper" [class]="style"> | ||||
|   <ng-container *ngIf="channelsObservable | async"> | ||||
|     <div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')"> | ||||
|       <div *ngIf="style === 'graph'" class="card-header"> | ||||
|         <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> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)" | ||||
|         (chartFinished)="onChartFinished($event)"> | ||||
|       <div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions" | ||||
|         (chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)"> | ||||
|       </div> | ||||
| 
 | ||||
|       <div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="text-center loading-spinner" [class]="style" *ngIf="isLoading"> | ||||
|     <div class="spinner-border text-light"></div> | ||||
|   </div> | ||||
| </div> | ||||
|     <div class="text-center loading-spinner" [class]="style" *ngIf="isLoading"> | ||||
|       <div class="spinner-border text-light"></div> | ||||
|     </div> | ||||
|   </ng-container> | ||||
| </div> | ||||
| @ -1,3 +1,15 @@ | ||||
| .map-wrapper { | ||||
|   height: 100%; | ||||
| 
 | ||||
|   &.widget { | ||||
|     height: 250px; | ||||
|   } | ||||
| 
 | ||||
|   &.graph { | ||||
|     height: auto; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .card-header { | ||||
|   border-bottom: 0; | ||||
|   font-size: 18px; | ||||
| @ -12,11 +24,6 @@ | ||||
|   width: 100%; | ||||
|   min-height: 600px; | ||||
|   height: calc(100% - 150px); | ||||
| 
 | ||||
|   @media (max-width: 992px) { | ||||
|     height: 100%; | ||||
|     padding-bottom: 100px; | ||||
|   } | ||||
| } | ||||
| .full-container.nodepage { | ||||
|   min-height: 400px; | ||||
| @ -27,6 +34,7 @@ | ||||
|   min-height: 400px; | ||||
|   margin-top: 25px; | ||||
|   margin-bottom: 25px; | ||||
|   min-height: 100%; | ||||
| } | ||||
| .full-container.widget { | ||||
|   height: 250px; | ||||
| @ -68,21 +76,21 @@ | ||||
|   min-height: 600px; | ||||
| } | ||||
| .chart.nodepage { | ||||
|   min-height: 400px; | ||||
|   min-height: 100%; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   padding-bottom: 0px; | ||||
| } | ||||
| .chart.channelpage { | ||||
|   min-height: 400px; | ||||
| } | ||||
| 
 | ||||
| .widget { | ||||
|   width: 90vw; | ||||
|   width: 100%; | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
|   height: 250px; | ||||
|   -webkit-mask: linear-gradient(0deg, #11131f00 5%, #11131fff 25%); | ||||
|   @media (max-width: 767.98px) { | ||||
|     width: 100vw; | ||||
|   } | ||||
| } | ||||
| .widget > .chart { | ||||
|   min-height: 250px; | ||||
| @ -107,4 +115,4 @@ | ||||
|   @media (max-width: 767.98px) { | ||||
|     top: 250px; | ||||
|   } | ||||
| } | ||||
| } | ||||
| @ -165,7 +165,7 @@ export class NodesChannelsMap implements OnInit { | ||||
| 
 | ||||
|           if (this.style === 'nodepage' && thisNodeGPS) { | ||||
|             this.center = [thisNodeGPS[0], thisNodeGPS[1]]; | ||||
|             this.zoom = 10; | ||||
|             this.zoom = 5; | ||||
|             this.channelWidth = 1; | ||||
|             this.channelOpacity = 1; | ||||
|           } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user