diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 66bcb2569..2c3fd9467 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -510,7 +510,12 @@ class BitcoinRoutes { private getDifficultyChange(req: Request, res: Response) { try { - res.json(difficultyAdjustment.getDifficultyAdjustment()); + const da = difficultyAdjustment.getDifficultyAdjustment(); + if (da) { + res.json(da); + } else { + res.status(503).send(`Service Temporarily Unavailable`); + } } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 8635ee96f..b9cc1453c 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -228,34 +228,75 @@ export class Common { return d.toISOString().split('T')[0] + ' ' + d.toTimeString().split(' ')[0]; } - static formatSocket(publicKey: string, socket: {network: string, addr: string}): NodeSocket { + static findSocketNetwork(addr: string): {network: string | null, url: string} { let network: string | null = null; + let url = addr.split('://')[1]; - if (config.LIGHTNING.BACKEND === 'cln') { - network = socket.network; - } else if (config.LIGHTNING.BACKEND === 'lnd') { - if (socket.addr.indexOf('onion') !== -1) { - if (socket.addr.split('.')[0].length >= 56) { - network = 'torv3'; - } else { - network = 'torv2'; - } - } else if (socket.addr.indexOf('i2p') !== -1) { - network = 'i2p'; + if (!url) { + return { + network: null, + url: addr, + }; + } + + if (addr.indexOf('onion') !== -1) { + if (url.split('.')[0].length >= 56) { + network = 'torv3'; } else { - const ipv = isIP(socket.addr.split(':')[0]); - if (ipv === 4) { - network = 'ipv4'; - } else if (ipv === 6) { - network = 'ipv6'; - } + network = 'torv2'; } + } else if (addr.indexOf('i2p') !== -1) { + network = 'i2p'; + } else if (addr.indexOf('ipv4') !== -1) { + const ipv = isIP(url.split(':')[0]); + if (ipv === 4) { + network = 'ipv4'; + } else { + return { + network: null, + url: addr, + }; + } + } else if (addr.indexOf('ipv6') !== -1) { + url = url.split('[')[1].split(']')[0]; + const ipv = isIP(url); + if (ipv === 6) { + const parts = addr.split(':'); + network = 'ipv6'; + url = `[${url}]:${parts[parts.length - 1]}`; + } else { + return { + network: null, + url: addr, + }; + } + } else { + return { + network: null, + url: addr, + }; } return { - publicKey: publicKey, network: network, - addr: socket.addr, + url: url, }; } + + static formatSocket(publicKey: string, socket: {network: string, addr: string}): NodeSocket { + if (config.LIGHTNING.BACKEND === 'cln') { + return { + publicKey: publicKey, + network: socket.network, + addr: socket.addr, + }; + } else /* if (config.LIGHTNING.BACKEND === 'lnd') */ { + const formatted = this.findSocketNetwork(socket.addr); + return { + publicKey: publicKey, + network: formatted.network, + addr: formatted.url, + }; + } + } } diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index 43c6e463f..a1b6ab70e 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -81,14 +81,15 @@ export function calcDifficultyAdjustment( } class DifficultyAdjustmentApi { - constructor() { } - - public getDifficultyAdjustment(): IDifficultyAdjustment { + public getDifficultyAdjustment(): IDifficultyAdjustment | null { const DATime = blocks.getLastDifficultyAdjustmentTime(); const previousRetarget = blocks.getPreviousDifficultyRetarget(); const blockHeight = blocks.getCurrentBlockHeight(); const blocksCache = blocks.getBlocks(); const latestBlock = blocksCache[blocksCache.length - 1]; + if (!latestBlock) { + return null; + } const nowSeconds = Math.floor(new Date().getTime() / 1000); return calcDifficultyAdjustment( diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 56cfebe9f..fcd5b8815 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -503,6 +503,18 @@ class NodesApi { } } + /** + * Update node sockets + */ + public async $updateNodeSockets(publicKey: string, sockets: {network: string; addr: string}[]): Promise { + const formattedSockets = (sockets.map(a => a.addr).join(',')) ?? ''; + try { + await DB.query(`UPDATE nodes SET sockets = ? WHERE public_key = ?`, [formattedSockets, publicKey]); + } catch (e) { + logger.err(`Cannot update node sockets for ${publicKey}. Reason: ${e instanceof Error ? e.message : e}`); + } + } + /** * Set all nodes not in `nodesPubkeys` as inactive (status = 0) */ diff --git a/backend/src/api/explorer/statistics.api.ts b/backend/src/api/explorer/statistics.api.ts index 7bf3d9107..558ee86fd 100644 --- a/backend/src/api/explorer/statistics.api.ts +++ b/backend/src/api/explorer/statistics.api.ts @@ -27,7 +27,7 @@ class StatisticsApi { public async $getLatestStatistics(): Promise { try { const [rows]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY added DESC LIMIT 1`); - const [rows2]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY added DESC LIMIT 1 OFFSET 7`); + const [rows2]: any = await DB.query(`SELECT * FROM lightning_stats WHERE DATE(added) = DATE(NOW() - INTERVAL 7 DAY)`); return { latest: rows[0], previous: rows2[0], diff --git a/backend/src/api/lightning/clightning/clightning-convert.ts b/backend/src/api/lightning/clightning/clightning-convert.ts index 656c3c6da..8b055832e 100644 --- a/backend/src/api/lightning/clightning/clightning-convert.ts +++ b/backend/src/api/lightning/clightning/clightning-convert.ts @@ -13,9 +13,13 @@ export function convertNode(clNode: any): ILightningApi.Node { features: [], // TODO parse and return clNode.feature pub_key: clNode.nodeid, addresses: clNode.addresses?.map((addr) => { + let address = addr.address; + if (addr.type === 'ipv6') { + address = `[${address}]`; + } return { network: addr.type, - addr: `${addr.address}:${addr.port}` + addr: `${address}:${addr.port}` }; }) ?? [], last_update: clNode?.last_timestamp ?? 0, diff --git a/backend/src/tasks/lightning/sync-tasks/node-locations.ts b/backend/src/tasks/lightning/sync-tasks/node-locations.ts index 30a6bfc2a..9069e0fff 100644 --- a/backend/src/tasks/lightning/sync-tasks/node-locations.ts +++ b/backend/src/tasks/lightning/sync-tasks/node-locations.ts @@ -4,6 +4,7 @@ import nodesApi from '../../../api/explorer/nodes.api'; import config from '../../../config'; import DB from '../../../database'; import logger from '../../../logger'; +import * as IPCheck from '../../../utils/ipcheck.js'; export async function $lookupNodeLocation(): Promise { let loggerTimer = new Date().getTime() / 1000; @@ -27,6 +28,26 @@ export async function $lookupNodeLocation(): Promise { const asn = lookupAsn.get(ip); const isp = lookupIsp.get(ip); + let asOverwrite: any | undefined; + if (asn && (IPCheck.match(ip, '170.75.160.0/20') || IPCheck.match(ip, '172.81.176.0/21'))) { + asOverwrite = { + asn: 394745, + name: 'Lunanode', + }; + } + else if (asn && (IPCheck.match(ip, '50.7.0.0/16') || IPCheck.match(ip, '66.90.64.0/18'))) { + asOverwrite = { + asn: 30058, + name: 'FDCservers.net', + }; + } + else if (asn && asn.autonomous_system_number === 174) { + asOverwrite = { + asn: 174, + name: 'Cogent Communications', + }; + } + if (city && (asn || isp)) { const query = ` UPDATE nodes SET @@ -41,7 +62,7 @@ export async function $lookupNodeLocation(): Promise { `; const params = [ - isp?.autonomous_system_number ?? asn?.autonomous_system_number, + asOverwrite?.asn ?? isp?.autonomous_system_number ?? asn?.autonomous_system_number, city.city?.geoname_id, city.country?.geoname_id, city.subdivisions ? city.subdivisions[0].geoname_id : null, @@ -91,7 +112,10 @@ export async function $lookupNodeLocation(): Promise { if (isp?.autonomous_system_organization ?? asn?.autonomous_system_organization) { await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'as_organization', ?)`, - [isp?.autonomous_system_number ?? asn?.autonomous_system_number, JSON.stringify(isp?.isp ?? asn?.autonomous_system_organization)]); + [ + asOverwrite?.asn ?? isp?.autonomous_system_number ?? asn?.autonomous_system_number, + JSON.stringify(asOverwrite?.name ?? isp?.isp ?? asn?.autonomous_system_organization) + ]); } } diff --git a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts index dd958a6e3..e3dfe6652 100644 --- a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts +++ b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts @@ -57,6 +57,8 @@ class LightningStatsImporter { features: node.features, }); nodesInDb[node.pub_key] = node; + } else { + await nodesApi.$updateNodeSockets(node.pub_key, node.addresses); } let hasOnion = false; @@ -369,7 +371,7 @@ class LightningStatsImporter { graph = JSON.parse(fileContent); graph = await this.cleanupTopology(graph); } catch (e) { - logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content`); + logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`); continue; } @@ -419,9 +421,10 @@ class LightningStatsImporter { const addressesParts = (node.addresses ?? '').split(','); const addresses: any[] = []; for (const address of addressesParts) { + const formatted = Common.findSocketNetwork(address); addresses.push({ - network: '', - addr: address + network: formatted.network, + addr: formatted.url }); } diff --git a/backend/src/utils/ipcheck.js b/backend/src/utils/ipcheck.js new file mode 100644 index 000000000..06d4a6f15 --- /dev/null +++ b/backend/src/utils/ipcheck.js @@ -0,0 +1,119 @@ +var net = require('net'); + +var IPCheck = module.exports = function(input) { + var self = this; + + if (!(self instanceof IPCheck)) { + return new IPCheck(input); + } + + self.input = input; + self.parse(); +}; + +IPCheck.prototype.parse = function() { + var self = this; + + if (!self.input || typeof self.input !== 'string') return self.valid = false; + + var ip; + + var pos = self.input.lastIndexOf('/'); + if (pos !== -1) { + ip = self.input.substring(0, pos); + self.mask = +self.input.substring(pos + 1); + } else { + ip = self.input; + self.mask = null; + } + + self.ipv = net.isIP(ip); + self.valid = !!self.ipv && !isNaN(self.mask); + + if (!self.valid) return; + + // default mask = 32 for ipv4 and 128 for ipv6 + if (self.mask === null) self.mask = self.ipv === 4 ? 32 : 128; + + if (self.ipv === 4) { + // difference between ipv4 and ipv6 masks + self.mask += 96; + } + + if (self.mask < 0 || self.mask > 128) { + self.valid = false; + return; + } + + self.address = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + + if(self.ipv === 4){ + self.parseIPv4(ip); + }else{ + self.parseIPv6(ip); + } +}; + +IPCheck.prototype.parseIPv4 = function(ip) { + var self = this; + + // ipv4 addresses live under ::ffff:0:0 + self.address[10] = self.address[11] = 0xff; + + var octets = ip.split('.'); + for (var i = 0; i < 4; i++) { + self.address[i + 12] = parseInt(octets[i], 10); + } +}; + + +var V6_TRANSITIONAL = /:(\d+\.\d+\.\d+\.\d+)$/; + +IPCheck.prototype.parseIPv6 = function(ip) { + var self = this; + + var transitionalMatch = V6_TRANSITIONAL.exec(ip); + if(transitionalMatch){ + self.parseIPv4(transitionalMatch[1]); + return; + } + + var bits = ip.split(':'); + if (bits.length < 8) { + ip = ip.replace('::', Array(11 - bits.length).join(':')); + bits = ip.split(':'); + } + + var j = 0; + for (var i = 0; i < bits.length; i += 1) { + var x = bits[i] ? parseInt(bits[i], 16) : 0; + self.address[j++] = x >> 8; + self.address[j++] = x & 0xff; + } +}; + +IPCheck.prototype.match = function(cidr) { + var self = this; + + if (!(cidr instanceof IPCheck)) cidr = new IPCheck(cidr); + if (!self.valid || !cidr.valid) return false; + + var mask = cidr.mask; + var i = 0; + + while (mask >= 8) { + if (self.address[i] !== cidr.address[i]) return false; + + i++; + mask -= 8; + } + + var shift = 8 - mask; + return (self.address[i] >>> shift) === (cidr.address[i] >>> shift); +}; + + +IPCheck.match = function(ip, cidr) { + ip = ip instanceof IPCheck ? ip : new IPCheck(ip); + return ip.match(cidr); +}; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 6a1970331..0670010e1 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -13,7 +13,8 @@ "node_modules/@types" ], "allowSyntheticDefaultImports": true, - "esModuleInterop": true + "esModuleInterop": true, + "allowJs": true, }, "include": [ "src/**/*.ts" diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss index 5aaf8a91b..c5a217983 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss @@ -61,7 +61,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss index 5aaf8a91b..c5a217983 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss @@ -61,7 +61,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/block-prediction-graph/block-prediction-graph.component.scss b/frontend/src/app/components/block-prediction-graph/block-prediction-graph.component.scss index 5aaf8a91b..c5a217983 100644 --- a/frontend/src/app/components/block-prediction-graph/block-prediction-graph.component.scss +++ b/frontend/src/app/components/block-prediction-graph/block-prediction-graph.component.scss @@ -61,7 +61,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.scss b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.scss index 5aaf8a91b..c5a217983 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.scss +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.scss @@ -61,7 +61,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.scss b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.scss index a47f63923..7b1395d78 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.scss +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.scss @@ -61,7 +61,7 @@ flex-direction: column; @media (min-width: 1130px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 1130px) { position: relative; diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss index 52b5b2c2f..3021cf689 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss @@ -61,7 +61,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss index b15df44fa..c382d9886 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss @@ -55,7 +55,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.scss b/frontend/src/app/components/pool-ranking/pool-ranking.component.scss index 855b4e65c..8cb82d92d 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.scss +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.scss @@ -39,7 +39,7 @@ flex-direction: column; @media (min-width: 991px) { position: relative; - top: -65px; + top: -100px; } @media (min-width: 830px) and (max-width: 991px) { position: relative; diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index 83ec77acf..9f62fffce 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -11,7 +11,7 @@
+ [class]="(stateService.env.MINING_DASHBOARD || stateService.env.LIGHTNING) ? 'mining' : 'no-menu'" (click)="saveGraphPreference()">