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