diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index e6ed68115..a14f7336f 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -129,7 +129,7 @@ class NodesApi { public async $getNodesPerCountry(countryId: string) { try { const query = ` - SELECT DISTINCT node_stats.public_key, node_stats.capacity, node_stats.channels, nodes.alias, + SELECT node_stats.public_key, node_stats.capacity, node_stats.channels, nodes.alias, UNIX_TIMESTAMP(nodes.first_seen) as first_seen, UNIX_TIMESTAMP(nodes.updated_at) as updated_at, geo_names_city.names as city FROM node_stats @@ -139,8 +139,8 @@ class NodesApi { GROUP BY public_key ) as b ON b.public_key = node_stats.public_key AND b.last_added = node_stats.added JOIN nodes ON nodes.public_key = node_stats.public_key - JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id - LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id + JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' + LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' WHERE geo_names_country.id = ? ORDER BY capacity DESC `; @@ -186,6 +186,39 @@ class NodesApi { throw e; } } + + public async $getNodesCountries() { + try { + let query = `SELECT geo_names.names as names, geo_names_iso.names as iso_code, COUNT(DISTINCT nodes.public_key) as nodesCount, SUM(capacity) as capacity + FROM nodes + JOIN geo_names ON geo_names.id = nodes.country_id AND geo_names.type = 'country' + JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code' + JOIN channels ON channels.node1_public_key = nodes.public_key OR channels.node2_public_key = nodes.public_key + GROUP BY country_id + ORDER BY COUNT(DISTINCT nodes.public_key) DESC + `; + const [nodesCountPerCountry]: any = await DB.query(query); + + query = `SELECT COUNT(*) as total FROM nodes WHERE country_id IS NOT NULL`; + const [nodesWithAS]: any = await DB.query(query); + + const nodesPerCountry: any[] = []; + for (const country of nodesCountPerCountry) { + nodesPerCountry.push({ + name: JSON.parse(country.names), + iso: country.iso_code, + count: country.nodesCount, + share: Math.floor(country.nodesCount / nodesWithAS[0].total * 10000) / 100, + capacity: country.capacity, + }) + } + + return nodesPerCountry; + } catch (e) { + logger.err(`Cannot get nodes grouped by AS. Reason: ${e instanceof Error ? e.message : e}`); + throw e; + } + } } export default new NodesApi(); diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index 9840ddbeb..bbc8efb5a 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -13,6 +13,7 @@ class NodesRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/search/:search', this.$searchNode) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/top', this.$getTopNodes) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/isp', this.$getNodesISP) + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/countries', this.$getNodesCountries) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) ; @@ -128,6 +129,18 @@ class NodesRoutes { res.status(500).send(e instanceof Error ? e.message : e); } } + + private async $getNodesCountries(req: Request, res: Response) { + try { + const nodesPerAs = await nodesApi.$getNodesCountries(); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString()); + res.json(nodesPerAs); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } } export default new NodesRoutes(); diff --git a/backend/src/tasks/price-feeds/kraken-api.ts b/backend/src/tasks/price-feeds/kraken-api.ts index ce76d62c2..ddb3c4f65 100644 --- a/backend/src/tasks/price-feeds/kraken-api.ts +++ b/backend/src/tasks/price-feeds/kraken-api.ts @@ -62,7 +62,7 @@ class KrakenApi implements PriceFeed { // CHF weekly price history goes back to timestamp 1575504000 (December 5, 2019) // AUD weekly price history goes back to timestamp 1591833600 (June 11, 2020) - const priceHistory: any = {}; // map: timestamp -> Prices + let priceHistory: any = {}; // map: timestamp -> Prices for (const currency of this.currencies) { const response = await query(this.urlHist.replace('{GRANULARITY}', '10080') + currency); @@ -83,6 +83,10 @@ class KrakenApi implements PriceFeed { } for (const time in priceHistory) { + if (priceHistory[time].USD === -1) { + delete priceHistory[time]; + continue; + } await PricesRepository.$savePrices(parseInt(time, 10), priceHistory[time]); } diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index ab3671459..0849c8acd 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -31,11 +31,13 @@
Nodes per network + i18n="lightning.nodes-networks">Lightning nodes per network Network capacity Lightning nodes per ISP + Lightning nodes per country
diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index cacbe4198..8952a27ce 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -109,7 +109,7 @@ export class HashrateChartComponent implements OnInit { while (hashIndex < data.hashrates.length) { diffFixed.push({ timestamp: data.hashrates[hashIndex].timestamp, - difficulty: data.difficulty[data.difficulty.length - 1].difficulty + difficulty: data.difficulty.length > 0 ? data.difficulty[data.difficulty.length - 1].difficulty : null }); ++hashIndex; } @@ -231,11 +231,15 @@ export class HashrateChartComponent implements OnInit { } else if (tick.seriesIndex === 1) { // Difficulty let difficultyPowerOfTen = hashratePowerOfTen; let difficulty = tick.data[1]; - if (this.isMobile()) { - difficultyPowerOfTen = selectPowerOfTen(tick.data[1]); - difficulty = Math.round(tick.data[1] / difficultyPowerOfTen.divider); + if (difficulty === null) { + difficultyString = `${tick.marker} ${tick.seriesName}: No data
`; + } else { + if (this.isMobile()) { + difficultyPowerOfTen = selectPowerOfTen(tick.data[1]); + difficulty = Math.round(tick.data[1] / difficultyPowerOfTen.divider); + } + difficultyString = `${tick.marker} ${tick.seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}
`; } - difficultyString = `${tick.marker} ${tick.seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}
`; } else if (tick.seriesIndex === 2) { // Hashrate MA let hashrate = tick.data[1]; if (this.isMobile()) { diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 564bd1b1e..a4979e00d 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -44,9 +44,6 @@ - diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index a685535a7..98e7b83e1 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -12,6 +12,13 @@
+
+ +